aboutsummaryrefslogtreecommitdiff
path: root/Tests/pens
diff options
context:
space:
mode:
Diffstat (limited to 'Tests/pens')
-rw-r--r--Tests/pens/data/test_even_odd_fill.pgmbin0 -> 2513 bytes
-rw-r--r--Tests/pens/data/test_non_zero_fill.pgmbin0 -> 2513 bytes
-rw-r--r--Tests/pens/data/test_rotate.pgmbin0 -> 1309 bytes
-rw-r--r--Tests/pens/data/test_skew.pgmbin0 -> 1263 bytes
-rw-r--r--Tests/pens/freetypePen_test.py235
-rw-r--r--Tests/pens/ttGlyphPen_test.py381
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
new file mode 100644
index 00000000..70130f33
--- /dev/null
+++ b/Tests/pens/data/test_even_odd_fill.pgm
Binary files differ
diff --git a/Tests/pens/data/test_non_zero_fill.pgm b/Tests/pens/data/test_non_zero_fill.pgm
new file mode 100644
index 00000000..698f6424
--- /dev/null
+++ b/Tests/pens/data/test_non_zero_fill.pgm
Binary files differ
diff --git a/Tests/pens/data/test_rotate.pgm b/Tests/pens/data/test_rotate.pgm
new file mode 100644
index 00000000..14edd9fc
--- /dev/null
+++ b/Tests/pens/data/test_rotate.pgm
Binary files differ
diff --git a/Tests/pens/data/test_skew.pgm b/Tests/pens/data/test_skew.pgm
new file mode 100644
index 00000000..5ba4a067
--- /dev/null
+++ b/Tests/pens/data/test_skew.pgm
Binary files differ
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()