You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
307 lines
9.3 KiB
307 lines
9.3 KiB
import os
|
|
import unittest
|
|
import struct
|
|
|
|
from fontTools import ttLib
|
|
from fontTools.misc.testTools import TestCase
|
|
from fontTools.pens.ttGlyphPen import TTGlyphPen, MAX_F2DOT14
|
|
|
|
|
|
class TTGlyphPenTest(TestCase):
|
|
|
|
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)
|
|
font.importXML(ttx_path)
|
|
|
|
glyphSet = font.getGlyphSet()
|
|
glyfTable = font['glyf']
|
|
pen = TTGlyphPen(font.getGlyphSet())
|
|
|
|
for name in font.getGlyphOrder():
|
|
oldGlyph = glyphSet[name]
|
|
oldGlyph.draw(pen)
|
|
oldGlyph = oldGlyph._glyph
|
|
newGlyph = pen.glyph()
|
|
|
|
if hasattr(oldGlyph, 'program'):
|
|
newGlyph.program = oldGlyph.program
|
|
|
|
self.assertEqual(
|
|
oldGlyph.compile(glyfTable), newGlyph.compile(glyfTable))
|
|
|
|
def test_e2e_linesAndSimpleComponents(self):
|
|
self.runEndToEnd('TestTTF-Regular.ttx')
|
|
|
|
def test_e2e_curvesAndComponentTransforms(self):
|
|
self.runEndToEnd('TestTTFComplex-Regular.ttx')
|
|
|
|
def test_moveTo_errorWithinContour(self):
|
|
pen = TTGlyphPen(None)
|
|
pen.moveTo((0, 0))
|
|
with self.assertRaises(AssertionError):
|
|
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)
|
|
|
|
def test_endPath_sameAsClosePath(self):
|
|
pen = TTGlyphPen(None)
|
|
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((0, 1))
|
|
pen.lineTo((1, 0))
|
|
pen.closePath()
|
|
closePathGlyph = pen.glyph()
|
|
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((0, 1))
|
|
pen.lineTo((1, 0))
|
|
pen.endPath()
|
|
endPathGlyph = pen.glyph()
|
|
|
|
self.assertEqual(closePathGlyph, endPathGlyph)
|
|
|
|
def test_glyph_errorOnUnendedContour(self):
|
|
pen = TTGlyphPen(None)
|
|
pen.moveTo((0, 0))
|
|
with self.assertRaises(AssertionError):
|
|
pen.glyph()
|
|
|
|
def test_glyph_decomposes(self):
|
|
componentName = 'a'
|
|
glyphSet = {}
|
|
pen = TTGlyphPen(glyphSet)
|
|
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((0, 1))
|
|
pen.lineTo((1, 0))
|
|
pen.closePath()
|
|
glyphSet[componentName] = _TestGlyph(pen.glyph())
|
|
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((0, 1))
|
|
pen.lineTo((1, 0))
|
|
pen.closePath()
|
|
pen.addComponent(componentName, (1, 0, 0, 1, 2, 0))
|
|
pen.addComponent("missing", (1, 0, 0, 1, 0, 0)) # skipped
|
|
compositeGlyph = pen.glyph()
|
|
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((0, 1))
|
|
pen.lineTo((1, 0))
|
|
pen.closePath()
|
|
pen.moveTo((2, 0))
|
|
pen.lineTo((2, 1))
|
|
pen.lineTo((3, 0))
|
|
pen.closePath()
|
|
plainGlyph = pen.glyph()
|
|
|
|
self.assertEqual(plainGlyph, compositeGlyph)
|
|
|
|
def test_remove_extra_move_points(self):
|
|
pen = TTGlyphPen(None)
|
|
pen.moveTo((0, 0))
|
|
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))
|
|
|
|
def test_keep_move_point(self):
|
|
pen = TTGlyphPen(None)
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((100, 0))
|
|
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))
|
|
|
|
def test_keep_duplicate_end_point(self):
|
|
pen = TTGlyphPen(None)
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((100, 0))
|
|
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))
|
|
|
|
def test_within_range_component_transform(self):
|
|
componentName = 'a'
|
|
glyphSet = {}
|
|
pen = TTGlyphPen(glyphSet)
|
|
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((0, 1))
|
|
pen.lineTo((1, 0))
|
|
pen.closePath()
|
|
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()
|
|
|
|
self.assertEqual(expectedGlyph, compositeGlyph)
|
|
|
|
def test_clamp_to_almost_2_component_transform(self):
|
|
componentName = 'a'
|
|
glyphSet = {}
|
|
pen = TTGlyphPen(glyphSet)
|
|
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((0, 1))
|
|
pen.lineTo((1, 0))
|
|
pen.closePath()
|
|
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()
|
|
|
|
self.assertEqual(expectedGlyph, compositeGlyph)
|
|
|
|
def test_out_of_range_transform_decomposed(self):
|
|
componentName = 'a'
|
|
glyphSet = {}
|
|
pen = TTGlyphPen(glyphSet)
|
|
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((0, 1))
|
|
pen.lineTo((1, 0))
|
|
pen.closePath()
|
|
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.moveTo((0, 0))
|
|
pen.lineTo((0, 2))
|
|
pen.lineTo((3, 0))
|
|
pen.closePath()
|
|
pen.moveTo((-1, 2))
|
|
pen.lineTo((-1, 3))
|
|
pen.lineTo((0, 2))
|
|
pen.closePath()
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((0, -3))
|
|
pen.lineTo((2, 0))
|
|
pen.closePath()
|
|
expectedGlyph = pen.glyph()
|
|
|
|
self.assertEqual(expectedGlyph, compositeGlyph)
|
|
|
|
def test_no_handle_overflowing_transform(self):
|
|
componentName = 'a'
|
|
glyphSet = {}
|
|
pen = TTGlyphPen(glyphSet, handleOverflowingTransforms=False)
|
|
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((0, 1))
|
|
pen.lineTo((1, 0))
|
|
pen.closePath()
|
|
baseGlyph = pen.glyph()
|
|
glyphSet[componentName] = _TestGlyph(baseGlyph)
|
|
|
|
pen.addComponent(componentName, (3, 0, 0, 1, 0, 0))
|
|
compositeGlyph = pen.glyph()
|
|
|
|
self.assertEqual(compositeGlyph.components[0].transform,
|
|
((3, 0), (0, 1)))
|
|
|
|
with self.assertRaises(struct.error):
|
|
compositeGlyph.compile({'a': baseGlyph})
|
|
|
|
def assertGlyphBoundsEqual(self, glyph, bounds):
|
|
self.assertEqual((glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax), bounds)
|
|
|
|
def test_round_float_coordinates_and_component_offsets(self):
|
|
glyphSet = {}
|
|
pen = TTGlyphPen(glyphSet)
|
|
|
|
pen.moveTo((0, 0))
|
|
pen.lineTo((0, 1))
|
|
pen.lineTo((367.6, 0))
|
|
pen.closePath()
|
|
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 = TTGlyphPen(glyphSet)
|
|
pen.moveTo((-231, 939))
|
|
pen.lineTo((-55, 939))
|
|
pen.lineTo((-55, 745))
|
|
pen.lineTo((-231, 745))
|
|
pen.closePath()
|
|
glyphSet["gravecomb"] = gravecomb = pen.glyph()
|
|
|
|
pen = TTGlyphPen(glyphSet)
|
|
pen.moveTo((-278, 939))
|
|
pen.lineTo((8, 939))
|
|
pen.lineTo((8, 745))
|
|
pen.lineTo((-278, 745))
|
|
pen.closePath()
|
|
glyphSet["circumflexcomb"] = circumflexcomb = pen.glyph()
|
|
|
|
pen = TTGlyphPen(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))
|
|
|
|
|
|
class _TestGlyph(object):
|
|
def __init__(self, glyph):
|
|
self.coordinates = glyph.coordinates
|
|
|
|
def draw(self, pen):
|
|
pen.moveTo(self.coordinates[0])
|
|
for point in self.coordinates[1:]:
|
|
pen.lineTo(point)
|
|
pen.closePath()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
sys.exit(unittest.main())
|