diff options
Diffstat (limited to 'Tests/pens')
-rw-r--r-- | Tests/pens/data/test_even_odd_fill.pgm | bin | 0 -> 2513 bytes | |||
-rw-r--r-- | Tests/pens/data/test_non_zero_fill.pgm | bin | 0 -> 2513 bytes | |||
-rw-r--r-- | Tests/pens/data/test_rotate.pgm | bin | 0 -> 1309 bytes | |||
-rw-r--r-- | Tests/pens/data/test_skew.pgm | bin | 0 -> 1263 bytes | |||
-rw-r--r-- | Tests/pens/freetypePen_test.py | 235 | ||||
-rw-r--r-- | Tests/pens/ttGlyphPen_test.py | 381 |
6 files changed, 569 insertions, 47 deletions
diff --git a/Tests/pens/data/test_even_odd_fill.pgm b/Tests/pens/data/test_even_odd_fill.pgm Binary files differnew file mode 100644 index 00000000..70130f33 --- /dev/null +++ b/Tests/pens/data/test_even_odd_fill.pgm diff --git a/Tests/pens/data/test_non_zero_fill.pgm b/Tests/pens/data/test_non_zero_fill.pgm Binary files differnew file mode 100644 index 00000000..698f6424 --- /dev/null +++ b/Tests/pens/data/test_non_zero_fill.pgm diff --git a/Tests/pens/data/test_rotate.pgm b/Tests/pens/data/test_rotate.pgm Binary files differnew file mode 100644 index 00000000..14edd9fc --- /dev/null +++ b/Tests/pens/data/test_rotate.pgm diff --git a/Tests/pens/data/test_skew.pgm b/Tests/pens/data/test_skew.pgm Binary files differnew file mode 100644 index 00000000..5ba4a067 --- /dev/null +++ b/Tests/pens/data/test_skew.pgm diff --git a/Tests/pens/freetypePen_test.py b/Tests/pens/freetypePen_test.py new file mode 100644 index 00000000..b6edc8bb --- /dev/null +++ b/Tests/pens/freetypePen_test.py @@ -0,0 +1,235 @@ +import unittest +import os +import math + +try: + from fontTools.pens.freetypePen import FreeTypePen + + FREETYPE_PY_AVAILABLE = True +except ImportError: + FREETYPE_PY_AVAILABLE = False + +from fontTools.misc.transform import Scale, Offset + +DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data") + + +def box(pen, offset=(0, 0)): + pen.moveTo((0 + offset[0], 0 + offset[1])) + pen.lineTo((0 + offset[0], 500 + offset[1])) + pen.lineTo((500 + offset[0], 500 + offset[1])) + pen.lineTo((500 + offset[0], 0 + offset[1])) + pen.closePath() + + +def draw_cubic(pen): + pen.moveTo((50, 0)) + pen.lineTo((50, 500)) + pen.lineTo((200, 500)) + pen.curveTo((350, 500), (450, 400), (450, 250)) + pen.curveTo((450, 100), (350, 0), (200, 0)) + pen.closePath() + + +def draw_quadratic(pen): + pen.moveTo((50, 0)) + pen.lineTo((50, 500)) + pen.lineTo((200, 500)) + pen.qCurveTo((274, 500), (388, 438), (450, 324), (450, 250)) + pen.qCurveTo((450, 176), (388, 62), (274, 0), (200, 0)) + pen.closePath() + + +def star(pen): + pen.moveTo((0, 420)) + pen.lineTo((1000, 420)) + pen.lineTo((200, -200)) + pen.lineTo((500, 800)) + pen.lineTo((800, -200)) + pen.closePath() + + +# For the PGM format, see the following resources: +# https://en.wikipedia.org/wiki/Netpbm +# http://netpbm.sourceforge.net/doc/pgm.html +def load_pgm(filename): + with open(filename, "rb") as fp: + assert fp.readline() == "P5\n".encode() + w, h = (int(c) for c in fp.readline().decode().rstrip().split(" ")) + assert fp.readline() == "255\n".encode() + return fp.read(), (w, h) + + +def save_pgm(filename, buf, size): + with open(filename, "wb") as fp: + fp.write("P5\n".encode()) + fp.write("{0:d} {1:d}\n".format(*size).encode()) + fp.write("255\n".encode()) + fp.write(buf) + + +# Assume the buffers are equal when PSNR > 38dB. See also: +# Peak signal-to-noise ratio +# https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio +PSNR_THRESHOLD = 38.0 + + +def psnr(b1, b2): + import math + + mse = sum((c1 - c2) * (c1 - c2) for c1, c2 in zip(b1, b2)) / float(len(b1)) + return 10.0 * math.log10((255.0**2) / float(mse)) if mse > 0 else math.inf + + +@unittest.skipUnless(FREETYPE_PY_AVAILABLE, "freetype-py not installed") +class FreeTypePenTest(unittest.TestCase): + def test_draw(self): + pen = FreeTypePen(None) + box(pen) + width, height = 500, 500 + buf1, _ = pen.buffer(width=width, height=height) + buf2 = b"\xff" * width * height + self.assertEqual(buf1, buf2) + + def test_empty(self): + pen = FreeTypePen(None) + width, height = 500, 500 + buf, size = pen.buffer(width=width, height=height) + self.assertEqual(b"\0" * size[0] * size[1], buf) + + def test_bbox_and_cbox(self): + pen = FreeTypePen(None) + draw_cubic(pen) + self.assertEqual(pen.bbox, (50.0, 0.0, 450.0, 500.0)) + self.assertEqual(pen.cbox, (50.0, 0.0, 450.0, 500.0)) + + def test_non_zero_fill(self): + pen = FreeTypePen(None) + star(pen) + t = Scale(0.05, 0.05) + width, height = t.transformPoint((1000, 1000)) + t = t.translate(0, 200) + buf1, size1 = pen.buffer(width=width, height=height, transform=t, evenOdd=False) + buf2, size2 = load_pgm(os.path.join(DATA_DIR, "test_non_zero_fill.pgm")) + self.assertEqual(len(buf1), len(buf2)) + self.assertEqual(size1, size2) + self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) + + def test_even_odd_fill(self): + pen = FreeTypePen(None) + star(pen) + t = Scale(0.05, 0.05) + width, height = t.transformPoint((1000, 1000)) + t = t.translate(0, 200) + buf1, size1 = pen.buffer(width=width, height=height, transform=t, evenOdd=True) + buf2, size2 = load_pgm(os.path.join(DATA_DIR, "test_even_odd_fill.pgm")) + self.assertEqual(len(buf1), len(buf2)) + self.assertEqual(size1, size2) + self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) + + def test_cubic_vs_quadratic(self): + pen1, pen2 = FreeTypePen(None), FreeTypePen(None) + draw_cubic(pen1) + draw_quadratic(pen2) + width, height = 500, 500 + buf1, _ = pen1.buffer(width=width, height=height) + buf2, _ = pen2.buffer(width=width, height=height) + self.assertEqual(len(buf1), len(buf2)) + self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) + + def test_contain(self): + pen = FreeTypePen(None) + star(pen) + t = Scale(0.05, 0.05) + width, height = 0, 0 + buf1, size1 = pen.buffer(width=width, height=height, transform=t, contain=True) + buf2, size2 = load_pgm(os.path.join(DATA_DIR, "test_non_zero_fill.pgm")) + self.assertEqual(len(buf1), len(buf2)) + self.assertEqual(size1, size2) + self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) + + def test_rotate(self): + pen = FreeTypePen(None) + box(pen) + t = Scale(0.05, 0.05).rotate(math.pi / 4.0).translate(1234, 5678) + width, height = None, None + buf1, size1 = pen.buffer(width=width, height=height, transform=t) + buf2, size2 = load_pgm(os.path.join(DATA_DIR, "test_rotate.pgm")) + self.assertEqual(len(buf1), len(buf2)) + self.assertEqual(size1, size2) + self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) + + def test_skew(self): + pen = FreeTypePen(None) + box(pen) + t = Scale(0.05, 0.05).skew(math.pi / 4.0).translate(1234, 5678) + width, height = None, None + buf1, size1 = pen.buffer(width=width, height=height, transform=t) + buf2, size2 = load_pgm(os.path.join(DATA_DIR, "test_skew.pgm")) + self.assertEqual(len(buf1), len(buf2)) + self.assertEqual(size1, size2) + self.assertGreater(psnr(buf1, buf2), PSNR_THRESHOLD) + + def test_none_size(self): + pen = FreeTypePen(None) + star(pen) + width, height = None, None + buf1, size = pen.buffer(width=width, height=height, transform=Offset(0, 200)) + buf2, _ = pen.buffer(width=1000, height=1000, transform=Offset(0, 200)) + self.assertEqual(size, (1000, 1000)) + self.assertEqual(buf1, buf2) + + pen = FreeTypePen(None) + box(pen, offset=(250, 250)) + width, height = None, None + buf1, size = pen.buffer(width=width, height=height) + buf2, _ = pen.buffer(width=500, height=500, transform=Offset(-250, -250)) + self.assertEqual(size, (500, 500)) + self.assertEqual(buf1, buf2) + + pen = FreeTypePen(None) + box(pen, offset=(-1234, -5678)) + width, height = None, None + buf1, size = pen.buffer(width=width, height=height) + buf2, _ = pen.buffer(width=500, height=500, transform=Offset(1234, 5678)) + self.assertEqual(size, (500, 500)) + self.assertEqual(buf1, buf2) + + def test_zero_size(self): + pen = FreeTypePen(None) + star(pen) + width, height = 0, 0 + buf1, size = pen.buffer( + width=width, height=height, transform=Offset(0, 200), contain=True + ) + buf2, _ = pen.buffer( + width=1000, height=1000, transform=Offset(0, 200), contain=True + ) + self.assertEqual(size, (1000, 1000)) + self.assertEqual(buf1, buf2) + + pen = FreeTypePen(None) + box(pen, offset=(250, 250)) + width, height = 0, 0 + buf1, size = pen.buffer(width=width, height=height, contain=True) + buf2, _ = pen.buffer( + width=500, height=500, transform=Offset(0, 0), contain=True + ) + self.assertEqual(size, (750, 750)) + self.assertEqual(buf1, buf2) + + pen = FreeTypePen(None) + box(pen, offset=(-1234, -5678)) + width, height = 0, 0 + buf1, size = pen.buffer(width=width, height=height, contain=True) + buf2, _ = pen.buffer( + width=500, height=500, transform=Offset(1234, 5678), contain=True + ) + self.assertEqual(size, (500, 500)) + self.assertEqual(buf1, buf2) + + +if __name__ == "__main__": + import sys + + sys.exit(unittest.main()) diff --git a/Tests/pens/ttGlyphPen_test.py b/Tests/pens/ttGlyphPen_test.py index 53db025c..96d75a19 100644 --- a/Tests/pens/ttGlyphPen_test.py +++ b/Tests/pens/ttGlyphPen_test.py @@ -1,56 +1,63 @@ import os -import unittest +import pytest import struct from fontTools import ttLib -from fontTools.misc.testTools import TestCase -from fontTools.pens.ttGlyphPen import TTGlyphPen, MAX_F2DOT14 +from fontTools.pens.basePen import PenError +from fontTools.pens.ttGlyphPen import TTGlyphPen, TTGlyphPointPen, MAX_F2DOT14 -class TTGlyphPenTest(TestCase): - +class TTGlyphPenTestBase: def runEndToEnd(self, filename): font = ttLib.TTFont() ttx_path = os.path.join( os.path.abspath(os.path.dirname(os.path.realpath(__file__))), - '..', 'ttLib', 'data', filename) + "..", + "ttLib", + "data", + filename, + ) font.importXML(ttx_path) glyphSet = font.getGlyphSet() - glyfTable = font['glyf'] - pen = TTGlyphPen(font.getGlyphSet()) + glyfTable = font["glyf"] + pen = self.penClass(font.getGlyphSet()) for name in font.getGlyphOrder(): oldGlyph = glyphSet[name] - oldGlyph.draw(pen) + getattr(oldGlyph, self.drawMethod)(pen) oldGlyph = oldGlyph._glyph newGlyph = pen.glyph() - if hasattr(oldGlyph, 'program'): + if hasattr(oldGlyph, "program"): newGlyph.program = oldGlyph.program - self.assertEqual( - oldGlyph.compile(glyfTable), newGlyph.compile(glyfTable)) + assert oldGlyph.compile(glyfTable) == newGlyph.compile(glyfTable) def test_e2e_linesAndSimpleComponents(self): - self.runEndToEnd('TestTTF-Regular.ttx') + self.runEndToEnd("TestTTF-Regular.ttx") def test_e2e_curvesAndComponentTransforms(self): - self.runEndToEnd('TestTTFComplex-Regular.ttx') + self.runEndToEnd("TestTTFComplex-Regular.ttx") + + +class TTGlyphPenTest(TTGlyphPenTestBase): + penClass = TTGlyphPen + drawMethod = "draw" def test_moveTo_errorWithinContour(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) - with self.assertRaises(AssertionError): + with pytest.raises(PenError): pen.moveTo((1, 0)) def test_closePath_ignoresAnchors(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) pen.closePath() - self.assertFalse(pen.points) - self.assertFalse(pen.types) - self.assertFalse(pen.endPts) + assert not pen.points + assert not pen.types + assert not pen.endPts def test_endPath_sameAsClosePath(self): pen = TTGlyphPen(None) @@ -67,16 +74,16 @@ class TTGlyphPenTest(TestCase): pen.endPath() endPathGlyph = pen.glyph() - self.assertEqual(closePathGlyph, endPathGlyph) + assert closePathGlyph == endPathGlyph def test_glyph_errorOnUnendedContour(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) - with self.assertRaises(AssertionError): + with pytest.raises(PenError): pen.glyph() def test_glyph_decomposes(self): - componentName = 'a' + componentName = "a" glyphSet = {} pen = TTGlyphPen(glyphSet) @@ -104,7 +111,7 @@ class TTGlyphPenTest(TestCase): pen.closePath() plainGlyph = pen.glyph() - self.assertEqual(plainGlyph, compositeGlyph) + assert plainGlyph == compositeGlyph def test_remove_extra_move_points(self): pen = TTGlyphPen(None) @@ -112,8 +119,8 @@ class TTGlyphPenTest(TestCase): pen.lineTo((100, 0)) pen.qCurveTo((100, 50), (50, 100), (0, 0)) pen.closePath() - self.assertEqual(len(pen.points), 4) - self.assertEqual(pen.points[0], (0, 0)) + assert len(pen.points) == 4 + assert pen.points[0] == (0, 0) def test_keep_move_point(self): pen = TTGlyphPen(None) @@ -122,8 +129,8 @@ class TTGlyphPenTest(TestCase): pen.qCurveTo((100, 50), (50, 100), (30, 30)) # when last and move pts are different, closePath() implies a lineTo pen.closePath() - self.assertEqual(len(pen.points), 5) - self.assertEqual(pen.points[0], (0, 0)) + assert len(pen.points) == 5 + assert pen.points[0] == (0, 0) def test_keep_duplicate_end_point(self): pen = TTGlyphPen(None) @@ -132,11 +139,11 @@ class TTGlyphPenTest(TestCase): pen.qCurveTo((100, 50), (50, 100), (0, 0)) pen.lineTo((0, 0)) # the duplicate point is not removed pen.closePath() - self.assertEqual(len(pen.points), 5) - self.assertEqual(pen.points[0], (0, 0)) + assert len(pen.points) == 5 + assert pen.points[0] == (0, 0) def test_within_range_component_transform(self): - componentName = 'a' + componentName = "a" glyphSet = {} pen = TTGlyphPen(glyphSet) @@ -154,10 +161,10 @@ class TTGlyphPenTest(TestCase): pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) expectedGlyph = pen.glyph() - self.assertEqual(expectedGlyph, compositeGlyph) + assert expectedGlyph == compositeGlyph def test_clamp_to_almost_2_component_transform(self): - componentName = 'a' + componentName = "a" glyphSet = {} pen = TTGlyphPen(glyphSet) @@ -182,10 +189,10 @@ class TTGlyphPenTest(TestCase): pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) expectedGlyph = pen.glyph() - self.assertEqual(expectedGlyph, compositeGlyph) + assert expectedGlyph == compositeGlyph def test_out_of_range_transform_decomposed(self): - componentName = 'a' + componentName = "a" glyphSet = {} pen = TTGlyphPen(glyphSet) @@ -214,10 +221,10 @@ class TTGlyphPenTest(TestCase): pen.closePath() expectedGlyph = pen.glyph() - self.assertEqual(expectedGlyph, compositeGlyph) + assert expectedGlyph == compositeGlyph def test_no_handle_overflowing_transform(self): - componentName = 'a' + componentName = "a" glyphSet = {} pen = TTGlyphPen(glyphSet, handleOverflowingTransforms=False) @@ -231,14 +238,13 @@ class TTGlyphPenTest(TestCase): pen.addComponent(componentName, (3, 0, 0, 1, 0, 0)) compositeGlyph = pen.glyph() - self.assertEqual(compositeGlyph.components[0].transform, - ((3, 0), (0, 1))) + assert compositeGlyph.components[0].transform == ((3, 0), (0, 1)) - with self.assertRaises(struct.error): - compositeGlyph.compile({'a': baseGlyph}) + with pytest.raises(struct.error): + compositeGlyph.compile({"a": baseGlyph}) def assertGlyphBoundsEqual(self, glyph, bounds): - self.assertEqual((glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax), bounds) + assert (glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax) == bounds def test_round_float_coordinates_and_component_offsets(self): glyphSet = {} @@ -253,7 +259,7 @@ class TTGlyphPenTest(TestCase): simpleGlyph.recalcBounds(glyphSet) self.assertGlyphBoundsEqual(simpleGlyph, (0, 0, 368, 1)) - componentName = 'a' + componentName = "a" glyphSet[componentName] = simpleGlyph pen.addComponent(componentName, (1, 0, 0, 1, -86.4, 0)) @@ -271,7 +277,7 @@ class TTGlyphPenTest(TestCase): pen.lineTo((-55, 745)) pen.lineTo((-231, 745)) pen.closePath() - glyphSet["gravecomb"] = gravecomb = pen.glyph() + glyphSet["gravecomb"] = pen.glyph() pen = TTGlyphPen(glyphSet) pen.moveTo((-278, 939)) @@ -279,7 +285,7 @@ class TTGlyphPenTest(TestCase): pen.lineTo((8, 745)) pen.lineTo((-278, 745)) pen.closePath() - glyphSet["circumflexcomb"] = circumflexcomb = pen.glyph() + glyphSet["circumflexcomb"] = pen.glyph() pen = TTGlyphPen(glyphSet) pen.addComponent("circumflexcomb", (1, 0, 0, 1, 0, 0)) @@ -290,6 +296,286 @@ class TTGlyphPenTest(TestCase): self.assertGlyphBoundsEqual(uni0302_uni0300, (-278, 745, 148, 1025)) +class TTGlyphPointPenTest(TTGlyphPenTestBase): + penClass = TTGlyphPointPen + drawMethod = "drawPoints" + + def test_glyph_simple(self): + pen = TTGlyphPointPen(None) + pen.beginPath() + pen.addPoint((50, 0), "line") + pen.addPoint((450, 0), "line") + pen.addPoint((450, 700), "line") + pen.addPoint((50, 700), "line") + pen.endPath() + glyph = pen.glyph() + assert glyph.numberOfContours == 1 + assert glyph.endPtsOfContours == [3] + + def test_addPoint_errorOnCurve(self): + pen = TTGlyphPointPen(None) + pen.beginPath() + with pytest.raises(NotImplementedError): + pen.addPoint((0, 0), "curve") + + def test_beginPath_beginPathOnOpenPath(self): + pen = TTGlyphPointPen(None) + pen.beginPath() + pen.addPoint((0, 0)) + with pytest.raises(PenError): + pen.beginPath() + + def test_glyph_errorOnUnendedContour(self): + pen = TTGlyphPointPen(None) + pen.beginPath() + pen.addPoint((0, 0)) + with pytest.raises(PenError): + pen.glyph() + + def test_glyph_errorOnEmptyContour(self): + pen = TTGlyphPointPen(None) + pen.beginPath() + with pytest.raises(PenError): + pen.endPath() + + def test_glyph_decomposes(self): + componentName = "a" + glyphSet = {} + pen = TTGlyphPointPen(glyphSet) + + pen.beginPath() + pen.addPoint((0, 0), "line") + pen.addPoint((0, 1), "line") + pen.addPoint((1, 0), "line") + pen.endPath() + glyphSet[componentName] = _TestGlyph(pen.glyph()) + + pen.beginPath() + pen.addPoint((0, 0), "line") + pen.addPoint((0, 1), "line") + pen.addPoint((1, 0), "line") + pen.endPath() + pen.addComponent(componentName, (1, 0, 0, 1, 2, 0)) + pen.addComponent("missing", (1, 0, 0, 1, 0, 0)) # skipped + compositeGlyph = pen.glyph() + + pen.beginPath() + pen.addPoint((0, 0), "line") + pen.addPoint((0, 1), "line") + pen.addPoint((1, 0), "line") + pen.endPath() + pen.beginPath() + pen.addPoint((2, 0), "line") + pen.addPoint((2, 1), "line") + pen.addPoint((3, 0), "line") + pen.endPath() + plainGlyph = pen.glyph() + + assert plainGlyph == compositeGlyph + + def test_keep_duplicate_end_point(self): + pen = TTGlyphPointPen(None) + pen.beginPath() + pen.addPoint((0, 0), "line") + pen.addPoint((100, 0), "line") + pen.addPoint((100, 50)) + pen.addPoint((50, 100)) + pen.addPoint((0, 0), "qcurve") + pen.addPoint((0, 0), "line") # the duplicate point is not removed + pen.endPath() + assert len(pen.points) == 6 + assert pen.points[0] == (0, 0) + + def test_within_range_component_transform(self): + componentName = "a" + glyphSet = {} + pen = TTGlyphPointPen(glyphSet) + + pen.beginPath() + pen.addPoint((0, 0), "line") + pen.addPoint((0, 1), "line") + pen.addPoint((1, 0), "line") + pen.endPath() + glyphSet[componentName] = _TestGlyph(pen.glyph()) + + pen.addComponent(componentName, (1.5, 0, 0, 1, 0, 0)) + pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) + compositeGlyph = pen.glyph() + + pen.addComponent(componentName, (1.5, 0, 0, 1, 0, 0)) + pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) + expectedGlyph = pen.glyph() + + assert expectedGlyph == compositeGlyph + + def test_clamp_to_almost_2_component_transform(self): + componentName = "a" + glyphSet = {} + pen = TTGlyphPointPen(glyphSet) + + pen.beginPath() + pen.addPoint((0, 0), "line") + pen.addPoint((0, 1), "line") + pen.addPoint((1, 0), "line") + pen.endPath() + glyphSet[componentName] = _TestGlyph(pen.glyph()) + + pen.addComponent(componentName, (1.99999, 0, 0, 1, 0, 0)) + pen.addComponent(componentName, (1, 2, 0, 1, 0, 0)) + pen.addComponent(componentName, (1, 0, 2, 1, 0, 0)) + pen.addComponent(componentName, (1, 0, 0, 2, 0, 0)) + pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) + compositeGlyph = pen.glyph() + + almost2 = MAX_F2DOT14 # 0b1.11111111111111 + pen.addComponent(componentName, (almost2, 0, 0, 1, 0, 0)) + pen.addComponent(componentName, (1, almost2, 0, 1, 0, 0)) + pen.addComponent(componentName, (1, 0, almost2, 1, 0, 0)) + pen.addComponent(componentName, (1, 0, 0, almost2, 0, 0)) + pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) + expectedGlyph = pen.glyph() + + assert expectedGlyph == compositeGlyph + + def test_out_of_range_transform_decomposed(self): + componentName = "a" + glyphSet = {} + pen = TTGlyphPointPen(glyphSet) + + pen.beginPath() + pen.addPoint((0, 0), "line") + pen.addPoint((0, 1), "line") + pen.addPoint((1, 0), "line") + pen.endPath() + glyphSet[componentName] = _TestGlyph(pen.glyph()) + + pen.addComponent(componentName, (3, 0, 0, 2, 0, 0)) + pen.addComponent(componentName, (1, 0, 0, 1, -1, 2)) + pen.addComponent(componentName, (2, 0, 0, -3, 0, 0)) + compositeGlyph = pen.glyph() + + pen.beginPath() + pen.addPoint((0, 0), "line") + pen.addPoint((0, 2), "line") + pen.addPoint((3, 0), "line") + pen.endPath() + pen.beginPath() + pen.addPoint((-1, 2), "line") + pen.addPoint((-1, 3), "line") + pen.addPoint((0, 2), "line") + pen.endPath() + pen.beginPath() + pen.addPoint((0, 0), "line") + pen.addPoint((0, -3), "line") + pen.addPoint((2, 0), "line") + pen.endPath() + expectedGlyph = pen.glyph() + + assert expectedGlyph == compositeGlyph + + def test_no_handle_overflowing_transform(self): + componentName = "a" + glyphSet = {} + pen = TTGlyphPointPen(glyphSet, handleOverflowingTransforms=False) + + pen.beginPath() + pen.addPoint((0, 0), "line") + pen.addPoint((0, 1), "line") + pen.addPoint((1, 0), "line") + pen.endPath() + baseGlyph = pen.glyph() + glyphSet[componentName] = _TestGlyph(baseGlyph) + + pen.addComponent(componentName, (3, 0, 0, 1, 0, 0)) + compositeGlyph = pen.glyph() + + assert compositeGlyph.components[0].transform == ((3, 0), (0, 1)) + + with pytest.raises(struct.error): + compositeGlyph.compile({"a": baseGlyph}) + + def assertGlyphBoundsEqual(self, glyph, bounds): + assert (glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax) == bounds + + def test_round_float_coordinates_and_component_offsets(self): + glyphSet = {} + pen = TTGlyphPointPen(glyphSet) + + pen.beginPath() + pen.addPoint((0, 0), "line") + pen.addPoint((0, 1), "line") + pen.addPoint((367.6, 0), "line") + pen.endPath() + simpleGlyph = pen.glyph() + + simpleGlyph.recalcBounds(glyphSet) + self.assertGlyphBoundsEqual(simpleGlyph, (0, 0, 368, 1)) + + componentName = "a" + glyphSet[componentName] = simpleGlyph + + pen.addComponent(componentName, (1, 0, 0, 1, -86.4, 0)) + compositeGlyph = pen.glyph() + + compositeGlyph.recalcBounds(glyphSet) + self.assertGlyphBoundsEqual(compositeGlyph, (-86, 0, 282, 1)) + + def test_scaled_component_bounds(self): + glyphSet = {} + + pen = TTGlyphPointPen(glyphSet) + pen.beginPath() + pen.addPoint((-231, 939), "line") + pen.addPoint((-55, 939), "line") + pen.addPoint((-55, 745), "line") + pen.addPoint((-231, 745), "line") + pen.endPath() + glyphSet["gravecomb"] = pen.glyph() + + pen = TTGlyphPointPen(glyphSet) + pen.beginPath() + pen.addPoint((-278, 939), "line") + pen.addPoint((8, 939), "line") + pen.addPoint((8, 745), "line") + pen.addPoint((-278, 745), "line") + pen.endPath() + glyphSet["circumflexcomb"] = pen.glyph() + + pen = TTGlyphPointPen(glyphSet) + pen.addComponent("circumflexcomb", (1, 0, 0, 1, 0, 0)) + pen.addComponent("gravecomb", (0.9, 0, 0, 0.9, 198, 180)) + glyphSet["uni0302_uni0300"] = uni0302_uni0300 = pen.glyph() + + uni0302_uni0300.recalcBounds(glyphSet) + self.assertGlyphBoundsEqual(uni0302_uni0300, (-278, 745, 148, 1025)) + + def test_open_path_starting_with_move(self): + # when a contour starts with a 'move' point, it signifies the beginnig + # of an open contour. + # https://unifiedfontobject.org/versions/ufo3/glyphs/glif/#point-types + pen1 = TTGlyphPointPen(None) + pen1.beginPath() + pen1.addPoint((0, 0), "move") # contour is open + pen1.addPoint((10, 10), "line") + pen1.addPoint((20, 20)) + pen1.addPoint((20, 0), "qcurve") + pen1.endPath() + + pen2 = TTGlyphPointPen(None) + pen2.beginPath() + pen2.addPoint((0, 0), "line") # contour is closed + pen2.addPoint((10, 10), "line") + pen2.addPoint((20, 20)) + pen2.addPoint((20, 0), "qcurve") + pen2.endPath() + + # Since TrueType contours are always implicitly closed, the pen will + # interpret both these paths as equivalent + assert pen1.points == pen2.points == [(0, 0), (10, 10), (20, 20), (20, 0)] + assert pen1.types == pen2.types == [1, 1, 0, 1] + + + class _TestGlyph(object): def __init__(self, glyph): self.coordinates = glyph.coordinates @@ -300,7 +586,8 @@ class _TestGlyph(object): pen.lineTo(point) pen.closePath() - -if __name__ == '__main__': - import sys - sys.exit(unittest.main()) + def drawPoints(self, pen): + pen.beginPath() + for point in self.coordinates: + pen.addPoint(point, "line") + pen.endPath() |