mirror of
https://github.com/pretix/pretix.git
synced 2026-08-29 13:44:40 +00:00
Implement basic font fallback
pretixprint-PR: https://github.com/pretix/libpretixprint/pull/10
This commit is contained in:
+33
-18
@@ -83,7 +83,8 @@ from pretix.base.templatetags.money import money_filter
|
|||||||
from pretix.base.templatetags.phone_format import phone_format
|
from pretix.base.templatetags.phone_format import phone_format
|
||||||
from pretix.helpers.daterange import datetimerange
|
from pretix.helpers.daterange import datetimerange
|
||||||
from pretix.helpers.reportlab import (
|
from pretix.helpers.reportlab import (
|
||||||
ThumbnailingImageReader, register_ttf_font_if_new, reshaper,
|
ThumbnailingImageReader, find_font_supporting_text,
|
||||||
|
register_ttf_font_if_new, reshaper,
|
||||||
)
|
)
|
||||||
from pretix.presale.style import get_fonts
|
from pretix.presale.style import get_fonts
|
||||||
|
|
||||||
@@ -805,7 +806,10 @@ class Renderer:
|
|||||||
else:
|
else:
|
||||||
self.bg_bytes = None
|
self.bg_bytes = None
|
||||||
self.bg_pdf = None
|
self.bg_pdf = None
|
||||||
self.event_fonts = list(get_fonts(event, pdf_support_required=True).keys()) + ['Open Sans']
|
|
||||||
|
event_fonts = get_fonts(event, pdf_support_required=True) | {'Open Sans': {"bold", "italic", "bolditalic"}}
|
||||||
|
# sorted by font name to match ordering of libpretixprint
|
||||||
|
self.event_fonts = dict(sorted(event_fonts.items(), key=lambda x: x[0]))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _register_fonts(cls, event: Event = None):
|
def _register_fonts(cls, event: Event = None):
|
||||||
@@ -1004,7 +1008,25 @@ class Renderer:
|
|||||||
)
|
)
|
||||||
canvas.restoreState()
|
canvas.restoreState()
|
||||||
|
|
||||||
def _text_paragraph(self, op: OrderPosition, order: Order, o: dict, legacy_lineheight=False, override_fontsize=None):
|
def _prepare_text_paragraph_text(self, op: OrderPosition, order: Order, o: dict):
|
||||||
|
# add an almost-invisible space   after hyphens as word-wrap in ReportLab only works on space chars
|
||||||
|
text = conditional_escape(
|
||||||
|
self._get_text_content(op, order, o) or "",
|
||||||
|
).replace("\n", "<br/>\n").replace("-", "- ")
|
||||||
|
|
||||||
|
# reportlab does not support unicode combination characters
|
||||||
|
# It's important we do this before we use ArabicReshaper
|
||||||
|
text = unicodedata.normalize("NFC", text)
|
||||||
|
|
||||||
|
# reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper
|
||||||
|
# to resolve all ligatures and python-bidi to switch RTL texts.
|
||||||
|
try:
|
||||||
|
text = "<br/>".join(get_display(reshaper.reshape(l)) for l in text.split("<br/>"))
|
||||||
|
except:
|
||||||
|
logger.exception('Reshaping/Bidi fixes failed on string {}'.format(repr(text)))
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _get_text_paragraph_font(self, o: dict, text: str):
|
||||||
font = o['fontfamily']
|
font = o['fontfamily']
|
||||||
|
|
||||||
# Since pdfmetrics.registerFont is global, we want to make sure that no one tries to sneak in a font, they
|
# Since pdfmetrics.registerFont is global, we want to make sure that no one tries to sneak in a font, they
|
||||||
@@ -1018,6 +1040,14 @@ class Renderer:
|
|||||||
if o['italic']:
|
if o['italic']:
|
||||||
font += ' I'
|
font += ' I'
|
||||||
|
|
||||||
|
font = find_font_supporting_text(self.event_fonts, text, font)
|
||||||
|
|
||||||
|
return font
|
||||||
|
|
||||||
|
def _text_paragraph(self, op: OrderPosition, order: Order, o: dict, legacy_lineheight=False, override_fontsize=None):
|
||||||
|
text = self._prepare_text_paragraph_text(op, order, o)
|
||||||
|
font = self._get_text_paragraph_font(o, text)
|
||||||
|
|
||||||
fontsize = override_fontsize if override_fontsize is not None else float(o['fontsize'])
|
fontsize = override_fontsize if override_fontsize is not None else float(o['fontsize'])
|
||||||
try:
|
try:
|
||||||
ad = getAscentDescent(font, fontsize)
|
ad = getAscentDescent(font, fontsize)
|
||||||
@@ -1046,21 +1076,6 @@ class Renderer:
|
|||||||
alignment=align_map[o['align']],
|
alignment=align_map[o['align']],
|
||||||
splitLongWords=o.get('splitlongwords', True),
|
splitLongWords=o.get('splitlongwords', True),
|
||||||
)
|
)
|
||||||
# add an almost-invisible space   after hyphens as word-wrap in ReportLab only works on space chars
|
|
||||||
text = conditional_escape(
|
|
||||||
self._get_text_content(op, order, o) or "",
|
|
||||||
).replace("\n", "<br/>\n").replace("-", "- ")
|
|
||||||
|
|
||||||
# reportlab does not support unicode combination characters
|
|
||||||
# It's important we do this before we use ArabicReshaper
|
|
||||||
text = unicodedata.normalize("NFC", text)
|
|
||||||
|
|
||||||
# reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper
|
|
||||||
# to resolve all ligatures and python-bidi to switch RTL texts.
|
|
||||||
try:
|
|
||||||
text = "<br/>".join(get_display(reshaper.reshape(l)) for l in text.split("<br/>"))
|
|
||||||
except:
|
|
||||||
logger.exception('Reshaping/Bidi fixes failed on string {}'.format(repr(text)))
|
|
||||||
|
|
||||||
p = Paragraph(text, style=style)
|
p = Paragraph(text, style=style)
|
||||||
return p, ad, lineheight
|
return p, ad, lineheight
|
||||||
|
|||||||
@@ -70,37 +70,40 @@ reshaper = SimpleLazyObject(lambda: ArabicReshaper(configuration={
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
def font_supports_text(text, font_name):
|
||||||
|
if not text:
|
||||||
|
return True
|
||||||
|
font = pdfmetrics.getFont(font_name)
|
||||||
|
return all(
|
||||||
|
ord(c) in font.face.charToGlyph or not c.isprintable()
|
||||||
|
for c in text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def find_font_supporting_text(fonts, text, preferred_font):
|
||||||
|
if font_supports_text(text, preferred_font):
|
||||||
|
return preferred_font
|
||||||
|
for family, styles in fonts.items():
|
||||||
|
if font_supports_text(text, family):
|
||||||
|
if (preferred_font.endswith("It") or preferred_font.endswith(" I")) and "italic" in styles:
|
||||||
|
return family + " I"
|
||||||
|
if (preferred_font.endswith("Bd") or preferred_font.endswith(" B")) and "bold" in styles:
|
||||||
|
return family + " B"
|
||||||
|
return family
|
||||||
|
return preferred_font
|
||||||
|
|
||||||
|
|
||||||
class FontFallbackParagraph(Paragraph):
|
class FontFallbackParagraph(Paragraph):
|
||||||
def __init__(self, text, style=None, *args, **kwargs):
|
def __init__(self, text, style=None, *args, **kwargs):
|
||||||
if style is None:
|
if style is None:
|
||||||
style = ParagraphStyle(name='paragraphImplicitDefaultStyle')
|
style = ParagraphStyle(name='paragraphImplicitDefaultStyle')
|
||||||
|
|
||||||
if not self._font_supports_text(text, style.fontName):
|
supporting_font = find_font_supporting_text(get_fonts(pdf_support_required=True), text, style.fontName)
|
||||||
newFont = self._find_font(text, style.fontName)
|
if supporting_font != style.fontName:
|
||||||
if newFont:
|
logger.debug(f"replacing {style.fontName} with {supporting_font} for {text!r}")
|
||||||
logger.debug(f"replacing {style.fontName} with {newFont} for {text!r}")
|
style = style.clone(name=style.name + '_' + supporting_font, fontName=supporting_font)
|
||||||
style = style.clone(name=style.name + '_' + newFont, fontName=newFont)
|
|
||||||
|
|
||||||
super().__init__(text, style, *args, **kwargs)
|
super().__init__(text, style, *args, **kwargs)
|
||||||
|
|
||||||
def _font_supports_text(self, text, font_name):
|
|
||||||
if not text:
|
|
||||||
return True
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def register_ttf_font_if_new(name, path):
|
def register_ttf_font_if_new(name, path):
|
||||||
from reportlab.pdfbase import pdfmetrics
|
from reportlab.pdfbase import pdfmetrics
|
||||||
|
|||||||
Reference in New Issue
Block a user