File: test_fix.py

package info (click to toggle)
gftools 0.9.95%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 12,584 kB
  • sloc: python: 15,785; sh: 33; makefile: 6
file content (283 lines) | stat: -rw-r--r-- 9,673 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
from fontTools.ttLib import newTable, TTFont
from gftools.fix import *
from glob import glob
import pytest
import os
from copy import deepcopy


TEST_DATA = os.path.join("data", "test")


@pytest.fixture
def static_font():
    return TTFont(os.path.join(TEST_DATA, "Lora-Regular.ttf"))


@pytest.fixture
def var_font():
    return TTFont(os.path.join(TEST_DATA, "Inconsolata[wdth,wght].ttf"))


@pytest.fixture
def var_fonts():
    paths = [
        os.path.join(TEST_DATA, "Raleway[wght].ttf"),
        os.path.join(TEST_DATA, "Raleway-Italic[wght].ttf"),
    ]
    return [TTFont(p) for p in paths]


@pytest.fixture
def static_fonts():
    return [TTFont(f) for f in glob(os.path.join("data", "test", "mavenpro", "*.ttf"))]


def test_remove_tables(static_font):
    # Test removing a table which is part of UNWANTED_TABLES
    tsi1_tbl = newTable("TSI1")
    static_font["TSI1"] = tsi1_tbl
    assert "TSI1" in static_font

    tsi2_tbl = newTable("TSI2")
    static_font["TSI2"] = tsi2_tbl
    remove_tables(static_font, ["TSI1", "TSI2"])
    assert "TSI1" not in static_font
    assert "TSI2" not in static_font

    # Test removing a table which is essential
    remove_tables(static_font, ["glyf"])
    assert "glyf" in static_font


def test_add_dummy_dsig(static_font):
    assert "DSIG" not in static_font
    add_dummy_dsig(static_font)
    assert "DSIG" in static_font


def test_fix_hinted_font(static_font):
    static_font["head"].flags &= ~(1 << 3)
    assert static_font["head"].flags & (1 << 3) != (1 << 3)
    static_font["fpgm"] = newTable("fpgm")
    fix_hinted_font(static_font)
    assert static_font["head"].flags & (1 << 3) == (1 << 3)


def test_fix_unhinted_font(static_font):
    for tbl in ("prep", "gasp"):
        if tbl in static_font:
            del static_font[tbl]

    fix_unhinted_font(static_font)
    assert static_font["gasp"].gaspRange == {65535: 15}
    assert "prep" in static_font


def test_fix_fs_type(static_font):
    static_font["OS/2"].fsType = 1
    assert static_font["OS/2"].fsType == 1
    fix_fs_type(static_font)
    assert static_font["OS/2"].fsType == 0


# Taken from https://github.com/googlefonts/gf-docs/tree/main/Spec#supported-styles
STYLE_HEADERS = "style, weight_class, fs_selection, mac_style"
STYLE_TABLE = [
    ("Hairline", 1, (1 << 6), (0 << 0)),
    ("Thin", 100, (1 << 6), (0 << 0)),
    ("ExtraLight", 200, (1 << 6), (0 << 0)),
    ("Light", 300, (1 << 6), (0 << 0)),
    ("Regular", 400, (1 << 6), (0 << 0)),
    ("Medium", 500, (1 << 6), (0 << 0)),
    ("SemiBold", 600, (1 << 6), (0 << 0)),
    ("Bold", 700, (1 << 5), (1 << 0)),
    ("ExtraBold", 800, (1 << 6), (0 << 0)),
    ("Black", 900, (1 << 6), (0 << 0)),
    ("ExtraBlack", 1000, (1 << 6), (0 << 0)),
    ("Hairline Italic", 1, (1 << 0), (1 << 1)),
    ("Thin Italic", 100, (1 << 0), (1 << 1)),
    ("ExtraLight Italic", 200, (1 << 0), (1 << 1)),
    ("Light Italic", 300, (1 << 0), (1 << 1)),
    ("Italic", 400, (1 << 0), (1 << 1)),
    ("Medium Italic", 500, (1 << 0), (1 << 1)),
    ("SemiBold Italic", 600, (1 << 0), (1 << 1)),
    ("Bold Italic", 700, (1 << 0) | (1 << 5), (1 << 0) | (1 << 1)),
    ("ExtraBold Italic", 800, (1 << 0), (1 << 1)),
    ("Black Italic", 900, (1 << 0), (1 << 1)),
    ("ExtraBlack Italic", 1000, (1 << 0), (1 << 1)),
    # Variable fonts may have tokens other than weight and italic in their names
    ("SemiCondensed Bold Italic", 700, (1 << 0) | (1 << 5), (1 << 0) | (1 << 1)),
    ("12pt Italic", 400, (1 << 0), (1 << 1)),
]


@pytest.mark.parametrize(STYLE_HEADERS, STYLE_TABLE)
def test_fix_weight_class(static_font, style, weight_class, fs_selection, mac_style):
    name = static_font["name"]
    name.setName(style, 2, 3, 1, 0x409)
    name.setName(style, 17, 3, 1, 0x409)
    fix_weight_class(static_font)
    assert static_font["OS/2"].usWeightClass == weight_class


def test_unknown_weight_class(static_font):
    name = static_font["name"]
    name.setName("Foobar", 2, 3, 1, 0x409)
    name.setName("Foobar", 17, 3, 1, 0x409)
    from gftools.fix import WEIGHT_NAMES

    with pytest.raises(ValueError, match="Cannot determine usWeightClass"):
        fix_weight_class(static_font)


@pytest.mark.parametrize(STYLE_HEADERS, STYLE_TABLE)
def test_fs_selection(static_font, style, weight_class, fs_selection, mac_style):
    # disable fsSelection bits above 6
    for i in range(7, 12):
        static_font["OS/2"].fsSelection &= ~(1 << i)
    name = static_font["name"]
    name.setName(style, 2, 3, 1, 0x409)
    name.setName(style, 17, 3, 1, 0x409)
    fix_fs_selection(static_font)
    assert static_font["OS/2"].fsSelection == fs_selection


@pytest.mark.parametrize(STYLE_HEADERS, STYLE_TABLE)
def test_fix_mac_style(static_font, style, weight_class, fs_selection, mac_style):
    name = static_font["name"]
    name.setName(style, 2, 3, 1, 0x409)
    name.setName(style, 17, 3, 1, 0x409)
    fix_mac_style(static_font)
    assert static_font["head"].macStyle == mac_style


def _check_vertical_metrics(fonts):
    ref_font = fonts[0]
    y_min = min(f["head"].yMin for f in fonts)
    y_max = max(f["head"].yMax for f in fonts)
    for font in fonts:
        # Check fsSelection bit 7 (USE_TYPO_METRICS) is enabled
        assert font["OS/2"].fsSelection & (1 << 7) > 0

        # Check metrics are consistent across family
        assert font["OS/2"].usWinAscent == ref_font["OS/2"].usWinAscent
        assert font["OS/2"].usWinDescent == ref_font["OS/2"].usWinDescent
        assert font["OS/2"].sTypoAscender == ref_font["OS/2"].sTypoAscender
        assert font["OS/2"].sTypoDescender == ref_font["OS/2"].sTypoDescender
        assert font["OS/2"].sTypoLineGap == ref_font["OS/2"].sTypoLineGap
        assert font["hhea"].ascent == ref_font["hhea"].ascent
        assert font["hhea"].descent == ref_font["hhea"].descent
        assert font["hhea"].lineGap == ref_font["hhea"].lineGap

        # Check typo and hhea match
        assert font["OS/2"].sTypoAscender == font["hhea"].ascent
        assert font["OS/2"].sTypoDescender == ref_font["hhea"].descent
        assert font["OS/2"].sTypoLineGap == ref_font["hhea"].lineGap

        # Check win matches family_bounds
        assert font["OS/2"].usWinAscent == y_max
        assert font["OS/2"].usWinDescent == abs(y_min)


def test_fix_vertical_metrics_family_consistency(static_fonts):
    _check_vertical_metrics(static_fonts)
    static_fonts[0]["OS/2"].sTypoLineGap = 1000
    static_fonts[0]["OS/2"].usWinAscent = 4000

    fix_vertical_metrics(static_fonts)
    _check_vertical_metrics(static_fonts)


def test_fix_vertical_metrics_win_values(static_fonts):
    _check_vertical_metrics(static_fonts)
    for font in static_fonts:
        font["OS/2"].usWinAscent = font["OS/2"].usWinDescent = 0
        assert font["OS/2"].usWinAscent == 0 and font["OS/2"].usWinDescent == 0

    fix_vertical_metrics(static_fonts)
    _check_vertical_metrics(static_fonts)


def test_fix_vertical_metrics_typo_and_hhea_match(static_fonts):
    _check_vertical_metrics(static_fonts)
    for font in static_fonts:
        font["hhea"].ascent = 5000
        font["OS/2"].sTypoAscender == 1000
        assert font["hhea"].ascent != font["OS/2"].sTypoAscender

    fix_vertical_metrics(static_fonts)
    _check_vertical_metrics(static_fonts)


def test_fix_vertical_metrics_typo_metrics_enabled(static_fonts):
    _check_vertical_metrics(static_fonts)

    # family currently has fsSelection bit 7 enabled, unset it and change
    # the win Metrics to Typo values
    for font in static_fonts:
        font["OS/2"].fsSelection &= ~(1 << 7)
        font["OS/2"].usWinAscent = 500
        font["OS/2"].usWinDescent = 300

    fix_vertical_metrics(static_fonts)
    # Since fsSelection bit 7 is now enabled, in order for the metrics to visually
    # match the unfixed metrics, the typo values should now be the same as the
    # unfixed win values.
    for font in static_fonts:
        assert font["OS/2"].sTypoAscender == 500
        assert font["OS/2"].sTypoDescender == -300
        assert font["OS/2"].sTypoLineGap == 0
    _check_vertical_metrics(static_fonts)


@pytest.mark.parametrize(
    "font_path",
    [
        (os.path.join(TEST_DATA, "CairoPlay[slnt,wght]-no-empty-glyphs.ttf")),
        (os.path.join(TEST_DATA, "CairoPlay[slnt,wght]-gid1-not-empty.ttf")),
    ],
)
def test_fix_colr_v0_font(font_path):
    # Fix a COLR v0 font.
    # maximum_color should not be run and GID 1 should have a blank glyph
    from gftools.fix import fix_colr_font

    font = TTFont(font_path)

    gid1 = font.getGlyphOrder()[1]
    assert font["glyf"][gid1].numberOfContours != 0

    fix_colr_font(font)
    gid1 = font.getGlyphOrder()[1]
    assert font["glyf"][gid1].numberOfContours == 0
    assert "SVG " not in font


@pytest.fixture
def colr_v1_font():
    return TTFont(os.path.join(TEST_DATA, "Nabla[EDPT,EHLT].subset.ttf"))


def test_fix_colr_v1_font(colr_v1_font):
    # Fix a COLR v1 font. Only maximum_color should be run
    from gftools.fix import fix_colr_font

    assert "SVG " not in colr_v1_font
    colr_v1_font["COLR"].version = 1
    fix_colr_font(colr_v1_font)
    assert "SVG " in colr_v1_font


def test_ofl_license_strings(static_font):
    from gftools.fix import fix_license_strings
    from gftools.constants import OFL_LICENSE_INFO, OFL_LICENSE_URL

    for id in (13, 14):
        assert (
            "http://scripts.sil.org/OFL"
            in static_font["name"].getName(id, 3, 1, 0x409).toUnicode()
        )
    fix_license_strings(static_font)
    for id, expected in ((13, OFL_LICENSE_INFO), (14, OFL_LICENSE_URL)):
        assert expected == static_font["name"].getName(id, 3, 1, 0x409).toUnicode()