mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Fix #41 -- Drag-and-drop ticket editor
Undo/redo Useful toolbox Font selection Add text content Use hex for colors JS-side dump and load Save Load layout, proper undo/redo First steps to Python rendering More PDF rendering Copy and paste Buttons for keyboard actions Splash Screen Block unbeforeunload in dirty state Remove debugging output Preview Upload new PDFs via the editor Fix bugs during PDF reload, link in settings form New default ticket Add OpenSans BI Custom fonts, fix tests
This commit is contained in:
@@ -1,17 +1,31 @@
|
||||
import copy
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
import uuid
|
||||
from io import BytesIO
|
||||
|
||||
from django import forms
|
||||
from django.contrib.staticfiles import finders
|
||||
from django.core.files import File
|
||||
from django.core.files.storage import default_storage
|
||||
from django.http import HttpRequest
|
||||
from django.template.loader import get_template
|
||||
from django.utils.formats import localize
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from reportlab.graphics import renderPDF
|
||||
from reportlab.graphics.barcode.qr import QrCodeWidget
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.lib.colors import Color
|
||||
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
|
||||
from reportlab.lib.styles import ParagraphStyle
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.pdfmetrics import getAscentDescent
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.platypus import Paragraph
|
||||
|
||||
from pretix.base.models import Order
|
||||
from pretix.base.models import Order, OrderPosition
|
||||
from pretix.base.ticketoutput import BaseTicketOutput
|
||||
from pretix.control.forms import ExtFileField
|
||||
from pretix.plugins.ticketoutputpdf.signals import get_fonts
|
||||
|
||||
logger = logging.getLogger('pretix.plugins.ticketoutputpdf')
|
||||
|
||||
@@ -21,71 +35,97 @@ class PdfTicketOutput(BaseTicketOutput):
|
||||
verbose_name = _('PDF output')
|
||||
download_button_text = _('PDF')
|
||||
|
||||
def _draw_page(self, p, op, order):
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.lib import units
|
||||
from reportlab.graphics.barcode.qr import QrCodeWidget
|
||||
from reportlab.graphics import renderPDF
|
||||
def __init__(self, event, override_layout=None, override_background=None):
|
||||
self.override_layout = override_layout
|
||||
self.override_background = override_background
|
||||
super().__init__(event)
|
||||
|
||||
event_s = self.settings.get('event_s', default=22, as_type=float)
|
||||
if event_s:
|
||||
p.setFont("Helvetica", event_s)
|
||||
event_x = self.settings.get('event_x', default=15, as_type=float)
|
||||
event_y = self.settings.get('event_y', default=235, as_type=float)
|
||||
p.drawString(event_x * units.mm, event_y * units.mm, str(self.event.name))
|
||||
def _register_fonts(self):
|
||||
pdfmetrics.registerFont(TTFont('Open Sans', finders.find('fonts/OpenSans-Regular.ttf')))
|
||||
pdfmetrics.registerFont(TTFont('Open Sans I', finders.find('fonts/OpenSans-Italic.ttf')))
|
||||
pdfmetrics.registerFont(TTFont('Open Sans B', finders.find('fonts/OpenSans-Bold.ttf')))
|
||||
pdfmetrics.registerFont(TTFont('Open Sans B I', finders.find('fonts/OpenSans-BoldItalic.ttf')))
|
||||
|
||||
order_s = self.settings.get('order_s', default=17, as_type=float)
|
||||
if order_s:
|
||||
p.setFont("Helvetica", order_s)
|
||||
order_x = self.settings.get('order_x', default=15, as_type=float)
|
||||
order_y = self.settings.get('order_y', default=220, as_type=float)
|
||||
p.drawString(order_x * units.mm, order_y * units.mm, _('Order code: {code}').format(code=order.code))
|
||||
for family, styles in get_fonts().items():
|
||||
print(family, finders.find(styles['regular']['truetype']))
|
||||
pdfmetrics.registerFont(TTFont(family, finders.find(styles['regular']['truetype'])))
|
||||
pdfmetrics.registerFont(TTFont(family + ' I', finders.find(styles['italic']['truetype'])))
|
||||
pdfmetrics.registerFont(TTFont(family + ' B', finders.find(styles['bold']['truetype'])))
|
||||
pdfmetrics.registerFont(TTFont(family + ' B I', finders.find(styles['bolditalic']['truetype'])))
|
||||
|
||||
name_s = self.settings.get('name_s', default=17, as_type=float)
|
||||
if name_s:
|
||||
p.setFont("Helvetica", name_s)
|
||||
name_x = self.settings.get('name_x', default=15, as_type=float)
|
||||
name_y = self.settings.get('name_y', default=210, as_type=float)
|
||||
item = str(op.item.name)
|
||||
if op.variation:
|
||||
item += " – " + str(op.variation)
|
||||
p.drawString(name_x * units.mm, name_y * units.mm, item)
|
||||
def _draw_barcodearea(self, canvas: Canvas, op: OrderPosition, o: dict):
|
||||
reqs = float(o['size']) * mm
|
||||
qrw = QrCodeWidget(op.secret, barLevel='H', barHeight=reqs, barWidth=reqs)
|
||||
d = Drawing(reqs, reqs)
|
||||
d.add(qrw)
|
||||
qr_x = float(o['left']) * mm
|
||||
qr_y = float(o['bottom']) * mm
|
||||
renderPDF.draw(d, canvas, qr_x, qr_y)
|
||||
|
||||
price_s = self.settings.get('price_s', default=17, as_type=float)
|
||||
if price_s:
|
||||
p.setFont("Helvetica", price_s)
|
||||
price_x = self.settings.get('price_x', default=15, as_type=float)
|
||||
price_y = self.settings.get('price_y', default=200, as_type=float)
|
||||
p.drawString(price_x * units.mm, price_y * units.mm, "%s %s" % (str(op.price), self.event.currency))
|
||||
def _get_text_content(self, op: OrderPosition, order: Order, o: dict):
|
||||
if o['content'] == 'other':
|
||||
return o['text'].replace("\n", "<br/>\n")
|
||||
elif o['content'] == 'order':
|
||||
return order.code
|
||||
elif o['content'] == 'item':
|
||||
return str(op.item)
|
||||
elif o['content'] == 'secret':
|
||||
return op.secret
|
||||
elif o['content'] == 'variation':
|
||||
return str(op.variation) if op.variation else ''
|
||||
elif o['content'] == 'itemvar':
|
||||
return '{} - {}'.format(op.item, op.variation) if op.variation else str(op.item)
|
||||
elif o['content'] == 'price':
|
||||
return '{} {}'.format(order.event.currency, localize(op.price))
|
||||
elif o['content'] == 'attendee_name':
|
||||
return op.attendee_name or (op.addon_to.attendee_name if op.addon_to else '')
|
||||
elif o['content'] == 'event_name':
|
||||
return str(order.event)
|
||||
elif o['content'] == 'event_location':
|
||||
return str(order.event.location)
|
||||
elif o['content'] == 'event_date':
|
||||
return order.event.get_date_from_display(show_times=False)
|
||||
elif o['content'] == 'event_begin_time':
|
||||
return order.event.get_date_from_display(show_times=False)
|
||||
return ''
|
||||
|
||||
qr_s = self.settings.get('qr_s', default=80, as_type=float)
|
||||
if qr_s:
|
||||
reqs = qr_s * units.mm
|
||||
qrw = QrCodeWidget(op.secret, barLevel='H')
|
||||
b = qrw.getBounds()
|
||||
w = b[2] - b[0]
|
||||
h = b[3] - b[1]
|
||||
d = Drawing(reqs, reqs, transform=[reqs / w, 0, 0, reqs / h, 0, 0])
|
||||
d.add(qrw)
|
||||
qr_x = self.settings.get('qr_x', default=10, as_type=float)
|
||||
qr_y = self.settings.get('qr_y', default=120, as_type=float)
|
||||
renderPDF.draw(d, p, qr_x * units.mm, qr_y * units.mm)
|
||||
def _draw_textarea(self, canvas: Canvas, op: OrderPosition, order: Order, o: dict):
|
||||
font = o['fontfamily']
|
||||
if o['bold']:
|
||||
font += ' B'
|
||||
if o['italic']:
|
||||
font += ' I'
|
||||
|
||||
code_s = self.settings.get('code_s', default=11, as_type=float)
|
||||
if code_s:
|
||||
p.setFont("Helvetica", code_s)
|
||||
code_x = self.settings.get('code_x', default=15, as_type=float)
|
||||
code_y = self.settings.get('code_y', default=120, as_type=float)
|
||||
p.drawString(code_x * units.mm, code_y * units.mm, op.secret)
|
||||
align_map = {
|
||||
'left': TA_LEFT,
|
||||
'center': TA_CENTER,
|
||||
'right': TA_RIGHT
|
||||
}
|
||||
style = ParagraphStyle(
|
||||
name=uuid.uuid4().hex,
|
||||
fontName=font,
|
||||
fontSize=float(o['fontsize']),
|
||||
leading=float(o['fontsize']),
|
||||
autoLeading="max",
|
||||
textColor=Color(o['color'][0] / 255, o['color'][1] / 255, o['color'][2] / 255),
|
||||
alignment=align_map[o['align']]
|
||||
)
|
||||
|
||||
attendee_s = self.settings.get('attendee_s', default=0, as_type=float)
|
||||
if attendee_s and op.attendee_name:
|
||||
p.setFont("Helvetica", attendee_s)
|
||||
attendee_x = self.settings.get('attendee_x', default=15, as_type=float)
|
||||
attendee_y = self.settings.get('attendee_y', default=90, as_type=float)
|
||||
p.drawString(attendee_x * units.mm, attendee_y * units.mm, op.attendee_name)
|
||||
p = Paragraph(self._get_text_content(op, order, o), style=style)
|
||||
p.wrapOn(canvas, float(o['width']) * mm, 1000 * mm)
|
||||
# p_size = p.wrap(float(o['width']) * mm, 1000 * mm)
|
||||
ad = getAscentDescent(font, float(o['fontsize']))
|
||||
p.drawOn(canvas, float(o['left']) * mm, float(o['bottom']) * mm - ad[1])
|
||||
|
||||
p.showPage()
|
||||
def _draw_page(self, canvas: Canvas, op: OrderPosition, order: Order):
|
||||
objs = self.override_layout or self.settings.get('layout', as_type=list) or self._legacy_layout()
|
||||
for o in objs:
|
||||
if o['type'] == "barcodearea":
|
||||
self._draw_barcodearea(canvas, op, o)
|
||||
elif o['type'] == "textarea":
|
||||
self._draw_textarea(canvas, op, order, o)
|
||||
|
||||
canvas.showPage()
|
||||
|
||||
def generate_order(self, order: Order):
|
||||
buffer = BytesIO()
|
||||
@@ -113,97 +153,198 @@ class PdfTicketOutput(BaseTicketOutput):
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib import pagesizes
|
||||
|
||||
pagesize = self.settings.get('pagesize', default='A4')
|
||||
if hasattr(pagesizes, pagesize):
|
||||
pagesize = getattr(pagesizes, pagesize)
|
||||
else:
|
||||
pagesize = pagesizes.A4
|
||||
orientation = self.settings.get('orientation', default='portrait')
|
||||
if hasattr(pagesizes, orientation):
|
||||
pagesize = getattr(pagesizes, orientation)(pagesize)
|
||||
# Doesn't matter as we'll overpaint it over a background later
|
||||
pagesize = pagesizes.A4
|
||||
|
||||
self._register_fonts()
|
||||
return canvas.Canvas(buffer, pagesize=pagesize)
|
||||
|
||||
def _render_with_background(self, buffer):
|
||||
def _render_with_background(self, buffer, title=_('Ticket')):
|
||||
from PyPDF2 import PdfFileWriter, PdfFileReader
|
||||
buffer.seek(0)
|
||||
new_pdf = PdfFileReader(buffer)
|
||||
output = PdfFileWriter()
|
||||
bg_file = self.settings.get('background', as_type=File)
|
||||
if isinstance(bg_file, File):
|
||||
if self.override_background:
|
||||
bgf = default_storage.open(self.override_background.name, "rb")
|
||||
elif isinstance(bg_file, File):
|
||||
bgf = default_storage.open(bg_file.name, "rb")
|
||||
else:
|
||||
bgf = open(finders.find('pretixpresale/pdf/ticket_default_a4.pdf'), "rb")
|
||||
bg_pdf = PdfFileReader(bgf)
|
||||
|
||||
for page in new_pdf.pages:
|
||||
bg_page = copy.copy(bg_pdf.getPage(0))
|
||||
bg_page.mergePage(page)
|
||||
output.addPage(bg_page)
|
||||
|
||||
output.addMetadata({
|
||||
'/Title': str(title),
|
||||
'/Creator': 'pretix',
|
||||
})
|
||||
outbuffer = BytesIO()
|
||||
output.write(outbuffer)
|
||||
outbuffer.seek(0)
|
||||
return outbuffer
|
||||
|
||||
@property
|
||||
def settings_form_fields(self) -> dict:
|
||||
return OrderedDict(
|
||||
list(super().settings_form_fields.items()) + [
|
||||
('paper_size',
|
||||
forms.ChoiceField(
|
||||
label=_('Paper size'),
|
||||
choices=(
|
||||
('A4', 'A4'),
|
||||
('A5', 'A5'),
|
||||
('B4', 'B4'),
|
||||
('B5', 'B5'),
|
||||
('letter', 'Letter'),
|
||||
('legal', 'Legal'),
|
||||
),
|
||||
required=False
|
||||
)),
|
||||
('orientation',
|
||||
forms.ChoiceField(
|
||||
label=_('Paper orientation'),
|
||||
choices=(
|
||||
('portrait', _('Portrait')),
|
||||
('landscape', _('Landscape')),
|
||||
),
|
||||
required=False
|
||||
)),
|
||||
('background',
|
||||
ExtFileField(
|
||||
label=_('Background PDF'),
|
||||
ext_whitelist=(".pdf", ),
|
||||
required=False
|
||||
)),
|
||||
('qr_x', forms.FloatField(label=_('QR-Code x position (mm)'), required=False)),
|
||||
('qr_y', forms.FloatField(label=_('QR-Code y position (mm)'), required=False)),
|
||||
('qr_s', forms.FloatField(label=_('QR-Code size (mm)'), required=False)),
|
||||
('code_x', forms.FloatField(label=_('Ticket code x position (mm)'), required=False)),
|
||||
('code_y', forms.FloatField(label=_('Ticket code y position (mm)'), required=False)),
|
||||
('code_s', forms.FloatField(label=_('Ticket code size (mm)'), required=False,
|
||||
help_text=_('Visible by default, set this to 0 to hide the element.'))),
|
||||
('order_x', forms.FloatField(label=_('Order x position (mm)'), required=False)),
|
||||
('order_y', forms.FloatField(label=_('Order y position (mm)'), required=False)),
|
||||
('order_s', forms.FloatField(label=_('Order size (mm)'), required=False,
|
||||
help_text=_('Visible by default, set this to 0 to hide the element.'))),
|
||||
('name_x', forms.FloatField(label=_('Product name x position (mm)'), required=False)),
|
||||
('name_y', forms.FloatField(label=_('Product name y position (mm)'), required=False)),
|
||||
('name_s', forms.FloatField(label=_('Product name size (mm)'), required=False,
|
||||
help_text=_('Visible by default, set this to 0 to hide the element.'))),
|
||||
('price_x', forms.FloatField(label=_('Price x position (mm)'), required=False)),
|
||||
('price_y', forms.FloatField(label=_('Price y position (mm)'), required=False)),
|
||||
('price_s', forms.FloatField(label=_('Price size (mm)'), required=False,
|
||||
help_text=_('Visible by default, set this to 0 to hide the element.'))),
|
||||
('event_x', forms.FloatField(label=_('Event name x position (mm)'), required=False)),
|
||||
('event_y', forms.FloatField(label=_('Event name y position (mm)'), required=False)),
|
||||
('event_s', forms.FloatField(label=_('Event name size (mm)'), required=False,
|
||||
help_text=_('Visible by default, set this to 0 to hide the element.'))),
|
||||
('attendee_x', forms.FloatField(label=_('Attendee name x position (mm)'), required=False)),
|
||||
('attendee_y', forms.FloatField(label=_('Attendee name y position (mm)'), required=False)),
|
||||
('attendee_s', forms.FloatField(label=_('Attendee name size (mm)'), required=False,
|
||||
help_text=_('Invisible by default, set this to a number greater than 0 '
|
||||
'to show.')))
|
||||
]
|
||||
)
|
||||
def settings_content_render(self, request: HttpRequest) -> str:
|
||||
"""
|
||||
When the event's administrator visits the event configuration
|
||||
page, this method is called. It may return HTML containing additional information
|
||||
that is displayed below the form fields configured in ``settings_form_fields``.
|
||||
"""
|
||||
template = get_template('pretixplugins/ticketoutputpdf/form.html')
|
||||
return template.render({
|
||||
'request': request
|
||||
})
|
||||
|
||||
def _legacy_layout(self):
|
||||
if self.settings.get('ticketoutput_pdf_background'):
|
||||
return self._migrate_from_old_settings()
|
||||
else:
|
||||
return self._default_layout()
|
||||
|
||||
def _default_layout(self):
|
||||
return [
|
||||
{"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_date",
|
||||
"text": "May 31st, 2017", "align": "left"},
|
||||
{"type": "textarea", "left": "17.50", "bottom": "234.30", "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"}
|
||||
]
|
||||
|
||||
def _migrate_from_old_settings(self):
|
||||
l = []
|
||||
|
||||
event_s = self.settings.get('event_s', default=22, as_type=float)
|
||||
if event_s:
|
||||
l.append({
|
||||
'type': 'textarea',
|
||||
'fontfamily': 'Helvetica',
|
||||
'left': self.settings.get('event_x', default=15, as_type=float),
|
||||
'bottom': self.settings.get('event_y', default=235, as_type=float),
|
||||
'fontsize': event_s,
|
||||
'color': [0, 0, 0, 1],
|
||||
'bold': False,
|
||||
'italic': False,
|
||||
'width': 150,
|
||||
'content': 'event_name',
|
||||
'text': 'Sample event',
|
||||
'align': 'left'
|
||||
})
|
||||
|
||||
order_s = self.settings.get('order_s', default=17, as_type=float)
|
||||
if order_s:
|
||||
l.append({
|
||||
'type': 'textarea',
|
||||
'fontfamily': 'Helvetica',
|
||||
'left': self.settings.get('order_x', default=15, as_type=float),
|
||||
'bottom': self.settings.get('order_y', default=220, as_type=float),
|
||||
'fontsize': order_s,
|
||||
'color': [0, 0, 0, 1],
|
||||
'bold': False,
|
||||
'italic': False,
|
||||
'width': 150,
|
||||
'content': 'order',
|
||||
'text': 'AB1C2',
|
||||
'align': 'left'
|
||||
})
|
||||
|
||||
name_s = self.settings.get('name_s', default=17, as_type=float)
|
||||
if name_s:
|
||||
l.append({
|
||||
'type': 'textarea',
|
||||
'fontfamily': 'Helvetica',
|
||||
'left': self.settings.get('name_x', default=15, as_type=float),
|
||||
'bottom': self.settings.get('name_y', default=210, as_type=float),
|
||||
'fontsize': name_s,
|
||||
'color': [0, 0, 0, 1],
|
||||
'bold': False,
|
||||
'italic': False,
|
||||
'width': 150,
|
||||
'content': 'itemvar',
|
||||
'text': 'Sample Producs - XS',
|
||||
'align': 'left'
|
||||
})
|
||||
|
||||
price_s = self.settings.get('price_s', default=17, as_type=float)
|
||||
if price_s:
|
||||
l.append({
|
||||
'type': 'textarea',
|
||||
'fontfamily': 'Helvetica',
|
||||
'left': self.settings.get('price_x', default=15, as_type=float),
|
||||
'bottom': self.settings.get('price_y', default=200, as_type=float),
|
||||
'fontsize': price_s,
|
||||
'color': [0, 0, 0, 1],
|
||||
'bold': False,
|
||||
'italic': False,
|
||||
'width': 150,
|
||||
'content': 'price',
|
||||
'text': 'EUR 12,34',
|
||||
'align': 'left'
|
||||
})
|
||||
|
||||
qr_s = self.settings.get('qr_s', default=80, as_type=float)
|
||||
if qr_s:
|
||||
l.append({
|
||||
'type': 'barcodearea',
|
||||
'left': self.settings.get('qr_x', default=10, as_type=float),
|
||||
'bottom': self.settings.get('qr_y', default=120, as_type=float),
|
||||
'size': qr_s,
|
||||
})
|
||||
|
||||
code_s = self.settings.get('code_s', default=11, as_type=float)
|
||||
if code_s:
|
||||
l.append({
|
||||
'type': 'textarea',
|
||||
'fontfamily': 'Helvetica',
|
||||
'left': self.settings.get('code_x', default=15, as_type=float),
|
||||
'bottom': self.settings.get('code_y', default=120, as_type=float),
|
||||
'fontsize': code_s,
|
||||
'color': [0, 0, 0, 1],
|
||||
'bold': False,
|
||||
'italic': False,
|
||||
'width': 150,
|
||||
'content': 'secret',
|
||||
'text': 'asdsdgjfgbgkjdastjrxfdg',
|
||||
'align': 'left'
|
||||
})
|
||||
|
||||
attendee_s = self.settings.get('attendee_s', default=0, as_type=float)
|
||||
if attendee_s:
|
||||
l.append({
|
||||
'type': 'textarea',
|
||||
'fontfamily': 'Helvetica',
|
||||
'left': self.settings.get('attendee_x', default=15, as_type=float),
|
||||
'bottom': self.settings.get('attendee_y', default=90, as_type=float),
|
||||
'fontsize': attendee_s,
|
||||
'color': [0, 0, 0, 1],
|
||||
'bold': False,
|
||||
'italic': False,
|
||||
'width': 150,
|
||||
'content': 'attendee_name',
|
||||
'text': 'John Doe',
|
||||
'align': 'left'
|
||||
})
|
||||
|
||||
return l
|
||||
|
||||
Reference in New Issue
Block a user