From 79f8e34e23071bc165d6f4f567936c2997aaa860 Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Thu, 19 Oct 2023 12:44:03 +0200 Subject: [PATCH 1/4] Fix variable font instantiation, part two (#1981) (1) Prevents identical wght variations from being doubly embedded under different named weights (typically normal and bold) (2) Assign good approximate named weights, which prevents metadata from being actively misleading. --- weasyprint/pdf/stream.py | 67 ++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/weasyprint/pdf/stream.py b/weasyprint/pdf/stream.py index ce309ebf6..269ec9691 100644 --- a/weasyprint/pdf/stream.py +++ b/weasyprint/pdf/stream.py @@ -34,6 +34,47 @@ def __init__(self, pango_font): self.style = pango.pango_font_description_get_style(description) self.family = ffi.string( pango.pango_font_description_get_family(description)) + + self.variations = pango.pango_font_description_get_variations( + self.description) + if self.variations == ffi.NULL: + self.variations = {} + else: + self.variations = { + part.split('=')[0]: float(part.split('=')[1]) + for part in ffi.string(self.variations).decode().split(',')} + if 'wght' in self.variations: + if 0 < self.variations['wght'] <= 150: + pango.pango_font_description_set_weight(self.description, + pango.PANGO_WEIGHT_THIN) + if 150 < self.variations['wght'] <= 250: + pango.pango_font_description_set_weight(self.description, + pango.PANGO_WEIGHT_ULTRALIGHT) + if 250 < self.variations['wght'] <= 350: + pango.pango_font_description_set_weight(self.description, + pango.PANGO_WEIGHT_LIGHT) + if 350 < self.variations['wght'] <= 450: + pango.pango_font_description_set_weight(self.description, + pango.PANGO_WEIGHT_NORMAL) + if 450 < self.variations['wght'] <= 550: + pango.pango_font_description_set_weight(self.description, + pango.PANGO_WEIGHT_MEDIUM) + if 550 < self.variations['wght'] <= 650: + pango.pango_font_description_set_weight(self.description, + pango.PANGO_WEIGHT_SEMIBOLD) + if 650 < self.variations['wght'] <= 750: + pango.pango_font_description_set_weight(self.description, + pango.PANGO_WEIGHT_BOLD) + if 750 < self.variations['wght'] <= 850: + pango.pango_font_description_set_weight(self.description, + pango.PANGO_WEIGHT_ULTRABOLD) + if 850 < self.variations['wght'] <= 950: + pango.pango_font_description_set_weight(self.description, + pango.PANGO_WEIGHT_HEAVY) + if 950 < self.variations['wght'] <= 1000: + pango.pango_font_description_set_weight(self.description, + pango.PANGO_WEIGHT_ULTRAHEAVY) + description_string = ffi.string( pango.pango_font_description_to_string(description)) # Never use the built-in hash function here: it’s not stable @@ -122,20 +163,12 @@ def clean(self, cmap, hinting): # Transform variable into static font if 'fvar' in self.ttfont: - variations = pango.pango_font_description_get_variations( - self.description) - if variations == ffi.NULL: - variations = {} - else: - variations = { - part.split('=')[0]: float(part.split('=')[1]) - for part in ffi.string(variations).decode().split(',')} - if 'wght' not in variations: - variations['wght'] = pango.pango_font_description_get_weight( + if 'wght' not in self.variations: + self.variations['wght'] = pango.pango_font_description_get_weight( self.description) - if 'opsz' not in variations: - variations['opsz'] = units_to_double(self.font_size) - if 'slnt' not in variations: + if 'opsz' not in self.variations: + self.variations['opsz'] = units_to_double(self.font_size) + if 'slnt' not in self.variations: slnt = 0 if self.style == 1: for axe in self.ttfont['fvar'].axes: @@ -145,12 +178,12 @@ def clean(self, cmap, hinting): else: slnt = axe.maxValue break - variations['slnt'] = slnt - if 'ital' not in variations: - variations['ital'] = int(self.style == 2) + self.variations['slnt'] = slnt + if 'ital' not in self.variations: + self.variations['ital'] = int(self.style == 2) partial_font = io.BytesIO() try: - ttfont = instantiateVariableFont(self.ttfont, variations) + ttfont = instantiateVariableFont(self.ttfont, self.variations) for key, (advance, bearing) in ttfont['hmtx'].metrics.items(): if advance < 0: ttfont['hmtx'].metrics[key] = (0, bearing) From 0b2d463f710f392ac8b333d9f268626433083011 Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Fri, 20 Oct 2023 11:53:09 +0200 Subject: [PATCH 2/4] Fix variable font instantiation, part three (#1981) Adds italic naming to font metadata where appropriate --- weasyprint/pdf/stream.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/weasyprint/pdf/stream.py b/weasyprint/pdf/stream.py index 269ec9691..2a294835c 100644 --- a/weasyprint/pdf/stream.py +++ b/weasyprint/pdf/stream.py @@ -74,6 +74,10 @@ def __init__(self, pango_font): if 950 < self.variations['wght'] <= 1000: pango.pango_font_description_set_weight(self.description, pango.PANGO_WEIGHT_ULTRAHEAVY) + if 'slnt' in self.variations: + if self.variations['slnt'] != 0: + pango.pango_font_description_set_style(self.description, + pango.PANGO_STYLE_ITALIC) description_string = ffi.string( pango.pango_font_description_to_string(description)) From e44f1221d873dcf3a2b1f8b38e08d63dcbae4c73 Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Fri, 20 Oct 2023 12:34:13 +0200 Subject: [PATCH 3/4] Fix variable font instantiation, part four (#1981) Adds stretch naming to font metadata where appropriate --- weasyprint/pdf/stream.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/weasyprint/pdf/stream.py b/weasyprint/pdf/stream.py index 2a294835c..7d9cb82b8 100644 --- a/weasyprint/pdf/stream.py +++ b/weasyprint/pdf/stream.py @@ -78,6 +78,34 @@ def __init__(self, pango_font): if self.variations['slnt'] != 0: pango.pango_font_description_set_style(self.description, pango.PANGO_STYLE_ITALIC) + if 'wdth' in self.variations: + if 0 < self.variations['wdth'] <= 56.25: + pango.pango_font_description_set_stretch(self.description, + pango.PANGO_STRETCH_ULTRA_CONDENSED) + if 56.25 < self.variations['wdth'] <= 68.75: + pango.pango_font_description_set_stretch(self.description, + pango.PANGO_STRETCH_EXTRA_CONDENSED) + if 68.75 < self.variations['wdth'] <= 81.25: + pango.pango_font_description_set_stretch(self.description, + pango.PANGO_STRETCH_CONDENSED) + if 81.25 < self.variations['wdth'] <= 93.75: + pango.pango_font_description_set_stretch(self.description, + pango.PANGO_STRETCH_SEMI_CONDENSED) + if 93.75 < self.variations['wdth'] <= 106.25: + pango.pango_font_description_set_stretch(self.description, + pango.PANGO_STRETCH_NORMAL) + if 106.25 < self.variations['wdth'] <= 118.75: + pango.pango_font_description_set_stretch(self.description, + pango.PANGO_STRETCH_SEMI_EXPANDED) + if 118.75 < self.variations['wdth'] <= 131.25: + pango.pango_font_description_set_stretch(self.description, + pango.PANGO_STRETCH_EXPANDED) + if 131.25 < self.variations['wdth'] <= 175.00: + pango.pango_font_description_set_stretch(self.description, + pango.PANGO_STRETCH_EXTRA_EXPANDED) + if 175.00 < self.variations['wdth'] <= 1000.00: + pango.pango_font_description_set_stretch(self.description, + pango.PANGO_STRETCH_ULTRA_EXPANDED) description_string = ffi.string( pango.pango_font_description_to_string(description)) From d596ca51aca308a413a9dbdc96f68a1633fe381a Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Tue, 5 Dec 2023 23:40:31 +0100 Subject: [PATCH 4/4] Simplify variable font instantiation --- weasyprint/pdf/stream.py | 88 ++++++++---------------------------- weasyprint/text/constants.py | 12 +++++ 2 files changed, 32 insertions(+), 68 deletions(-) diff --git a/weasyprint/pdf/stream.py b/weasyprint/pdf/stream.py index 7d9cb82b8..0f82ad175 100644 --- a/weasyprint/pdf/stream.py +++ b/weasyprint/pdf/stream.py @@ -11,6 +11,7 @@ from ..logger import LOGGER from ..matrix import Matrix +from ..text.constants import PANGO_STRETCH_PERCENT from ..text.ffi import ffi, harfbuzz, pango, units_to_double @@ -35,80 +36,30 @@ def __init__(self, pango_font): self.family = ffi.string( pango.pango_font_description_get_family(description)) - self.variations = pango.pango_font_description_get_variations( + self.variations = {} + variations = pango.pango_font_description_get_variations( self.description) - if self.variations == ffi.NULL: - self.variations = {} - else: + if variations != ffi.NULL: self.variations = { part.split('=')[0]: float(part.split('=')[1]) - for part in ffi.string(self.variations).decode().split(',')} + for part in ffi.string(variations).decode().split(',')} if 'wght' in self.variations: - if 0 < self.variations['wght'] <= 150: - pango.pango_font_description_set_weight(self.description, - pango.PANGO_WEIGHT_THIN) - if 150 < self.variations['wght'] <= 250: - pango.pango_font_description_set_weight(self.description, - pango.PANGO_WEIGHT_ULTRALIGHT) - if 250 < self.variations['wght'] <= 350: - pango.pango_font_description_set_weight(self.description, - pango.PANGO_WEIGHT_LIGHT) - if 350 < self.variations['wght'] <= 450: - pango.pango_font_description_set_weight(self.description, - pango.PANGO_WEIGHT_NORMAL) - if 450 < self.variations['wght'] <= 550: - pango.pango_font_description_set_weight(self.description, - pango.PANGO_WEIGHT_MEDIUM) - if 550 < self.variations['wght'] <= 650: - pango.pango_font_description_set_weight(self.description, - pango.PANGO_WEIGHT_SEMIBOLD) - if 650 < self.variations['wght'] <= 750: - pango.pango_font_description_set_weight(self.description, - pango.PANGO_WEIGHT_BOLD) - if 750 < self.variations['wght'] <= 850: - pango.pango_font_description_set_weight(self.description, - pango.PANGO_WEIGHT_ULTRABOLD) - if 850 < self.variations['wght'] <= 950: - pango.pango_font_description_set_weight(self.description, - pango.PANGO_WEIGHT_HEAVY) - if 950 < self.variations['wght'] <= 1000: - pango.pango_font_description_set_weight(self.description, - pango.PANGO_WEIGHT_ULTRAHEAVY) - if 'slnt' in self.variations: - if self.variations['slnt'] != 0: - pango.pango_font_description_set_style(self.description, - pango.PANGO_STYLE_ITALIC) + pango.pango_font_description_set_weight( + self.description, int(round(self.variations['wght']))) + if self.variations.get('ital'): + pango.pango_font_description_set_style( + self.description, pango.PANGO_STYLE_ITALIC) + elif self.variations.get('slnt'): + pango.pango_font_description_set_style( + self.description, pango.PANGO_STYLE_OBLIQUE) if 'wdth' in self.variations: - if 0 < self.variations['wdth'] <= 56.25: - pango.pango_font_description_set_stretch(self.description, - pango.PANGO_STRETCH_ULTRA_CONDENSED) - if 56.25 < self.variations['wdth'] <= 68.75: - pango.pango_font_description_set_stretch(self.description, - pango.PANGO_STRETCH_EXTRA_CONDENSED) - if 68.75 < self.variations['wdth'] <= 81.25: - pango.pango_font_description_set_stretch(self.description, - pango.PANGO_STRETCH_CONDENSED) - if 81.25 < self.variations['wdth'] <= 93.75: - pango.pango_font_description_set_stretch(self.description, - pango.PANGO_STRETCH_SEMI_CONDENSED) - if 93.75 < self.variations['wdth'] <= 106.25: - pango.pango_font_description_set_stretch(self.description, - pango.PANGO_STRETCH_NORMAL) - if 106.25 < self.variations['wdth'] <= 118.75: - pango.pango_font_description_set_stretch(self.description, - pango.PANGO_STRETCH_SEMI_EXPANDED) - if 118.75 < self.variations['wdth'] <= 131.25: - pango.pango_font_description_set_stretch(self.description, - pango.PANGO_STRETCH_EXPANDED) - if 131.25 < self.variations['wdth'] <= 175.00: - pango.pango_font_description_set_stretch(self.description, - pango.PANGO_STRETCH_EXTRA_EXPANDED) - if 175.00 < self.variations['wdth'] <= 1000.00: - pango.pango_font_description_set_stretch(self.description, - pango.PANGO_STRETCH_ULTRA_EXPANDED) - + stretch = min( + PANGO_STRETCH_PERCENT.items(), + key=lambda item: abs(item[0] - self.variations['wdth']))[1] + pango.pango_font_description_set_stretch(self.description, stretch) description_string = ffi.string( pango.pango_font_description_to_string(description)) + # Never use the built-in hash function here: it’s not stable self.hash = ''.join( chr(65 + letter % 26) for letter @@ -196,8 +147,9 @@ def clean(self, cmap, hinting): # Transform variable into static font if 'fvar' in self.ttfont: if 'wght' not in self.variations: - self.variations['wght'] = pango.pango_font_description_get_weight( + weight = pango.pango_font_description_get_weight( self.description) + self.variations['wght'] = weight if 'opsz' not in self.variations: self.variations['opsz'] = units_to_double(self.font_size) if 'slnt' not in self.variations: diff --git a/weasyprint/text/constants.py b/weasyprint/text/constants.py index bc88f689e..087e557fc 100644 --- a/weasyprint/text/constants.py +++ b/weasyprint/text/constants.py @@ -19,6 +19,18 @@ 'extra-expanded': pango.PANGO_STRETCH_EXTRA_EXPANDED, 'ultra-expanded': pango.PANGO_STRETCH_ULTRA_EXPANDED, } +# From https://drafts.csswg.org/css-fonts/#font-stretch-prop +PANGO_STRETCH_PERCENT = { + 50: pango.PANGO_STRETCH_ULTRA_CONDENSED, + 62.5: pango.PANGO_STRETCH_EXTRA_CONDENSED, + 75: pango.PANGO_STRETCH_CONDENSED, + 87.5: pango.PANGO_STRETCH_SEMI_CONDENSED, + 100: pango.PANGO_STRETCH_NORMAL, + 112.5: pango.PANGO_STRETCH_SEMI_EXPANDED, + 125: pango.PANGO_STRETCH_EXPANDED, + 150: pango.PANGO_STRETCH_EXTRA_EXPANDED, + 200: pango.PANGO_STRETCH_ULTRA_EXPANDED, +} PANGO_WRAP_MODE = { 'WRAP_WORD': pango.PANGO_WRAP_WORD, 'WRAP_CHAR': pango.PANGO_WRAP_CHAR,