xref: /aosp_15_r20/external/fonttools/Tests/ttLib/ttGlyphSet_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1from fontTools.ttLib import TTFont
2from fontTools.ttLib import ttGlyphSet
3from fontTools.ttLib.ttGlyphSet import LerpGlyphSet
4from fontTools.pens.recordingPen import (
5    RecordingPen,
6    RecordingPointPen,
7    DecomposingRecordingPen,
8)
9from fontTools.misc.roundTools import otRound
10from fontTools.misc.transform import DecomposedTransform
11import os
12import pytest
13
14
15class TTGlyphSetTest(object):
16    @staticmethod
17    def getpath(testfile):
18        path = os.path.dirname(__file__)
19        return os.path.join(path, "data", testfile)
20
21    @pytest.mark.parametrize(
22        "fontfile, location, expected",
23        [
24            (
25                "I.ttf",
26                None,
27                [
28                    ("moveTo", ((175, 0),)),
29                    ("lineTo", ((367, 0),)),
30                    ("lineTo", ((367, 1456),)),
31                    ("lineTo", ((175, 1456),)),
32                    ("closePath", ()),
33                ],
34            ),
35            (
36                "I.ttf",
37                {},
38                [
39                    ("moveTo", ((175, 0),)),
40                    ("lineTo", ((367, 0),)),
41                    ("lineTo", ((367, 1456),)),
42                    ("lineTo", ((175, 1456),)),
43                    ("closePath", ()),
44                ],
45            ),
46            (
47                "I.ttf",
48                {"wght": 100},
49                [
50                    ("moveTo", ((175, 0),)),
51                    ("lineTo", ((271, 0),)),
52                    ("lineTo", ((271, 1456),)),
53                    ("lineTo", ((175, 1456),)),
54                    ("closePath", ()),
55                ],
56            ),
57            (
58                "I.ttf",
59                {"wght": 1000},
60                [
61                    ("moveTo", ((128, 0),)),
62                    ("lineTo", ((550, 0),)),
63                    ("lineTo", ((550, 1456),)),
64                    ("lineTo", ((128, 1456),)),
65                    ("closePath", ()),
66                ],
67            ),
68            (
69                "I.ttf",
70                {"wght": 1000, "wdth": 25},
71                [
72                    ("moveTo", ((140, 0),)),
73                    ("lineTo", ((553, 0),)),
74                    ("lineTo", ((553, 1456),)),
75                    ("lineTo", ((140, 1456),)),
76                    ("closePath", ()),
77                ],
78            ),
79            (
80                "I.ttf",
81                {"wght": 1000, "wdth": 50},
82                [
83                    ("moveTo", ((136, 0),)),
84                    ("lineTo", ((552, 0),)),
85                    ("lineTo", ((552, 1456),)),
86                    ("lineTo", ((136, 1456),)),
87                    ("closePath", ()),
88                ],
89            ),
90            (
91                "I.otf",
92                {"wght": 1000},
93                [
94                    ("moveTo", ((179, 74),)),
95                    ("lineTo", ((28, 59),)),
96                    ("lineTo", ((28, 0),)),
97                    ("lineTo", ((367, 0),)),
98                    ("lineTo", ((367, 59),)),
99                    ("lineTo", ((212, 74),)),
100                    ("lineTo", ((179, 74),)),
101                    ("closePath", ()),
102                    ("moveTo", ((179, 578),)),
103                    ("lineTo", ((212, 578),)),
104                    ("lineTo", ((367, 593),)),
105                    ("lineTo", ((367, 652),)),
106                    ("lineTo", ((28, 652),)),
107                    ("lineTo", ((28, 593),)),
108                    ("lineTo", ((179, 578),)),
109                    ("closePath", ()),
110                    ("moveTo", ((98, 310),)),
111                    ("curveTo", ((98, 205), (98, 101), (95, 0))),
112                    ("lineTo", ((299, 0),)),
113                    ("curveTo", ((296, 103), (296, 207), (296, 311))),
114                    ("lineTo", ((296, 342),)),
115                    ("curveTo", ((296, 447), (296, 551), (299, 652))),
116                    ("lineTo", ((95, 652),)),
117                    ("curveTo", ((98, 549), (98, 445), (98, 342))),
118                    ("lineTo", ((98, 310),)),
119                    ("closePath", ()),
120                ],
121            ),
122            (
123                # In this font, /I has an lsb of 30, but an xMin of 25, so an
124                # offset of 5 units needs to be applied when drawing the outline.
125                # See https://github.com/fonttools/fonttools/issues/2824
126                "issue2824.ttf",
127                None,
128                [
129                    ("moveTo", ((309, 180),)),
130                    ("qCurveTo", ((274, 151), (187, 136), (104, 166), (74, 201))),
131                    ("qCurveTo", ((45, 236), (30, 323), (59, 407), (95, 436))),
132                    ("qCurveTo", ((130, 466), (217, 480), (301, 451), (330, 415))),
133                    ("qCurveTo", ((360, 380), (374, 293), (345, 210), (309, 180))),
134                    ("closePath", ()),
135                ],
136            ),
137        ],
138    )
139    def test_glyphset(self, fontfile, location, expected):
140        font = TTFont(self.getpath(fontfile))
141        glyphset = font.getGlyphSet(location=location)
142
143        assert isinstance(glyphset, ttGlyphSet._TTGlyphSet)
144
145        assert list(glyphset.keys()) == [".notdef", "I"]
146
147        assert "I" in glyphset
148        with pytest.deprecated_call():
149            assert glyphset.has_key("I")  # we should really get rid of this...
150
151        assert len(glyphset) == 2
152
153        pen = RecordingPen()
154        glyph = glyphset["I"]
155
156        assert glyphset.get("foobar") is None
157
158        assert isinstance(glyph, ttGlyphSet._TTGlyph)
159        is_glyf = fontfile.endswith(".ttf")
160        glyphType = ttGlyphSet._TTGlyphGlyf if is_glyf else ttGlyphSet._TTGlyphCFF
161        assert isinstance(glyph, glyphType)
162
163        glyph.draw(pen)
164        actual = pen.value
165
166        assert actual == expected, (location, actual, expected)
167
168    @pytest.mark.parametrize(
169        "fontfile, locations, factor, expected",
170        [
171            (
172                "I.ttf",
173                ({"wght": 400}, {"wght": 1000}),
174                0.5,
175                [
176                    ("moveTo", ((151.5, 0.0),)),
177                    ("lineTo", ((458.5, 0.0),)),
178                    ("lineTo", ((458.5, 1456.0),)),
179                    ("lineTo", ((151.5, 1456.0),)),
180                    ("closePath", ()),
181                ],
182            ),
183            (
184                "I.ttf",
185                ({"wght": 400}, {"wght": 1000}),
186                0.25,
187                [
188                    ("moveTo", ((163.25, 0.0),)),
189                    ("lineTo", ((412.75, 0.0),)),
190                    ("lineTo", ((412.75, 1456.0),)),
191                    ("lineTo", ((163.25, 1456.0),)),
192                    ("closePath", ()),
193                ],
194            ),
195        ],
196    )
197    def test_lerp_glyphset(self, fontfile, locations, factor, expected):
198        font = TTFont(self.getpath(fontfile))
199        glyphset1 = font.getGlyphSet(location=locations[0])
200        glyphset2 = font.getGlyphSet(location=locations[1])
201        glyphset = LerpGlyphSet(glyphset1, glyphset2, factor)
202
203        assert "I" in glyphset
204
205        pen = RecordingPen()
206        glyph = glyphset["I"]
207
208        assert glyphset.get("foobar") is None
209
210        glyph.draw(pen)
211        actual = pen.value
212
213        assert actual == expected, (locations, actual, expected)
214
215    def test_glyphset_varComposite_components(self):
216        font = TTFont(self.getpath("varc-ac00-ac01.ttf"))
217        glyphset = font.getGlyphSet()
218
219        pen = RecordingPen()
220        glyph = glyphset["uniAC00"]
221
222        glyph.draw(pen)
223        actual = pen.value
224
225        expected = [
226            (
227                "addVarComponent",
228                (
229                    "glyph00003",
230                    DecomposedTransform(460.0, 676.0, 0, 1, 1, 0, 0, 0, 0),
231                    {
232                        "0000": 0.84661865234375,
233                        "0001": 0.98944091796875,
234                        "0002": 0.47283935546875,
235                        "0003": 0.446533203125,
236                    },
237                ),
238            ),
239            (
240                "addVarComponent",
241                (
242                    "glyph00004",
243                    DecomposedTransform(932.0, 382.0, 0, 1, 1, 0, 0, 0, 0),
244                    {
245                        "0000": 0.93359375,
246                        "0001": 0.916015625,
247                        "0002": 0.523193359375,
248                        "0003": 0.32806396484375,
249                        "0004": 0.85089111328125,
250                    },
251                ),
252            ),
253        ]
254
255        assert actual == expected, (actual, expected)
256
257    def test_glyphset_varComposite1(self):
258        font = TTFont(self.getpath("varc-ac00-ac01.ttf"))
259        glyphset = font.getGlyphSet(location={"wght": 600})
260
261        pen = DecomposingRecordingPen(glyphset)
262        glyph = glyphset["uniAC00"]
263
264        glyph.draw(pen)
265        actual = pen.value
266
267        expected = [
268            ("moveTo", ((432, 678),)),
269            ("lineTo", ((432, 620),)),
270            (
271                "qCurveTo",
272                (
273                    (419, 620),
274                    (374, 621),
275                    (324, 619),
276                    (275, 618),
277                    (237, 617),
278                    (228, 616),
279                ),
280            ),
281            ("qCurveTo", ((218, 616), (188, 612), (160, 605), (149, 601))),
282            ("qCurveTo", ((127, 611), (83, 639), (67, 654))),
283            ("qCurveTo", ((64, 657), (63, 662), (64, 666))),
284            ("lineTo", ((72, 678),)),
285            ("qCurveTo", ((93, 674), (144, 672), (164, 672))),
286            (
287                "qCurveTo",
288                (
289                    (173, 672),
290                    (213, 672),
291                    (266, 673),
292                    (323, 674),
293                    (377, 675),
294                    (421, 678),
295                    (432, 678),
296                ),
297            ),
298            ("closePath", ()),
299            ("moveTo", ((525, 619),)),
300            ("lineTo", ((412, 620),)),
301            ("lineTo", ((429, 678),)),
302            ("lineTo", ((466, 697),)),
303            ("qCurveTo", ((470, 698), (482, 698), (486, 697))),
304            ("qCurveTo", ((494, 693), (515, 682), (536, 670), (541, 667))),
305            ("qCurveTo", ((545, 663), (545, 656), (543, 652))),
306            ("lineTo", ((525, 619),)),
307            ("closePath", ()),
308            ("moveTo", ((63, 118),)),
309            ("lineTo", ((47, 135),)),
310            ("qCurveTo", ((42, 141), (48, 146))),
311            ("qCurveTo", ((135, 213), (278, 373), (383, 541), (412, 620))),
312            ("lineTo", ((471, 642),)),
313            ("lineTo", ((525, 619),)),
314            ("qCurveTo", ((496, 529), (365, 342), (183, 179), (75, 121))),
315            ("qCurveTo", ((72, 119), (65, 118), (63, 118))),
316            ("closePath", ()),
317            ("moveTo", ((925, 372),)),
318            ("lineTo", ((739, 368),)),
319            ("lineTo", ((739, 427),)),
320            ("lineTo", ((822, 430),)),
321            ("lineTo", ((854, 451),)),
322            ("qCurveTo", ((878, 453), (930, 449), (944, 445))),
323            ("qCurveTo", ((961, 441), (962, 426))),
324            ("qCurveTo", ((964, 411), (956, 386), (951, 381))),
325            ("qCurveTo", ((947, 376), (931, 372), (925, 372))),
326            ("closePath", ()),
327            ("moveTo", ((729, -113),)),
328            ("lineTo", ((674, -113),)),
329            ("qCurveTo", ((671, -98), (669, -42), (666, 22), (665, 83), (665, 102))),
330            ("lineTo", ((665, 763),)),
331            ("qCurveTo", ((654, 780), (608, 810), (582, 820))),
332            ("lineTo", ((593, 850),)),
333            ("qCurveTo", ((594, 852), (599, 856), (607, 856))),
334            ("qCurveTo", ((628, 855), (684, 846), (736, 834), (752, 827))),
335            ("qCurveTo", ((766, 818), (766, 802))),
336            ("lineTo", ((762, 745),)),
337            ("lineTo", ((762, 134),)),
338            ("qCurveTo", ((762, 107), (757, 43), (749, -25), (737, -87), (729, -113))),
339            ("closePath", ()),
340        ]
341
342        actual = [
343            (op, tuple((otRound(pt[0]), otRound(pt[1])) for pt in args))
344            for op, args in actual
345        ]
346
347        assert actual == expected, (actual, expected)
348
349        # Test that drawing twice works, we accidentally don't change the component
350        pen = DecomposingRecordingPen(glyphset)
351        glyph.draw(pen)
352        actual = pen.value
353        actual = [
354            (op, tuple((otRound(pt[0]), otRound(pt[1])) for pt in args))
355            for op, args in actual
356        ]
357        assert actual == expected, (actual, expected)
358
359        pen = RecordingPointPen()
360        glyph.drawPoints(pen)
361        assert pen.value
362
363    def test_glyphset_varComposite2(self):
364        # This test font has axis variations
365
366        font = TTFont(self.getpath("varc-6868.ttf"))
367        glyphset = font.getGlyphSet(location={"wght": 600})
368
369        pen = DecomposingRecordingPen(glyphset)
370        glyph = glyphset["uni6868"]
371
372        glyph.draw(pen)
373        actual = pen.value
374
375        expected = [
376            ("moveTo", ((460, 565),)),
377            (
378                "qCurveTo",
379                (
380                    (482, 577),
381                    (526, 603),
382                    (568, 632),
383                    (607, 663),
384                    (644, 698),
385                    (678, 735),
386                    (708, 775),
387                    (721, 796),
388                ),
389            ),
390            ("lineTo", ((632, 835),)),
391            (
392                "qCurveTo",
393                (
394                    (621, 817),
395                    (595, 784),
396                    (566, 753),
397                    (534, 724),
398                    (499, 698),
399                    (462, 675),
400                    (423, 653),
401                    (403, 644),
402                ),
403            ),
404            ("closePath", ()),
405            ("moveTo", ((616, 765),)),
406            ("lineTo", ((590, 682),)),
407            ("lineTo", ((830, 682),)),
408            ("lineTo", ((833, 682),)),
409            ("lineTo", ((828, 693),)),
410            (
411                "qCurveTo",
412                (
413                    (817, 671),
414                    (775, 620),
415                    (709, 571),
416                    (615, 525),
417                    (492, 490),
418                    (413, 480),
419                ),
420            ),
421            ("lineTo", ((454, 386),)),
422            (
423                "qCurveTo",
424                (
425                    (544, 403),
426                    (687, 455),
427                    (798, 519),
428                    (877, 590),
429                    (926, 655),
430                    (937, 684),
431                ),
432            ),
433            ("lineTo", ((937, 765),)),
434            ("closePath", ()),
435            ("moveTo", ((723, 555),)),
436            (
437                "qCurveTo",
438                (
439                    (713, 563),
440                    (693, 579),
441                    (672, 595),
442                    (651, 610),
443                    (629, 625),
444                    (606, 638),
445                    (583, 651),
446                    (572, 657),
447                ),
448            ),
449            ("lineTo", ((514, 590),)),
450            (
451                "qCurveTo",
452                (
453                    (525, 584),
454                    (547, 572),
455                    (568, 559),
456                    (589, 545),
457                    (609, 531),
458                    (629, 516),
459                    (648, 500),
460                    (657, 492),
461                ),
462            ),
463            ("closePath", ()),
464            ("moveTo", ((387, 375),)),
465            ("lineTo", ((387, 830),)),
466            ("lineTo", ((289, 830),)),
467            ("lineTo", ((289, 375),)),
468            ("closePath", ()),
469            ("moveTo", ((96, 383),)),
470            (
471                "qCurveTo",
472                (
473                    (116, 390),
474                    (156, 408),
475                    (194, 427),
476                    (231, 449),
477                    (268, 472),
478                    (302, 497),
479                    (335, 525),
480                    (351, 539),
481                ),
482            ),
483            ("lineTo", ((307, 610),)),
484            (
485                "qCurveTo",
486                (
487                    (291, 597),
488                    (257, 572),
489                    (221, 549),
490                    (185, 528),
491                    (147, 509),
492                    (108, 492),
493                    (69, 476),
494                    (48, 469),
495                ),
496            ),
497            ("closePath", ()),
498            ("moveTo", ((290, 653),)),
499            (
500                "qCurveTo",
501                (
502                    (281, 664),
503                    (261, 687),
504                    (240, 708),
505                    (219, 729),
506                    (196, 749),
507                    (173, 768),
508                    (148, 786),
509                    (136, 794),
510                ),
511            ),
512            ("lineTo", ((69, 727),)),
513            (
514                "qCurveTo",
515                (
516                    (81, 719),
517                    (105, 702),
518                    (129, 684),
519                    (151, 665),
520                    (173, 645),
521                    (193, 625),
522                    (213, 604),
523                    (222, 593),
524                ),
525            ),
526            ("closePath", ()),
527            ("moveTo", ((913, -57),)),
528            ("lineTo", ((953, 30),)),
529            (
530                "qCurveTo",
531                (
532                    (919, 41),
533                    (854, 67),
534                    (790, 98),
535                    (729, 134),
536                    (671, 173),
537                    (616, 217),
538                    (564, 264),
539                    (540, 290),
540                ),
541            ),
542            ("lineTo", ((522, 286),)),
543            ("qCurveTo", ((511, 267), (498, 235), (493, 213), (492, 206))),
544            ("lineTo", ((515, 209),)),
545            ("qCurveTo", ((569, 146), (695, 44), (835, -32), (913, -57))),
546            ("closePath", ()),
547            ("moveTo", ((474, 274),)),
548            ("lineTo", ((452, 284),)),
549            (
550                "qCurveTo",
551                (
552                    (428, 260),
553                    (377, 214),
554                    (323, 172),
555                    (266, 135),
556                    (206, 101),
557                    (144, 71),
558                    (80, 46),
559                    (47, 36),
560                ),
561            ),
562            ("lineTo", ((89, -53),)),
563            ("qCurveTo", ((163, -29), (299, 46), (423, 142), (476, 201))),
564            ("lineTo", ((498, 196),)),
565            ("qCurveTo", ((498, 203), (494, 225), (482, 255), (474, 274))),
566            ("closePath", ()),
567            ("moveTo", ((450, 250),)),
568            ("lineTo", ((550, 250),)),
569            ("lineTo", ((550, 379),)),
570            ("lineTo", ((450, 379),)),
571            ("closePath", ()),
572            ("moveTo", ((68, 215),)),
573            ("lineTo", ((932, 215),)),
574            ("lineTo", ((932, 305),)),
575            ("lineTo", ((68, 305),)),
576            ("closePath", ()),
577            ("moveTo", ((450, -71),)),
578            ("lineTo", ((550, -71),)),
579            ("lineTo", ((550, -71),)),
580            ("lineTo", ((550, 267),)),
581            ("lineTo", ((450, 267),)),
582            ("lineTo", ((450, -71),)),
583            ("closePath", ()),
584        ]
585
586        actual = [
587            (op, tuple((otRound(pt[0]), otRound(pt[1])) for pt in args))
588            for op, args in actual
589        ]
590
591        assert actual == expected, (actual, expected)
592
593        pen = RecordingPointPen()
594        glyph.drawPoints(pen)
595        assert pen.value
596
597    def test_cubic_glyf(self):
598        font = TTFont(self.getpath("dot-cubic.ttf"))
599        glyphset = font.getGlyphSet()
600
601        expected = [
602            ("moveTo", ((76, 181),)),
603            ("curveTo", ((103, 181), (125, 158), (125, 131))),
604            ("curveTo", ((125, 104), (103, 82), (76, 82))),
605            ("curveTo", ((48, 82), (26, 104), (26, 131))),
606            ("curveTo", ((26, 158), (48, 181), (76, 181))),
607            ("closePath", ()),
608        ]
609
610        pen = RecordingPen()
611        glyphset["one"].draw(pen)
612        assert pen.value == expected
613
614        expectedPoints = [
615            ("beginPath", (), {}),
616            ("addPoint", ((76, 181), "curve", False, None), {}),
617            ("addPoint", ((103, 181), None, False, None), {}),
618            ("addPoint", ((125, 158), None, False, None), {}),
619            ("addPoint", ((125, 104), None, False, None), {}),
620            ("addPoint", ((103, 82), None, False, None), {}),
621            ("addPoint", ((76, 82), "curve", False, None), {}),
622            ("addPoint", ((48, 82), None, False, None), {}),
623            ("addPoint", ((26, 104), None, False, None), {}),
624            ("addPoint", ((26, 158), None, False, None), {}),
625            ("addPoint", ((48, 181), None, False, None), {}),
626            ("endPath", (), {}),
627        ]
628        pen = RecordingPointPen()
629        glyphset["one"].drawPoints(pen)
630        assert pen.value == expectedPoints
631
632        pen = RecordingPen()
633        glyphset["two"].draw(pen)
634        assert pen.value == expected
635
636        expectedPoints = [
637            ("beginPath", (), {}),
638            ("addPoint", ((26, 158), None, False, None), {}),
639            ("addPoint", ((48, 181), None, False, None), {}),
640            ("addPoint", ((76, 181), "curve", False, None), {}),
641            ("addPoint", ((103, 181), None, False, None), {}),
642            ("addPoint", ((125, 158), None, False, None), {}),
643            ("addPoint", ((125, 104), None, False, None), {}),
644            ("addPoint", ((103, 82), None, False, None), {}),
645            ("addPoint", ((76, 82), "curve", False, None), {}),
646            ("addPoint", ((48, 82), None, False, None), {}),
647            ("addPoint", ((26, 104), None, False, None), {}),
648            ("endPath", (), {}),
649        ]
650        pen = RecordingPointPen()
651        glyphset["two"].drawPoints(pen)
652        assert pen.value == expectedPoints
653
654        pen = RecordingPen()
655        glyphset["three"].draw(pen)
656        assert pen.value == expected
657
658        expectedPoints = [
659            ("beginPath", (), {}),
660            ("addPoint", ((48, 82), None, False, None), {}),
661            ("addPoint", ((26, 104), None, False, None), {}),
662            ("addPoint", ((26, 158), None, False, None), {}),
663            ("addPoint", ((48, 181), None, False, None), {}),
664            ("addPoint", ((76, 181), "curve", False, None), {}),
665            ("addPoint", ((103, 181), None, False, None), {}),
666            ("addPoint", ((125, 158), None, False, None), {}),
667            ("addPoint", ((125, 104), None, False, None), {}),
668            ("addPoint", ((103, 82), None, False, None), {}),
669            ("addPoint", ((76, 82), "curve", False, None), {}),
670            ("endPath", (), {}),
671        ]
672        pen = RecordingPointPen()
673        glyphset["three"].drawPoints(pen)
674        assert pen.value == expectedPoints
675
676        pen = RecordingPen()
677        glyphset["four"].draw(pen)
678        assert pen.value == [
679            ("moveTo", ((75.5, 181),)),
680            ("curveTo", ((103, 181), (125, 158), (125, 131))),
681            ("curveTo", ((125, 104), (103, 82), (75.5, 82))),
682            ("curveTo", ((48, 82), (26, 104), (26, 131))),
683            ("curveTo", ((26, 158), (48, 181), (75.5, 181))),
684            ("closePath", ()),
685        ]
686
687        # Ouch! We can't represent all-cubic-offcurves in pointPen!
688        # https://github.com/fonttools/fonttools/issues/3191
689        expectedPoints = [
690            ("beginPath", (), {}),
691            ("addPoint", ((103, 181), None, False, None), {}),
692            ("addPoint", ((125, 158), None, False, None), {}),
693            ("addPoint", ((125, 104), None, False, None), {}),
694            ("addPoint", ((103, 82), None, False, None), {}),
695            ("addPoint", ((48, 82), None, False, None), {}),
696            ("addPoint", ((26, 104), None, False, None), {}),
697            ("addPoint", ((26, 158), None, False, None), {}),
698            ("addPoint", ((48, 181), None, False, None), {}),
699            ("endPath", (), {}),
700        ]
701        pen = RecordingPointPen()
702        glyphset["four"].drawPoints(pen)
703        print(pen.value)
704        assert pen.value == expectedPoints
705