from fontTools.misc.fixedTools import otRound
from fontTools.misc.testTools import getXML, parseXML
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.recordingPen import RecordingPen, RecordingPointPen
from fontTools.pens.pointPen import PointToSegmentPen
from fontTools.ttLib import TTFont, newTable, TTLibError
from fontTools.ttLib.tables._g_l_y_f import (
Glyph,
GlyphCoordinates,
GlyphComponent,
flagOnCurve,
flagCubic,
ARGS_ARE_XY_VALUES,
SCALED_COMPONENT_OFFSET,
UNSCALED_COMPONENT_OFFSET,
WE_HAVE_A_SCALE,
WE_HAVE_A_TWO_BY_TWO,
WE_HAVE_AN_X_AND_Y_SCALE,
)
from fontTools.ttLib.tables import ttProgram
import sys
import array
from io import StringIO, BytesIO
import itertools
import pytest
import re
import os
import unittest
class GlyphCoordinatesTest(object):
def test_translate(self):
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((0.5, 0))
assert g == GlyphCoordinates([(0.5, 0.0)])
def test_transform(self):
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)])
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)])
assert not (g != g2)
assert g != g3
assert g2 != g3
assert g != object()
def test__pos__(self):
g = GlyphCoordinates([(1, 2)])
g2 = +g
assert g == g2
def test__neg__(self):
g = GlyphCoordinates([(1, 2)])
g2 = -g
assert g2 == GlyphCoordinates([(-1, -2)])
@pytest.mark.skipif(sys.version_info[0] < 3, reason="__round___ requires Python 3")
def test__round__(self):
g = GlyphCoordinates([(-1.5, 2)])
g2 = round(g)
assert g2 == GlyphCoordinates([(-1, 2)])
def test__add__(self):
g1 = GlyphCoordinates([(1, 2)])
g2 = GlyphCoordinates([(3, 4)])
g3 = GlyphCoordinates([(4, 6)])
assert g1 + g2 == g3
assert g1 + (1, 1) == GlyphCoordinates([(2, 3)])
with pytest.raises(TypeError) as excinfo:
assert g1 + object()
assert "unsupported operand" in str(excinfo.value)
def test__sub__(self):
g1 = GlyphCoordinates([(1, 2)])
g2 = GlyphCoordinates([(3, 4)])
g3 = GlyphCoordinates([(-2, -2)])
assert g1 - g2 == g3
assert g1 - (1, 1) == GlyphCoordinates([(0, 1)])
with pytest.raises(TypeError) as excinfo:
assert g1 - object()
assert "unsupported operand" in str(excinfo.value)
def test__rsub__(self):
g = GlyphCoordinates([(1, 2)])
# other + (-self)
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
with pytest.raises(TypeError) as excinfo:
assert g * object()
assert "unsupported operand" in str(excinfo.value)
def test__truediv__(self):
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)
def test__iadd__(self):
g = GlyphCoordinates([(1, 2)])
g += (0.5, 0)
assert g == GlyphCoordinates([(1.5, 2.0)])
g2 = GlyphCoordinates([(3, 4)])
g += g2
assert g == GlyphCoordinates([(4.5, 6.0)])
def test__isub__(self):
g = GlyphCoordinates([(1, 2)])
g -= (0.5, 0)
assert g == GlyphCoordinates([(0.5, 2.0)])
g2 = GlyphCoordinates([(3, 4)])
g -= g2
assert g == GlyphCoordinates([(-2.5, -2.0)])
def __test__imul__(self):
g = GlyphCoordinates([(1, 2)])
g *= (2, 0.5)
g *= 2
assert g == GlyphCoordinates([(4.0, 2.0)])
g = GlyphCoordinates([(1, 2)])
g *= 2
assert g == GlyphCoordinates([(2, 4)])
def test__itruediv__(self):
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, 0)])
assert bool(g) == True
g = GlyphCoordinates([(0, 0), (1, 0)])
assert bool(g) == True
g = GlyphCoordinates([(0, 0.5), (0, 0)])
assert bool(g) == True
def test_double_precision_float(self):
# https://github.com/fonttools/fonttools/issues/963
afloat = 242.50000000000003
g = GlyphCoordinates([(afloat, 0)])
g.toInt()
# this would return 242 if the internal array.array typecode is 'f',
# since the Python float is truncated to a C float.
# when using typecode 'd' it should return the correct value 243
assert g[0][0] == otRound(afloat)
def test__checkFloat_overflow(self):
g = GlyphCoordinates([(1, 1)])
g.append((0x8000, 0))
assert list(g.array) == [1.0, 1.0, 32768.0, 0.0]
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
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)
class GlyfTableTest(unittest.TestCase):
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
# and fires deprecation warnings if a program uses the old name.
if not hasattr(self, "assertRaisesRegex"):
self.assertRaisesRegex = self.assertRaisesRegexp
@classmethod
def setUpClass(cls):
with open(GLYF_BIN, "rb") as f:
cls.glyfData = f.read()
with open(HEAD_BIN, "rb") as f:
cls.headData = f.read()
with open(LOCA_BIN, "rb") as f:
cls.locaData = f.read()
with open(MAXP_BIN, "rb") as f:
cls.maxpData = f.read()
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.decompile(self.glyfData, font)
out = StringIO()
font.saveXML(out)
glyfXML = strip_ttLibVersion(out.getvalue()).splitlines()
self.assertEqual(glyfXML, self.glyfXML)
def test_fromXML(self):
font = TTFont(sfntVersion="\x00\x01\x00\x00")
font.importXML(GLYF_TTX)
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)
glyph_dummy = pen_dummy.glyph()
glyphSet["A"] = glyph_dummy
glyphSet["B"] = glyph_dummy
pen_A = TTGlyphPen(glyphSet)
pen_A.addComponent("B", (1, 0, 0, 1, 0, 0))
pen_B = TTGlyphPen(glyphSet)
pen_B.addComponent("A", (1, 0, 0, 1, 0, 0))
glyph_A = pen_A.glyph()
glyph_B = pen_B.glyph()
glyphSet["A"] = glyph_A
glyphSet["B"] = glyph_B
with self.assertRaisesRegex(
TTLibError, "glyph '.' contains a recursive component reference"
):
glyph_A.getCoordinates(glyphSet)
def test_trim_remove_hinting_composite_glyph(self):
glyphSet = {"dummy": TTGlyphPen(None).glyph()}
pen = TTGlyphPen(glyphSet)
pen.addComponent("dummy", (1, 0, 0, 1, 0, 0))
composite = pen.glyph()
p = ttProgram.Program()
p.fromAssembly(["SVTCA[0]"])
composite.program = p
glyphSet["composite"] = composite
glyfTable = newTable("glyf")
glyfTable.glyphs = glyphSet
glyfTable.glyphOrder = sorted(glyphSet)
composite.compact(glyfTable)
self.assertTrue(hasattr(composite, "data"))
# remove hinting from the compacted composite glyph, without expanding it
composite.trim(remove_hinting=True)
# check that, after expanding the glyph, we have no instructions
composite.expand(glyfTable)
self.assertFalse(hasattr(composite, "program"))
# now remove hinting from expanded composite glyph
composite.program = p
composite.trim(remove_hinting=True)
# check we have no instructions
self.assertFalse(hasattr(composite, "program"))
composite.compact(glyfTable)
def test_bit6_draw_to_pen_issue1771(self):
# https://github.com/fonttools/fonttools/issues/1771
font = TTFont(sfntVersion="\x00\x01\x00\x00")
# glyph00003 contains a bit 6 flag on the first point,
# which triggered the issue
font.importXML(GLYF_TTX)
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", ()),
]
self.assertEqual(pen.value, expected)
def test_bit6_draw_to_pointpen(self):
# https://github.com/fonttools/fonttools/issues/1771
font = TTFont(sfntVersion="\x00\x01\x00\x00")
# glyph00003 contains a bit 6 flag on the first point
# which triggered the issue
font.importXML(GLYF_TTX)
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), {}),
]
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"]
pen1 = RecordingPen()
pen2 = RecordingPen()
glyfTable["glyph00003"].draw(pen1, glyfTable)
glyfTable["glyph00003"].drawPoints(PointToSegmentPen(pen2), glyfTable)
self.assertEqual(pen1.value, pen2.value)
def test_compile_empty_table(self):
font = TTFont(sfntVersion="\x00\x01\x00\x00")
font.importXML(GLYF_TTX)
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))
def test_decompile_empty_table(self):
font = TTFont()
glyphNames = [".notdef", "space"]
font.setGlyphOrder(glyphNames)
font["loca"] = newTable("loca")
font["loca"].locations = [0] * (len(glyphNames) + 1)
font["glyf"] = newTable("glyf")
font["glyf"].decompile(b"\x00", font)
self.assertEqual(len(font["glyf"]), 2)
self.assertEqual(font["glyf"][".notdef"].numberOfContours, 0)
self.assertEqual(font["glyf"]["space"].numberOfContours, 0)
def test_getPhantomPoints(self):
# https://github.com/fonttools/fonttools/issues/2295
font = TTFont()
glyphNames = [".notdef"]
font.setGlyphOrder(glyphNames)
font["loca"] = newTable("loca")
font["loca"].locations = [0] * (len(glyphNames) + 1)
font["glyf"] = newTable("glyf")
font["glyf"].decompile(b"\x00", font)
font["hmtx"] = newTable("hmtx")
font["hmtx"].metrics = {".notdef": (100, 0)}
font["head"] = newTable("head")
font["head"].unitsPerEm = 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_getCoordinates(self):
glyphSet = {}
pen = TTGlyphPen(glyphSet)
pen.moveTo((0, 0))
pen.lineTo((100, 0))
pen.lineTo((100, 100))
pen.lineTo((0, 100))
pen.closePath()
# simple contour glyph
glyphSet["a"] = a = pen.glyph()
assert a.getCoordinates(glyphSet) == (
GlyphCoordinates([(0, 0), (100, 0), (100, 100), (0, 100)]),
[3],
array.array("B", [1, 1, 1, 1]),
)
# composite glyph with only XY offset
pen = TTGlyphPen(glyphSet)
pen.addComponent("a", (1, 0, 0, 1, 10, 20))
glyphSet["b"] = b = pen.glyph()
assert b.getCoordinates(glyphSet) == (
GlyphCoordinates([(10, 20), (110, 20), (110, 120), (10, 120)]),
[3],
array.array("B", [1, 1, 1, 1]),
)
# composite glyph with a scale (and referencing another composite glyph)
pen = TTGlyphPen(glyphSet)
pen.addComponent("b", (0.5, 0, 0, 0.5, 0, 0))
glyphSet["c"] = c = pen.glyph()
assert c.getCoordinates(glyphSet) == (
GlyphCoordinates([(5, 10), (55, 10), (55, 60), (5, 60)]),
[3],
array.array("B", [1, 1, 1, 1]),
)
# composite glyph with unscaled offset (MS-style)
pen = TTGlyphPen(glyphSet)
pen.addComponent("a", (0.5, 0, 0, 0.5, 10, 20))
glyphSet["d"] = d = pen.glyph()
d.components[0].flags |= UNSCALED_COMPONENT_OFFSET
assert d.getCoordinates(glyphSet) == (
GlyphCoordinates([(10, 20), (60, 20), (60, 70), (10, 70)]),
[3],
array.array("B", [1, 1, 1, 1]),
)
# composite glyph with a scaled offset (Apple-style)
pen = TTGlyphPen(glyphSet)
pen.addComponent("a", (0.5, 0, 0, 0.5, 10, 20))
glyphSet["e"] = e = pen.glyph()
e.components[0].flags |= SCALED_COMPONENT_OFFSET
assert e.getCoordinates(glyphSet) == (
GlyphCoordinates([(5, 10), (55, 10), (55, 60), (5, 60)]),
[3],
array.array("B", [1, 1, 1, 1]),
)
# composite glyph where the 2nd and 3rd components use anchor points
pen = TTGlyphPen(glyphSet)
pen.addComponent("a", (1, 0, 0, 1, 0, 0))
glyphSet["f"] = f = pen.glyph()
comp1 = GlyphComponent()
comp1.glyphName = "a"
# aling the new component's pt 0 to pt 2 of contour points added so far
comp1.firstPt = 2
comp1.secondPt = 0
comp1.flags = 0
f.components.append(comp1)
comp2 = GlyphComponent()
comp2.glyphName = "a"
# aling the new component's pt 0 to pt 6 of contour points added so far
comp2.firstPt = 6
comp2.secondPt = 0
comp2.transform = [[0.707107, 0.707107], [-0.707107, 0.707107]] # rotate 45 deg
comp2.flags = WE_HAVE_A_TWO_BY_TWO
f.components.append(comp2)
coords, end_pts, flags = f.getCoordinates(glyphSet)
assert end_pts == [3, 7, 11]
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,
]
)
def test_getCompositeMaxpValues(self):
# https://github.com/fonttools/fonttools/issues/2044
glyphSet = {}
pen = TTGlyphPen(glyphSet) # empty non-composite glyph
glyphSet["fraction"] = pen.glyph()
glyphSet["zero.numr"] = pen.glyph()
pen = TTGlyphPen(glyphSet)
pen.addComponent("zero.numr", (1, 0, 0, 1, 0, 0))
glyphSet["zero.dnom"] = pen.glyph()
pen = TTGlyphPen(glyphSet)
pen.addComponent("zero.numr", (1, 0, 0, 1, 0, 0))
pen.addComponent("fraction", (1, 0, 0, 1, 0, 0))
pen.addComponent("zero.dnom", (1, 0, 0, 1, 0, 0))
glyphSet["percent"] = pen.glyph()
pen = TTGlyphPen(glyphSet)
pen.addComponent("zero.numr", (1, 0, 0, 1, 0, 0))
pen.addComponent("fraction", (1, 0, 0, 1, 0, 0))
pen.addComponent("zero.dnom", (1, 0, 0, 1, 0, 0))
pen.addComponent("zero.dnom", (1, 0, 0, 1, 0, 0))
glyphSet["perthousand"] = pen.glyph()
assert glyphSet["zero.dnom"].getCompositeMaxpValues(glyphSet)[2] == 1
assert glyphSet["percent"].getCompositeMaxpValues(glyphSet)[2] == 2
assert glyphSet["perthousand"].getCompositeMaxpValues(glyphSet)[2] == 2
class GlyphComponentTest:
def test_toXML_no_transform(self):
comp = GlyphComponent()
comp.glyphName = "a"
comp.flags = ARGS_ARE_XY_VALUES
comp.x, comp.y = 1, 2
assert getXML(comp.toXML) == [
''
]
def test_toXML_transform_scale(self):
comp = GlyphComponent()
comp.glyphName = "a"
comp.flags = ARGS_ARE_XY_VALUES | WE_HAVE_A_SCALE
comp.x, comp.y = 1, 2
comp.transform = [[0.2999878, 0], [0, 0.2999878]]
assert getXML(comp.toXML) == [
''
]
def test_toXML_transform_xy_scale(self):
comp = GlyphComponent()
comp.glyphName = "a"
comp.flags = ARGS_ARE_XY_VALUES | WE_HAVE_AN_X_AND_Y_SCALE
comp.x, comp.y = 1, 2
comp.transform = [[0.5999756, 0], [0, 0.2999878]]
assert getXML(comp.toXML) == [
''
]
def test_toXML_transform_2x2_scale(self):
comp = GlyphComponent()
comp.glyphName = "a"
comp.flags = ARGS_ARE_XY_VALUES | WE_HAVE_A_TWO_BY_TWO
comp.x, comp.y = 1, 2
comp.transform = [[0.5999756, -0.2000122], [0.2000122, 0.2999878]]
assert getXML(comp.toXML) == [
''
]
def test_fromXML_no_transform(self):
comp = GlyphComponent()
for name, attrs, content in parseXML(
['']
):
comp.fromXML(name, attrs, content, ttFont=None)
assert comp.glyphName == "a"
assert comp.flags & ARGS_ARE_XY_VALUES != 0
assert (comp.x, comp.y) == (1, 2)
assert not hasattr(comp, "transform")
def test_fromXML_transform_scale(self):
comp = GlyphComponent()
for name, attrs, content in parseXML(
['']
):
comp.fromXML(name, attrs, content, ttFont=None)
assert comp.glyphName == "a"
assert comp.flags & ARGS_ARE_XY_VALUES != 0
assert comp.flags & WE_HAVE_A_SCALE != 0
assert (comp.x, comp.y) == (1, 2)
assert hasattr(comp, "transform")
for value, expected in zip(
itertools.chain(*comp.transform), [0.2999878, 0, 0, 0.2999878]
):
assert value == pytest.approx(expected)
def test_fromXML_transform_xy_scale(self):
comp = GlyphComponent()
for name, attrs, content in parseXML(
[
''
]
):
comp.fromXML(name, attrs, content, ttFont=None)
assert comp.glyphName == "a"
assert comp.flags & ARGS_ARE_XY_VALUES != 0
assert comp.flags & WE_HAVE_AN_X_AND_Y_SCALE != 0
assert (comp.x, comp.y) == (1, 2)
assert hasattr(comp, "transform")
for value, expected in zip(
itertools.chain(*comp.transform), [0.5999756, 0, 0, 0.2999878]
):
assert value == pytest.approx(expected)
def test_fromXML_transform_2x2_scale(self):
comp = GlyphComponent()
for name, attrs, content in parseXML(
[
''
]
):
comp.fromXML(name, attrs, content, ttFont=None)
assert comp.glyphName == "a"
assert comp.flags & ARGS_ARE_XY_VALUES != 0
assert comp.flags & WE_HAVE_A_TWO_BY_TWO != 0
assert (comp.x, comp.y) == (1, 2)
assert hasattr(comp, "transform")
for value, expected in zip(
itertools.chain(*comp.transform),
[0.5999756, -0.2000122, 0.2000122, 0.2999878],
):
assert value == pytest.approx(expected)
def test_toXML_reference_points(self):
comp = GlyphComponent()
comp.glyphName = "a"
comp.flags = 0
comp.firstPt = 1
comp.secondPt = 2
assert getXML(comp.toXML) == [
''
]
def test_fromXML_reference_points(self):
comp = GlyphComponent()
for name, attrs, content in parseXML(
['']
):
comp.fromXML(name, attrs, content, ttFont=None)
assert comp.glyphName == "a"
assert comp.flags == 0
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 == [
("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", ()),
]
if __name__ == "__main__":
import sys
sys.exit(unittest.main())