diff --git a/src/pretix/base/invoice.py b/src/pretix/base/invoice.py index 3f0a2b251..5e91ffc2c 100644 --- a/src/pretix/base/invoice.py +++ b/src/pretix/base/invoice.py @@ -48,7 +48,7 @@ from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfgen.canvas import Canvas from reportlab.platypus import ( BaseDocTemplate, Flowable, Frame, KeepTogether, NextPageTemplate, - PageTemplate, Paragraph, Spacer, Table, TableStyle, + PageTemplate, Spacer, Table, TableStyle, ) from pretix.base.decimal import round_decimal @@ -56,7 +56,9 @@ from pretix.base.models import Event, Invoice, Order, OrderPayment from pretix.base.services.currencies import SOURCE_NAMES from pretix.base.signals import register_invoice_renderers from pretix.base.templatetags.money import money_filter -from pretix.helpers.reportlab import ThumbnailingImageReader, reshaper +from pretix.helpers.reportlab import ( + FontFallbackParagraph, ThumbnailingImageReader, reshaper, +) from pretix.presale.style import get_fonts logger = logging.getLogger(__name__) @@ -235,16 +237,17 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer): italic='OpenSansIt', boldItalic='OpenSansBI') for family, styles in get_fonts(event=self.event, pdf_support_required=True).items(): + pdfmetrics.registerFont(TTFont(family, finders.find(styles['regular']['truetype']))) if family == self.event.settings.invoice_renderer_font: - pdfmetrics.registerFont(TTFont(family, finders.find(styles['regular']['truetype']))) self.font_regular = family - if 'italic' in styles: - pdfmetrics.registerFont(TTFont(family + ' I', finders.find(styles['italic']['truetype']))) if 'bold' in styles: - pdfmetrics.registerFont(TTFont(family + ' B', finders.find(styles['bold']['truetype']))) self.font_bold = family + ' B' - if 'bolditalic' in styles: - pdfmetrics.registerFont(TTFont(family + ' B I', finders.find(styles['bolditalic']['truetype']))) + if 'italic' in styles: + pdfmetrics.registerFont(TTFont(family + ' I', finders.find(styles['italic']['truetype']))) + if 'bold' in styles: + pdfmetrics.registerFont(TTFont(family + ' B', finders.find(styles['bold']['truetype']))) + if 'bolditalic' in styles: + pdfmetrics.registerFont(TTFont(family + ' B I', finders.find(styles['bolditalic']['truetype']))) def _normalize(self, text): # reportlab does not support unicode combination characters @@ -393,8 +396,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): invoice_to_top = 52 * mm def _draw_invoice_to(self, canvas): - p = Paragraph(self._clean_text(self.invoice.address_invoice_to), - style=self.stylesheet['Normal']) + p = FontFallbackParagraph(self._clean_text(self.invoice.address_invoice_to), + style=self.stylesheet['Normal']) p.wrapOn(canvas, self.invoice_to_width, self.invoice_to_height) p_size = p.wrap(self.invoice_to_width, self.invoice_to_height) p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - p_size[1] - self.invoice_to_top) @@ -405,7 +408,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): invoice_from_top = 17 * mm def _draw_invoice_from(self, canvas): - p = Paragraph( + p = FontFallbackParagraph( self._clean_text(self.invoice.full_invoice_from), style=self.stylesheet['InvoiceFrom'] ) @@ -523,12 +526,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): def shorten(txt): txt = str(txt) txt = bleach.clean(txt, tags=set()).strip() - p = Paragraph(self._normalize(txt.strip().replace('\n', '
\n')), style=self.stylesheet['Normal']) + p = FontFallbackParagraph(self._normalize(txt.strip().replace('\n', '
\n')), style=self.stylesheet['Normal']) p_size = p.wrap(self.event_width, self.event_height) while p_size[1] > 2 * self.stylesheet['Normal'].leading: txt = ' '.join(txt.replace('…', '').split()[:-1]) + '…' - p = Paragraph(self._normalize(txt.strip().replace('\n', '
\n')), style=self.stylesheet['Normal']) + p = FontFallbackParagraph(self._normalize(txt.strip().replace('\n', '
\n')), style=self.stylesheet['Normal']) p_size = p.wrap(self.event_width, self.event_height) return txt @@ -554,7 +557,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): else: p_str = shorten(self.invoice.event.name) - p = Paragraph(self._normalize(p_str.strip().replace('\n', '
\n')), style=self.stylesheet['Normal']) + p = FontFallbackParagraph(self._normalize(p_str.strip().replace('\n', '
\n')), style=self.stylesheet['Normal']) p.wrapOn(canvas, self.event_width, self.event_height) p_size = p.wrap(self.event_width, self.event_height) p.drawOn(canvas, self.event_left, self.pagesize[1] - self.event_top - p_size[1]) @@ -608,7 +611,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): def _get_intro(self): story = [] if self.invoice.custom_field: - story.append(Paragraph( + story.append(FontFallbackParagraph( '{}: {}'.format( self._clean_text(str(self.invoice.event.settings.invoice_address_custom_field)), self._clean_text(self.invoice.custom_field), @@ -617,7 +620,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): )) if self.invoice.internal_reference: - story.append(Paragraph( + story.append(FontFallbackParagraph( self._normalize(pgettext('invoice', 'Customer reference: {reference}').format( reference=self._clean_text(self.invoice.internal_reference), )), @@ -625,14 +628,14 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): )) if self.invoice.invoice_to_vat_id: - story.append(Paragraph( + story.append(FontFallbackParagraph( self._normalize(pgettext('invoice', 'Customer VAT ID')) + ': ' + self._clean_text(self.invoice.invoice_to_vat_id), self.stylesheet['Normal'] )) if self.invoice.invoice_to_beneficiary: - story.append(Paragraph( + story.append(FontFallbackParagraph( self._normalize(pgettext('invoice', 'Beneficiary')) + ':
' + self._clean_text(self.invoice.invoice_to_beneficiary), self.stylesheet['Normal'] @@ -644,7 +647,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): if story: story.append(Spacer(1, 5 * mm)) - story.append(Paragraph( + story.append(FontFallbackParagraph( self._clean_text(self.invoice.introductory_text, tags=['br']), self.stylesheet['Normal'] )) @@ -657,7 +660,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): story = [ NextPageTemplate('FirstPage'), - Paragraph( + FontFallbackParagraph( self._normalize( pgettext('invoice', 'Tax Invoice') if str(self.invoice.invoice_from_country) == 'AU' else pgettext('invoice', 'Invoice') @@ -683,17 +686,17 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): ] if has_taxes: tdata = [( - Paragraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']), - Paragraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']), - Paragraph(self._normalize(pgettext('invoice', 'Tax rate')), self.stylesheet['BoldRightNoSplit']), - Paragraph(self._normalize(pgettext('invoice', 'Net')), self.stylesheet['BoldRightNoSplit']), - Paragraph(self._normalize(pgettext('invoice', 'Gross')), self.stylesheet['BoldRightNoSplit']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Tax rate')), self.stylesheet['BoldRightNoSplit']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Net')), self.stylesheet['BoldRightNoSplit']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Gross')), self.stylesheet['BoldRightNoSplit']), )] else: tdata = [( - Paragraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']), - Paragraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']), - Paragraph(self._normalize(pgettext('invoice', 'Amount')), self.stylesheet['BoldRightNoSplit']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Amount')), self.stylesheet['BoldRightNoSplit']), )] def _group_key(line): @@ -715,14 +718,20 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): ) description = description + "\n" + single_price_line tdata.append(( - Paragraph( + FontFallbackParagraph( self._clean_text(description, tags=['br']), self.stylesheet['Normal'] ), str(len(lines)), localize(tax_rate) + " %", - Paragraph(money_filter(net_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '), self.stylesheet['NormalRight']), - Paragraph(money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '), self.stylesheet['NormalRight']), + FontFallbackParagraph( + money_filter(net_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '), + self.stylesheet['NormalRight'] + ), + FontFallbackParagraph( + money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '), + self.stylesheet['NormalRight'] + ), )) else: if len(lines) > 1: @@ -731,12 +740,15 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): ) description = description + "\n" + single_price_line tdata.append(( - Paragraph( + FontFallbackParagraph( self._clean_text(description, tags=['br']), self.stylesheet['Normal'] ), str(len(lines)), - Paragraph(money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '), self.stylesheet['NormalRight']), + FontFallbackParagraph( + money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '), + self.stylesheet['NormalRight'] + ), )) taxvalue_map[tax_rate, tax_name] += (gross_value - net_value) * len(lines) grossvalue_map[tax_rate, tax_name] += gross_value * len(lines) @@ -744,13 +756,13 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): if has_taxes: tdata.append([ - Paragraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '', '', '', + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '', '', '', money_filter(total, self.invoice.event.currency) ]) colwidths = [a * doc.width for a in (.50, .05, .15, .15, .15)] else: tdata.append([ - Paragraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '', + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '', money_filter(total, self.invoice.event.currency) ]) colwidths = [a * doc.width for a in (.65, .20, .15)] @@ -760,12 +772,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): pending_sum = self.invoice.order.pending_sum if pending_sum != total: tdata.append( - [Paragraph(self._normalize(pgettext('invoice', 'Received payments')), self.stylesheet['Normal'])] + + [FontFallbackParagraph(self._normalize(pgettext('invoice', 'Received payments')), self.stylesheet['Normal'])] + (['', '', ''] if has_taxes else ['']) + [money_filter(pending_sum - total, self.invoice.event.currency)] ) tdata.append( - [Paragraph(self._normalize(pgettext('invoice', 'Outstanding payments')), self.stylesheet['Bold'])] + + [FontFallbackParagraph(self._normalize(pgettext('invoice', 'Outstanding payments')), self.stylesheet['Bold'])] + (['', '', ''] if has_taxes else ['']) + [money_filter(pending_sum, self.invoice.event.currency)] ) @@ -782,12 +794,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): s=Sum('amount') )['s'] or Decimal('0.00') tdata.append( - [Paragraph(self._normalize(pgettext('invoice', 'Paid by gift card')), self.stylesheet['Normal'])] + + [FontFallbackParagraph(self._normalize(pgettext('invoice', 'Paid by gift card')), self.stylesheet['Normal'])] + (['', '', ''] if has_taxes else ['']) + [money_filter(giftcard_sum, self.invoice.event.currency)] ) tdata.append( - [Paragraph(self._normalize(pgettext('invoice', 'Remaining amount')), self.stylesheet['Bold'])] + + [FontFallbackParagraph(self._normalize(pgettext('invoice', 'Remaining amount')), self.stylesheet['Bold'])] + (['', '', ''] if has_taxes else ['']) + [money_filter(total - giftcard_sum, self.invoice.event.currency)] ) @@ -810,7 +822,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): story.append(Spacer(1, 10 * mm)) if self.invoice.payment_provider_text: - story.append(Paragraph( + story.append(FontFallbackParagraph( self._normalize(self.invoice.payment_provider_text), self.stylesheet['Normal'] )) @@ -819,7 +831,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): story.append(Spacer(1, 3 * mm)) if self.invoice.additional_text: - story.append(Paragraph( + story.append(FontFallbackParagraph( self._clean_text(self.invoice.additional_text, tags=['br']), self.stylesheet['Normal'] )) @@ -835,10 +847,10 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): ('FONTNAME', (0, 0), (-1, -1), self.font_regular), ] thead = [ - Paragraph(self._normalize(pgettext('invoice', 'Tax rate')), self.stylesheet['Fineprint']), - Paragraph(self._normalize(pgettext('invoice', 'Net value')), self.stylesheet['FineprintRight']), - Paragraph(self._normalize(pgettext('invoice', 'Gross value')), self.stylesheet['FineprintRight']), - Paragraph(self._normalize(pgettext('invoice', 'Tax')), self.stylesheet['FineprintRight']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Tax rate')), self.stylesheet['Fineprint']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Net value')), self.stylesheet['FineprintRight']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Gross value')), self.stylesheet['FineprintRight']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Tax')), self.stylesheet['FineprintRight']), '' ] tdata = [thead] @@ -849,7 +861,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): continue tax = taxvalue_map[idx] tdata.append([ - Paragraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']), + FontFallbackParagraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']), money_filter(gross - tax, self.invoice.event.currency), money_filter(gross, self.invoice.event.currency), money_filter(tax, self.invoice.event.currency), @@ -868,7 +880,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): table.setStyle(TableStyle(tstyledata)) story.append(Spacer(5 * mm, 5 * mm)) story.append(KeepTogether([ - Paragraph(self._normalize(pgettext('invoice', 'Included taxes')), self.stylesheet['FineprintHeading']), + FontFallbackParagraph(self._normalize(pgettext('invoice', 'Included taxes')), self.stylesheet['FineprintHeading']), table ])) @@ -885,7 +897,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): net = gross - tax tdata.append([ - Paragraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']), + FontFallbackParagraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']), fmt(net), fmt(gross), fmt(tax), '' ]) @@ -894,7 +906,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): story.append(KeepTogether([ Spacer(1, height=2 * mm), - Paragraph( + FontFallbackParagraph( self._normalize(pgettext( 'invoice', 'Using the conversion rate of 1:{rate} as published by the {authority} on ' '{date}, this corresponds to:' @@ -909,7 +921,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer): elif self.invoice.foreign_currency_display and self.invoice.foreign_currency_rate: foreign_total = round_decimal(total * self.invoice.foreign_currency_rate) story.append(Spacer(1, 5 * mm)) - story.append(Paragraph(self._normalize( + story.append(FontFallbackParagraph(self._normalize( pgettext( 'invoice', 'Using the conversion rate of 1:{rate} as published by the {authority} on ' '{date}, the invoice total corresponds to {total}.' @@ -962,7 +974,7 @@ class Modern1Renderer(ClassicInvoiceRenderer): self._clean_text(l) for l in self.invoice.address_invoice_from.strip().split('\n') ] - p = Paragraph(self._normalize(' · '.join(c)), style=self.stylesheet['Sender']) + p = FontFallbackParagraph(self._normalize(' · '.join(c)), style=self.stylesheet['Sender']) p.wrapOn(canvas, self.invoice_to_width, 15.7 * mm) p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - self.invoice_to_top + 2 * mm) super()._draw_invoice_from(canvas) @@ -1021,7 +1033,7 @@ class Modern1Renderer(ClassicInvoiceRenderer): _draw(pgettext('invoice', 'Order code'), self.invoice.order.full_code, value_size, self.left_margin, 45 * mm, **kwargs) ] - p = Paragraph( + p = FontFallbackParagraph( self._normalize(date_format(self.invoice.date, "DATE_FORMAT")), style=ParagraphStyle(name=f'Normal{value_size}', fontName=self.font_regular, fontSize=value_size, leading=value_size * 1.2) ) @@ -1079,7 +1091,7 @@ class Modern1SimplifiedRenderer(Modern1Renderer): i = [] if not self.invoice.event.has_subevents and self.invoice.event.settings.show_dates_on_frontpage: - i.append(Paragraph( + i.append(FontFallbackParagraph( pgettext('invoice', 'Event date: {date_range}').format( date_range=self.invoice.event.get_date_range_display(), ), diff --git a/src/pretix/helpers/reportlab.py b/src/pretix/helpers/reportlab.py index 275d0205c..1ca7b0cbc 100644 --- a/src/pretix/helpers/reportlab.py +++ b/src/pretix/helpers/reportlab.py @@ -19,11 +19,20 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # +import logging + from arabic_reshaper import ArabicReshaper from django.conf import settings from django.utils.functional import SimpleLazyObject from PIL import Image +from reportlab.lib.styles import ParagraphStyle from reportlab.lib.utils import ImageReader +from reportlab.pdfbase import pdfmetrics +from reportlab.platypus import Paragraph + +from pretix.presale.style import get_fonts + +logger = logging.getLogger(__name__) class ThumbnailingImageReader(ImageReader): @@ -59,3 +68,33 @@ reshaper = SimpleLazyObject(lambda: ArabicReshaper(configuration={ 'delete_harakat': True, 'support_ligatures': False, })) + + +class FontFallbackParagraph(Paragraph): + def __init__(self, text, style=None, *args, **kwargs): + if style is None: + style = ParagraphStyle(name='paragraphImplicitDefaultStyle') + + if not self._font_supports_text(text, style.fontName): + newFont = self._find_font(text, style.fontName) + if newFont: + logger.debug(f"replacing {style.fontName} with {newFont} for {text!r}") + style = style.clone(name=style.name + '_' + newFont, fontName=newFont) + + super().__init__(text, style, *args, **kwargs) + + def _font_supports_text(self, text, font_name): + font = pdfmetrics.getFont(font_name) + return all( + ord(c) in font.face.charToGlyph or not c.isprintable() + for c in text + ) + + def _find_font(self, text, original_font): + for family, styles in get_fonts(pdf_support_required=True).items(): + if self._font_supports_text(text, family): + if (original_font.endswith("It") or original_font.endswith(" I")) and "italic" in styles: + return family + " I" + if (original_font.endswith("Bd") or original_font.endswith(" B")) and "bold" in styles: + return family + " B" + return family diff --git a/src/pretix/plugins/checkinlists/exporters.py b/src/pretix/plugins/checkinlists/exporters.py index 53fff19fe..7f95a79cd 100644 --- a/src/pretix/plugins/checkinlists/exporters.py +++ b/src/pretix/plugins/checkinlists/exporters.py @@ -49,7 +49,7 @@ from django.utils.translation import ( gettext as _, gettext_lazy, pgettext, pgettext_lazy, ) from reportlab.lib.units import mm -from reportlab.platypus import Flowable, Paragraph, Spacer, Table, TableStyle +from reportlab.platypus import Flowable, Spacer, Table, TableStyle from pretix.base.exporter import BaseExporter, ListExporter from pretix.base.models import ( @@ -64,6 +64,7 @@ from pretix.base.timeframes import ( from pretix.control.forms.widgets import Select2 from pretix.helpers.filenames import safe_for_filename from pretix.helpers.iter import chunked_iterable +from pretix.helpers.reportlab import FontFallbackParagraph from pretix.helpers.templatetags.jsonfield import JSONExtract from pretix.plugins.reports.exporters import ReportlabExportMixin @@ -343,7 +344,7 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter): ] story = [ - Paragraph( + FontFallbackParagraph( cl.name, headlinestyle ), @@ -351,7 +352,7 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter): if cl.subevent: story += [ Spacer(1, 3 * mm), - Paragraph( + FontFallbackParagraph( '{} ({} {})'.format( cl.subevent.name, cl.subevent.get_date_range_display(), @@ -381,10 +382,10 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter): headrowstyle.fontName = 'OpenSansBd' for q in questions: txt = str(q.question) - p = Paragraph(txt, headrowstyle) + p = FontFallbackParagraph(txt, headrowstyle) while p.wrap(colwidths[len(tdata[0])], 5000)[1] > 30 * mm: txt = txt[:len(txt) - 50] + "..." - p = Paragraph(txt, headrowstyle) + p = FontFallbackParagraph(txt, headrowstyle) tdata[0].append(p) qs = self._get_queryset(cl, form_data) @@ -431,8 +432,8 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter): CBFlowable(bool(op.last_checked_in)) if not op.blocked else '—', '✘' if op.order.status != Order.STATUS_PAID else '✔', op.order.code, - Paragraph(name, self.get_style()), - Paragraph(bleach.clean(str(item), tags={'br'}).strip().replace('
', '
'), self.get_style()), + FontFallbackParagraph(name, self.get_style()), + FontFallbackParagraph(bleach.clean(str(item), tags={'br'}).strip().replace('
', '
'), self.get_style()), ] acache = {} if op.addon_to: @@ -443,10 +444,10 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter): for q in questions: txt = acache.get(q.pk, '') txt = bleach.clean(txt, tags={'br'}).strip().replace('
', '
') - p = Paragraph(txt, self.get_style()) + p = FontFallbackParagraph(txt, self.get_style()) while p.wrap(colwidths[len(row)], 5000)[1] > 50 * mm: txt = txt[:len(txt) - 50] + "..." - p = Paragraph(txt, self.get_style()) + p = FontFallbackParagraph(txt, self.get_style()) row.append(p) if op.order.status != Order.STATUS_PAID: tstyledata += [ diff --git a/src/pretix/plugins/reports/accountingreport.py b/src/pretix/plugins/reports/accountingreport.py index 8eeaf0744..7d5da2731 100644 --- a/src/pretix/plugins/reports/accountingreport.py +++ b/src/pretix/plugins/reports/accountingreport.py @@ -49,6 +49,7 @@ from pretix.base.timeframes import ( resolve_timeframe_to_datetime_start_inclusive_end_exclusive, ) from pretix.control.forms.filter import get_all_payment_providers +from pretix.helpers.reportlab import FontFallbackParagraph from pretix.plugins.reports.exporters import ReportlabExportMixin @@ -310,13 +311,13 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): tdata = [ [ - Paragraph(self._transaction_group_header_label(), tstyle_bold), - Paragraph(_("Price"), tstyle_bold_right), - Paragraph(_("Tax rate"), tstyle_bold_right), - Paragraph("#", tstyle_bold_right), - Paragraph(_("Net total"), tstyle_bold_right), - Paragraph(_("Tax total"), tstyle_bold_right), - Paragraph(_("Gross total"), tstyle_bold_right), + FontFallbackParagraph(self._transaction_group_header_label(), tstyle_bold), + FontFallbackParagraph(_("Price"), tstyle_bold_right), + FontFallbackParagraph(_("Tax rate"), tstyle_bold_right), + FontFallbackParagraph("#", tstyle_bold_right), + FontFallbackParagraph(_("Net total"), tstyle_bold_right), + FontFallbackParagraph(_("Tax total"), tstyle_bold_right), + FontFallbackParagraph(_("Gross total"), tstyle_bold_right), ] ] @@ -351,7 +352,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): tdata[last_group_head_idx][6] = Paragraph(money_filter(sum_price_by_group, currency), tstyle_bold_right), tdata.append( [ - Paragraph( + FontFallbackParagraph( e, tstyle_bold, ), @@ -374,7 +375,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): text = self._transaction_row_label(r) tdata.append( [ - Paragraph(text, tstyle), + FontFallbackParagraph(text, tstyle), Paragraph( money_filter(r["price"], currency) if "price" in r and r["price"] is not None @@ -405,7 +406,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): for tax_rate in sorted(sum_tax_by_tax_rate.keys(), reverse=True): tdata.append( [ - Paragraph(_("Sum"), tstyle), + FontFallbackParagraph(_("Sum"), tstyle), Paragraph("", tstyle_right), Paragraph(localize(tax_rate.normalize()) + " %", tstyle_right), Paragraph("", tstyle_right), @@ -438,7 +439,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): tdata.append( [ - Paragraph(_("Sum"), tstyle_bold), + FontFallbackParagraph(_("Sum"), tstyle_bold), Paragraph("", tstyle_right), Paragraph("", tstyle_right), Paragraph("", tstyle_bold_right), @@ -492,10 +493,10 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): tdata = [ [ - Paragraph(_("Payment method"), tstyle_bold), - Paragraph(_("Payments"), tstyle_bold_right), - Paragraph(_("Refunds"), tstyle_bold_right), - Paragraph(_("Total"), tstyle_bold_right), + FontFallbackParagraph(_("Payment method"), tstyle_bold), + FontFallbackParagraph(_("Payments"), tstyle_bold_right), + FontFallbackParagraph(_("Refunds"), tstyle_bold_right), + FontFallbackParagraph(_("Total"), tstyle_bold_right), ] ] @@ -537,7 +538,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): tdata.append( [ Paragraph(provider_names.get(p, p), tstyle), - Paragraph( + FontFallbackParagraph( money_filter(payments_by_provider[p], currency) if p in payments_by_provider else "", @@ -562,7 +563,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): tdata.append( [ - Paragraph(_("Sum"), tstyle_bold), + FontFallbackParagraph(_("Sum"), tstyle_bold), Paragraph( money_filter( sum(payments_by_provider.values(), Decimal("0.00")), currency @@ -640,7 +641,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): open_before = tx_before - p_before + r_before tdata.append( [ - Paragraph( + FontFallbackParagraph( _("Pending payments at {datetime}").format( datetime=date_format( df_start - datetime.timedelta.resolution, @@ -667,21 +668,21 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): ] or Decimal("0.00") tdata.append( [ - Paragraph(_("Orders"), tstyle), + FontFallbackParagraph(_("Orders"), tstyle), Paragraph("+", tstyle_center), Paragraph(money_filter(tx_during, currency), tstyle_right), ] ) tdata.append( [ - Paragraph(_("Payments"), tstyle), + FontFallbackParagraph(_("Payments"), tstyle), Paragraph("-", tstyle_center), Paragraph(money_filter(p_during, currency), tstyle_right), ] ) tdata.append( [ - Paragraph(_("Refunds"), tstyle), + FontFallbackParagraph(_("Refunds"), tstyle), Paragraph("+", tstyle_center), Paragraph(money_filter(r_during, currency), tstyle_right), ] @@ -767,7 +768,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): ] or Decimal("0.00") tdata.append( [ - Paragraph(_("Gift card transactions (credit)"), tstyle), + FontFallbackParagraph(_("Gift card transactions (credit)"), tstyle), Paragraph(money_filter(tx_during_pos, currency), tstyle_right), ] ) @@ -777,7 +778,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): ] or Decimal("0.00") tdata.append( [ - Paragraph(_("Gift card transactions (debit)"), tstyle), + FontFallbackParagraph(_("Gift card transactions (debit)"), tstyle), Paragraph(money_filter(tx_during_neg, currency), tstyle_right), ] ) @@ -845,9 +846,9 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): style_small.leading = 10 story = [ - Paragraph(self.verbose_name, style_h1), + FontFallbackParagraph(self.verbose_name, style_h1), Spacer(0, 3 * mm), - Paragraph( + FontFallbackParagraph( "
".join(escape(f) for f in self.describe_filters(form_data)), style_small, ), @@ -859,7 +860,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): c_head = f" [{c}]" if len(currencies) > 1 else "" story += [ Spacer(0, 3 * mm), - Paragraph(_("Orders") + c_head, style_h2), + FontFallbackParagraph(_("Orders") + c_head, style_h2), Spacer(0, 3 * mm), *self._table_transactions(form_data, c), ] @@ -868,7 +869,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): c_head = f" [{c}]" if len(currencies) > 1 else "" story += [ Spacer(0, 8 * mm), - Paragraph(_("Payments") + c_head, style_h2), + FontFallbackParagraph(_("Payments") + c_head, style_h2), Spacer(0, 3 * mm), *self._table_payments(form_data, c), ] @@ -879,7 +880,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): Spacer(0, 8 * mm), KeepTogether( [ - Paragraph(_("Open items") + c_head, style_h2), + FontFallbackParagraph(_("Open items") + c_head, style_h2), Spacer(0, 3 * mm), *self._table_open_items(form_data, c), ] @@ -895,7 +896,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter): Spacer(0, 8 * mm), KeepTogether( [ - Paragraph(_("Gift cards") + c_head, style_h2), + FontFallbackParagraph(_("Gift cards") + c_head, style_h2), Spacer(0, 3 * mm), *self._table_gift_cards(form_data, c), ] diff --git a/src/pretix/plugins/reports/exporters.py b/src/pretix/plugins/reports/exporters.py index 9baec7347..13aac28a9 100644 --- a/src/pretix/plugins/reports/exporters.py +++ b/src/pretix/plugins/reports/exporters.py @@ -56,7 +56,7 @@ from reportlab.lib import colors from reportlab.lib.enums import TA_CENTER from reportlab.lib.units import mm from reportlab.pdfgen.canvas import Canvas -from reportlab.platypus import PageBreak, Paragraph, Spacer, Table, TableStyle +from reportlab.platypus import PageBreak, Spacer, Table, TableStyle from pretix.base.decimal import round_decimal from pretix.base.exporter import BaseExporter, MultiSheetListExporter @@ -69,6 +69,8 @@ from pretix.base.timeframes import ( resolve_timeframe_to_datetime_start_inclusive_end_exclusive, ) from pretix.control.forms.filter import OverviewFilterForm +from pretix.helpers.reportlab import FontFallbackParagraph +from pretix.presale.style import get_fonts class NumberedCanvas(Canvas): @@ -135,6 +137,15 @@ class ReportlabExportMixin: pdfmetrics.registerFont(TTFont('OpenSansIt', finders.find('fonts/OpenSans-Italic.ttf'))) pdfmetrics.registerFont(TTFont('OpenSansBd', finders.find('fonts/OpenSans-Bold.ttf'))) + for family, styles in get_fonts(None, pdf_support_required=True).items(): + pdfmetrics.registerFont(TTFont(family, finders.find(styles['regular']['truetype']))) + if 'italic' in styles: + pdfmetrics.registerFont(TTFont(family + ' I', finders.find(styles['italic']['truetype']))) + if 'bold' in styles: + pdfmetrics.registerFont(TTFont(family + ' B', finders.find(styles['bold']['truetype']))) + if 'bolditalic' in styles: + pdfmetrics.registerFont(TTFont(family + ' B I', finders.find(styles['bolditalic']['truetype']))) + def get_doc_template(self): from reportlab.platypus import BaseDocTemplate @@ -272,7 +283,7 @@ class OverviewReport(Report): headlinestyle.fontSize = 15 headlinestyle.fontName = 'OpenSansBd' story = [ - Paragraph(_('Orders by product') + ' ' + (_('(excl. taxes)') if net else _('(incl. taxes)')), headlinestyle), + FontFallbackParagraph(_('Orders by product') + ' ' + (_('(excl. taxes)') if net else _('(incl. taxes)')), headlinestyle), Spacer(1, 5 * mm) ] return story @@ -282,7 +293,7 @@ class OverviewReport(Report): if form_data.get('date_axis') and form_data.get('date_range'): d_start, d_end = resolve_timeframe_to_dates_inclusive(now(), form_data['date_range'], self.timezone) story += [ - Paragraph(_('{axis} between {start} and {end}').format( + FontFallbackParagraph(_('{axis} between {start} and {end}').format( axis=dict(OverviewFilterForm(event=self.event).fields['date_axis'].choices)[form_data.get('date_axis')], start=date_format(d_start, 'SHORT_DATE_FORMAT') if d_start else '–', end=date_format(d_end, 'SHORT_DATE_FORMAT') if d_end else '–', @@ -295,13 +306,13 @@ class OverviewReport(Report): subevent = self.event.subevents.get(pk=self.form_data.get('subevent')) except SubEvent.DoesNotExist: subevent = self.form_data.get('subevent') - story.append(Paragraph(pgettext('subevent', 'Date: {}').format(subevent), self.get_style())) + story.append(FontFallbackParagraph(pgettext('subevent', 'Date: {}').format(subevent), self.get_style())) story.append(Spacer(1, 5 * mm)) if form_data.get('subevent_date_range'): d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['subevent_date_range'], self.timezone) story += [ - Paragraph(_('{axis} between {start} and {end}').format( + FontFallbackParagraph(_('{axis} between {start} and {end}').format( axis=_('Event date'), start=date_format(d_start, 'SHORT_DATE_FORMAT') if d_start else '–', end=date_format(d_end - timedelta(hours=1), 'SHORT_DATE_FORMAT') if d_end else '–', @@ -373,13 +384,13 @@ class OverviewReport(Report): tdata = [ [ _('Product'), - Paragraph(_('Canceled'), tstyle_th), + FontFallbackParagraph(_('Canceled'), tstyle_th), '', - Paragraph(_('Expired'), tstyle_th), + FontFallbackParagraph(_('Expired'), tstyle_th), '', - Paragraph(_('Approval pending'), tstyle_th), + FontFallbackParagraph(_('Approval pending'), tstyle_th), '', - Paragraph(_('Purchased'), tstyle_th), + FontFallbackParagraph(_('Purchased'), tstyle_th), '', '', '', '', '' ], [ @@ -410,14 +421,14 @@ class OverviewReport(Report): for tup in items_by_category: if tup[0]: tdata.append([ - Paragraph(str(tup[0]), tstyle_bold) + FontFallbackParagraph(str(tup[0]), tstyle_bold) ]) for l, s in states: tdata[-1].append(str(tup[0].num[l][0])) tdata[-1].append(floatformat(tup[0].num[l][2 if net else 1], places)) for item in tup[1]: tdata.append([ - Paragraph(str(item), tstyle) + FontFallbackParagraph(str(item), tstyle) ]) for l, s in states: tdata[-1].append(str(item.num[l][0])) @@ -425,7 +436,7 @@ class OverviewReport(Report): if item.has_variations: for var in item.all_variations: tdata.append([ - Paragraph(" " + str(var), tstyle) + FontFallbackParagraph(" " + str(var), tstyle) ]) for l, s in states: tdata[-1].append(str(var.num[l][0])) @@ -512,7 +523,7 @@ class OrderTaxListReportPDF(Report): def get_story(self, doc, form_data): from reportlab.lib.units import mm - from reportlab.platypus import Paragraph, Spacer, Table, TableStyle + from reportlab.platypus import Spacer, Table, TableStyle headlinestyle = self.get_style() headlinestyle.fontSize = 15 @@ -553,7 +564,7 @@ class OrderTaxListReportPDF(Report): tstyledata.append(('SPAN', (5 + 2 * i, 0), (6 + 2 * i, 0))) story = [ - Paragraph(_('Orders by tax rate ({currency})').format(currency=self.event.currency), headlinestyle), + FontFallbackParagraph(_('Orders by tax rate ({currency})').format(currency=self.event.currency), headlinestyle), Spacer(1, 5 * mm) ] tdata = [