aboutsummaryrefslogtreecommitdiff
path: root/Tests/ttLib/tables/_g_l_y_f_test.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tests/ttLib/tables/_g_l_y_f_test.py')
-rw-r--r--Tests/ttLib/tables/_g_l_y_f_test.py632
1 files changed, 512 insertions, 120 deletions
diff --git a/Tests/ttLib/tables/_g_l_y_f_test.py b/Tests/ttLib/tables/_g_l_y_f_test.py
index 84f30dc6..ce2e0e57 100644
--- a/Tests/ttLib/tables/_g_l_y_f_test.py
+++ b/Tests/ttLib/tables/_g_l_y_f_test.py
@@ -1,5 +1,6 @@
from fontTools.misc.fixedTools import otRound
from fontTools.misc.testTools import getXML, parseXML
+from fontTools.misc.transform import Transform
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.recordingPen import RecordingPen, RecordingPointPen
from fontTools.pens.pointPen import PointToSegmentPen
@@ -8,6 +9,9 @@ from fontTools.ttLib.tables._g_l_y_f import (
Glyph,
GlyphCoordinates,
GlyphComponent,
+ dropImpliedOnCurvePoints,
+ flagOnCurve,
+ flagCubic,
ARGS_ARE_XY_VALUES,
SCALED_COMPONENT_OFFSET,
UNSCALED_COMPONENT_OFFSET,
@@ -18,7 +22,8 @@ from fontTools.ttLib.tables._g_l_y_f import (
from fontTools.ttLib.tables import ttProgram
import sys
import array
-from io import StringIO
+from copy import deepcopy
+from io import StringIO, BytesIO
import itertools
import pytest
import re
@@ -27,139 +32,137 @@ import unittest
class GlyphCoordinatesTest(object):
-
def test_translate(self):
- g = GlyphCoordinates([(1,2)])
- g.translate((.5,0))
- assert g == GlyphCoordinates([(1.5,2.0)])
+ g = GlyphCoordinates([(1, 2)])
+ g.translate((0.5, 0))
+ assert g == GlyphCoordinates([(1.5, 2.0)])
def test_scale(self):
- g = GlyphCoordinates([(1,2)])
- g.scale((.5,0))
- assert g == GlyphCoordinates([(0.5,0.0)])
+ g = GlyphCoordinates([(1, 2)])
+ g.scale((0.5, 0))
+ assert g == GlyphCoordinates([(0.5, 0.0)])
def test_transform(self):
- g = GlyphCoordinates([(1,2)])
- g.transform(((.5,0),(.2,.5)))
- assert g[0] == GlyphCoordinates([(0.9,1.0)])[0]
+ g = GlyphCoordinates([(1, 2)])
+ g.transform(((0.5, 0), (0.2, 0.5)))
+ assert g[0] == GlyphCoordinates([(0.9, 1.0)])[0]
def test__eq__(self):
- g = GlyphCoordinates([(1,2)])
- g2 = GlyphCoordinates([(1.0,2)])
- g3 = GlyphCoordinates([(1.5,2)])
+ g = GlyphCoordinates([(1, 2)])
+ g2 = GlyphCoordinates([(1.0, 2)])
+ g3 = GlyphCoordinates([(1.5, 2)])
assert g == g2
assert not g == g3
assert not g2 == g3
assert not g == object()
def test__ne__(self):
- g = GlyphCoordinates([(1,2)])
- g2 = GlyphCoordinates([(1.0,2)])
- g3 = GlyphCoordinates([(1.5,2)])
+ g = GlyphCoordinates([(1, 2)])
+ g2 = GlyphCoordinates([(1.0, 2)])
+ g3 = GlyphCoordinates([(1.5, 2)])
assert not (g != g2)
assert g != g3
assert g2 != g3
assert g != object()
def test__pos__(self):
- g = GlyphCoordinates([(1,2)])
+ g = GlyphCoordinates([(1, 2)])
g2 = +g
assert g == g2
def test__neg__(self):
- g = GlyphCoordinates([(1,2)])
+ g = GlyphCoordinates([(1, 2)])
g2 = -g
assert g2 == GlyphCoordinates([(-1, -2)])
- @pytest.mark.skipif(sys.version_info[0] < 3,
- reason="__round___ requires Python 3")
+ @pytest.mark.skipif(sys.version_info[0] < 3, reason="__round___ requires Python 3")
def test__round__(self):
- g = GlyphCoordinates([(-1.5,2)])
+ g = GlyphCoordinates([(-1.5, 2)])
g2 = round(g)
- assert g2 == GlyphCoordinates([(-1,2)])
+ assert g2 == GlyphCoordinates([(-1, 2)])
def test__add__(self):
- g1 = GlyphCoordinates([(1,2)])
- g2 = GlyphCoordinates([(3,4)])
- g3 = GlyphCoordinates([(4,6)])
+ g1 = GlyphCoordinates([(1, 2)])
+ g2 = GlyphCoordinates([(3, 4)])
+ g3 = GlyphCoordinates([(4, 6)])
assert g1 + g2 == g3
- assert g1 + (1, 1) == GlyphCoordinates([(2,3)])
+ assert g1 + (1, 1) == GlyphCoordinates([(2, 3)])
with pytest.raises(TypeError) as excinfo:
assert g1 + object()
- assert 'unsupported operand' in str(excinfo.value)
+ assert "unsupported operand" in str(excinfo.value)
def test__sub__(self):
- g1 = GlyphCoordinates([(1,2)])
- g2 = GlyphCoordinates([(3,4)])
- g3 = GlyphCoordinates([(-2,-2)])
+ g1 = GlyphCoordinates([(1, 2)])
+ g2 = GlyphCoordinates([(3, 4)])
+ g3 = GlyphCoordinates([(-2, -2)])
assert g1 - g2 == g3
- assert g1 - (1, 1) == GlyphCoordinates([(0,1)])
+ assert g1 - (1, 1) == GlyphCoordinates([(0, 1)])
with pytest.raises(TypeError) as excinfo:
assert g1 - object()
- assert 'unsupported operand' in str(excinfo.value)
+ assert "unsupported operand" in str(excinfo.value)
def test__rsub__(self):
- g = GlyphCoordinates([(1,2)])
+ g = GlyphCoordinates([(1, 2)])
# other + (-self)
- assert (1, 1) - g == GlyphCoordinates([(0,-1)])
+ assert (1, 1) - g == GlyphCoordinates([(0, -1)])
def test__mul__(self):
- g = GlyphCoordinates([(1,2)])
- assert g * 3 == GlyphCoordinates([(3,6)])
- assert g * (3,2) == GlyphCoordinates([(3,4)])
- assert g * (1,1) == g
+ g = GlyphCoordinates([(1, 2)])
+ assert g * 3 == GlyphCoordinates([(3, 6)])
+ assert g * (3, 2) == GlyphCoordinates([(3, 4)])
+ assert g * (1, 1) == g
with pytest.raises(TypeError) as excinfo:
assert g * object()
- assert 'unsupported operand' in str(excinfo.value)
+ assert "unsupported operand" in str(excinfo.value)
def test__truediv__(self):
- g = GlyphCoordinates([(1,2)])
- assert g / 2 == GlyphCoordinates([(.5,1)])
- assert g / (1, 2) == GlyphCoordinates([(1,1)])
+ g = GlyphCoordinates([(1, 2)])
+ assert g / 2 == GlyphCoordinates([(0.5, 1)])
+ assert g / (1, 2) == GlyphCoordinates([(1, 1)])
assert g / (1, 1) == g
with pytest.raises(TypeError) as excinfo:
assert g / object()
- assert 'unsupported operand' in str(excinfo.value)
+ assert "unsupported operand" in str(excinfo.value)
def test__iadd__(self):
- g = GlyphCoordinates([(1,2)])
- g += (.5,0)
+ g = GlyphCoordinates([(1, 2)])
+ g += (0.5, 0)
assert g == GlyphCoordinates([(1.5, 2.0)])
- g2 = GlyphCoordinates([(3,4)])
+ g2 = GlyphCoordinates([(3, 4)])
g += g2
assert g == GlyphCoordinates([(4.5, 6.0)])
def test__isub__(self):
- g = GlyphCoordinates([(1,2)])
- g -= (.5, 0)
+ g = GlyphCoordinates([(1, 2)])
+ g -= (0.5, 0)
assert g == GlyphCoordinates([(0.5, 2.0)])
- g2 = GlyphCoordinates([(3,4)])
+ g2 = GlyphCoordinates([(3, 4)])
g -= g2
assert g == GlyphCoordinates([(-2.5, -2.0)])
def __test__imul__(self):
- g = GlyphCoordinates([(1,2)])
- g *= (2,.5)
+ g = GlyphCoordinates([(1, 2)])
+ g *= (2, 0.5)
g *= 2
assert g == GlyphCoordinates([(4.0, 2.0)])
- g = GlyphCoordinates([(1,2)])
+ g = GlyphCoordinates([(1, 2)])
g *= 2
assert g == GlyphCoordinates([(2, 4)])
def test__itruediv__(self):
- g = GlyphCoordinates([(1,3)])
- g /= (.5,1.5)
+ g = GlyphCoordinates([(1, 3)])
+ g /= (0.5, 1.5)
g /= 2
assert g == GlyphCoordinates([(1.0, 1.0)])
def test__bool__(self):
g = GlyphCoordinates([])
assert bool(g) == False
- g = GlyphCoordinates([(0,0), (0.,0)])
+ g = GlyphCoordinates([(0, 0), (0.0, 0)])
assert bool(g) == True
- g = GlyphCoordinates([(0,0), (1,0)])
+ g = GlyphCoordinates([(0, 0), (1, 0)])
assert bool(g) == True
- g = GlyphCoordinates([(0,.5), (0,0)])
+ g = GlyphCoordinates([(0, 0.5), (0, 0)])
assert bool(g) == True
def test_double_precision_float(self):
@@ -179,21 +182,21 @@ class GlyphCoordinatesTest(object):
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
-DATA_DIR = os.path.join(CURR_DIR, 'data')
+DATA_DIR = os.path.join(CURR_DIR, "data")
GLYF_TTX = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.ttx")
GLYF_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.glyf.bin")
HEAD_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.head.bin")
LOCA_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.loca.bin")
MAXP_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.maxp.bin")
+INST_TTX = os.path.join(DATA_DIR, "_g_l_y_f_instructions.ttx")
def strip_ttLibVersion(string):
- return re.sub(' ttLibVersion=".*"', '', string)
+ return re.sub(' ttLibVersion=".*"', "", string)
class GlyfTableTest(unittest.TestCase):
-
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
@@ -203,26 +206,26 @@ class GlyfTableTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
- with open(GLYF_BIN, 'rb') as f:
+ with open(GLYF_BIN, "rb") as f:
cls.glyfData = f.read()
- with open(HEAD_BIN, 'rb') as f:
+ with open(HEAD_BIN, "rb") as f:
cls.headData = f.read()
- with open(LOCA_BIN, 'rb') as f:
+ with open(LOCA_BIN, "rb") as f:
cls.locaData = f.read()
- with open(MAXP_BIN, 'rb') as f:
+ with open(MAXP_BIN, "rb") as f:
cls.maxpData = f.read()
- with open(GLYF_TTX, 'r') as f:
+ with open(GLYF_TTX, "r") as f:
cls.glyfXML = strip_ttLibVersion(f.read()).splitlines()
def test_toXML(self):
font = TTFont(sfntVersion="\x00\x01\x00\x00")
- glyfTable = font['glyf'] = newTable('glyf')
- font['head'] = newTable('head')
- font['loca'] = newTable('loca')
- font['maxp'] = newTable('maxp')
- font['maxp'].decompile(self.maxpData, font)
- font['head'].decompile(self.headData, font)
- font['loca'].decompile(self.locaData, font)
+ glyfTable = font["glyf"] = newTable("glyf")
+ font["head"] = newTable("head")
+ font["loca"] = newTable("loca")
+ font["maxp"] = newTable("maxp")
+ font["maxp"].decompile(self.maxpData, font)
+ font["head"].decompile(self.headData, font)
+ font["loca"].decompile(self.locaData, font)
glyfTable.decompile(self.glyfData, font)
out = StringIO()
font.saveXML(out)
@@ -232,10 +235,22 @@ class GlyfTableTest(unittest.TestCase):
def test_fromXML(self):
font = TTFont(sfntVersion="\x00\x01\x00\x00")
font.importXML(GLYF_TTX)
- glyfTable = font['glyf']
+ glyfTable = font["glyf"]
glyfData = glyfTable.compile(font)
self.assertEqual(glyfData, self.glyfData)
+ def test_instructions_roundtrip(self):
+ font = TTFont(sfntVersion="\x00\x01\x00\x00")
+ font.importXML(INST_TTX)
+ glyfTable = font["glyf"]
+ self.glyfData = glyfTable.compile(font)
+ out = StringIO()
+ font.saveXML(out)
+ glyfXML = strip_ttLibVersion(out.getvalue()).splitlines()
+ with open(INST_TTX, "r") as f:
+ origXML = strip_ttLibVersion(f.read()).splitlines()
+ self.assertEqual(glyfXML, origXML)
+
def test_recursiveComponent(self):
glyphSet = {}
pen_dummy = TTGlyphPen(glyphSet)
@@ -250,7 +265,9 @@ class GlyfTableTest(unittest.TestCase):
glyph_B = pen_B.glyph()
glyphSet["A"] = glyph_A
glyphSet["B"] = glyph_B
- with self.assertRaisesRegex(TTLibError, "glyph '.' contains a recursive component reference"):
+ with self.assertRaisesRegex(
+ TTLibError, "glyph '.' contains a recursive component reference"
+ ):
glyph_A.getCoordinates(glyphSet)
def test_trim_remove_hinting_composite_glyph(self):
@@ -260,7 +277,7 @@ class GlyfTableTest(unittest.TestCase):
pen.addComponent("dummy", (1, 0, 0, 1, 0, 0))
composite = pen.glyph()
p = ttProgram.Program()
- p.fromAssembly(['SVTCA[0]'])
+ p.fromAssembly(["SVTCA[0]"])
composite.program = p
glyphSet["composite"] = composite
@@ -294,22 +311,24 @@ class GlyfTableTest(unittest.TestCase):
# glyph00003 contains a bit 6 flag on the first point,
# which triggered the issue
font.importXML(GLYF_TTX)
- glyfTable = font['glyf']
+ glyfTable = font["glyf"]
pen = RecordingPen()
glyfTable["glyph00003"].draw(pen, glyfTable=glyfTable)
- expected = [('moveTo', ((501, 1430),)),
- ('lineTo', ((683, 1430),)),
- ('lineTo', ((1172, 0),)),
- ('lineTo', ((983, 0),)),
- ('lineTo', ((591, 1193),)),
- ('lineTo', ((199, 0),)),
- ('lineTo', ((12, 0),)),
- ('closePath', ()),
- ('moveTo', ((249, 514),)),
- ('lineTo', ((935, 514),)),
- ('lineTo', ((935, 352),)),
- ('lineTo', ((249, 352),)),
- ('closePath', ())]
+ expected = [
+ ("moveTo", ((501, 1430),)),
+ ("lineTo", ((683, 1430),)),
+ ("lineTo", ((1172, 0),)),
+ ("lineTo", ((983, 0),)),
+ ("lineTo", ((591, 1193),)),
+ ("lineTo", ((199, 0),)),
+ ("lineTo", ((12, 0),)),
+ ("closePath", ()),
+ ("moveTo", ((249, 514),)),
+ ("lineTo", ((935, 514),)),
+ ("lineTo", ((935, 352),)),
+ ("lineTo", ((249, 352),)),
+ ("closePath", ()),
+ ]
self.assertEqual(pen.value, expected)
def test_bit6_draw_to_pointpen(self):
@@ -318,22 +337,22 @@ class GlyfTableTest(unittest.TestCase):
# glyph00003 contains a bit 6 flag on the first point
# which triggered the issue
font.importXML(GLYF_TTX)
- glyfTable = font['glyf']
+ glyfTable = font["glyf"]
pen = RecordingPointPen()
glyfTable["glyph00003"].drawPoints(pen, glyfTable=glyfTable)
expected = [
- ('beginPath', (), {}),
- ('addPoint', ((501, 1430), 'line', False, None), {}),
- ('addPoint', ((683, 1430), 'line', False, None), {}),
- ('addPoint', ((1172, 0), 'line', False, None), {}),
- ('addPoint', ((983, 0), 'line', False, None), {}),
+ ("beginPath", (), {}),
+ ("addPoint", ((501, 1430), "line", False, None), {}),
+ ("addPoint", ((683, 1430), "line", False, None), {}),
+ ("addPoint", ((1172, 0), "line", False, None), {}),
+ ("addPoint", ((983, 0), "line", False, None), {}),
]
- self.assertEqual(pen.value[:len(expected)], expected)
+ self.assertEqual(pen.value[: len(expected)], expected)
def test_draw_vs_drawpoints(self):
font = TTFont(sfntVersion="\x00\x01\x00\x00")
font.importXML(GLYF_TTX)
- glyfTable = font['glyf']
+ glyfTable = font["glyf"]
pen1 = RecordingPen()
pen2 = RecordingPen()
glyfTable["glyph00003"].draw(pen1, glyfTable)
@@ -343,12 +362,12 @@ class GlyfTableTest(unittest.TestCase):
def test_compile_empty_table(self):
font = TTFont(sfntVersion="\x00\x01\x00\x00")
font.importXML(GLYF_TTX)
- glyfTable = font['glyf']
+ glyfTable = font["glyf"]
# set all glyphs to zero contours
glyfTable.glyphs = {glyphName: Glyph() for glyphName in font.getGlyphOrder()}
glyfData = glyfTable.compile(font)
self.assertEqual(glyfData, b"\x00")
- self.assertEqual(list(font["loca"]), [0] * (font["maxp"].numGlyphs+1))
+ self.assertEqual(list(font["loca"]), [0] * (font["maxp"].numGlyphs + 1))
def test_decompile_empty_table(self):
font = TTFont()
@@ -372,16 +391,36 @@ class GlyfTableTest(unittest.TestCase):
font["glyf"] = newTable("glyf")
font["glyf"].decompile(b"\x00", font)
font["hmtx"] = newTable("hmtx")
- font["hmtx"].metrics = {".notdef": (100,0)}
+ font["hmtx"].metrics = {".notdef": (100, 0)}
font["head"] = newTable("head")
font["head"].unitsPerEm = 1000
- self.assertEqual(
- font["glyf"].getPhantomPoints(".notdef", font, 0),
- [(0, 0), (100, 0), (0, 0), (0, -1000)]
- )
+ with pytest.deprecated_call():
+ self.assertEqual(
+ font["glyf"].getPhantomPoints(".notdef", font, 0),
+ [(0, 0), (100, 0), (0, 0), (0, -1000)],
+ )
-class GlyphTest:
+ def test_getGlyphID(self):
+ # https://github.com/fonttools/fonttools/pull/3301#discussion_r1360405861
+ glyf = newTable("glyf")
+ glyf.setGlyphOrder([".notdef", "a", "b"])
+ glyf.glyphs = {}
+ for glyphName in glyf.glyphOrder:
+ glyf[glyphName] = Glyph()
+
+ assert glyf.getGlyphID("a") == 1
+ with pytest.raises(ValueError):
+ glyf.getGlyphID("c")
+
+ glyf["c"] = Glyph()
+ assert glyf.getGlyphID("c") == 3
+
+ del glyf["b"]
+ assert glyf.getGlyphID("c") == 2
+
+
+class GlyphTest:
def test_getCoordinates(self):
glyphSet = {}
pen = TTGlyphPen(glyphSet)
@@ -472,18 +511,30 @@ class GlyphTest:
assert flags == array.array("B", [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
assert list(sum(coords, ())) == pytest.approx(
[
- 0, 0,
- 100, 0,
- 100, 100,
- 0, 100,
- 100, 100,
- 200, 100,
- 200, 200,
- 100, 200,
- 200, 200,
- 270.7107, 270.7107,
- 200.0, 341.4214,
- 129.2893, 270.7107,
+ 0,
+ 0,
+ 100,
+ 0,
+ 100,
+ 100,
+ 0,
+ 100,
+ 100,
+ 100,
+ 200,
+ 100,
+ 200,
+ 200,
+ 100,
+ 200,
+ 200,
+ 200,
+ 270.7107,
+ 270.7107,
+ 200.0,
+ 341.4214,
+ 129.2893,
+ 270.7107,
]
)
@@ -513,7 +564,6 @@ class GlyphTest:
class GlyphComponentTest:
-
def test_toXML_no_transform(self):
comp = GlyphComponent()
comp.glyphName = "a"
@@ -625,7 +675,7 @@ class GlyphComponentTest:
assert hasattr(comp, "transform")
for value, expected in zip(
itertools.chain(*comp.transform),
- [0.5999756, -0.2000122, 0.2000122, 0.2999878]
+ [0.5999756, -0.2000122, 0.2000122, 0.2999878],
):
assert value == pytest.approx(expected)
@@ -652,7 +702,349 @@ class GlyphComponentTest:
assert (comp.firstPt, comp.secondPt) == (1, 2)
assert not hasattr(comp, "transform")
+ def test_trim_varComposite_glyph(self):
+ font_path = os.path.join(DATA_DIR, "..", "..", "data", "varc-ac00-ac01.ttf")
+ font = TTFont(font_path)
+ glyf = font["glyf"]
+
+ glyf.glyphs["uniAC00"].trim()
+ glyf.glyphs["uniAC01"].trim()
+
+ font_path = os.path.join(DATA_DIR, "..", "..", "data", "varc-6868.ttf")
+ font = TTFont(font_path)
+ glyf = font["glyf"]
+
+ glyf.glyphs["uni6868"].trim()
+
+ def test_varComposite_basic(self):
+ font_path = os.path.join(DATA_DIR, "..", "..", "data", "varc-ac00-ac01.ttf")
+ font = TTFont(font_path)
+ tables = [
+ table_tag
+ for table_tag in font.keys()
+ if table_tag not in {"head", "maxp", "hhea"}
+ ]
+ xml = StringIO()
+ font.saveXML(xml)
+ xml1 = StringIO()
+ font.saveXML(xml1, tables=tables)
+ xml.seek(0)
+ font = TTFont()
+ font.importXML(xml)
+ ttf = BytesIO()
+ font.save(ttf)
+ ttf.seek(0)
+ font = TTFont(ttf)
+ xml2 = StringIO()
+ font.saveXML(xml2, tables=tables)
+ assert xml1.getvalue() == xml2.getvalue()
+
+ font_path = os.path.join(DATA_DIR, "..", "..", "data", "varc-6868.ttf")
+ font = TTFont(font_path)
+ tables = [
+ table_tag
+ for table_tag in font.keys()
+ if table_tag not in {"head", "maxp", "hhea", "name", "fvar"}
+ ]
+ xml = StringIO()
+ font.saveXML(xml)
+ xml1 = StringIO()
+ font.saveXML(xml1, tables=tables)
+ xml.seek(0)
+ font = TTFont()
+ font.importXML(xml)
+ ttf = BytesIO()
+ font.save(ttf)
+ ttf.seek(0)
+ font = TTFont(ttf)
+ xml2 = StringIO()
+ font.saveXML(xml2, tables=tables)
+ assert xml1.getvalue() == xml2.getvalue()
+
+
+class GlyphCubicTest:
+ def test_roundtrip(self):
+ font_path = os.path.join(DATA_DIR, "NotoSans-VF-cubic.subset.ttf")
+ font = TTFont(font_path)
+ tables = [table_tag for table_tag in font.keys() if table_tag not in {"head"}]
+ xml = StringIO()
+ font.saveXML(xml)
+ xml1 = StringIO()
+ font.saveXML(xml1, tables=tables)
+ xml.seek(0)
+ font = TTFont()
+ font.importXML(xml)
+ ttf = BytesIO()
+ font.save(ttf)
+ ttf.seek(0)
+ font = TTFont(ttf)
+ xml2 = StringIO()
+ font.saveXML(xml2, tables=tables)
+ assert xml1.getvalue() == xml2.getvalue()
+
+ def test_no_oncurves(self):
+ glyph = Glyph()
+ glyph.numberOfContours = 1
+ glyph.coordinates = GlyphCoordinates(
+ [(0, 0), (1, 0), (1, 0), (1, 1), (1, 1), (0, 1), (0, 1), (0, 0)]
+ )
+ glyph.flags = array.array("B", [flagCubic] * 8)
+ glyph.endPtsOfContours = [7]
+ glyph.program = ttProgram.Program()
+
+ for i in range(2):
+ if i == 1:
+ glyph.compile(None)
+
+ pen = RecordingPen()
+ glyph.draw(pen, None)
+
+ assert pen.value == [
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((0, 0), (1, 0), (1, 0))),
+ ("curveTo", ((1, 0), (1, 1), (1, 1))),
+ ("curveTo", ((1, 1), (0, 1), (0, 1))),
+ ("curveTo", ((0, 1), (0, 0), (0, 0))),
+ ("closePath", ()),
+ ]
+
+ def test_spline(self):
+ glyph = Glyph()
+ glyph.numberOfContours = 1
+ glyph.coordinates = GlyphCoordinates(
+ [(0, 0), (1, 0), (1, 0), (1, 1), (1, 1), (0, 1), (0, 1)]
+ )
+ glyph.flags = array.array("B", [flagOnCurve] + [flagCubic] * 6)
+ glyph.endPtsOfContours = [6]
+ glyph.program = ttProgram.Program()
+
+ for i in range(2):
+ if i == 1:
+ glyph.compile(None)
+
+ pen = RecordingPen()
+ glyph.draw(pen, None)
+
+ assert pen.value == [
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((1, 0), (1, 0), (1.0, 0.5))),
+ ("curveTo", ((1, 1), (1, 1), (0.5, 1.0))),
+ ("curveTo", ((0, 1), (0, 1), (0, 0))),
+ ("closePath", ()),
+ ]
+
+
+def build_interpolatable_glyphs(contours, *transforms):
+ # given a list of lists of (point, flag) tuples (one per contour), build a Glyph
+ # then make len(transforms) copies transformed accordingly, and return a
+ # list of such interpolatable glyphs.
+ glyph1 = Glyph()
+ glyph1.numberOfContours = len(contours)
+ glyph1.coordinates = GlyphCoordinates(
+ [pt for contour in contours for pt, _flag in contour]
+ )
+ glyph1.flags = array.array(
+ "B", [flag for contour in contours for _pt, flag in contour]
+ )
+ glyph1.endPtsOfContours = [
+ sum(len(contour) for contour in contours[: i + 1]) - 1
+ for i in range(len(contours))
+ ]
+ result = [glyph1]
+ for t in transforms:
+ glyph = deepcopy(glyph1)
+ glyph.coordinates.transform((t[0:2], t[2:4]))
+ glyph.coordinates.translate(t[4:6])
+ result.append(glyph)
+ return result
+
+
+def test_dropImpliedOnCurvePoints_all_quad_off_curves():
+ # Two interpolatable glyphs with same structure, the coordinates of one are 2x the
+ # other; all the on-curve points are impliable in each one, thus are dropped from
+ # both, leaving contours with off-curve points only.
+ glyph1, glyph2 = build_interpolatable_glyphs(
+ [
+ [
+ ((0, 1), flagOnCurve),
+ ((1, 1), 0),
+ ((1, 0), flagOnCurve),
+ ((1, -1), 0),
+ ((0, -1), flagOnCurve),
+ ((-1, -1), 0),
+ ((-1, 0), flagOnCurve),
+ ((-1, 1), 0),
+ ],
+ [
+ ((0, 2), flagOnCurve),
+ ((2, 2), 0),
+ ((2, 0), flagOnCurve),
+ ((2, -2), 0),
+ ((0, -2), flagOnCurve),
+ ((-2, -2), 0),
+ ((-2, 0), flagOnCurve),
+ ((-2, 2), 0),
+ ],
+ ],
+ Transform().scale(2.0),
+ )
+ # also add an empty glyph (will be ignored); we use this trick for 'sparse' masters
+ glyph3 = Glyph()
+ glyph3.numberOfContours = 0
+
+ assert dropImpliedOnCurvePoints(glyph1, glyph2, glyph3) == {
+ 0,
+ 2,
+ 4,
+ 6,
+ 8,
+ 10,
+ 12,
+ 14,
+ }
+
+ assert glyph1.flags == glyph2.flags == array.array("B", [0, 0, 0, 0, 0, 0, 0, 0])
+ assert glyph1.coordinates == GlyphCoordinates(
+ [(1, 1), (1, -1), (-1, -1), (-1, 1), (2, 2), (2, -2), (-2, -2), (-2, 2)]
+ )
+ assert glyph2.coordinates == GlyphCoordinates(
+ [(2, 2), (2, -2), (-2, -2), (-2, 2), (4, 4), (4, -4), (-4, -4), (-4, 4)]
+ )
+ assert glyph1.endPtsOfContours == glyph2.endPtsOfContours == [3, 7]
+ assert glyph3.numberOfContours == 0
+
+
+def test_dropImpliedOnCurvePoints_all_cubic_off_curves():
+ # same as above this time using cubic curves
+ glyph1, glyph2 = build_interpolatable_glyphs(
+ [
+ [
+ ((0, 1), flagOnCurve),
+ ((1, 1), flagCubic),
+ ((1, 1), flagCubic),
+ ((1, 0), flagOnCurve),
+ ((1, -1), flagCubic),
+ ((1, -1), flagCubic),
+ ((0, -1), flagOnCurve),
+ ((-1, -1), flagCubic),
+ ((-1, -1), flagCubic),
+ ((-1, 0), flagOnCurve),
+ ((-1, 1), flagCubic),
+ ((-1, 1), flagCubic),
+ ]
+ ],
+ Transform().translate(10.0),
+ )
+ glyph3 = Glyph()
+ glyph3.numberOfContours = 0
+
+ assert dropImpliedOnCurvePoints(glyph1, glyph2, glyph3) == {0, 3, 6, 9}
+
+ assert glyph1.flags == glyph2.flags == array.array("B", [flagCubic] * 8)
+ assert glyph1.coordinates == GlyphCoordinates(
+ [(1, 1), (1, 1), (1, -1), (1, -1), (-1, -1), (-1, -1), (-1, 1), (-1, 1)]
+ )
+ assert glyph2.coordinates == GlyphCoordinates(
+ [(11, 1), (11, 1), (11, -1), (11, -1), (9, -1), (9, -1), (9, 1), (9, 1)]
+ )
+ assert glyph1.endPtsOfContours == glyph2.endPtsOfContours == [7]
+ assert glyph3.numberOfContours == 0
+
+
+def test_dropImpliedOnCurvePoints_not_all_impliable():
+ # same input as in in test_dropImpliedOnCurvePoints_all_quad_off_curves but we
+ # perturbate one of the glyphs such that the 2nd on-curve is no longer half-way
+ # between the neighboring off-curves.
+ glyph1, glyph2, glyph3 = build_interpolatable_glyphs(
+ [
+ [
+ ((0, 1), flagOnCurve),
+ ((1, 1), 0),
+ ((1, 0), flagOnCurve),
+ ((1, -1), 0),
+ ((0, -1), flagOnCurve),
+ ((-1, -1), 0),
+ ((-1, 0), flagOnCurve),
+ ((-1, 1), 0),
+ ]
+ ],
+ Transform().translate(10.0),
+ Transform().translate(10.0).scale(2.0),
+ )
+ p2 = glyph2.coordinates[2]
+ glyph2.coordinates[2] = (p2[0] + 2.0, p2[1] - 2.0)
+
+ assert dropImpliedOnCurvePoints(glyph1, glyph2, glyph3) == {
+ 0,
+ # 2, this is NOT implied because it's no longer impliable for all glyphs
+ 4,
+ 6,
+ }
+
+ assert glyph2.flags == array.array("B", [0, flagOnCurve, 0, 0, 0])
+
+
+def test_dropImpliedOnCurvePoints_all_empty_glyphs():
+ glyph1 = Glyph()
+ glyph1.numberOfContours = 0
+ glyph2 = Glyph()
+ glyph2.numberOfContours = 0
+
+ assert dropImpliedOnCurvePoints(glyph1, glyph2) == set()
+
+
+def test_dropImpliedOnCurvePoints_incompatible_number_of_contours():
+ glyph1 = Glyph()
+ glyph1.numberOfContours = 1
+ glyph1.endPtsOfContours = [3]
+ glyph1.flags = array.array("B", [1, 1, 1, 1])
+ glyph1.coordinates = GlyphCoordinates([(0, 0), (1, 1), (2, 2), (3, 3)])
+
+ glyph2 = Glyph()
+ glyph2.numberOfContours = 2
+ glyph2.endPtsOfContours = [1, 3]
+ glyph2.flags = array.array("B", [1, 1, 1, 1])
+ glyph2.coordinates = GlyphCoordinates([(0, 0), (1, 1), (2, 2), (3, 3)])
+
+ with pytest.raises(ValueError, match="Incompatible numberOfContours"):
+ dropImpliedOnCurvePoints(glyph1, glyph2)
+
+
+def test_dropImpliedOnCurvePoints_incompatible_flags():
+ glyph1 = Glyph()
+ glyph1.numberOfContours = 1
+ glyph1.endPtsOfContours = [3]
+ glyph1.flags = array.array("B", [1, 1, 1, 1])
+ glyph1.coordinates = GlyphCoordinates([(0, 0), (1, 1), (2, 2), (3, 3)])
+
+ glyph2 = Glyph()
+ glyph2.numberOfContours = 1
+ glyph2.endPtsOfContours = [3]
+ glyph2.flags = array.array("B", [0, 0, 0, 0])
+ glyph2.coordinates = GlyphCoordinates([(0, 0), (1, 1), (2, 2), (3, 3)])
+
+ with pytest.raises(ValueError, match="Incompatible flags"):
+ dropImpliedOnCurvePoints(glyph1, glyph2)
+
+
+def test_dropImpliedOnCurvePoints_incompatible_endPtsOfContours():
+ glyph1 = Glyph()
+ glyph1.numberOfContours = 2
+ glyph1.endPtsOfContours = [2, 6]
+ glyph1.flags = array.array("B", [1, 1, 1, 1, 1, 1, 1])
+ glyph1.coordinates = GlyphCoordinates([(i, i) for i in range(7)])
+
+ glyph2 = Glyph()
+ glyph2.numberOfContours = 2
+ glyph2.endPtsOfContours = [3, 6]
+ glyph2.flags = array.array("B", [1, 1, 1, 1, 1, 1, 1])
+ glyph2.coordinates = GlyphCoordinates([(i, i) for i in range(7)])
+
+ with pytest.raises(ValueError, match="Incompatible endPtsOfContours"):
+ dropImpliedOnCurvePoints(glyph1, glyph2)
+
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())