PDF: Add font fallback on a pragraph level (Z#23203886) (#5367)

* PDF: Add font fallback on a pragraph level (Z#23203886)

* Fix empty texts
This commit is contained in:
Raphael Michel
2025-08-13 10:51:23 +02:00
committed by GitHub
parent 328867c089
commit b9e627a86c
5 changed files with 171 additions and 105 deletions

View File

@@ -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('<br>', '<br/>'), self.get_style()),
FontFallbackParagraph(name, self.get_style()),
FontFallbackParagraph(bleach.clean(str(item), tags={'br'}).strip().replace('<br>', '<br/>'), 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('<br>', '<br/>')
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 += [

View File

@@ -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(
"<br />".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),
]

View File

@@ -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 = [