From 3a208fe36b21b6dd3ee70ac55a0e0bb261686687 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 24 Apr 2024 16:45:00 +0200 Subject: [PATCH] Use a new strategy to retrieve font content This way seems to work everywhere, including strange Windows computers. Fix #2079. --- weasyprint/draw.py | 24 ++++++++---------------- weasyprint/pdf/stream.py | 21 +++++++-------------- weasyprint/text/ffi.py | 7 ++++--- weasyprint/text/fonts.py | 31 ++++++++++++++++++++++++++++++- 4 files changed, 49 insertions(+), 34 deletions(-) diff --git a/weasyprint/draw.py b/weasyprint/draw.py index 1a0368886..ef15737f2 100644 --- a/weasyprint/draw.py +++ b/weasyprint/draw.py @@ -15,7 +15,8 @@ from .layout.background import BackgroundLayer from .matrix import Matrix from .stacking import StackingContext -from .text.ffi import ffi, harfbuzz, pango, units_from_double, units_to_double +from .text.ffi import ffi, pango, units_from_double, units_to_double +from .text.fonts import get_hb_face_blob_data, get_pango_font_hb_face from .text.line_break import get_last_word_end SIDES = ('top', 'right', 'bottom', 'left') @@ -1232,14 +1233,9 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix): previous_utf8_position = utf8_position if font.svg: - hb_font = pango.pango_font_get_hb_font(pango_font) - hb_face = harfbuzz.hb_font_get_face(hb_font) - hb_blob = ffi.gc( - harfbuzz.hb_ot_color_glyph_reference_svg(hb_face, glyph), - harfbuzz.hb_blob_destroy) - hb_data = harfbuzz.hb_blob_get_data(hb_blob, stream.length) - if hb_data != ffi.NULL: - svg_data = ffi.unpack(hb_data, int(stream.length[0])) + hb_face = get_pango_font_hb_face(pango_font) + svg_data = get_hb_face_blob_data(hb_face, ot_color='svg') + if svg_data: # Do as explained in specification # https://learn.microsoft.com/typography/opentype/spec/svg tree = ElementTree.fromstring(svg_data) @@ -1254,13 +1250,9 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix): a = d = font.widths[glyph] / 1000 / font.upem * font_size emojis.append([image, font, a, d, x_advance, 0]) elif font.png: - hb_font = pango.pango_font_get_hb_font(pango_font) - hb_blob = ffi.gc( - harfbuzz.hb_ot_color_glyph_reference_png(hb_font, glyph), - harfbuzz.hb_blob_destroy) - hb_data = harfbuzz.hb_blob_get_data(hb_blob, stream.length) - if hb_data != ffi.NULL: - png_data = ffi.unpack(hb_data, int(stream.length[0])) + hb_face = get_pango_font_hb_face(pango_font) + png_data = get_hb_face_blob_data(hb_face, ot_color='png') + if png_data: pillow_image = Image.open(BytesIO(png_data)) image_id = f'{font.hash}{glyph}' image = RasterImage(pillow_image, image_id, png_data) diff --git a/weasyprint/pdf/stream.py b/weasyprint/pdf/stream.py index 2b657cd36..08a770fde 100644 --- a/weasyprint/pdf/stream.py +++ b/weasyprint/pdf/stream.py @@ -13,19 +13,13 @@ from ..matrix import Matrix from ..text.constants import PANGO_STRETCH_PERCENT from ..text.ffi import ffi, harfbuzz, pango, units_to_double +from ..text.fonts import get_hb_face_blob_data, get_pango_font_hb_face class Font: def __init__(self, pango_font): - hb_font = pango.pango_font_get_hb_font(pango_font) - hb_face = harfbuzz.hb_font_get_face(hb_font) - hb_blob = ffi.gc( - harfbuzz.hb_face_reference_blob(hb_face), - harfbuzz.hb_blob_destroy) - with ffi.new('unsigned int *') as length: - hb_data = harfbuzz.hb_blob_get_data(hb_blob, length) - self.file_content = ffi.unpack(hb_data, int(length[0])) - self.index = harfbuzz.hb_face_get_index(hb_face) + hb_face = get_pango_font_hb_face(pango_font) + self.file_content = get_hb_face_blob_data(hb_face) pango_metrics = pango.pango_font_get_metrics(pango_font, ffi.NULL) self.description = description = ffi.gc( @@ -88,18 +82,17 @@ def __init__(self, pango_font): # Fonttools full_font = io.BytesIO(self.file_content) + index = harfbuzz.hb_face_get_index(hb_face) try: - self.ttfont = TTFont(full_font, fontNumber=self.index) + self.ttfont = TTFont(full_font, fontNumber=index) except Exception: LOGGER.warning('Unable to read font') self.ttfont = None self.bitmap = False else: self.bitmap = ( - 'EBDT' in self.ttfont and - 'EBLC' in self.ttfont and ( - 'glyf' not in self.ttfont or - not self.ttfont['glyf'].glyphs)) + 'EBDT' in self.ttfont and 'EBLC' in self.ttfont and ( + 'glyf' not in self.ttfont or not self.ttfont['glyf'].glyphs)) # Various properties self.italic_angle = 0 # TODO: this should be different diff --git a/weasyprint/text/ffi.py b/weasyprint/text/ffi.py index fee304293..613cd10db 100644 --- a/weasyprint/text/ffi.py +++ b/weasyprint/text/ffi.py @@ -13,7 +13,6 @@ typedef ... hb_face_t; typedef ... hb_blob_t; typedef uint32_t hb_codepoint_t; - hb_face_t * hb_font_get_face (hb_font_t *font); hb_blob_t * hb_face_reference_blob (hb_face_t *face); unsigned int hb_face_get_index (const hb_face_t *face); unsigned int hb_face_get_upem (const hb_face_t *face); @@ -226,12 +225,11 @@ PangoLayoutLine * pango_layout_get_line_readonly ( PangoLayout *layout, int line); - hb_font_t * pango_font_get_hb_font (PangoFont *font); - PangoFontDescription * pango_font_description_new (void); void pango_font_description_free (PangoFontDescription *desc); PangoFontDescription * pango_font_description_copy ( const PangoFontDescription *desc); + PangoFontMap* pango_font_get_font_map (PangoFont* font); void pango_font_description_set_family ( PangoFontDescription *desc, const char *family); @@ -390,12 +388,15 @@ // PangoFT2 + typedef ... PangoFcFont; typedef ... PangoFcFontMap; PangoFontMap * pango_ft2_font_map_new (void); void pango_fc_font_map_set_config ( PangoFcFontMap *fcfontmap, FcConfig *fcconfig); void pango_fc_font_map_config_changed (PangoFcFontMap *fcfontmap); + hb_face_t* pango_fc_font_map_get_hb_face ( + PangoFcFontMap* fcfontmap, PangoFcFont* fcfont); ''') diff --git a/weasyprint/text/fonts.py b/weasyprint/text/fonts.py index 969587d11..7e6c34a2e 100644 --- a/weasyprint/text/fonts.py +++ b/weasyprint/text/fonts.py @@ -16,7 +16,7 @@ CAPS_KEYS, EAST_ASIAN_KEYS, FONTCONFIG_STRETCH, FONTCONFIG_STYLE, FONTCONFIG_WEIGHT, LIGATURE_KEYS, NUMERIC_KEYS, PANGO_STRETCH, PANGO_STYLE) from .ffi import ( # isort:skip - ffi, fontconfig, gobject, pango, pangoft2, unicode_to_char_p, + ffi, fontconfig, gobject, harfbuzz, pango, pangoft2, unicode_to_char_p, units_from_double) @@ -333,6 +333,7 @@ def font_features(font_kerning='normal', font_variant_ligatures='normal', def get_font_description(style): + """Get font description string out of given style.""" font_description = ffi.gc( pango.pango_font_description_new(), pango.pango_font_description_free) @@ -353,3 +354,31 @@ def get_font_description(style): pango.pango_font_description_set_variations( font_description, string) return font_description + + +def get_pango_font_hb_face(pango_font): + """Get Harfbuzz face out of given Pango font.""" + fc_font = ffi.cast('PangoFcFont *', pango_font) + fontmap = ffi.cast( + 'PangoFcFontMap *', pango.pango_font_get_font_map(pango_font)) + return pangoft2.pango_fc_font_map_get_hb_face(fontmap, fc_font) + + +def get_hb_face_blob_data(hb_face, ot_color=None): + """Get binary data out of given Harfbuzz face. + + If ``ot_color`` is 'svg', return the SVG color glyph reference. If it’s 'png', + return the PNG color glyph reference. Otherwise, return the whole face blob. + + """ + if ot_color == 'png': + function = harfbuzz.hb_ot_color_glyph_reference_svg + elif ot_color == 'svg': + function = harfbuzz.hb_ot_color_glyph_reference_png + else: + function = harfbuzz.hb_face_reference_blob + hb_blob = ffi.gc(function(hb_face), harfbuzz.hb_blob_destroy) + with ffi.new('unsigned int *') as length: + hb_data = harfbuzz.hb_blob_get_data(hb_blob, length) + if hb_data != ffi.NULL: + return ffi.unpack(hb_data, int(length[0]))