[GX] In ‘gvar’ tuples, treat (0,0) different from unspecified entries
After this change, MacOS Yosemite 10.3.3 renders the Oslash glyph of Skia.ttf with the exact same outline before and after round-tripping the font through TTX. Before this change, the outlines were slightly different after round-tripping through TTX. In my initial reading of the “gvar” specification, I had assumed that (0,0) entries had no significance. However, that is not how the current MacOS implementation interprets it.
This commit is contained in:
parent
64d8411c1c
commit
624b2b7529
@ -5,7 +5,6 @@ from fontTools.misc import sstruct
|
||||
from fontTools.misc.fixedTools import fixedToFloat, floatToFixed
|
||||
from fontTools.misc.textTools import safeEval
|
||||
from fontTools.ttLib import TTLibError
|
||||
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
|
||||
from . import DefaultTable
|
||||
import array
|
||||
import io
|
||||
@ -106,10 +105,6 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
||||
|
||||
def compileGlyph_(self, glyphName, numPointsInGlyph, axisTags, sharedCoordIndices):
|
||||
variations = self.variations.get(glyphName, [])
|
||||
# Omit variations that have no user-visible impact because their deltas
|
||||
# are all (0, 0). In the Apple Skia font, about 5% of all glyph variation
|
||||
# tuples can be omitted. On the other hand, in the JamRegular and
|
||||
# BuffaloGalRegular fonts, all tuples have at least one non-zero delta.
|
||||
variations = [v for v in variations if v.hasImpact()]
|
||||
if len(variations) == 0:
|
||||
return b""
|
||||
@ -286,7 +281,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
||||
points = sharedPoints
|
||||
deltas_x, pos = GlyphVariation.decompileDeltas_(len(points), tupleData, pos)
|
||||
deltas_y, pos = GlyphVariation.decompileDeltas_(len(points), tupleData, pos)
|
||||
deltas = GlyphCoordinates.zeros(numPoints)
|
||||
deltas = [None] * numPoints
|
||||
for p, x, y in zip(points, deltas_x, deltas_y):
|
||||
deltas[p] = (x, y)
|
||||
return GlyphVariation(axes, deltas)
|
||||
@ -333,7 +328,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
||||
if isinstance(element, tuple):
|
||||
name, attrs, content = element
|
||||
if name == "tuple":
|
||||
gvar = GlyphVariation({}, GlyphCoordinates.zeros(numPoints))
|
||||
gvar = GlyphVariation({}, [None] * numPoints)
|
||||
glyphVariations.append(gvar)
|
||||
for tupleElement in content:
|
||||
if isinstance(tupleElement, tuple):
|
||||
@ -366,7 +361,7 @@ class GlyphVariation(object):
|
||||
def getUsedPoints(self):
|
||||
result = set()
|
||||
for i, point in enumerate(self.coordinates):
|
||||
if point != (0, 0):
|
||||
if point != None:
|
||||
result.add(i)
|
||||
return result
|
||||
|
||||
@ -377,7 +372,7 @@ class GlyphVariation(object):
|
||||
without making any visible difference.
|
||||
"""
|
||||
for c in self.coordinates:
|
||||
if c != (0, 0):
|
||||
if c != None:
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -396,13 +391,13 @@ class GlyphVariation(object):
|
||||
writer.simpletag("coord", axis=axis, value=value, min=minValue, max=maxValue)
|
||||
writer.newline()
|
||||
wrote_any_points = False
|
||||
for i, (x, y) in enumerate(self.coordinates):
|
||||
if x != 0 or y != 0:
|
||||
writer.simpletag("delta", pt=i, x=x, y=y)
|
||||
for i, point in enumerate(self.coordinates):
|
||||
if point != None:
|
||||
writer.simpletag("delta", pt=i, x=point[0], y=point[1])
|
||||
writer.newline()
|
||||
wrote_any_points = True
|
||||
if not wrote_any_points:
|
||||
writer.comment("all deltas are (0,0)")
|
||||
writer.comment("no deltas")
|
||||
writer.newline()
|
||||
writer.endtag("tuple")
|
||||
writer.newline()
|
||||
@ -572,9 +567,13 @@ class GlyphVariation(object):
|
||||
return (result, pos)
|
||||
|
||||
def compileDeltas(self, points):
|
||||
points = sorted(list(points))
|
||||
deltaX = [self.coordinates[p][0] for p in points]
|
||||
deltaY = [self.coordinates[p][1] for p in points]
|
||||
deltaX = []
|
||||
deltaY = []
|
||||
for p in sorted(list(points)):
|
||||
c = self.coordinates[p]
|
||||
if c is not None:
|
||||
deltaX.append(c[0])
|
||||
deltaY.append(c[1])
|
||||
return self.compileDeltaValues_(deltaX) + self.compileDeltaValues_(deltaY)
|
||||
|
||||
@staticmethod
|
||||
|
@ -3,7 +3,6 @@ from fontTools.misc.py23 import *
|
||||
from fontTools.misc.textTools import deHexStr, hexStr
|
||||
from fontTools.misc.xmlWriter import XMLWriter
|
||||
from fontTools.ttLib import TTLibError
|
||||
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
|
||||
from fontTools.ttLib.tables._g_v_a_r import table__g_v_a_r, GlyphVariation
|
||||
import random
|
||||
import unittest
|
||||
@ -75,9 +74,9 @@ class GlyphVariationTableTest(unittest.TestCase):
|
||||
table = table__g_v_a_r()
|
||||
axes = {"wght": (0.3, 0.4, 0.5), "opsz": (0.7, 0.8, 0.9)}
|
||||
table.variations = {"glyphname": [
|
||||
GlyphVariation(axes, GlyphCoordinates.zeros(4)),
|
||||
GlyphVariation(axes, GlyphCoordinates.zeros(4)),
|
||||
GlyphVariation(axes, GlyphCoordinates.zeros(4))
|
||||
GlyphVariation(axes, [None] * 4),
|
||||
GlyphVariation(axes, [None] * 4),
|
||||
GlyphVariation(axes, [None] * 4)
|
||||
]}
|
||||
self.assertEqual(b"", table.compileGlyph_("glyphname", 8, ["wght", "opsz"], {}))
|
||||
|
||||
@ -85,7 +84,7 @@ class GlyphVariationTableTest(unittest.TestCase):
|
||||
table = table__g_v_a_r()
|
||||
axisTags = ["wght", "wdth"]
|
||||
numPoints = 4
|
||||
glyphCoords = GlyphCoordinates([(1,1), (2,2), (3,3), (4,4)])
|
||||
glyphCoords = [(1,1), (2,2), (3,3), (4,4)]
|
||||
gvar1 = GlyphVariation({"wght": (0.5, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)}, glyphCoords)
|
||||
gvar2 = GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)}, glyphCoords)
|
||||
table.variations = {"oslash": [gvar1, gvar2]}
|
||||
@ -99,17 +98,18 @@ class GlyphVariationTableTest(unittest.TestCase):
|
||||
font = FakeFont()
|
||||
table = table__g_v_a_r()
|
||||
table.variations = {}
|
||||
deltas = [None] * 4
|
||||
table.variations["A"] = [
|
||||
GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.5, 0.7, 1.0)}, GlyphCoordinates.zeros(4))
|
||||
GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.5, 0.7, 1.0)}, deltas)
|
||||
]
|
||||
table.variations["B"] = [
|
||||
GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.2, 0.7, 1.0)}, GlyphCoordinates.zeros(4)),
|
||||
GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.2, 0.8, 1.0)}, GlyphCoordinates.zeros(4))
|
||||
GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.2, 0.7, 1.0)}, deltas),
|
||||
GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.2, 0.8, 1.0)}, deltas)
|
||||
]
|
||||
table.variations["C"] = [
|
||||
GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.7, 1.0)}, GlyphCoordinates.zeros(4)),
|
||||
GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.8, 1.0)}, GlyphCoordinates.zeros(4)),
|
||||
GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.9, 1.0)}, GlyphCoordinates.zeros(4))
|
||||
GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.7, 1.0)}, deltas),
|
||||
GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.8, 1.0)}, deltas),
|
||||
GlyphVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.9, 1.0)}, deltas)
|
||||
]
|
||||
# {"wght":1.0, "wdth":0.7} is shared 3 times; {"wght":1.0, "wdth":0.8} is shared twice.
|
||||
# Min and max values are not part of the shared coordinate pool and should get ignored.
|
||||
@ -164,34 +164,39 @@ class GlyphVariationTableTest(unittest.TestCase):
|
||||
|
||||
class GlyphVariationTest(unittest.TestCase):
|
||||
def test_equal(self):
|
||||
gvar1 = GlyphVariation({"wght":(0.0, 1.0, 1.0)}, GlyphCoordinates([(0,0), (9,8), (7,6)]))
|
||||
gvar2 = GlyphVariation({"wght":(0.0, 1.0, 1.0)}, GlyphCoordinates([(0,0), (9,8), (7,6)]))
|
||||
gvar1 = GlyphVariation({"wght":(0.0, 1.0, 1.0)}, [(0,0), (9,8), (7,6)])
|
||||
gvar2 = GlyphVariation({"wght":(0.0, 1.0, 1.0)}, [(0,0), (9,8), (7,6)])
|
||||
self.assertEqual(gvar1, gvar2)
|
||||
|
||||
def test_equal_differentAxes(self):
|
||||
gvar1 = GlyphVariation({"wght":(0.0, 1.0, 1.0)}, GlyphCoordinates([(0,0), (9,8), (7,6)]))
|
||||
gvar2 = GlyphVariation({"wght":(0.7, 0.8, 0.9)}, GlyphCoordinates([(0,0), (9,8), (7,6)]))
|
||||
gvar1 = GlyphVariation({"wght":(0.0, 1.0, 1.0)}, [(0,0), (9,8), (7,6)])
|
||||
gvar2 = GlyphVariation({"wght":(0.7, 0.8, 0.9)}, [(0,0), (9,8), (7,6)])
|
||||
self.assertNotEqual(gvar1, gvar2)
|
||||
|
||||
def test_equal_differentCoordinates(self):
|
||||
gvar1 = GlyphVariation({"wght":(0.0, 1.0, 1.0)}, GlyphCoordinates([(0,0), (9,8), (7,6)]))
|
||||
gvar2 = GlyphVariation({"wght":(0.0, 1.0, 1.0)}, GlyphCoordinates([(0,0), (9,8)]))
|
||||
gvar1 = GlyphVariation({"wght":(0.0, 1.0, 1.0)}, [(0,0), (9,8), (7,6)])
|
||||
gvar2 = GlyphVariation({"wght":(0.0, 1.0, 1.0)}, [(0,0), (9,8)])
|
||||
self.assertNotEqual(gvar1, gvar2)
|
||||
|
||||
def test_hasImpact_someDeltasNotZero(self):
|
||||
axes = {"wght":(0.0, 1.0, 1.0)}
|
||||
gvar = GlyphVariation(axes, GlyphCoordinates([(0,0), (9,8), (7,6)]))
|
||||
gvar = GlyphVariation(axes, [(0,0), (9,8), (7,6)])
|
||||
self.assertTrue(gvar.hasImpact())
|
||||
|
||||
def test_hasImpact_allDeltasZero(self):
|
||||
axes = {"wght":(0.0, 1.0, 1.0)}
|
||||
gvar = GlyphVariation(axes, GlyphCoordinates([(0,0), (0,0), (0,0)]))
|
||||
gvar = GlyphVariation(axes, [(0,0), (0,0), (0,0)])
|
||||
self.assertTrue(gvar.hasImpact())
|
||||
|
||||
def test_hasImpact_allDeltasNone(self):
|
||||
axes = {"wght":(0.0, 1.0, 1.0)}
|
||||
gvar = GlyphVariation(axes, [None, None, None])
|
||||
self.assertFalse(gvar.hasImpact())
|
||||
|
||||
def test_toXML(self):
|
||||
writer = XMLWriter(StringIO())
|
||||
axes = {"wdth":(0.3, 0.4, 0.5), "wght":(0.0, 1.0, 1.0), "opsz":(-0.7, -0.7, 0.0)}
|
||||
g = GlyphVariation(axes, GlyphCoordinates([(9,8), (7,6), (0,0), (-1,-2)]))
|
||||
g = GlyphVariation(axes, [(9,8), None, (7,6), (0,0), (-1,-2), None])
|
||||
g.toXML(writer, ["wdth", "wght", "opsz"])
|
||||
self.assertEqual([
|
||||
'<tuple>',
|
||||
@ -199,25 +204,26 @@ class GlyphVariationTest(unittest.TestCase):
|
||||
'<coord axis="wght" value="1.0"/>',
|
||||
'<coord axis="opsz" value="-0.7"/>',
|
||||
'<delta pt="0" x="9" y="8"/>',
|
||||
'<delta pt="1" x="7" y="6"/>',
|
||||
'<delta pt="3" x="-1" y="-2"/>',
|
||||
'<delta pt="2" x="7" y="6"/>',
|
||||
'<delta pt="3" x="0" y="0"/>',
|
||||
'<delta pt="4" x="-1" y="-2"/>',
|
||||
'</tuple>'
|
||||
], GlyphVariationTest.xml_lines(writer))
|
||||
|
||||
def test_toXML_allDeltasZero(self):
|
||||
def test_toXML_allDeltasNone(self):
|
||||
writer = XMLWriter(StringIO())
|
||||
axes = {"wght":(0.0, 1.0, 1.0)}
|
||||
g = GlyphVariation(axes, GlyphCoordinates.zeros(5))
|
||||
g = GlyphVariation(axes, [None] * 5)
|
||||
g.toXML(writer, ["wght", "wdth"])
|
||||
self.assertEqual([
|
||||
'<tuple>',
|
||||
'<coord axis="wght" value="1.0"/>',
|
||||
'<!-- all deltas are (0,0) -->',
|
||||
'<!-- no deltas -->',
|
||||
'</tuple>'
|
||||
], GlyphVariationTest.xml_lines(writer))
|
||||
|
||||
def test_fromXML(self):
|
||||
g = GlyphVariation({}, GlyphCoordinates.zeros(4))
|
||||
g = GlyphVariation({}, [None] * 4)
|
||||
g.fromXML("coord", {"axis":"wdth", "min":"0.3", "value":"0.4", "max":"0.5"}, [])
|
||||
g.fromXML("coord", {"axis":"wght", "value":"1.0"}, [])
|
||||
g.fromXML("coord", {"axis":"opsz", "value":"-0.5"}, [])
|
||||
@ -228,11 +234,11 @@ class GlyphVariationTest(unittest.TestCase):
|
||||
"wght":( 0.0, 1.0, 1.0),
|
||||
"opsz":(-0.5, -0.5, 0.0)
|
||||
}, g.axes)
|
||||
self.assertEqual("0,0 33,44 -2,170 0,0", " ".join(["%d,%d" % c for c in g.coordinates]))
|
||||
self.assertEqual([None, (33, 44), (-2, 170), None], g.coordinates)
|
||||
|
||||
def test_compile_sharedCoords_nonIntermediate_sharedPoints(self):
|
||||
gvar = GlyphVariation({"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
|
||||
GlyphCoordinates([(7,4), (8,5), (9,6)]))
|
||||
[(7,4), (8,5), (9,6)])
|
||||
axisTags = ["wght", "wdth"]
|
||||
sharedCoordIndices = { gvar.compileCoord(axisTags): 0x77 }
|
||||
tuple, data = gvar.compile(axisTags, sharedCoordIndices, sharedPoints=set([0,1,2]))
|
||||
@ -245,7 +251,7 @@ class GlyphVariationTest(unittest.TestCase):
|
||||
|
||||
def test_compile_sharedCoords_intermediate_sharedPoints(self):
|
||||
gvar = GlyphVariation({"wght": (0.3, 0.5, 0.7), "wdth": (0.1, 0.8, 0.9)},
|
||||
GlyphCoordinates([(7,4), (8,5), (9,6)]))
|
||||
[(7,4), (8,5), (9,6)])
|
||||
axisTags = ["wght", "wdth"]
|
||||
sharedCoordIndices = { gvar.compileCoord(axisTags): 0x77 }
|
||||
tuple, data = gvar.compile(axisTags, sharedCoordIndices, sharedPoints=set([0,1,2]))
|
||||
@ -258,7 +264,7 @@ class GlyphVariationTest(unittest.TestCase):
|
||||
|
||||
def test_compile_sharedCoords_nonIntermediate_privatePoints(self):
|
||||
gvar = GlyphVariation({"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
|
||||
GlyphCoordinates([(7,4), (8,5), (9,6)]))
|
||||
[(7,4), (8,5), (9,6)])
|
||||
axisTags = ["wght", "wdth"]
|
||||
sharedCoordIndices = { gvar.compileCoord(axisTags): 0x77 }
|
||||
tuple, data = gvar.compile(axisTags, sharedCoordIndices, sharedPoints=None)
|
||||
@ -272,7 +278,7 @@ class GlyphVariationTest(unittest.TestCase):
|
||||
|
||||
def test_compile_sharedCoords_intermediate_privatePoints(self):
|
||||
gvar = GlyphVariation({"wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 1.0)},
|
||||
GlyphCoordinates([(7,4), (8,5), (9,6)]))
|
||||
[(7,4), (8,5), (9,6)])
|
||||
axisTags = ["wght", "wdth"]
|
||||
sharedCoordIndices = { gvar.compileCoord(axisTags): 0x77 }
|
||||
tuple, data = gvar.compile(axisTags, sharedCoordIndices, sharedPoints=None)
|
||||
@ -286,7 +292,7 @@ class GlyphVariationTest(unittest.TestCase):
|
||||
|
||||
def test_compile_embeddedCoords_nonIntermediate_sharedPoints(self):
|
||||
gvar = GlyphVariation({"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
|
||||
GlyphCoordinates([(7,4), (8,5), (9,6)]))
|
||||
[(7,4), (8,5), (9,6)])
|
||||
axisTags = ["wght", "wdth"]
|
||||
tuple, data = gvar.compile(axisTags, sharedCoordIndices={}, sharedPoints=set([0,1,2]))
|
||||
# len(data)=8; flags=EMBEDDED_TUPLE_COORD
|
||||
@ -298,7 +304,7 @@ class GlyphVariationTest(unittest.TestCase):
|
||||
|
||||
def test_compile_embeddedCoords_intermediate_sharedPoints(self):
|
||||
gvar = GlyphVariation({"wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 0.8)},
|
||||
GlyphCoordinates([(7,4), (8,5), (9,6)]))
|
||||
[(7,4), (8,5), (9,6)])
|
||||
axisTags = ["wght", "wdth"]
|
||||
tuple, data = gvar.compile(axisTags, sharedCoordIndices={}, sharedPoints=set([0,1,2]))
|
||||
# len(data)=8; flags=EMBEDDED_TUPLE_COORD
|
||||
@ -310,7 +316,7 @@ class GlyphVariationTest(unittest.TestCase):
|
||||
|
||||
def test_compile_embeddedCoords_nonIntermediate_privatePoints(self):
|
||||
gvar = GlyphVariation({"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
|
||||
GlyphCoordinates([(7,4), (8,5), (9,6)]))
|
||||
[(7,4), (8,5), (9,6)])
|
||||
axisTags = ["wght", "wdth"]
|
||||
tuple, data = gvar.compile(axisTags, sharedCoordIndices={}, sharedPoints=None)
|
||||
# len(data)=13; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_TUPLE_COORD
|
||||
@ -323,7 +329,7 @@ class GlyphVariationTest(unittest.TestCase):
|
||||
|
||||
def test_compile_embeddedCoords_intermediate_privatePoints(self):
|
||||
gvar = GlyphVariation({"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)},
|
||||
GlyphCoordinates([(7,4), (8,5), (9,6)]))
|
||||
[(7,4), (8,5), (9,6)])
|
||||
axisTags = ["wght", "wdth"]
|
||||
tuple, data = gvar.compile(axisTags, sharedCoordIndices={}, sharedPoints=None)
|
||||
# len(data)=13; flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_TUPLE|EMBEDDED_TUPLE_COORD
|
||||
@ -335,13 +341,13 @@ class GlyphVariationTest(unittest.TestCase):
|
||||
hexencode(data))
|
||||
|
||||
def test_compileCoord(self):
|
||||
gvar = GlyphVariation({"wght": (-1.0, -1.0, -1.0), "wdth": (0.4, 0.5, 0.6)}, GlyphCoordinates.zeros(4))
|
||||
gvar = GlyphVariation({"wght": (-1.0, -1.0, -1.0), "wdth": (0.4, 0.5, 0.6)}, [None] * 4)
|
||||
self.assertEqual("C0 00 20 00", hexencode(gvar.compileCoord(["wght", "wdth"])))
|
||||
self.assertEqual("20 00 C0 00", hexencode(gvar.compileCoord(["wdth", "wght"])))
|
||||
self.assertEqual("C0 00", hexencode(gvar.compileCoord(["wght"])))
|
||||
|
||||
def test_compileIntermediateCoord(self):
|
||||
gvar = GlyphVariation({"wght": (-1.0, -1.0, 0.0), "wdth": (0.4, 0.5, 0.6)}, GlyphCoordinates.zeros(4))
|
||||
gvar = GlyphVariation({"wght": (-1.0, -1.0, 0.0), "wdth": (0.4, 0.5, 0.6)}, [None] * 4)
|
||||
self.assertEqual("C0 00 19 9A 00 00 26 66", hexencode(gvar.compileIntermediateCoord(["wght", "wdth"])))
|
||||
self.assertEqual("19 9A C0 00 26 66 00 00", hexencode(gvar.compileIntermediateCoord(["wdth", "wght"])))
|
||||
self.assertEqual(None, gvar.compileIntermediateCoord(["wght"]))
|
||||
@ -357,7 +363,7 @@ class GlyphVariationTest(unittest.TestCase):
|
||||
data = deHexStr("7F B9 80 35")
|
||||
values, _ = GlyphVariation.decompileCoord_(["wght", "wdth"], data, 0)
|
||||
axisValues = dict([(axis, (val, val, val)) for axis, val in values.items()])
|
||||
gvar = GlyphVariation(axisValues, GlyphCoordinates.zeros(4))
|
||||
gvar = GlyphVariation(axisValues, [None] * 4)
|
||||
self.assertEqual("7F B9 80 35", hexencode(gvar.compileCoord(["wght", "wdth"])))
|
||||
|
||||
def test_decompileCoords(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user