blob: 177b8a4e7bef6dd97ead449c88009cc6dea66308 [file] [log] [blame]
from fontTools.ttLib import TTFont
from fontTools.ttLib import ttGlyphSet
from fontTools.ttLib.ttGlyphSet import LerpGlyphSet
from fontTools.pens.recordingPen import (
RecordingPen,
RecordingPointPen,
DecomposingRecordingPen,
)
from fontTools.misc.roundTools import otRound
from fontTools.misc.transform import DecomposedTransform
import os
import pytest
class TTGlyphSetTest(object):
@staticmethod
def getpath(testfile):
path = os.path.dirname(__file__)
return os.path.join(path, "data", testfile)
@pytest.mark.parametrize(
"fontfile, location, expected",
[
(
"I.ttf",
None,
[
("moveTo", ((175, 0),)),
("lineTo", ((367, 0),)),
("lineTo", ((367, 1456),)),
("lineTo", ((175, 1456),)),
("closePath", ()),
],
),
(
"I.ttf",
{},
[
("moveTo", ((175, 0),)),
("lineTo", ((367, 0),)),
("lineTo", ((367, 1456),)),
("lineTo", ((175, 1456),)),
("closePath", ()),
],
),
(
"I.ttf",
{"wght": 100},
[
("moveTo", ((175, 0),)),
("lineTo", ((271, 0),)),
("lineTo", ((271, 1456),)),
("lineTo", ((175, 1456),)),
("closePath", ()),
],
),
(
"I.ttf",
{"wght": 1000},
[
("moveTo", ((128, 0),)),
("lineTo", ((550, 0),)),
("lineTo", ((550, 1456),)),
("lineTo", ((128, 1456),)),
("closePath", ()),
],
),
(
"I.ttf",
{"wght": 1000, "wdth": 25},
[
("moveTo", ((140, 0),)),
("lineTo", ((553, 0),)),
("lineTo", ((553, 1456),)),
("lineTo", ((140, 1456),)),
("closePath", ()),
],
),
(
"I.ttf",
{"wght": 1000, "wdth": 50},
[
("moveTo", ((136, 0),)),
("lineTo", ((552, 0),)),
("lineTo", ((552, 1456),)),
("lineTo", ((136, 1456),)),
("closePath", ()),
],
),
(
"I.otf",
{"wght": 1000},
[
("moveTo", ((179, 74),)),
("lineTo", ((28, 59),)),
("lineTo", ((28, 0),)),
("lineTo", ((367, 0),)),
("lineTo", ((367, 59),)),
("lineTo", ((212, 74),)),
("lineTo", ((179, 74),)),
("closePath", ()),
("moveTo", ((179, 578),)),
("lineTo", ((212, 578),)),
("lineTo", ((367, 593),)),
("lineTo", ((367, 652),)),
("lineTo", ((28, 652),)),
("lineTo", ((28, 593),)),
("lineTo", ((179, 578),)),
("closePath", ()),
("moveTo", ((98, 310),)),
("curveTo", ((98, 205), (98, 101), (95, 0))),
("lineTo", ((299, 0),)),
("curveTo", ((296, 103), (296, 207), (296, 311))),
("lineTo", ((296, 342),)),
("curveTo", ((296, 447), (296, 551), (299, 652))),
("lineTo", ((95, 652),)),
("curveTo", ((98, 549), (98, 445), (98, 342))),
("lineTo", ((98, 310),)),
("closePath", ()),
],
),
(
# In this font, /I has an lsb of 30, but an xMin of 25, so an
# offset of 5 units needs to be applied when drawing the outline.
# See https://github.com/fonttools/fonttools/issues/2824
"issue2824.ttf",
None,
[
("moveTo", ((309, 180),)),
("qCurveTo", ((274, 151), (187, 136), (104, 166), (74, 201))),
("qCurveTo", ((45, 236), (30, 323), (59, 407), (95, 436))),
("qCurveTo", ((130, 466), (217, 480), (301, 451), (330, 415))),
("qCurveTo", ((360, 380), (374, 293), (345, 210), (309, 180))),
("closePath", ()),
],
),
],
)
def test_glyphset(self, fontfile, location, expected):
font = TTFont(self.getpath(fontfile))
glyphset = font.getGlyphSet(location=location)
assert isinstance(glyphset, ttGlyphSet._TTGlyphSet)
assert list(glyphset.keys()) == [".notdef", "I"]
assert "I" in glyphset
with pytest.deprecated_call():
assert glyphset.has_key("I") # we should really get rid of this...
assert len(glyphset) == 2
pen = RecordingPen()
glyph = glyphset["I"]
assert glyphset.get("foobar") is None
assert isinstance(glyph, ttGlyphSet._TTGlyph)
is_glyf = fontfile.endswith(".ttf")
glyphType = ttGlyphSet._TTGlyphGlyf if is_glyf else ttGlyphSet._TTGlyphCFF
assert isinstance(glyph, glyphType)
glyph.draw(pen)
actual = pen.value
assert actual == expected, (location, actual, expected)
@pytest.mark.parametrize(
"fontfile, locations, factor, expected",
[
(
"I.ttf",
({"wght": 400}, {"wght": 1000}),
0.5,
[
("moveTo", ((151.5, 0.0),)),
("lineTo", ((458.5, 0.0),)),
("lineTo", ((458.5, 1456.0),)),
("lineTo", ((151.5, 1456.0),)),
("closePath", ()),
],
),
(
"I.ttf",
({"wght": 400}, {"wght": 1000}),
0.25,
[
("moveTo", ((163.25, 0.0),)),
("lineTo", ((412.75, 0.0),)),
("lineTo", ((412.75, 1456.0),)),
("lineTo", ((163.25, 1456.0),)),
("closePath", ()),
],
),
],
)
def test_lerp_glyphset(self, fontfile, locations, factor, expected):
font = TTFont(self.getpath(fontfile))
glyphset1 = font.getGlyphSet(location=locations[0])
glyphset2 = font.getGlyphSet(location=locations[1])
glyphset = LerpGlyphSet(glyphset1, glyphset2, factor)
assert "I" in glyphset
pen = RecordingPen()
glyph = glyphset["I"]
assert glyphset.get("foobar") is None
glyph.draw(pen)
actual = pen.value
assert actual == expected, (locations, actual, expected)
def test_glyphset_varComposite_components(self):
font = TTFont(self.getpath("varc-ac00-ac01.ttf"))
glyphset = font.getGlyphSet()
pen = RecordingPen()
glyph = glyphset["uniAC00"]
glyph.draw(pen)
actual = pen.value
expected = [
(
"addVarComponent",
(
"glyph00003",
DecomposedTransform(460.0, 676.0, 0, 1, 1, 0, 0, 0, 0),
{
"0000": 0.84661865234375,
"0001": 0.98944091796875,
"0002": 0.47283935546875,
"0003": 0.446533203125,
},
),
),
(
"addVarComponent",
(
"glyph00004",
DecomposedTransform(932.0, 382.0, 0, 1, 1, 0, 0, 0, 0),
{
"0000": 0.93359375,
"0001": 0.916015625,
"0002": 0.523193359375,
"0003": 0.32806396484375,
"0004": 0.85089111328125,
},
),
),
]
assert actual == expected, (actual, expected)
def test_glyphset_varComposite1(self):
font = TTFont(self.getpath("varc-ac00-ac01.ttf"))
glyphset = font.getGlyphSet(location={"wght": 600})
pen = DecomposingRecordingPen(glyphset)
glyph = glyphset["uniAC00"]
glyph.draw(pen)
actual = pen.value
expected = [
("moveTo", ((432, 678),)),
("lineTo", ((432, 620),)),
(
"qCurveTo",
(
(419, 620),
(374, 621),
(324, 619),
(275, 618),
(237, 617),
(228, 616),
),
),
("qCurveTo", ((218, 616), (188, 612), (160, 605), (149, 601))),
("qCurveTo", ((127, 611), (83, 639), (67, 654))),
("qCurveTo", ((64, 657), (63, 662), (64, 666))),
("lineTo", ((72, 678),)),
("qCurveTo", ((93, 674), (144, 672), (164, 672))),
(
"qCurveTo",
(
(173, 672),
(213, 672),
(266, 673),
(323, 674),
(377, 675),
(421, 678),
(432, 678),
),
),
("closePath", ()),
("moveTo", ((525, 619),)),
("lineTo", ((412, 620),)),
("lineTo", ((429, 678),)),
("lineTo", ((466, 697),)),
("qCurveTo", ((470, 698), (482, 698), (486, 697))),
("qCurveTo", ((494, 693), (515, 682), (536, 670), (541, 667))),
("qCurveTo", ((545, 663), (545, 656), (543, 652))),
("lineTo", ((525, 619),)),
("closePath", ()),
("moveTo", ((63, 118),)),
("lineTo", ((47, 135),)),
("qCurveTo", ((42, 141), (48, 146))),
("qCurveTo", ((135, 213), (278, 373), (383, 541), (412, 620))),
("lineTo", ((471, 642),)),
("lineTo", ((525, 619),)),
("qCurveTo", ((496, 529), (365, 342), (183, 179), (75, 121))),
("qCurveTo", ((72, 119), (65, 118), (63, 118))),
("closePath", ()),
("moveTo", ((925, 372),)),
("lineTo", ((739, 368),)),
("lineTo", ((739, 427),)),
("lineTo", ((822, 430),)),
("lineTo", ((854, 451),)),
("qCurveTo", ((878, 453), (930, 449), (944, 445))),
("qCurveTo", ((961, 441), (962, 426))),
("qCurveTo", ((964, 411), (956, 386), (951, 381))),
("qCurveTo", ((947, 376), (931, 372), (925, 372))),
("closePath", ()),
("moveTo", ((729, -113),)),
("lineTo", ((674, -113),)),
("qCurveTo", ((671, -98), (669, -42), (666, 22), (665, 83), (665, 102))),
("lineTo", ((665, 763),)),
("qCurveTo", ((654, 780), (608, 810), (582, 820))),
("lineTo", ((593, 850),)),
("qCurveTo", ((594, 852), (599, 856), (607, 856))),
("qCurveTo", ((628, 855), (684, 846), (736, 834), (752, 827))),
("qCurveTo", ((766, 818), (766, 802))),
("lineTo", ((762, 745),)),
("lineTo", ((762, 134),)),
("qCurveTo", ((762, 107), (757, 43), (749, -25), (737, -87), (729, -113))),
("closePath", ()),
]
actual = [
(op, tuple((otRound(pt[0]), otRound(pt[1])) for pt in args))
for op, args in actual
]
assert actual == expected, (actual, expected)
# Test that drawing twice works, we accidentally don't change the component
pen = DecomposingRecordingPen(glyphset)
glyph.draw(pen)
actual = pen.value
actual = [
(op, tuple((otRound(pt[0]), otRound(pt[1])) for pt in args))
for op, args in actual
]
assert actual == expected, (actual, expected)
pen = RecordingPointPen()
glyph.drawPoints(pen)
assert pen.value
def test_glyphset_varComposite2(self):
# This test font has axis variations
font = TTFont(self.getpath("varc-6868.ttf"))
glyphset = font.getGlyphSet(location={"wght": 600})
pen = DecomposingRecordingPen(glyphset)
glyph = glyphset["uni6868"]
glyph.draw(pen)
actual = pen.value
expected = [
("moveTo", ((460, 565),)),
(
"qCurveTo",
(
(482, 577),
(526, 603),
(568, 632),
(607, 663),
(644, 698),
(678, 735),
(708, 775),
(721, 796),
),
),
("lineTo", ((632, 835),)),
(
"qCurveTo",
(
(621, 817),
(595, 784),
(566, 753),
(534, 724),
(499, 698),
(462, 675),
(423, 653),
(403, 644),
),
),
("closePath", ()),
("moveTo", ((616, 765),)),
("lineTo", ((590, 682),)),
("lineTo", ((830, 682),)),
("lineTo", ((833, 682),)),
("lineTo", ((828, 693),)),
(
"qCurveTo",
(
(817, 671),
(775, 620),
(709, 571),
(615, 525),
(492, 490),
(413, 480),
),
),
("lineTo", ((454, 386),)),
(
"qCurveTo",
(
(544, 403),
(687, 455),
(798, 519),
(877, 590),
(926, 655),
(937, 684),
),
),
("lineTo", ((937, 765),)),
("closePath", ()),
("moveTo", ((723, 555),)),
(
"qCurveTo",
(
(713, 563),
(693, 579),
(672, 595),
(651, 610),
(629, 625),
(606, 638),
(583, 651),
(572, 657),
),
),
("lineTo", ((514, 590),)),
(
"qCurveTo",
(
(525, 584),
(547, 572),
(568, 559),
(589, 545),
(609, 531),
(629, 516),
(648, 500),
(657, 492),
),
),
("closePath", ()),
("moveTo", ((387, 375),)),
("lineTo", ((387, 830),)),
("lineTo", ((289, 830),)),
("lineTo", ((289, 375),)),
("closePath", ()),
("moveTo", ((96, 383),)),
(
"qCurveTo",
(
(116, 390),
(156, 408),
(194, 427),
(231, 449),
(268, 472),
(302, 497),
(335, 525),
(351, 539),
),
),
("lineTo", ((307, 610),)),
(
"qCurveTo",
(
(291, 597),
(257, 572),
(221, 549),
(185, 528),
(147, 509),
(108, 492),
(69, 476),
(48, 469),
),
),
("closePath", ()),
("moveTo", ((290, 653),)),
(
"qCurveTo",
(
(281, 664),
(261, 687),
(240, 708),
(219, 729),
(196, 749),
(173, 768),
(148, 786),
(136, 794),
),
),
("lineTo", ((69, 727),)),
(
"qCurveTo",
(
(81, 719),
(105, 702),
(129, 684),
(151, 665),
(173, 645),
(193, 625),
(213, 604),
(222, 593),
),
),
("closePath", ()),
("moveTo", ((913, -57),)),
("lineTo", ((953, 30),)),
(
"qCurveTo",
(
(919, 41),
(854, 67),
(790, 98),
(729, 134),
(671, 173),
(616, 217),
(564, 264),
(540, 290),
),
),
("lineTo", ((522, 286),)),
("qCurveTo", ((511, 267), (498, 235), (493, 213), (492, 206))),
("lineTo", ((515, 209),)),
("qCurveTo", ((569, 146), (695, 44), (835, -32), (913, -57))),
("closePath", ()),
("moveTo", ((474, 274),)),
("lineTo", ((452, 284),)),
(
"qCurveTo",
(
(428, 260),
(377, 214),
(323, 172),
(266, 135),
(206, 101),
(144, 71),
(80, 46),
(47, 36),
),
),
("lineTo", ((89, -53),)),
("qCurveTo", ((163, -29), (299, 46), (423, 142), (476, 201))),
("lineTo", ((498, 196),)),
("qCurveTo", ((498, 203), (494, 225), (482, 255), (474, 274))),
("closePath", ()),
("moveTo", ((450, 250),)),
("lineTo", ((550, 250),)),
("lineTo", ((550, 379),)),
("lineTo", ((450, 379),)),
("closePath", ()),
("moveTo", ((68, 215),)),
("lineTo", ((932, 215),)),
("lineTo", ((932, 305),)),
("lineTo", ((68, 305),)),
("closePath", ()),
("moveTo", ((450, -71),)),
("lineTo", ((550, -71),)),
("lineTo", ((550, -71),)),
("lineTo", ((550, 267),)),
("lineTo", ((450, 267),)),
("lineTo", ((450, -71),)),
("closePath", ()),
]
actual = [
(op, tuple((otRound(pt[0]), otRound(pt[1])) for pt in args))
for op, args in actual
]
assert actual == expected, (actual, expected)
pen = RecordingPointPen()
glyph.drawPoints(pen)
assert pen.value
def test_cubic_glyf(self):
font = TTFont(self.getpath("dot-cubic.ttf"))
glyphset = font.getGlyphSet()
expected = [
("moveTo", ((76, 181),)),
("curveTo", ((103, 181), (125, 158), (125, 131))),
("curveTo", ((125, 104), (103, 82), (76, 82))),
("curveTo", ((48, 82), (26, 104), (26, 131))),
("curveTo", ((26, 158), (48, 181), (76, 181))),
("closePath", ()),
]
pen = RecordingPen()
glyphset["one"].draw(pen)
assert pen.value == expected
expectedPoints = [
("beginPath", (), {}),
("addPoint", ((76, 181), "curve", False, None), {}),
("addPoint", ((103, 181), None, False, None), {}),
("addPoint", ((125, 158), None, False, None), {}),
("addPoint", ((125, 104), None, False, None), {}),
("addPoint", ((103, 82), None, False, None), {}),
("addPoint", ((76, 82), "curve", False, None), {}),
("addPoint", ((48, 82), None, False, None), {}),
("addPoint", ((26, 104), None, False, None), {}),
("addPoint", ((26, 158), None, False, None), {}),
("addPoint", ((48, 181), None, False, None), {}),
("endPath", (), {}),
]
pen = RecordingPointPen()
glyphset["one"].drawPoints(pen)
assert pen.value == expectedPoints
pen = RecordingPen()
glyphset["two"].draw(pen)
assert pen.value == expected
expectedPoints = [
("beginPath", (), {}),
("addPoint", ((26, 158), None, False, None), {}),
("addPoint", ((48, 181), None, False, None), {}),
("addPoint", ((76, 181), "curve", False, None), {}),
("addPoint", ((103, 181), None, False, None), {}),
("addPoint", ((125, 158), None, False, None), {}),
("addPoint", ((125, 104), None, False, None), {}),
("addPoint", ((103, 82), None, False, None), {}),
("addPoint", ((76, 82), "curve", False, None), {}),
("addPoint", ((48, 82), None, False, None), {}),
("addPoint", ((26, 104), None, False, None), {}),
("endPath", (), {}),
]
pen = RecordingPointPen()
glyphset["two"].drawPoints(pen)
assert pen.value == expectedPoints
pen = RecordingPen()
glyphset["three"].draw(pen)
assert pen.value == expected
expectedPoints = [
("beginPath", (), {}),
("addPoint", ((48, 82), None, False, None), {}),
("addPoint", ((26, 104), None, False, None), {}),
("addPoint", ((26, 158), None, False, None), {}),
("addPoint", ((48, 181), None, False, None), {}),
("addPoint", ((76, 181), "curve", False, None), {}),
("addPoint", ((103, 181), None, False, None), {}),
("addPoint", ((125, 158), None, False, None), {}),
("addPoint", ((125, 104), None, False, None), {}),
("addPoint", ((103, 82), None, False, None), {}),
("addPoint", ((76, 82), "curve", False, None), {}),
("endPath", (), {}),
]
pen = RecordingPointPen()
glyphset["three"].drawPoints(pen)
assert pen.value == expectedPoints
pen = RecordingPen()
glyphset["four"].draw(pen)
assert pen.value == [
("moveTo", ((75.5, 181),)),
("curveTo", ((103, 181), (125, 158), (125, 131))),
("curveTo", ((125, 104), (103, 82), (75.5, 82))),
("curveTo", ((48, 82), (26, 104), (26, 131))),
("curveTo", ((26, 158), (48, 181), (75.5, 181))),
("closePath", ()),
]
# Ouch! We can't represent all-cubic-offcurves in pointPen!
# https://github.com/fonttools/fonttools/issues/3191
expectedPoints = [
("beginPath", (), {}),
("addPoint", ((103, 181), None, False, None), {}),
("addPoint", ((125, 158), None, False, None), {}),
("addPoint", ((125, 104), None, False, None), {}),
("addPoint", ((103, 82), None, False, None), {}),
("addPoint", ((48, 82), None, False, None), {}),
("addPoint", ((26, 104), None, False, None), {}),
("addPoint", ((26, 158), None, False, None), {}),
("addPoint", ((48, 181), None, False, None), {}),
("endPath", (), {}),
]
pen = RecordingPointPen()
glyphset["four"].drawPoints(pen)
print(pen.value)
assert pen.value == expectedPoints