-
{% trans "Content" %}
+
{% trans "Content" %}
{% for varname, var in variables.items %}
{% if not var.hidden %}
@@ -293,31 +293,31 @@
-
-
{% trans "Width (mm)" %}
+
+ {% trans "Width (mm)" %}
- {% trans "Rotation (°)" %}
+ {% trans "Rotation (°)" %}
@@ -349,7 +349,7 @@
- {% trans "Font" %}
+ {% trans "Font" %}
Open Sans
{% for family in fonts.keys %}
@@ -360,43 +360,50 @@
-
+
-
+
-
+
+ data-toggle="tooltip" title="{% trans "Flow multiple lines downward from specified position" %}">
-
+
+
+
+
@@ -423,9 +469,13 @@
{% trans "Add a new object" %}
+
+
+ {% trans "Text box" %}
+
- {% trans "Text" %}
+ {% trans "Text (deprecated)" %}
@@ -456,7 +506,6 @@
-
+
+
+ {% blocktrans trimmed with print_version="2.18" scan_version="1.22" %}
+ This layout uses new features. If you print from your device, make sure you use pretixPRINT version
+ {{ print_version }} (or newer) or pretixSCAN Desktop version {{ scan_version }} (or newer).
+ {% endblocktrans %}
+
diff --git a/src/pretix/plugins/badges/templates.py b/src/pretix/plugins/badges/templates.py
index 2293350ac5..b45a1644b5 100644
--- a/src/pretix/plugins/badges/templates.py
+++ b/src/pretix/plugins/badges/templates.py
@@ -30,11 +30,11 @@ def _simple_template(w, h):
company_size = name_size - 2
return [
{
- "type": "textarea",
+ "type": "textcontainer",
"page": 1,
"locale": "",
"left": "5.00",
- "bottom": "%.2f" % (((h - company_size * 1.5 - name_size) / 2 + company_size * 1.5) / mm),
+ "bottom": "%.2f" % (h / mm / 2 + 2),
"fontsize": name_size,
"lineheight": "1",
"color": [0, 0, 0, 1],
@@ -42,19 +42,22 @@ def _simple_template(w, h):
"bold": True,
"italic": False,
"width": "%.2f" % (w / mm - 10),
- "downward": False,
+ "height": "%.2f" % (h / mm / 2 - 7),
"content": "attendee_name",
- "text": "John Doe",
+ "text": "Dr John Doe",
"text_i18n": {},
"rotation": 0,
"align": "center",
+ "verticalalign": "bottom",
+ "autoresize": True,
+ "splitlongwords": False,
},
{
- "type": "textarea",
+ "type": "textcontainer",
"page": 1,
"locale": "",
"left": "5.00",
- "bottom": "%.2f" % ((((h - company_size * 1.5 - name_size) / 2) + company_size) / mm),
+ "bottom": "5.00",
"fontsize": company_size,
"lineheight": "1",
"color": [0, 0, 0, 1],
@@ -62,12 +65,15 @@ def _simple_template(w, h):
"bold": False,
"italic": False,
"width": "%.2f" % (w / mm - 10),
- "downward": True,
+ "height": "%.2f" % (h / mm / 2 - 7),
"content": "attendee_company",
"text": "Sample company",
"text_i18n": {},
"rotation": 0,
"align": "center",
+ "verticalalign": "top",
+ "autoresize": True,
+ "splitlongwords": False,
},
]
@@ -94,15 +100,17 @@ TEMPLATES = {
"layout": _simple_template(*pagesizes.portrait(pagesizes.A7)),
},
"82x203butterfly": {
- "label": format_lazy(_("{width} x {height} mm butterfly badge"), width=82, height=203),
+ "label": format_lazy(
+ _("{width} x {height} mm butterfly badge"), width=82, height=203
+ ),
"pagesize": (82 * mm, 203 * mm),
"layout": [
{
- "type": "textarea",
+ "type": "textcontainer",
"page": 1,
"locale": "",
"left": "5.00",
- "bottom": "152.55",
+ "bottom": "153.00",
"fontsize": "20.0",
"lineheight": "1",
"color": [0, 0, 0, 1],
@@ -110,19 +118,22 @@ TEMPLATES = {
"bold": True,
"italic": False,
"width": "72.00",
- "downward": False,
+ "height": "20.00",
"content": "attendee_name",
- "text": "John Doe",
+ "text": "Dr John Doe",
"text_i18n": {},
"rotation": 0,
"align": "center",
+ "verticalalign": "bottom",
+ "autoresize": True,
+ "splitlongwords": False,
},
{
- "type": "textarea",
+ "type": "textcontainer",
"page": 1,
"locale": "",
"left": "5.00",
- "bottom": "144.55",
+ "bottom": "132.10",
"fontsize": "18.0",
"lineheight": "1",
"color": [0, 0, 0, 1],
@@ -130,19 +141,22 @@ TEMPLATES = {
"bold": False,
"italic": False,
"width": "72.00",
- "downward": False,
+ "height": "20.00",
"content": "attendee_company",
"text": "Sample company",
"text_i18n": {},
"rotation": 0,
"align": "center",
+ "verticalalign": "top",
+ "autoresize": True,
+ "splitlongwords": False,
},
{
- "type": "textarea",
+ "type": "textcontainer",
"page": 1,
"locale": "",
- "left": "77.10",
- "bottom": "34.68",
+ "left": "76.97",
+ "bottom": "10.86",
"fontsize": "20.0",
"lineheight": "1",
"color": [0, 0, 0, 1],
@@ -150,19 +164,22 @@ TEMPLATES = {
"bold": True,
"italic": False,
"width": "72.00",
- "downward": False,
+ "height": "20.00",
"content": "attendee_name",
- "text": "John Doe",
+ "text": "Dr John Doe",
"text_i18n": {},
- "rotation": 180,
+ "rotation": -180,
"align": "center",
+ "verticalalign": "bottom",
+ "autoresize": True,
+ "splitlongwords": False,
},
{
- "type": "textarea",
+ "type": "textcontainer",
"page": 1,
"locale": "",
- "left": "77.06",
- "bottom": "44.28",
+ "left": "77.07",
+ "bottom": "31.76",
"fontsize": "18.0",
"lineheight": "1",
"color": [0, 0, 0, 1],
@@ -170,12 +187,15 @@ TEMPLATES = {
"bold": False,
"italic": False,
"width": "72.00",
- "downward": False,
+ "height": "20.00",
"content": "attendee_company",
"text": "Sample company",
"text_i18n": {},
- "rotation": 180,
+ "rotation": -180,
"align": "center",
+ "verticalalign": "top",
+ "autoresize": True,
+ "splitlongwords": False,
},
],
},
@@ -235,7 +255,9 @@ TEMPLATES = {
"layout": _simple_template(40 * mm, 40 * mm),
},
"88.9x33.87": {
- "label": format_lazy(_("{width} x {height} mm label"), width=88.9, height=33.87),
+ "label": format_lazy(
+ _("{width} x {height} mm label"), width=88.9, height=33.87
+ ),
"pagesize": (88.9 * mm, 33.87 * mm),
"layout": _simple_template(88.9 * mm, 33.87 * mm),
},
diff --git a/src/pretix/plugins/ticketoutputpdf/models.py b/src/pretix/plugins/ticketoutputpdf/models.py
index 8619b627ed..337e69d534 100644
--- a/src/pretix/plugins/ticketoutputpdf/models.py
+++ b/src/pretix/plugins/ticketoutputpdf/models.py
@@ -27,191 +27,279 @@ from django.utils.translation import gettext_lazy as _
from pretix.base.models import LoggedModel
-DEFAULT_TICKET_LAYOUT = '''[{
- "type":"textarea",
- "left":"17.50",
- "bottom":"274.60",
- "fontsize":"16.0",
- "color":[
- 0,
- 0,
- 0,
- 1
- ],
- "fontfamily":"Open Sans",
- "bold":false,
- "italic":false,
- "width":"175.00",
- "content":"event_name",
- "text":"Sample event name",
- "align":"left"
-},
-{
- "type":"textarea",
- "left":"17.50",
- "bottom":"262.90",
- "fontsize":"13.0",
- "color":[
- 0,
- 0,
- 0,
- 1
- ],
- "fontfamily":"Open Sans",
- "bold":false,
- "italic":false,
- "width":"110.00",
- "content":"itemvar",
- "text":"Sample product – sample variation",
- "align":"left"
-},
-{
- "type":"textarea",
- "left":"17.50",
- "bottom":"252.50",
- "fontsize":"13.0",
- "color":[
- 0,
- 0,
- 0,
- 1
- ],
- "fontfamily":"Open Sans",
- "bold":false,
- "italic":false,
- "width":"110.00",
- "content":"attendee_name",
- "text":"John Doe",
- "align":"left"
-},
-{
- "type":"textarea",
- "left":"17.50",
- "bottom":"242.10",
- "fontsize":"13.0",
- "color":[
- 0,
- 0,
- 0,
- 1
- ],
- "fontfamily":"Open Sans",
- "bold":false,
- "italic":false,
- "width":"110.00",
- "content":"event_begin",
- "text":"2016-05-31 20:00",
- "align":"left"
-},
-{
- "type":"textarea",
- "left":"17.50",
- "bottom":"231.70",
- "fontsize":"13.0",
- "color":[
- 0,
- 0,
- 0,
- 1
- ],
- "fontfamily":"Open Sans",
- "bold":false,
- "italic":false,
- "width":"110.00",
- "content":"seat",
- "text":"Ground floor, Row 3, Seat 4",
- "align":"left"
-},
-{
- "type":"textarea",
- "left":"17.50",
- "bottom":"204.80",
- "fontsize":"13.0",
- "color":[
- 0,
- 0,
- 0,
- 1
- ],
- "fontfamily":"Open Sans",
- "bold":false,
- "italic":false,
- "width":"110.00",
- "content":"event_location",
- "text":"Random City",
- "align":"left"
-},
-{
- "type":"textarea",
- "left":"17.50",
- "bottom":"194.50",
- "fontsize":"13.0",
- "color":[
- 0,
- 0,
- 0,
- 1
- ],
- "fontfamily":"Open Sans",
- "bold":false,
- "italic":false,
- "width":"30.00",
- "content":"order",
- "text":"A1B2C",
- "align":"left"
-},
-{
- "type":"textarea",
- "left":"52.50",
- "bottom":"194.50",
- "fontsize":"13.0",
- "color":[
- 0,
- 0,
- 0,
- 1
- ],
- "fontfamily":"Open Sans",
- "bold":false,
- "italic":false,
- "width":"45.00",
- "content":"price",
- "text":"123.45 EUR",
- "align":"right"
-},
-{
- "type":"textarea",
- "left":"102.50",
- "bottom":"194.50",
- "fontsize":"13.0",
- "color":[
- 0,
- 0,
- 0,
- 1
- ],
- "fontfamily":"Open Sans",
- "bold":false,
- "italic":false,
- "width":"90.00",
- "content":"secret",
- "text":"tdmruoekvkpbv1o2mv8xccvqcikvr58u",
- "align":"left"
-},
-{
- "type":"barcodearea",
- "left":"130.40",
- "bottom":"204.50",
- "size":"64.00",
- "content":"secret"
-},
-{
- "type":"poweredby",
- "left":"88.72",
- "bottom":"10.00",
- "size":"20.00",
- "content":"dark"
-}]'''
+DEFAULT_TICKET_LAYOUT = '''[
+ {
+ "type": "barcodearea",
+ "page": 1,
+ "left": "130.40",
+ "bottom": "204.50",
+ "size": "64.00",
+ "content": "secret",
+ "text": "",
+ "text_i18n": {},
+ "nowhitespace": false
+ },
+ {
+ "type": "poweredby",
+ "page": 1,
+ "left": "88.72",
+ "bottom": "10.00",
+ "size": "20.00",
+ "content": "dark"
+ },
+ {
+ "type": "textcontainer",
+ "page": 1,
+ "locale": "",
+ "left": "16.35",
+ "bottom": "272.09",
+ "fontsize": "14.0",
+ "lineheight": "1",
+ "color": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "fontfamily": "Open Sans",
+ "bold": false,
+ "italic": false,
+ "width": "177.07",
+ "height": "11.80",
+ "content": "event_name",
+ "text": "Sample event name",
+ "text_i18n": {},
+ "rotation": 0,
+ "align": "left",
+ "verticalalign": "middle",
+ "autoresize": true,
+ "splitlongwords": true
+ },
+ {
+ "type": "textcontainer",
+ "page": 1,
+ "locale": "",
+ "left": "16.35",
+ "bottom": "261.77",
+ "fontsize": "13.0",
+ "lineheight": "1",
+ "color": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "fontfamily": "Open Sans",
+ "bold": false,
+ "italic": false,
+ "width": "113.03",
+ "height": "7.83",
+ "content": "itemvar",
+ "text": "Sample product – sample variation",
+ "text_i18n": {},
+ "rotation": 0,
+ "align": "left",
+ "verticalalign": "middle",
+ "autoresize": true,
+ "splitlongwords": true
+ },
+ {
+ "type": "textcontainer",
+ "page": 1,
+ "locale": "",
+ "left": "16.35",
+ "bottom": "251.30",
+ "fontsize": "13.0",
+ "lineheight": "1",
+ "color": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "fontfamily": "Open Sans",
+ "bold": false,
+ "italic": false,
+ "width": "113.03",
+ "height": "7.83",
+ "content": "attendee_name",
+ "text": "Dr John Doe",
+ "text_i18n": {},
+ "rotation": 0,
+ "align": "left",
+ "verticalalign": "middle",
+ "autoresize": true,
+ "splitlongwords": true
+ },
+ {
+ "type": "textcontainer",
+ "page": 1,
+ "locale": "",
+ "left": "16.35",
+ "bottom": "240.30",
+ "fontsize": "13.0",
+ "lineheight": "1",
+ "color": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "fontfamily": "Open Sans",
+ "bold": false,
+ "italic": false,
+ "width": "113.03",
+ "height": "7.83",
+ "content": "event_begin",
+ "text": "2017-05-31 20:00",
+ "text_i18n": {},
+ "rotation": 0,
+ "align": "left",
+ "verticalalign": "middle",
+ "autoresize": true,
+ "splitlongwords": true
+ },
+ {
+ "type": "textcontainer",
+ "page": 1,
+ "locale": "",
+ "left": "16.35",
+ "bottom": "231.30",
+ "fontsize": "13.0",
+ "lineheight": "1",
+ "color": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "fontfamily": "Open Sans",
+ "bold": false,
+ "italic": false,
+ "width": "113.03",
+ "height": "7.83",
+ "content": "seat",
+ "text": "Ground floor, Row 3, Seat 4",
+ "text_i18n": {},
+ "rotation": 0,
+ "align": "left",
+ "verticalalign": "middle",
+ "autoresize": true,
+ "splitlongwords": true
+ },
+ {
+ "type": "textcontainer",
+ "page": 1,
+ "locale": "",
+ "left": "16.35",
+ "bottom": "203.43",
+ "fontsize": "13.0",
+ "lineheight": "1",
+ "color": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "fontfamily": "Open Sans",
+ "bold": false,
+ "italic": false,
+ "width": "113.03",
+ "height": "25.70",
+ "content": "event_location",
+ "text": "Random City",
+ "text_i18n": {},
+ "rotation": 0,
+ "align": "left",
+ "verticalalign": "bottom",
+ "autoresize": true,
+ "splitlongwords": true
+ },
+ {
+ "type": "textcontainer",
+ "page": 1,
+ "locale": "",
+ "left": "101.50",
+ "bottom": "193.33",
+ "fontsize": "13.0",
+ "lineheight": "1",
+ "color": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "fontfamily": "Open Sans",
+ "bold": false,
+ "italic": false,
+ "width": "91.93",
+ "height": "7.83",
+ "content": "secret",
+ "text": "tdmruoekvkpbv1o2mv8xccvqcikvr58u",
+ "text_i18n": {},
+ "rotation": 0,
+ "align": "left",
+ "verticalalign": "middle",
+ "autoresize": true,
+ "splitlongwords": true
+ },
+ {
+ "type": "textcontainer",
+ "page": 1,
+ "locale": "",
+ "left": "51.50",
+ "bottom": "193.33",
+ "fontsize": "13.0",
+ "lineheight": "1",
+ "color": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "fontfamily": "Open Sans",
+ "bold": false,
+ "italic": false,
+ "width": "47.38",
+ "height": "7.83",
+ "content": "price",
+ "text": "123.45 EUR",
+ "text_i18n": {},
+ "rotation": 0,
+ "align": "right",
+ "verticalalign": "middle",
+ "autoresize": true,
+ "splitlongwords": true
+ },
+ {
+ "type": "textcontainer",
+ "page": 1,
+ "locale": "",
+ "left": "16.50",
+ "bottom": "193.33",
+ "fontsize": "13.0",
+ "lineheight": "1",
+ "color": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "fontfamily": "Open Sans",
+ "bold": false,
+ "italic": false,
+ "width": "32.32",
+ "height": "7.83",
+ "content": "order",
+ "text": "A1B2C",
+ "text_i18n": {},
+ "rotation": 0,
+ "align": "left",
+ "verticalalign": "middle",
+ "autoresize": true,
+ "splitlongwords": true
+ }
+]'''
def bg_name(instance, filename: str) -> str:
diff --git a/src/pretix/static/pretixcontrol/js/ui/editor.js b/src/pretix/static/pretixcontrol/js/ui/editor.js
index 426e19ca26..a5e731254d 100644
--- a/src/pretix/static/pretixcontrol/js/ui/editor.js
+++ b/src/pretix/static/pretixcontrol/js/ui/editor.js
@@ -47,6 +47,105 @@ fabric.Imagearea = fabric.util.createClass(fabric.Rect, {
fabric.Imagearea.fromObject = function (object, callback, forceAsync) {
return fabric.Object._fromObject('Imagearea', object, callback, forceAsync);
};
+fabric.Textcontainer = fabric.util.createClass(fabric.Rect, {
+ type: 'textcontainer',
+
+ initialize: function (text, options) {
+ options || (options = {});
+
+ this.callSuper('initialize', options);
+
+ //this.textbox = new fabric.Textbox(text, JSON.parse(JSON.stringify(options)));
+ this.set('content', options.content || '');
+
+ this.cacheProperties.push(
+ "width", "height", "fontSize", "lineHeight", "fill", "fontFamily", "fontWeight",
+ "fontStyle", "text", "textAlign", "splitLongWords", "splitByGrapheme", "verticalAlign",
+ "autoResize",
+ )
+ },
+
+ toObject: function (propertiesToInclude) {
+ return this.callSuper('toObject', ['content'].concat(propertiesToInclude));
+ },
+
+ _internalTextbox: function () {
+ var fontSize = parseFloat(this.fontSize);
+ var text = (this.text || "").replace("-", "-\u200B");
+ while (true) {
+ var tmptext = new fabric.Textbox(text, {
+ _wordJoiners: /[ \t\r\u200B]/u,
+ left: 0,
+ top: 0,
+ originY: 'top',
+ originX: 'left',
+ width: this.width,
+ height: this.height,
+ fontSize: fontSize,
+ lineHeight: this.lineHeight,
+ fill: this.fill,
+ fontFamily: this.fontFamily,
+ fontWeight: this.fontWeight,
+ fontStyle: this.fontStyle,
+ text: text,
+ textAlign: this.textAlign,
+ splitByGrapheme: this.splitLongWords
+ })
+ tmptext.setCoords();
+ var lineHeights = 0;
+ for (var i = 0, len = tmptext._textLines.length; i < len; i++) {
+ var heightOfLine = tmptext.getHeightOfLine(i);
+ lineHeights += heightOfLine;
+ }
+ if (!this.autoResize || (lineHeights <= this.height && tmptext.width <= this.width) || fontSize <= 1.0) {
+ return {textbox: tmptext, height: lineHeights, width: tmptext.width}
+ }
+ if (lineHeights > this.height) { // we can do larger steps for height
+ fontSize = fontSize - Math.max(1.0, fontSize * .1)
+ } else {
+ fontSize = fontSize - Math.max(.25, fontSize * .025)
+ }
+ }
+ },
+
+ _render: function (ctx) {
+ var h = this.height, w = this.width;
+
+ /*
+ var x = -this.width / 2,
+ y = -this.height / 2;
+ ctx.fillStyle = '#ccc';
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ ctx.lineTo(x + w, y);
+ ctx.lineTo(x + w, y + h);
+ ctx.lineTo(x, y + h);
+ ctx.lineTo(x, y);
+ ctx.closePath();
+ ctx.fill();
+ */
+ var { textbox, height, width } = this._internalTextbox();
+
+ ctx.save();
+ if (this.verticalAlign === "top") {
+ ctx.translate(0, - (h - height) / 2);
+ } else if (this.verticalAlign === "bottom") {
+ ctx.translate(0, (h - height) / 2);
+ }
+
+ // it is entirely unclear to me why overflow is always rendered centered, so we manually readjust
+ if (this.textAlign === "left" && width > w) {
+ ctx.translate(-(w - width) / 2, 0);
+ } else if (this.verticalAlign === "right" && width > w) {
+ ctx.translate((w - width) / 2, 0);
+ }
+ textbox._render(ctx);
+ ctx.restore();
+ },
+});
+fabric.Textcontainer.fromObject = function (object, callback, forceAsync) {
+ return fabric.Object._fromObject('Textcontainer', object, callback, forceAsync);
+};
fabric.Barcodearea = fabric.util.createClass(fabric.Rect, {
type: 'barcodearea',
@@ -150,9 +249,10 @@ var editor = {
top += o.group.top + o.group.height / 2;
left += o.group.left + o.group.width / 2;
}
+ var col, bottom;
if (o.type === "textarea") {
- var col = (new fabric.Color(o.fill))._source;
- var bottom = editor.pdf_viewport.height - o.height - top;
+ col = (new fabric.Color(o.fill))._source;
+ bottom = editor.pdf_viewport.height - o.height - top;
if (o.downward) {
bottom = editor.pdf_viewport.height - top;
}
@@ -176,6 +276,32 @@ var editor = {
rotation: o.angle,
align: o.textAlign,
});
+ } else if (o.type === "textcontainer") {
+ col = (new fabric.Color(o.fill))._source;
+ bottom = editor.pdf_viewport.height - o.height - top;
+ d.push({
+ type: "textcontainer",
+ page: editor.pdf_page_number,
+ locale: $("#pdf-info-locale").val(),
+ left: editor._px2mm(left).toFixed(2),
+ bottom: editor._px2mm(bottom).toFixed(2),
+ fontsize: editor._px2pt(o.fontSize).toFixed(1),
+ lineheight: o.lineHeight,
+ color: col,
+ fontfamily: o.fontFamily,
+ bold: o.fontWeight === 'bold',
+ italic: o.fontStyle === 'italic',
+ width: editor._px2mm(o.width).toFixed(2),
+ height: editor._px2mm(o.height).toFixed(2),
+ content: o.content,
+ text: o.text,
+ text_i18n: o.text_i18n || {},
+ rotation: o.angle,
+ align: o.textAlign || 'left',
+ verticalalign: o.verticalAlign || 'middle',
+ autoresize: o.autoResize || false,
+ splitlongwords: o.splitLongWords || false,
+ });
} else if (o.type === "imagearea") {
d.push({
type: "imagearea",
@@ -239,6 +365,36 @@ var editor = {
o = editor._add_poweredby(d.content);
o.content = d.content;
o.scaleToHeight(editor._mm2px(d.size));
+ } else if (d.type === "textcontainer") {
+ o = editor._add_textcontainer();
+ o.set('fill', 'rgb(' + d.color[0] + ',' + d.color[1] + ',' + d.color[2] + ')');
+ o.set('fontSize', editor._pt2px(d.fontsize));
+ o.set('lineHeight', d.lineheight || 1);
+ o.set('fontFamily', d.fontfamily);
+ o.set('fontWeight', d.bold ? 'bold' : 'normal');
+ o.set('fontStyle', d.italic ? 'italic' : 'normal');
+ o.content = d.content;
+ o.set('textAlign', d.align);
+ o.set('verticalAlign', d.verticalalign);
+ o.set('autoResize', d.autoresize);
+ o.set('splitLongWords', d.splitlongwords);
+ if (d.rotation) {
+ o.rotate(d.rotation);
+ }
+ if (d.content === "other") {
+ o.set('text', d.text);
+ } else if (d.content === "other_i18n") {
+ o.text_i18n = d.text_i18n
+ o.set('text', d.text_i18n[Object.keys(d.text_i18n)[0]]);
+ } else if (d.content) {
+ o.set('text', editor._get_text_sample(d.content));
+ }
+ o.set('width', editor._mm2px(d.width)); // needs to be after setText
+ o.set('height', editor._mm2px(d.height)); // needs to be after setText
+ if (d.locale) {
+ // The data format allows to set the locale per text field but we currently only expose a global field
+ $("#pdf-info-locale").val(d.locale);
+ }
} else if (d.type === "textarea" || o.type === "text") {
o = editor._add_text();
o.set('fill', 'rgb(' + d.color[0] + ',' + d.color[1] + ',' + d.color[2] + ')');
@@ -455,6 +611,11 @@ var editor = {
},
_update_toolbox_values: function () {
+ $("#version-notice").toggle(
+ editor._other_page_objects.some((o) => o.type === "textcontainer") ||
+ editor.fabric.getObjects().some((o) => o.type === "textcontainer")
+ );
+
var o = editor.fabric.getActiveObject();
if (!o) {
return;
@@ -484,6 +645,35 @@ var editor = {
} else if (o.type === "poweredby") {
$("#toolbox-squaresize").val(editor._px2mm(o.height * o.scaleY).toFixed(2));
$("#toolbox-poweredby-style").val(o.content);
+ } else if (o.type === "textcontainer") {
+ var col = (new fabric.Color(o.fill))._source;
+ $("#toolbox-col").val("#" + ((1 << 24) + (col[0] << 16) + (col[1] << 8) + col[2]).toString(16).slice(1));
+ $("#toolbox-fontsize").val(editor._px2pt(o.fontSize).toFixed(1));
+ $("#toolbox-lineheight").val(o.lineHeight || 1);
+ $("#toolbox-fontfamily").val(o.fontFamily);
+ $("#toolbox").find("button[data-action=bold]").toggleClass('active', o.fontWeight === 'bold');
+ $("#toolbox").find("button[data-action=italic]").toggleClass('active', o.fontStyle === 'italic');
+ $("#toolbox").find("button[data-action=autoresize]").toggleClass('active', o.autoResize || false)
+ $("#toolbox").find("button[data-action=splitlongwords]").toggleClass('active', o.splitLongWords || false)
+ $("#toolbox").find("button[data-action=left]").toggleClass('active', o.textAlign === 'left' || !o.textAlign);
+ $("#toolbox").find("button[data-action=center]").toggleClass('active', o.textAlign === 'center');
+ $("#toolbox").find("button[data-action=right]").toggleClass('active', o.textAlign === 'right');
+ $("#toolbox").find("button[data-action=top]").toggleClass('active', o.verticalAlign === 'top' || !o.verticalAlign);
+ $("#toolbox").find("button[data-action=middle]").toggleClass('active', o.verticalAlign === 'middle');
+ $("#toolbox").find("button[data-action=bottom]").toggleClass('active', o.verticalAlign === 'bottom');
+
+ if (o.scaleY !== 1 || o.scaleX !== 1) {
+ o.set({
+ height: o.height * o.scaleY,
+ width: o.width * o.scaleX,
+ scaleX: 1,
+ scaleY: 1
+ });
+ }
+
+ $("#toolbox-height").val(editor._px2mm(o.height).toFixed(2));
+ $("#toolbox-width").val(editor._px2mm(o.width).toFixed(2));
+ $("#toolbox-textrotation").val((o.angle || 0.0).toFixed(1));
} else if (o.type === "text" || o.type === "textarea") {
var col = (new fabric.Color(o.fill))._source;
$("#toolbox-col").val("#" + ((1 << 24) + (col[0] << 16) + (col[1] << 8) + col[2]).toString(16).slice(1));
@@ -500,7 +690,7 @@ var editor = {
$("#toolbox-textrotation").val((o.angle || 0.0).toFixed(1));
}
- if (o.type === "textarea" || o.type === "barcodearea") {
+ if (o.type === "textarea" || o.type === "barcodearea" || o.type === "textcontainer") {
if (!o.content && o.type == "barcodearea") {
o.content = "secret";
}
@@ -598,6 +788,50 @@ var editor = {
editor.fabric.discardActiveObject();
editor.fabric.setActiveObject(newo);
}
+ } else if (o.type === "textcontainer") {
+ o.set('fill', $("#toolbox-col").val());
+ o.set('fontSize', editor._pt2px($("#toolbox-fontsize").val()));
+ o.set('lineHeight', $("#toolbox-lineheight").val() || 1);
+ o.set('fontFamily', $("#toolbox-fontfamily").val());
+ o.set('fontWeight', $("#toolbox").find("button[data-action=bold]").is('.active') ? 'bold' : 'normal');
+ o.set('fontStyle', $("#toolbox").find("button[data-action=italic]").is('.active') ? 'italic' : 'normal');
+ var align = $("#toolbox-align").find(".active").attr("data-action");
+ if (align) {
+ o.set('textAlign', align);
+ }
+ var verticalAlign = $("#toolbox-verticalalign").find(".active").attr("data-action");
+ if (verticalAlign) {
+ o.set('verticalAlign', verticalAlign);
+ }
+ o.set('autoResize', $("#toolbox").find("button[data-action=autoresize]").is('.active'));
+ o.set('splitLongWords', $("#toolbox").find("button[data-action=splitlongwords]").is('.active'));
+ // todo: verticalalign
+ o.rotate(parseFloat($("#toolbox-textrotation").val()));
+ $("#toolbox-content-other").toggle($("#toolbox-content").val() === "other");
+ $("#toolbox-content-other-i18n").toggle($("#toolbox-content").val() === "other_i18n");
+ $("#toolbox-content-other-help").toggle($("#toolbox-content").val() === "other" || $("#toolbox-content").val() === "other_i18n");
+ o.content = $("#toolbox-content").val();
+ if ($("#toolbox-content").val() === "other") {
+ if (e.target.id === "toolbox-content") {
+ // user used dropdown to switch content-type, update value with value from i18n textarea
+ $("#toolbox-content-other").val($("#toolbox-content-other-i18n textarea").val());
+ }
+ o.set('text', $("#toolbox-content-other").val());
+ } else if ($("#toolbox-content").val() === "other_i18n") {
+ if (e.target.id === "toolbox-content") {
+ // user used dropdown to switch content-type, update value with value from "other" textarea
+ $("#toolbox-content-other-i18n textarea").val($("#toolbox-content-other").val());
+ }
+ o.text_i18n = {}
+ $("#toolbox-content-other-i18n textarea").each(function () {
+ o.text_i18n[$(this).attr("lang")] = $(this).val();
+ });
+ o.set('text', $("#toolbox-content-other-i18n textarea").first().val());
+ } else {
+ o.set('text', editor._get_text_sample($("#toolbox-content").val()));
+ }
+ o.set('width', editor._mm2px($("#toolbox-width").val()));
+ o.set('height', editor._mm2px($("#toolbox-height").val()));
} else if (o.type === "textarea" || o.type === "text") {
o.set('fill', $("#toolbox-col").val());
o.set('fontSize', editor._pt2px($("#toolbox-fontsize").val()));
@@ -658,7 +892,9 @@ var editor = {
var o = selected[0];
$("#toolbox").attr("data-type", o.type);
if (o.type === "textarea" || o.type === "text") {
- $("#toolbox-heading").text(gettext("Text object"));
+ $("#toolbox-heading").text(gettext("Text object (deprecated)"));
+ } else if (o.type === "textarea" || o.type === "text" || o.type === "textcontainer") {
+ $("#toolbox-heading").text(gettext("Text box"));
} else if (o.type === "barcodearea") {
$("#toolbox-heading").text(gettext("Barcode area"));
} else if (o.type === "imagearea") {
@@ -727,6 +963,44 @@ var editor = {
return rect;
},
+ _add_textcontainer: function () {
+ var rect = new fabric.Textcontainer(editor._get_text_sample('event_name'), {
+ left: 100,
+ top: 100,
+ width: 300,
+ height: 29,
+ lockRotation: false,
+ fill: '#000',
+ content: 'event_name',
+ text: editor._get_text_sample('event_name'),
+ fontFamily: 'Open Sans',
+ fontStyle: 'normal',
+ lineHeight: 1,
+ editable: false,
+ fontSize: editor._pt2px(13),
+ verticalAlign: 'middle',
+ textAlign: 'left',
+ splitLongWords: true,
+ autoResize: true,
+ lockScalingFlip: true,
+ });
+ rect.setControlsVisibility({
+ 'tr': true,
+ 'tl': true,
+ 'mt': true,
+ 'br': true,
+ 'bl': true,
+ 'mb': true,
+ 'mr': true,
+ 'ml': true,
+ 'mtr': true
+ });
+ editor.fabric.add(rect);
+ editor._create_savepoint();
+ $("#version-notice").show();
+ return rect;
+ },
+
_add_imagearea: function () {
var rect = new fabric.Imagearea({
left: 100,
@@ -1063,6 +1337,7 @@ var editor = {
editor._load_pdf();
$("#editor-add-qrcode, #editor-add-qrcode-lead, #editor-add-qrcode-other").click(editor._add_qrcode);
$("#editor-add-image").click(editor._add_imagearea);
+ $("#editor-add-textcontainer").click(editor._add_textcontainer);
$("#editor-add-text").click(editor._add_text);
$("#editor-add-poweredby").click(function() {editor._add_poweredby("dark")});
editor.$cva.get(0).tabIndex = 1000;
@@ -1144,6 +1419,7 @@ var editor = {
editor._update_save_button();
});
$("#pdf-info-width, #pdf-info-height").bind('change input', editor._paper_size_warning);
+ editor._update_toolbox_values();
$.getJSON($("#schema-url").text(), function (data) {
editor.schema = data;
diff --git a/src/pretix/static/pretixcontrol/js/ui/main.js b/src/pretix/static/pretixcontrol/js/ui/main.js
index 07d8349b85..a58dac8128 100644
--- a/src/pretix/static/pretixcontrol/js/ui/main.js
+++ b/src/pretix/static/pretixcontrol/js/ui/main.js
@@ -298,29 +298,45 @@ var form_handlers = function (el) {
}
}
}).not(".no-contrast").on('changeColor create', function (e) {
+ if (e.type == 'changeColor' && !e.value) {
+ return;
+ }
var rgb = $(this).colorpicker('color').toRGB();
var c = contrast([255,255,255], [rgb.r, rgb.g, rgb.b]);
var mark = 'times';
- if ($(this).parent().find(".contrast-state").length === 0) {
+ var $icon = $(this).parent().find(".contrast-icon");
+ if ($icon.length === 0 && $(this).parent().find(".contrast-state").length === 0) {
$(this).parent().append("
");
}
var $note = $(this).parent().find(".contrast-state");
if ($(this).val() === "") {
$note.remove();
}
+ var icon, text, cls;
if (c > 7) {
- $note.html("
")
- .append(gettext('Your color has great contrast and is very easy to read!'));
- $note.addClass("text-success").removeClass("text-warning").removeClass("text-danger");
+ icon = "fa-check-circle";
+ text = gettext('Your color has great contrast and is very easy to read!');
+ cls = "text-success";
} else if (c > 2.5) {
- $note.html("
")
- .append(gettext('Your color has decent contrast and is probably good-enough to read!'));
- $note.removeClass("text-success").removeClass("text-warning").removeClass("text-danger");
+ icon = "fa-info-circle";
+ text = gettext('Your color has decent contrast and is probably good-enough to read!');
+ cls = "";
} else {
- $note.html("
")
- .append(gettext('Your color has bad contrast for text on white background, please choose a darker ' +
- 'shade.'));
- $note.addClass("text-danger").removeClass("text-success").removeClass("text-warning");
+ icon = "fa-warning";
+ text = gettext('Your color has bad contrast for text on white background, please choose a darker shade.');
+ cls = "text-danger";
+ }
+ if ($icon.length === 0) {
+ $note.html("
")
+ .append(text);
+ $note.removeClass("text-success").removeClass("text-danger").addClass(cls);
+ } else {
+ $icon.html("
")
+ $icon.attr("title", text);
+ $icon.tooltip('destroy');
+ window.setTimeout(function() {
+ $icon.tooltip({"title": text});
+ }, 250);
}
});
diff --git a/src/pretix/static/pretixcontrol/scss/pdfeditor.css b/src/pretix/static/pretixcontrol/scss/pdfeditor.css
index c4a84e5e65..6615b74b83 100644
--- a/src/pretix/static/pretixcontrol/scss/pdfeditor.css
+++ b/src/pretix/static/pretixcontrol/scss/pdfeditor.css
@@ -19,6 +19,8 @@ body {
#toolbox .poweredby,
#toolbox[data-type] .pdf-info,
#toolbox .text,
+#toolbox .textarea,
+#toolbox .textcontainer,
#toolbox .imagecontent,
#toolbox .object-buttons {
display: none;
@@ -30,10 +32,23 @@ body {
#toolbox[data-type=imagearea] .imagecontent,
#toolbox[data-type=poweredby] .poweredby,
#toolbox[data-type=text] .text,
+#toolbox[data-type=text] .textarea,
+#toolbox[data-type=textcontainer] .text,
+#toolbox[data-type=textcontainer] .textcontainer,
+#toolbox[data-type=textcontainer] .rectsize,
#toolbox[data-type=textarea] .text,
+#toolbox[data-type=textarea] .textarea,
#toolbox[data-type] .object-buttons {
display: block;
}
+#toolbox[data-type=text] .btn-group-justified > .btn-group.text,
+#toolbox[data-type=text] .btn-group-justified > .btn-group.textarea,
+#toolbox[data-type=textcontainer] .btn-group-justified > .btn-group.text,
+#toolbox[data-type=textcontainer] .btn-group-justified > .btn-group.textcontainer,
+#toolbox[data-type=textarea] .btn-group-justified > .btn-group.text,
+#toolbox[data-type=textarea] .btn-group-justified > .btn-group.textarea {
+ display: table-cell;
+}
#loading-container {
position: absolute;
top: 0;