[cvar] Support XML input/output for TupleVariations on constant values

OpenType TupleVariations can be used in two places:

* In the `gvar` table, they modify glyph contour points by shifting
  them towards a point in 2D space.

* In the `cvar` table, they modify constant values.

Before this change, we only had code to handle the `gvar` version
which shifts points around by delta values.

After this change, the XML parsing and generation routines of
TupleVariations can handle deltas that modify constant values in the
CVT table, as used in `cvar`.

An upcoming change will add support for the binary encoding of
TupleVariations as needed for `cvar`.
This commit is contained in:
Sascha Brawer 2017-01-04 18:08:55 +01:00
parent 01f95fc190
commit 6275668840
2 changed files with 90 additions and 21 deletions

View File

@ -69,13 +69,22 @@ class TupleVariation(object):
else:
writer.simpletag("coord", axis=axis, value=value, min=minValue, max=maxValue)
writer.newline()
wrote_any_points = False
for i, point in enumerate(self.coordinates):
if point is not None:
writer.simpletag("delta", pt=i, x=point[0], y=point[1])
wrote_any_deltas = False
for i, delta in enumerate(self.coordinates):
if type(delta) == tuple and len(delta) == 2:
writer.simpletag("delta", pt=i, x=delta[0], y=delta[1])
writer.newline()
wrote_any_points = True
if not wrote_any_points:
wrote_any_deltas = True
elif type(delta) == int:
writer.simpletag("delta", cvt=i, value=delta)
writer.newline()
wrote_any_deltas = True
elif delta is not None:
log.error("bad delta format")
writer.comment("bad delta #%d" % i)
writer.newline()
wrote_any_deltas = True
if not wrote_any_deltas:
writer.comment("no deltas")
writer.newline()
writer.endtag("tuple")
@ -91,10 +100,18 @@ class TupleVariation(object):
maxValue = float(attrs.get("max", defaultMaxValue))
self.axes[axis] = (minValue, value, maxValue)
elif name == "delta":
point = safeEval(attrs["pt"])
x = safeEval(attrs["x"])
y = safeEval(attrs["y"])
self.coordinates[point] = (x, y)
if "pt" in attrs:
point = safeEval(attrs["pt"])
x = safeEval(attrs["x"])
y = safeEval(attrs["y"])
self.coordinates[point] = (x, y)
elif "cvt" in attrs:
cvt = safeEval(attrs["cvt"])
value = safeEval(attrs["value"])
self.coordinates[cvt] = value
else:
log.warning("bad delta format: %s" %
", ".join(sorted(attrs.keys())))
def compile(self, axisTags, sharedCoordIndices, sharedPoints):
tupleData = []

View File

@ -1,9 +1,10 @@
from __future__ import print_function, division, absolute_import, unicode_literals
from fontTools.misc.py23 import *
from fontTools.misc.loggingTools import CapturingLogHandler
from fontTools.misc.testTools import parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib.tables.TupleVariation import TupleVariation
from fontTools.ttLib.tables.TupleVariation import log, TupleVariation
import random
import unittest
@ -13,6 +14,13 @@ def hexencode(s):
return ' '.join([h[i:i+2] for i in range(0, len(h), 2)])
AXES = {
"wdth":(0.3, 0.4, 0.5),
"wght":(0.0, 1.0, 1.0),
"opsz":(-0.7, -0.7, 0.0)
}
class TupleVariationTest(unittest.TestCase):
def test_equal(self):
gvar1 = TupleVariation({"wght":(0.0, 1.0, 1.0)}, [(0,0), (9,8), (7,6)])
@ -44,10 +52,38 @@ class TupleVariationTest(unittest.TestCase):
gvar = TupleVariation(axes, [None, None, None])
self.assertFalse(gvar.hasImpact())
def test_toXML(self):
def test_toXML_badDeltaFormat(self):
writer = XMLWriter(BytesIO())
axes = {"wdth":(0.3, 0.4, 0.5), "wght":(0.0, 1.0, 1.0), "opsz":(-0.7, -0.7, 0.0)}
g = TupleVariation(axes, [(9,8), None, (7,6), (0,0), (-1,-2), None])
g = TupleVariation(AXES, ["String"])
with CapturingLogHandler(log, "ERROR") as captor:
g.toXML(writer, ["wdth"])
self.assertIn("bad delta format", [r.msg for r in captor.records])
self.assertEqual([
'<tuple>',
'<coord axis="wdth" max="0.5" min="0.3" value="0.4"/>',
'<!-- bad delta #0 -->',
'</tuple>',
], TupleVariationTest.xml_lines(writer))
def test_toXML_constants(self):
writer = XMLWriter(BytesIO())
g = TupleVariation(AXES, [42, None, 23, 0, -17, None])
g.toXML(writer, ["wdth", "wght", "opsz"])
self.assertEqual([
'<tuple>',
'<coord axis="wdth" max="0.5" min="0.3" value="0.4"/>',
'<coord axis="wght" value="1.0"/>',
'<coord axis="opsz" value="-0.7"/>',
'<delta cvt="0" value="42"/>',
'<delta cvt="2" value="23"/>',
'<delta cvt="3" value="0"/>',
'<delta cvt="4" value="-17"/>',
'</tuple>'
], TupleVariationTest.xml_lines(writer))
def test_toXML_points(self):
writer = XMLWriter(BytesIO())
g = TupleVariation(AXES, [(9,8), None, (7,6), (0,0), (-1,-2), None])
g.toXML(writer, ["wdth", "wght", "opsz"])
self.assertEqual([
'<tuple>',
@ -73,20 +109,36 @@ class TupleVariationTest(unittest.TestCase):
'</tuple>'
], TupleVariationTest.xml_lines(writer))
def test_fromXML(self):
def test_fromXML_badDeltaFormat(self):
g = TupleVariation({}, [])
with CapturingLogHandler(log, "WARNING") as captor:
for name, attrs, content in parseXML('<delta a="1" b="2"/>'):
g.fromXML(name, attrs, content)
self.assertIn("bad delta format: a, b",
[r.msg for r in captor.records])
def test_fromXML_constants(self):
g = TupleVariation({}, [None] * 4)
for name, attrs, content in parseXML(
'<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>'
'<coord axis="wght" value="1.0"/>'
'<coord axis="opsz" value="-0.5"/>'
'<coord axis="opsz" value="-0.7"/>'
'<delta cvt="1" value="42"/>'
'<delta cvt="2" value="-23"/>'):
g.fromXML(name, attrs, content)
self.assertEqual(AXES, g.axes)
self.assertEqual([None, 42, -23, None], g.coordinates)
def test_fromXML_points(self):
g = TupleVariation({}, [None] * 4)
for name, attrs, content in parseXML(
'<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>'
'<coord axis="wght" value="1.0"/>'
'<coord axis="opsz" value="-0.7"/>'
'<delta pt="1" x="33" y="44"/>'
'<delta pt="2" x="-2" y="170"/>'):
g.fromXML(name, attrs, content)
self.assertEqual({
"wdth":( 0.3, 0.4, 0.5),
"wght":( 0.0, 1.0, 1.0),
"opsz":(-0.5, -0.5, 0.0)
}, g.axes)
self.assertEqual(AXES, g.axes)
self.assertEqual([None, (33, 44), (-2, 170), None], g.coordinates)
def test_compile_sharedCoords_nonIntermediate_sharedPoints(self):