Merge pull request #969 from fonttools/varlib-iup

Varlib iup
This commit is contained in:
Cosimo Lupo 2017-05-24 11:44:59 +01:00 committed by GitHub
commit fd3cd6f367
8 changed files with 1079 additions and 38 deletions

View File

@ -31,8 +31,8 @@ log = logging.getLogger(__name__)
class TupleVariation(object):
def __init__(self, axes, coordinates):
self.axes = axes
self.coordinates = coordinates
self.axes = axes.copy()
self.coordinates = coordinates[:]
def __repr__(self):
axes = ",".join(sorted(["%s=%s" % (name, value) for (name, value) in self.axes.items()]))

View File

@ -1355,22 +1355,6 @@ class GlyphCoordinates(object):
for i in range(len(a)):
a[i] = -a[i]
return r
def __abs__(self):
"""
>>> g = GlyphCoordinates([(-1.5,2)])
>>> g
GlyphCoordinates([(-1.5, 2.0)])
>>> g2 = abs(g)
>>> g
GlyphCoordinates([(-1.5, 2.0)])
>>> g2
GlyphCoordinates([(1.5, 2.0)])
"""
r = self.copy()
a = r._a
for i in range(len(a)):
a[i] = abs(a[i])
return r
def __round__(self):
"""
Note: This is Python 3 only. Python 2 does not call __round__.
@ -1481,9 +1465,12 @@ class GlyphCoordinates(object):
def __bool__(self):
"""
>>> g = GlyphCoordinates([(0,0), (0.,0)])
>>> g = GlyphCoordinates([])
>>> bool(g)
False
>>> g = GlyphCoordinates([(0,0), (0.,0)])
>>> bool(g)
True
>>> g = GlyphCoordinates([(0,0), (1,0)])
>>> bool(g)
True
@ -1491,7 +1478,7 @@ class GlyphCoordinates(object):
>>> bool(g)
True
"""
return any(v for v in self._a)
return bool(self._a)
__nonzero__ = __bool__

View File

@ -25,7 +25,7 @@ from fontTools.ttLib import TTFont, newTable
from fontTools.ttLib.tables._n_a_m_e import NameRecord
from fontTools.ttLib.tables._f_v_a_r import Axis, NamedInstance
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
from fontTools.ttLib.tables._g_v_a_r import TupleVariation
from fontTools.ttLib.tables.TupleVariation import TupleVariation
from fontTools.ttLib.tables import otTables as ot
from fontTools.varLib import builder, designspace, models
from fontTools.varLib.merger import VariationMerger, _all_equal
@ -142,11 +142,11 @@ def _GetCoordinates(font, glyphName):
glyph = glyf[glyphName]
if glyph.isComposite():
coord = GlyphCoordinates([(getattr(c, 'x', 0),getattr(c, 'y', 0)) for c in glyph.components])
control = [c.glyphName for c in glyph.components]
control = (glyph.numberOfContours,[c.glyphName for c in glyph.components])
else:
allData = glyph.getCoordinates(glyf)
coord = allData[0]
control = allData[1:]
control = (glyph.numberOfContours,)+allData[1:]
# Add phantom points for (left, right, top, bottom) positions.
horizontalAdvanceWidth, leftSideBearing = font["hmtx"].metrics[glyphName]
@ -202,7 +202,36 @@ def _SetCoordinates(font, glyphName, coord):
font["hmtx"].metrics[glyphName] = horizontalAdvanceWidth, leftSideBearing
def _add_gvar(font, model, master_ttfs, tolerance=.5):
def _optimize_contour(delta, coords, tolerance=0.):
n = len(delta)
if all(abs(x) <= tolerance >= abs(y) for x,y in delta):
return [None] * n
if n == 1:
return delta
d0 = delta[0]
if all(d0 == d for d in delta):
return [d0] + [None] * (n-1)
# TODO
return delta
def _optimize_delta(delta, coords, ends, tolerance=0.):
assert sorted(ends) == ends and len(coords) == (ends[-1]+1 if ends else 0) + 4
n = len(coords)
ends = ends + [n-4, n-3, n-2, n-1]
out = []
start = 0
for end in ends:
contour = _optimize_contour(delta[start:end+1], coords[start:end+1], tolerance)
out.extend(contour)
start = end+1
return out
def _add_gvar(font, model, master_ttfs, tolerance=.5, optimize=True):
assert tolerance >= 0
log.info("Generating gvar")
assert "gvar" not in font
@ -227,12 +256,31 @@ def _add_gvar(font, model, master_ttfs, tolerance=.5):
deltas = model.getDeltas(allCoords)
supports = model.supports
assert len(deltas) == len(supports)
# Prepare for IUP optimization
origCoords = deltas[0]
endPts = control[1] if control[0] >= 1 else list(range(len(control[1])))
for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])):
if not delta:
continue
if tolerance and max(abs(delta).array) <= tolerance:
if all(abs(v) <= tolerance for v in delta.array):
continue
var = TupleVariation(support, delta)
if optimize:
delta_opt = _optimize_delta(delta, origCoords, endPts, tolerance=tolerance)
if None in delta_opt:
# Use "optimized" version only if smaller...
var_opt = TupleVariation(support, delta_opt)
axis_tags = sorted(support.keys()) # Shouldn't matter that this is different from fvar...?
tupleData, auxData = var.compile(axis_tags, [], None)
unoptimized_len = len(tupleData) + len(auxData)
tupleData, auxData = var_opt.compile(axis_tags, [], None)
optimized_len = len(tupleData) + len(auxData)
if optimized_len < unoptimized_len:
var = var_opt
gvar.variations[glyph].append(var)
def _add_HVAR(font, model, master_ttfs, axisTags):
@ -505,14 +553,14 @@ def load_designspace(designspace_filename):
for obj in masters+instances:
obj_name = obj.get('name', obj.get('stylename', ''))
loc = obj['location']
for name in loc.keys():
assert name in axes, "Location axis '%s' unknown for '%s'." % (name, obj_name)
for axis_name in loc.keys():
assert axis_name in axes, "Location axis '%s' unknown for '%s'." % (axis_name, obj_name)
for axis_name,axis in axes.items():
if axis_name not in loc:
loc[axis_name] = axis.default
else:
v = axis.map_backward(loc[axis_name])
assert axis.minimum <= v <= axis.maximum, "Location for axis '%s' (mapped to %s) out of range for '%s' [%s..%s]" % (name, v, obj_name, axis.minimum, axis.maximum)
assert axis.minimum <= v <= axis.maximum, "Location for axis '%s' (mapped to %s) out of range for '%s' [%s..%s]" % (axis_name, v, obj_name, axis.minimum, axis.maximum)
# Normalize master locations

View File

@ -11,6 +11,94 @@ from fontTools.varLib import _GetCoordinates, _SetCoordinates
from fontTools.varLib.models import VariationModel, supportScalar, normalizeLocation
import os.path
def _iup_segment(coords, rc1, rd1, rc2, rd2):
# rc1 = reference coord 1
# rd1 = reference delta 1
out_arrays = [None, None]
for j in 0,1:
out_arrays[j] = out = []
x1, x2, d1, d2 = rc1[j], rc2[j], rd1[j], rd2[j]
if x1 == x2:
n = len(coords)
if d1 == d2:
out.extend([d1]*n)
else:
out.extend([0]*n)
continue
if x1 > x2:
x1, x2 = x2, x1
d1, d2 = d2, d1
# x1 < x2
scale = (d2 - d1) / (x2 - x1)
for pair in coords:
x = pair[j]
if x <= x1:
d = d1
elif x >= x2:
d = d2
else:
# Interpolate
d = d1 + (x - x1) * scale
out.append(d)
return zip(*out_arrays)
def _iup_contour(delta, coords):
assert len(delta) == len(coords)
if None not in delta:
return delta
n = len(delta)
# indices of points with explicit deltas
indices = [i for i,v in enumerate(delta) if v is not None]
if not indices:
# All deltas are None. Return 0,0 for all.
return [(0,0)]*n
out = []
it = iter(indices)
start = next(it)
if start != 0:
# Initial segment that wraps around
i1, i2, ri1, ri2 = 0, start, start, indices[-1]
out.extend(_iup_segment(coords[i1:i2], coords[ri1], delta[ri1], coords[ri2], delta[ri2]))
out.append(delta[start])
for end in it:
if end - start > 1:
i1, i2, ri1, ri2 = start+1, end, start, end
out.extend(_iup_segment(coords[i1:i2], coords[ri1], delta[ri1], coords[ri2], delta[ri2]))
out.append(delta[end])
start = end
if start != n-1:
# Final segment that wraps around
i1, i2, ri1, ri2 = start+1, n, start, indices[0]
out.extend(_iup_segment(coords[i1:i2], coords[ri1], delta[ri1], coords[ri2], delta[ri2]))
assert len(delta) == len(out), (len(delta), len(out))
return out
def _iup_delta(delta, coords, ends):
assert sorted(ends) == ends and len(coords) == (ends[-1]+1 if ends else 0) + 4
n = len(coords)
ends = ends + [n-4, n-3, n-2, n-1]
out = []
start = 0
for end in ends:
end += 1
contour = _iup_contour(delta[start:end], coords[start:end])
out.extend(contour)
start = end
return out
def main(args=None):
if args is None:
@ -51,11 +139,18 @@ def main(args=None):
for glyphname in glyphnames:
variations = gvar.variations[glyphname]
coordinates,_ = _GetCoordinates(varfont, glyphname)
origCoords, endPts = None, None
for var in variations:
scalar = supportScalar(loc, var.axes)
if not scalar: continue
# TODO Do IUP / handle None items
coordinates += GlyphCoordinates(var.coordinates) * scalar
delta = var.coordinates
if None in delta:
if origCoords is None:
origCoords,control = _GetCoordinates(varfont, glyphname)
endPts = control[1] if control[0] >= 1 else list(range(len(control[1])))
delta = _iup_delta(delta, origCoords, endPts)
# TODO Do IUP / handle None items
coordinates += GlyphCoordinates(delta) * scalar
_SetCoordinates(varfont, glyphname, coordinates)
print("Removing variable tables")

View File

@ -50,11 +50,6 @@ class GlyphCoordinatesTest(object):
g2 = -g
assert g2 == GlyphCoordinates([(-1, -2)])
def test__abs__(self):
g = GlyphCoordinates([(-1.5,2)])
g2 = abs(g)
assert g2 == GlyphCoordinates([(1.5,2)])
@pytest.mark.skipif(sys.version_info[0] < 3,
reason="__round___ requires Python 3")
def test__round__(self):
@ -137,8 +132,10 @@ class GlyphCoordinatesTest(object):
assert g == GlyphCoordinates([(1.0, 1.0)])
def test__bool__(self):
g = GlyphCoordinates([(0,0), (0.,0)])
g = GlyphCoordinates([])
assert bool(g) == False
g = GlyphCoordinates([(0,0), (0.,0)])
assert bool(g) == True
g = GlyphCoordinates([(0,0), (1,0)])
assert bool(g) == True
g = GlyphCoordinates([(0,.5), (0,0)])

View File

@ -0,0 +1,549 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.10">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="NULL"/>
<GlyphID id="2" name="nonmarkingreturn"/>
<GlyphID id="3" name="space"/>
<GlyphID id="4" name="b"/>
<GlyphID id="5" name="q"/>
<GlyphID id="6" name="a"/>
</GlyphOrder>
<head>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="1.0"/>
<checkSumAdjustment value="0xe59c28a1"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00001011"/>
<unitsPerEm value="1000"/>
<created value="Thu Apr 27 12:41:42 2017"/>
<modified value="Tue May 2 16:43:12 2017"/>
<xMin value="38"/>
<yMin value="-152"/>
<xMax value="456"/>
<yMax value="608"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="9"/>
<fontDirectionHint value="2"/>
<indexToLocFormat value="0"/>
<glyphDataFormat value="0"/>
</head>
<hhea>
<tableVersion value="0x00010000"/>
<ascent value="750"/>
<descent value="-250"/>
<lineGap value="9"/>
<advanceWidthMax value="494"/>
<minLeftSideBearing value="38"/>
<minRightSideBearing value="38"/>
<xMaxExtent value="456"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
<reserved0 value="0"/>
<reserved1 value="0"/>
<reserved2 value="0"/>
<reserved3 value="0"/>
<metricDataFormat value="0"/>
<numberOfHMetrics value="5"/>
</hhea>
<maxp>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="0x10000"/>
<numGlyphs value="7"/>
<maxPoints value="20"/>
<maxContours value="2"/>
<maxCompositePoints value="20"/>
<maxCompositeContours value="2"/>
<maxZones value="1"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
<maxFunctionDefs value="0"/>
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
<maxComponentElements value="1"/>
<maxComponentDepth value="1"/>
</maxp>
<OS_2>
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="3"/>
<xAvgCharWidth value="347"/>
<usWeightClass value="400"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000100"/>
<ySubscriptXSize value="700"/>
<ySubscriptYSize value="650"/>
<ySubscriptXOffset value="0"/>
<ySubscriptYOffset value="140"/>
<ySuperscriptXSize value="700"/>
<ySuperscriptYSize value="650"/>
<ySuperscriptXOffset value="0"/>
<ySuperscriptYOffset value="477"/>
<yStrikeoutSize value="50"/>
<yStrikeoutPosition value="250"/>
<sFamilyClass value="0"/>
<panose>
<bFamilyType value="0"/>
<bSerifStyle value="0"/>
<bWeight value="0"/>
<bProportion value="0"/>
<bContrast value="0"/>
<bStrokeVariation value="0"/>
<bArmStyle value="0"/>
<bLetterForm value="0"/>
<bMidline value="0"/>
<bXHeight value="0"/>
</panose>
<ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
<ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
<achVendID value="LuFo"/>
<fsSelection value="00000000 01000000"/>
<usFirstCharIndex value="0"/>
<usLastCharIndex value="113"/>
<sTypoAscender value="750"/>
<sTypoDescender value="-250"/>
<sTypoLineGap value="0"/>
<usWinAscent value="608"/>
<usWinDescent value="152"/>
<ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
<ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
<sxHeight value="456"/>
<sCapHeight value="608"/>
<usDefaultChar value="0"/>
<usBreakChar value="32"/>
<usMaxContext value="0"/>
</OS_2>
<hmtx>
<mtx name=".notdef" width="200" lsb="0"/>
<mtx name="NULL" width="0" lsb="0"/>
<mtx name="a" width="494" lsb="38"/>
<mtx name="b" width="494" lsb="76"/>
<mtx name="nonmarkingreturn" width="200" lsb="0"/>
<mtx name="q" width="494" lsb="38"/>
<mtx name="space" width="200" lsb="0"/>
</hmtx>
<cmap>
<tableVersion version="0"/>
<cmap_format_4 platformID="0" platEncID="3" language="0">
<map code="0x0" name="NULL"/><!-- ???? -->
<map code="0xd" name="nonmarkingreturn"/><!-- ???? -->
<map code="0x20" name="space"/><!-- SPACE -->
<map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
<map code="0x62" name="b"/><!-- LATIN SMALL LETTER B -->
<map code="0x71" name="q"/><!-- LATIN SMALL LETTER Q -->
</cmap_format_4>
<cmap_format_6 platformID="1" platEncID="0" language="0">
<map code="0x0" name="NULL"/>
<map code="0xd" name="nonmarkingreturn"/>
<map code="0x20" name="space"/>
<map code="0x61" name="a"/>
<map code="0x62" name="b"/>
<map code="0x71" name="q"/>
</cmap_format_6>
<cmap_format_4 platformID="3" platEncID="1" language="0">
<map code="0x0" name="NULL"/><!-- ???? -->
<map code="0xd" name="nonmarkingreturn"/><!-- ???? -->
<map code="0x20" name="space"/><!-- SPACE -->
<map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
<map code="0x62" name="b"/><!-- LATIN SMALL LETTER B -->
<map code="0x71" name="q"/><!-- LATIN SMALL LETTER Q -->
</cmap_format_4>
</cmap>
<loca>
<!-- The 'loca' table will be calculated by the compiler -->
</loca>
<glyf>
<!-- The xMin, yMin, xMax and yMax values
will be recalculated by the compiler. -->
<TTGlyph name=".notdef"/><!-- contains no outline data -->
<TTGlyph name="NULL"/><!-- contains no outline data -->
<TTGlyph name="a" xMin="38" yMin="-12" xMax="418" yMax="468">
<contour>
<pt x="342" y="0" on="1"/>
<pt x="342" y="64" on="1"/>
<pt x="264" y="-12" on="1"/>
<pt x="190" y="-12" on="1"/>
<pt x="38" y="140" on="1"/>
<pt x="38" y="316" on="1"/>
<pt x="190" y="468" on="1"/>
<pt x="266" y="468" on="1"/>
<pt x="342" y="392" on="1"/>
<pt x="342" y="456" on="1"/>
<pt x="418" y="456" on="1"/>
<pt x="418" y="0" on="1"/>
</contour>
<contour>
<pt x="266" y="64" on="1"/>
<pt x="342" y="140" on="1"/>
<pt x="342" y="316" on="1"/>
<pt x="266" y="392" on="1"/>
<pt x="190" y="392" on="1"/>
<pt x="114" y="316" on="1"/>
<pt x="114" y="140" on="1"/>
<pt x="190" y="64" on="1"/>
</contour>
<instructions/>
</TTGlyph>
<TTGlyph name="b" xMin="76" yMin="-12" xMax="456" yMax="608">
<contour>
<pt x="228" y="468" on="1"/>
<pt x="304" y="468" on="1"/>
<pt x="456" y="316" on="1"/>
<pt x="456" y="140" on="1"/>
<pt x="304" y="-12" on="1"/>
<pt x="230" y="-12" on="1"/>
<pt x="152" y="64" on="1"/>
<pt x="152" y="0" on="1"/>
<pt x="76" y="0" on="1"/>
<pt x="76" y="608" on="1"/>
<pt x="152" y="608" on="1"/>
<pt x="152" y="392" on="1"/>
</contour>
<contour>
<pt x="152" y="316" on="1"/>
<pt x="152" y="140" on="1"/>
<pt x="228" y="64" on="1"/>
<pt x="304" y="64" on="1"/>
<pt x="380" y="140" on="1"/>
<pt x="380" y="316" on="1"/>
<pt x="304" y="392" on="1"/>
<pt x="228" y="392" on="1"/>
</contour>
<instructions/>
</TTGlyph>
<TTGlyph name="nonmarkingreturn"/><!-- contains no outline data -->
<TTGlyph name="q" xMin="38" yMin="-152" xMax="418" yMax="468">
<component glyphName="b" x="494" y="456" scale="-0.99994" flags="0x4"/>
</TTGlyph>
<TTGlyph name="space"/><!-- contains no outline data -->
</glyf>
<name>
<namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
VarFont
</namerecord>
<namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
Regular
</namerecord>
<namerecord nameID="3" platformID="1" platEncID="0" langID="0x0" unicode="True">
VarFont Regular: 2017
</namerecord>
<namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
VarFont Regular
</namerecord>
<namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
VarFont-Regular
</namerecord>
<namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
Width
</namerecord>
<namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
Ascender
</namerecord>
<namerecord nameID="258" platformID="1" platEncID="0" langID="0x0" unicode="True">
Regular
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
VarFont
</namerecord>
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
Regular
</namerecord>
<namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
VarFont Regular: 2017
</namerecord>
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
VarFont Regular
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
VarFont-Regular
</namerecord>
<namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
Width
</namerecord>
<namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
Ascender
</namerecord>
<namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
Regular
</namerecord>
</name>
<post>
<formatType value="2.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="-75"/>
<underlineThickness value="50"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
<psNames>
<!-- This file uses unique glyph names based on the information
found in the 'post' table. Since these names might not be unique,
we have to invent artificial names in case of clashes. In order to
be able to retain the original information, we need a name to
ps name mapping for those cases where they differ. That's what
you see below.
-->
</psNames>
<extraNames>
<!-- following are the name that are not taken from the standard Mac glyph order -->
<psName name="NULL"/>
</extraNames>
</post>
<GDEF>
<Version value="0x00010003"/>
<VarStore Format="1">
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=2 -->
<!-- RegionCount=2 -->
<Region index="0">
<VarRegionAxis index="0">
<StartCoord value="0.0"/>
<PeakCoord value="0.0"/>
<EndCoord value="0.0"/>
</VarRegionAxis>
<VarRegionAxis index="1">
<StartCoord value="0.0"/>
<PeakCoord value="1.0"/>
<EndCoord value="1.0"/>
</VarRegionAxis>
</Region>
<Region index="1">
<VarRegionAxis index="0">
<StartCoord value="-1.0"/>
<PeakCoord value="-1.0"/>
<EndCoord value="0.0"/>
</VarRegionAxis>
<VarRegionAxis index="1">
<StartCoord value="0.0"/>
<PeakCoord value="0.0"/>
<EndCoord value="0.0"/>
</VarRegionAxis>
</Region>
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
<!-- ItemCount=0 -->
<NumShorts value="0"/>
<!-- VarRegionCount=2 -->
<VarRegionIndex index="0" value="0"/>
<VarRegionIndex index="1" value="1"/>
</VarData>
</VarStore>
</GDEF>
<HVAR>
<Version value="0x00010000"/>
<VarStore Format="1">
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=2 -->
<!-- RegionCount=2 -->
<Region index="0">
<VarRegionAxis index="0">
<StartCoord value="0.0"/>
<PeakCoord value="0.0"/>
<EndCoord value="0.0"/>
</VarRegionAxis>
<VarRegionAxis index="1">
<StartCoord value="0.0"/>
<PeakCoord value="1.0"/>
<EndCoord value="1.0"/>
</VarRegionAxis>
</Region>
<Region index="1">
<VarRegionAxis index="0">
<StartCoord value="-1.0"/>
<PeakCoord value="-1.0"/>
<EndCoord value="0.0"/>
</VarRegionAxis>
<VarRegionAxis index="1">
<StartCoord value="0.0"/>
<PeakCoord value="0.0"/>
<EndCoord value="0.0"/>
</VarRegionAxis>
</Region>
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
<!-- ItemCount=2 -->
<NumShorts value="0"/>
<!-- VarRegionCount=2 -->
<VarRegionIndex index="0" value="0"/>
<VarRegionIndex index="1" value="1"/>
<Item index="0" value="[0, -60]"/>
<Item index="1" value="[0, 0]"/>
</VarData>
</VarStore>
<AdvWidthMap>
<Map index="0" outer="0" inner="1"/>
<Map index="1" outer="0" inner="1"/>
<Map index="2" outer="0" inner="1"/>
<Map index="3" outer="0" inner="1"/>
<Map index="4" outer="0" inner="0"/>
</AdvWidthMap>
</HVAR>
<MVAR>
<Version value="0x00010000"/>
<Reserved value="0"/>
<ValueRecordSize value="0"/>
<!-- ValueRecordCount=0 -->
<VarStore Format="1">
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=2 -->
<!-- RegionCount=2 -->
<Region index="0">
<VarRegionAxis index="0">
<StartCoord value="0.0"/>
<PeakCoord value="0.0"/>
<EndCoord value="0.0"/>
</VarRegionAxis>
<VarRegionAxis index="1">
<StartCoord value="0.0"/>
<PeakCoord value="1.0"/>
<EndCoord value="1.0"/>
</VarRegionAxis>
</Region>
<Region index="1">
<VarRegionAxis index="0">
<StartCoord value="-1.0"/>
<PeakCoord value="-1.0"/>
<EndCoord value="0.0"/>
</VarRegionAxis>
<VarRegionAxis index="1">
<StartCoord value="0.0"/>
<PeakCoord value="0.0"/>
<EndCoord value="0.0"/>
</VarRegionAxis>
</Region>
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
<!-- ItemCount=0 -->
<NumShorts value="0"/>
<!-- VarRegionCount=2 -->
<VarRegionIndex index="0" value="0"/>
<VarRegionIndex index="1" value="1"/>
</VarData>
</VarStore>
</MVAR>
<fvar>
<!-- Width -->
<Axis>
<AxisTag>wdth</AxisTag>
<MinValue>60.0</MinValue>
<DefaultValue>100.0</DefaultValue>
<MaxValue>100.0</MaxValue>
<AxisNameID>256</AxisNameID>
</Axis>
<!-- Ascender -->
<Axis>
<AxisTag>ASCN</AxisTag>
<MinValue>608.0</MinValue>
<DefaultValue>608.0</DefaultValue>
<MaxValue>648.0</MaxValue>
<AxisNameID>257</AxisNameID>
</Axis>
<!-- Regular -->
<NamedInstance subfamilyNameID="258">
<coord axis="wdth" value="100.0"/>
<coord axis="ASCN" value="608.0"/>
</NamedInstance>
</fvar>
<gvar>
<version value="1"/>
<reserved value="0"/>
<glyphVariations glyph="b">
<tuple>
<coord axis="ASCN" value="1.0"/>
<delta pt="8" x="0" y="0"/>
<delta pt="9" x="0" y="40"/>
<delta pt="10" x="0" y="40"/>
<delta pt="11" x="0" y="0"/>
<delta pt="12" x="0" y="0"/>
<delta pt="22" x="0" y="40"/>
</tuple>
<tuple>
<coord axis="wdth" value="-1.0"/>
<delta pt="0" x="-20" y="0"/>
<delta pt="1" x="-40" y="0"/>
<delta pt="2" x="-60" y="0"/>
<delta pt="3" x="-60" y="0"/>
<delta pt="4" x="-40" y="0"/>
<delta pt="5" x="-20" y="0"/>
<delta pt="6" x="0" y="0"/>
<delta pt="11" x="0" y="0"/>
<delta pt="12" x="0" y="0"/>
<delta pt="17" x="-60" y="0"/>
<delta pt="21" x="-60" y="0"/>
</tuple>
</glyphVariations>
<glyphVariations glyph="q">
<tuple>
<coord axis="ASCN" value="1.0"/>
<delta pt="4" x="0" y="40"/>
</tuple>
<tuple>
<coord axis="wdth" value="-1.0"/>
<delta pt="0" x="-60" y="0"/>
<delta pt="2" x="-60" y="0"/>
</tuple>
</glyphVariations>
<glyphVariations glyph="a">
<tuple>
<coord axis="wdth" value="-1.0"/>
<delta pt="0" x="-60" y="0"/>
<delta pt="1" x="-60" y="0"/>
<delta pt="2" x="-40" y="0"/>
<delta pt="3" x="-20" y="0"/>
<delta pt="4" x="0" y="0"/>
<delta pt="5" x="0" y="0"/>
<delta pt="6" x="-20" y="0"/>
<delta pt="7" x="-40" y="0"/>
<delta pt="8" x="-60" y="0"/>
<delta pt="14" x="-60" y="0"/>
<delta pt="20" x="0" y="0"/>
<delta pt="21" x="-60" y="0"/>
<delta pt="22" x="0" y="0"/>
<delta pt="23" x="0" y="0"/>
</tuple>
</glyphVariations>
</gvar>
</ttFont>

View File

@ -0,0 +1,343 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.10">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="NULL"/>
<GlyphID id="2" name="nonmarkingreturn"/>
<GlyphID id="3" name="space"/>
<GlyphID id="4" name="b"/>
<GlyphID id="5" name="q"/>
<GlyphID id="6" name="a"/>
</GlyphOrder>
<hhea>
<tableVersion value="0x00010000"/>
<ascent value="750"/>
<descent value="-250"/>
<lineGap value="9"/>
<advanceWidthMax value="464"/>
<minLeftSideBearing value="38"/>
<minRightSideBearing value="38"/>
<xMaxExtent value="426"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
<reserved0 value="0"/>
<reserved1 value="0"/>
<reserved2 value="0"/>
<reserved3 value="0"/>
<metricDataFormat value="0"/>
<numberOfHMetrics value="5"/>
</hhea>
<maxp>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="0x10000"/>
<numGlyphs value="7"/>
<maxPoints value="20"/>
<maxContours value="2"/>
<maxCompositePoints value="20"/>
<maxCompositeContours value="2"/>
<maxZones value="1"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
<maxFunctionDefs value="0"/>
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
<maxComponentElements value="1"/>
<maxComponentDepth value="1"/>
</maxp>
<OS_2>
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="3"/>
<xAvgCharWidth value="347"/>
<usWeightClass value="400"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000100"/>
<ySubscriptXSize value="700"/>
<ySubscriptYSize value="650"/>
<ySubscriptXOffset value="0"/>
<ySubscriptYOffset value="140"/>
<ySuperscriptXSize value="700"/>
<ySuperscriptYSize value="650"/>
<ySuperscriptXOffset value="0"/>
<ySuperscriptYOffset value="477"/>
<yStrikeoutSize value="50"/>
<yStrikeoutPosition value="250"/>
<sFamilyClass value="0"/>
<panose>
<bFamilyType value="0"/>
<bSerifStyle value="0"/>
<bWeight value="0"/>
<bProportion value="0"/>
<bContrast value="0"/>
<bStrokeVariation value="0"/>
<bArmStyle value="0"/>
<bLetterForm value="0"/>
<bMidline value="0"/>
<bXHeight value="0"/>
</panose>
<ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
<ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
<achVendID value="LuFo"/>
<fsSelection value="00000000 01000000"/>
<usFirstCharIndex value="0"/>
<usLastCharIndex value="113"/>
<sTypoAscender value="750"/>
<sTypoDescender value="-250"/>
<sTypoLineGap value="0"/>
<usWinAscent value="608"/>
<usWinDescent value="152"/>
<ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
<ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
<sxHeight value="456"/>
<sCapHeight value="608"/>
<usDefaultChar value="0"/>
<usBreakChar value="32"/>
<usMaxContext value="0"/>
</OS_2>
<hmtx>
<mtx name=".notdef" width="200" lsb="0"/>
<mtx name="NULL" width="0" lsb="0"/>
<mtx name="a" width="464" lsb="38"/>
<mtx name="b" width="464" lsb="76"/>
<mtx name="nonmarkingreturn" width="200" lsb="0"/>
<mtx name="q" width="464" lsb="38"/>
<mtx name="space" width="200" lsb="0"/>
</hmtx>
<cmap>
<tableVersion version="0"/>
<cmap_format_4 platformID="0" platEncID="3" language="0">
<map code="0x0" name="NULL"/><!-- ???? -->
<map code="0xd" name="nonmarkingreturn"/><!-- ???? -->
<map code="0x20" name="space"/><!-- SPACE -->
<map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
<map code="0x62" name="b"/><!-- LATIN SMALL LETTER B -->
<map code="0x71" name="q"/><!-- LATIN SMALL LETTER Q -->
</cmap_format_4>
<cmap_format_6 platformID="1" platEncID="0" language="0">
<map code="0x0" name="NULL"/>
<map code="0xd" name="nonmarkingreturn"/>
<map code="0x20" name="space"/>
<map code="0x61" name="a"/>
<map code="0x62" name="b"/>
<map code="0x71" name="q"/>
</cmap_format_6>
<cmap_format_4 platformID="3" platEncID="1" language="0">
<map code="0x0" name="NULL"/><!-- ???? -->
<map code="0xd" name="nonmarkingreturn"/><!-- ???? -->
<map code="0x20" name="space"/><!-- SPACE -->
<map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
<map code="0x62" name="b"/><!-- LATIN SMALL LETTER B -->
<map code="0x71" name="q"/><!-- LATIN SMALL LETTER Q -->
</cmap_format_4>
</cmap>
<loca>
<!-- The 'loca' table will be calculated by the compiler -->
</loca>
<glyf>
<!-- The xMin, yMin, xMax and yMax values
will be recalculated by the compiler. -->
<TTGlyph name=".notdef"/><!-- contains no outline data -->
<TTGlyph name="NULL"/><!-- contains no outline data -->
<TTGlyph name="a" xMin="38" yMin="-12" xMax="388" yMax="468">
<contour>
<pt x="312" y="0" on="1"/>
<pt x="312" y="64" on="1"/>
<pt x="244" y="-12" on="1"/>
<pt x="180" y="-12" on="1"/>
<pt x="38" y="140" on="1"/>
<pt x="38" y="316" on="1"/>
<pt x="180" y="468" on="1"/>
<pt x="246" y="468" on="1"/>
<pt x="312" y="392" on="1"/>
<pt x="312" y="456" on="1"/>
<pt x="388" y="456" on="1"/>
<pt x="388" y="0" on="1"/>
</contour>
<contour>
<pt x="236" y="64" on="1"/>
<pt x="312" y="140" on="1"/>
<pt x="312" y="316" on="1"/>
<pt x="236" y="392" on="1"/>
<pt x="160" y="392" on="1"/>
<pt x="84" y="316" on="1"/>
<pt x="84" y="140" on="1"/>
<pt x="160" y="64" on="1"/>
</contour>
<instructions/>
</TTGlyph>
<TTGlyph name="b" xMin="76" yMin="-12" xMax="426" yMax="628">
<contour>
<pt x="218" y="468" on="1"/>
<pt x="284" y="468" on="1"/>
<pt x="426" y="316" on="1"/>
<pt x="426" y="140" on="1"/>
<pt x="284" y="-12" on="1"/>
<pt x="220" y="-12" on="1"/>
<pt x="152" y="64" on="1"/>
<pt x="152" y="0" on="1"/>
<pt x="76" y="0" on="1"/>
<pt x="76" y="628" on="1"/>
<pt x="152" y="628" on="1"/>
<pt x="152" y="392" on="1"/>
</contour>
<contour>
<pt x="152" y="316" on="1"/>
<pt x="152" y="140" on="1"/>
<pt x="218" y="64" on="1"/>
<pt x="284" y="64" on="1"/>
<pt x="350" y="140" on="1"/>
<pt x="350" y="316" on="1"/>
<pt x="284" y="392" on="1"/>
<pt x="218" y="392" on="1"/>
</contour>
<instructions/>
</TTGlyph>
<TTGlyph name="nonmarkingreturn"/><!-- contains no outline data -->
<TTGlyph name="q" xMin="38" yMin="-172" xMax="388" yMax="468">
<component glyphName="b" x="464" y="456" scale="-0.99994" flags="0x4"/>
</TTGlyph>
<TTGlyph name="space"/><!-- contains no outline data -->
</glyf>
<name>
<namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
VarFont
</namerecord>
<namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
Regular
</namerecord>
<namerecord nameID="3" platformID="1" platEncID="0" langID="0x0" unicode="True">
VarFont Regular: 2017
</namerecord>
<namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
VarFont Regular
</namerecord>
<namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
VarFont-Regular
</namerecord>
<namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
Width
</namerecord>
<namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
Ascender
</namerecord>
<namerecord nameID="258" platformID="1" platEncID="0" langID="0x0" unicode="True">
Regular
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
VarFont
</namerecord>
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
Regular
</namerecord>
<namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
VarFont Regular: 2017
</namerecord>
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
VarFont Regular
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
VarFont-Regular
</namerecord>
<namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
Width
</namerecord>
<namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
Ascender
</namerecord>
<namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
Regular
</namerecord>
</name>
<post>
<formatType value="2.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="-75"/>
<underlineThickness value="50"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
<psNames>
<!-- This file uses unique glyph names based on the information
found in the 'post' table. Since these names might not be unique,
we have to invent artificial names in case of clashes. In order to
be able to retain the original information, we need a name to
ps name mapping for those cases where they differ. That's what
you see below.
-->
</psNames>
<extraNames>
<!-- following are the name that are not taken from the standard Mac glyph order -->
<psName name="NULL"/>
</extraNames>
</post>
<GDEF>
<Version value="0x00010003"/>
<VarStore Format="1">
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=2 -->
<!-- RegionCount=2 -->
<Region index="0">
<VarRegionAxis index="0">
<StartCoord value="0.0"/>
<PeakCoord value="0.0"/>
<EndCoord value="0.0"/>
</VarRegionAxis>
<VarRegionAxis index="1">
<StartCoord value="0.0"/>
<PeakCoord value="1.0"/>
<EndCoord value="1.0"/>
</VarRegionAxis>
</Region>
<Region index="1">
<VarRegionAxis index="0">
<StartCoord value="-1.0"/>
<PeakCoord value="-1.0"/>
<EndCoord value="0.0"/>
</VarRegionAxis>
<VarRegionAxis index="1">
<StartCoord value="0.0"/>
<PeakCoord value="0.0"/>
<EndCoord value="0.0"/>
</VarRegionAxis>
</Region>
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
<!-- ItemCount=0 -->
<NumShorts value="0"/>
<!-- VarRegionCount=2 -->
<VarRegionIndex index="0" value="0"/>
<VarRegionIndex index="1" value="1"/>
</VarData>
</VarStore>
</GDEF>
</ttFont>

View File

@ -117,6 +117,28 @@ class MutatorTest(unittest.TestCase):
expected_ttx_path = self.get_test_output(varfont_name + '.ttx')
self.expect_ttx(instfont, expected_ttx_path, tables)
def test_varlib_mutator_iup_ttf(self):
suffix = '.ttf'
ufo_dir = self.get_test_input('master_ufo')
ttx_dir = self.get_test_input('master_ttx_varfont_ttf')
self.temp_dir()
ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'Mutator_IUP')
for path in ttx_paths:
self.compile_font(path, suffix, self.tempdir)
varfont_name = 'Mutator_IUP'
varfont_path = os.path.join(self.tempdir, varfont_name + suffix)
args = [varfont_path, 'wdth=80', 'ASCN=628']
mutator(args)
instfont_path = os.path.splitext(varfont_path)[0] + '-instance' + suffix
instfont = TTFont(instfont_path)
tables = [table_tag for table_tag in instfont.keys() if table_tag != 'head']
expected_ttx_path = self.get_test_output(varfont_name + '-instance.ttx')
self.expect_ttx(instfont, expected_ttx_path, tables)
if __name__ == "__main__":
sys.exit(unittest.main())