aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHaibo Huang <hhb@google.com>2019-02-20 04:33:58 -0800
committerandroid-build-merger <android-build-merger@google.com>2019-02-20 04:33:58 -0800
commit0147a0b89cc37a4507421a938c8a6f5dfa05d04c (patch)
tree1633f5f8518178a8c7226fbf418ca0f55da0d791
parent31d7f6986521a5a7013de2e4edf18ffeb5358220 (diff)
parent448bb82634612ced2a2abf672c5b999a5dbbcc34 (diff)
downloadfonttools-0147a0b89cc37a4507421a938c8a6f5dfa05d04c.tar.gz
Upgrade fonttools to 3.38.0 am: 1a848cf109
am: 448bb82634 Change-Id: I35a4216283ad74b7c8d052e5c61370b75c505773
-rw-r--r--.travis.yml3
-rw-r--r--Lib/fontTools/__init__.py2
-rw-r--r--Lib/fontTools/cffLib/__init__.py17
-rw-r--r--Lib/fontTools/feaLib/__main__.py7
-rw-r--r--Lib/fontTools/feaLib/ast.py11
-rw-r--r--Lib/fontTools/feaLib/lexer.py4
-rw-r--r--Lib/fontTools/feaLib/parser.py4
-rw-r--r--Lib/fontTools/subset/cff.py66
-rw-r--r--Lib/fontTools/svgLib/path/__init__.py10
-rw-r--r--Lib/fontTools/svgLib/path/shapes.py125
-rw-r--r--Lib/fontTools/ttLib/tables/C_P_A_L_.py26
-rw-r--r--Lib/fontTools/ttLib/ttFont.py4
-rwxr-xr-xLib/fontTools/ufoLib/glifLib.py2
-rw-r--r--Lib/fontTools/varLib/__init__.py14
-rw-r--r--Lib/fontTools/voltLib/ast.py9
-rw-r--r--Lib/fontTools/voltLib/parser.py9
-rw-r--r--Lib/fonttools.egg-info/PKG-INFO54
-rw-r--r--Lib/fonttools.egg-info/SOURCES.txt5
-rw-r--r--METADATA6
-rw-r--r--NEWS.rst40
-rw-r--r--PKG-INFO54
-rw-r--r--Tests/cffLib/cffLib_test.py17
-rw-r--r--Tests/feaLib/ast_test.py17
-rw-r--r--Tests/feaLib/lexer_test.py1
-rw-r--r--Tests/feaLib/parser_test.py20
-rw-r--r--Tests/subset/data/expect_no_hinting_desubroutinize_CFF.ttx217
-rw-r--r--Tests/subset/data/test_hinted_subrs_CFF.desub.ttx113
-rw-r--r--Tests/subset/data/test_hinted_subrs_CFF.ttx351
-rw-r--r--Tests/subset/subset_test.py12
-rw-r--r--Tests/svgLib/path/shapes_test.py68
-rw-r--r--Tests/varLib/interpolate_layout_test.py2
-rw-r--r--Tests/varLib/varLib_test.py83
-rw-r--r--Tests/voltLib/parser_test.py216
-rw-r--r--requirements.txt4
-rw-r--r--setup.cfg2
-rwxr-xr-xsetup.py2
36 files changed, 1226 insertions, 371 deletions
diff --git a/.travis.yml b/.travis.yml
index 0951eaf2..66f80fc0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -33,9 +33,10 @@ matrix:
# required to run python3.7 on Travis CI
# https://github.com/travis-ci/travis-ci/issues/9815
dist: xenial
- - python: pypy2.7-5.8.0
+ - python: pypy2.7-6.0
# disable coverage.py on pypy because of performance problems
env: TOXENV=pypy
+ dist: xenial
- language: generic
os: osx
env: TOXENV=py27-cov
diff --git a/Lib/fontTools/__init__.py b/Lib/fontTools/__init__.py
index f3759f15..99dc4e19 100644
--- a/Lib/fontTools/__init__.py
+++ b/Lib/fontTools/__init__.py
@@ -5,6 +5,6 @@ from fontTools.misc.loggingTools import configLogger
log = logging.getLogger(__name__)
-version = __version__ = "3.37.0"
+version = __version__ = "3.38.0"
__all__ = ["version", "log", "configLogger"]
diff --git a/Lib/fontTools/cffLib/__init__.py b/Lib/fontTools/cffLib/__init__.py
index 4ad0e442..c1750479 100644
--- a/Lib/fontTools/cffLib/__init__.py
+++ b/Lib/fontTools/cffLib/__init__.py
@@ -607,6 +607,9 @@ class Index(object):
def getCompiler(self, strings, parent, isCFF2=None):
return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
+ def clear(self):
+ del self.items[:]
+
class GlobalSubrsIndex(Index):
@@ -2242,6 +2245,12 @@ class BaseDict(object):
return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
def __getattr__(self, name):
+ if name[:2] == name[-2:] == "__":
+ # to make deepcopy() and pickle.load() work, we need to signal with
+ # AttributeError that dunder methods like '__deepcopy__' or '__getstate__'
+ # aren't implemented. For more details, see:
+ # https://github.com/fonttools/fonttools/pull/1488
+ raise AttributeError(name)
value = self.rawDict.get(name, None)
if value is None:
value = self.defaults.get(name)
@@ -2426,11 +2435,9 @@ class PrivateDict(BaseDict):
self.defaults = buildDefaults(privateDictOperators)
self.order = buildOrder(privateDictOperators)
- def __getattr__(self, name):
- if name == "in_cff2":
- return self._isCFF2
- value = BaseDict.__getattr__(self, name)
- return value
+ @property
+ def in_cff2(self):
+ return self._isCFF2
def getNumRegions(self, vi=None): # called from misc/psCharStrings.py
# if getNumRegions is being called, we can assume that VarStore exists.
diff --git a/Lib/fontTools/feaLib/__main__.py b/Lib/fontTools/feaLib/__main__.py
index e446db62..da4eff6c 100644
--- a/Lib/fontTools/feaLib/__main__.py
+++ b/Lib/fontTools/feaLib/__main__.py
@@ -1,7 +1,7 @@
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
-from fontTools.feaLib.builder import addOpenTypeFeatures
+from fontTools.feaLib.builder import addOpenTypeFeatures, Builder
from fontTools import configLogger
from fontTools.misc.cliTools import makeOutputFileName
import sys
@@ -23,6 +23,9 @@ def main(args=None):
"-o", "--output", dest="output_font", metavar="OUTPUT_FONT",
help="Path to the output font.")
parser.add_argument(
+ "-t", "--tables", metavar="TABLE_TAG", choices=Builder.supportedTables,
+ nargs='+', help="Specify the table(s) to be built.")
+ parser.add_argument(
"-v", "--verbose", help="increase the logger verbosity. Multiple -v "
"options are allowed.", action="count", default=0)
options = parser.parse_args(args)
@@ -34,7 +37,7 @@ def main(args=None):
log.info("Compiling features to '%s'" % (output_font))
font = TTFont(options.input_font)
- addOpenTypeFeatures(font, options.input_fea)
+ addOpenTypeFeatures(font, options.input_fea, tables=options.tables)
font.save(output_font)
diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py
index b7d20664..7bf64981 100644
--- a/Lib/fontTools/feaLib/ast.py
+++ b/Lib/fontTools/feaLib/ast.py
@@ -102,7 +102,8 @@ fea_keywords = set([
"required", "righttoleft", "reversesub", "rsub",
"script", "sub", "substitute", "subtable",
"table",
- "usemarkfilteringset", "useextension", "valuerecorddef"]
+ "usemarkfilteringset", "useextension", "valuerecorddef",
+ "base", "gdef", "head", "hhea", "name", "vhea", "vmtx"]
)
@@ -159,7 +160,7 @@ class GlyphName(Expression):
return (self.glyph,)
def asFea(self, indent=""):
- return str(self.glyph)
+ return asFea(self.glyph)
class GlyphClass(Expression):
@@ -425,7 +426,7 @@ class MarkClass(object):
return tuple(self.glyphs.keys())
def asFea(self, indent=""):
- res = "\n".join(d.asFea(indent=indent) for d in self.definitions)
+ res = "\n".join(d.asFea() for d in self.definitions)
return res
@@ -440,8 +441,8 @@ class MarkClassDefinition(Statement):
return self.glyphs.glyphSet()
def asFea(self, indent=""):
- return "{}markClass {} {} @{};".format(
- indent, self.glyphs.asFea(), self.anchor.asFea(),
+ return "markClass {} {} @{};".format(
+ self.glyphs.asFea(), self.anchor.asFea(),
self.markClass.name)
diff --git a/Lib/fontTools/feaLib/lexer.py b/Lib/fontTools/feaLib/lexer.py
index 18849ef0..095cb668 100644
--- a/Lib/fontTools/feaLib/lexer.py
+++ b/Lib/fontTools/feaLib/lexer.py
@@ -28,7 +28,7 @@ class Lexer(object):
CHAR_NAME_START_ = CHAR_LETTER_ + "_+*:.^~!\\"
CHAR_NAME_CONTINUATION_ = CHAR_LETTER_ + CHAR_DIGIT_ + "_.+*:^~!/-"
- RE_GLYPHCLASS = re.compile(r"^[A-Za-z_0-9.]+$")
+ RE_GLYPHCLASS = re.compile(r"^[A-Za-z_0-9.\-]+$")
MODE_NORMAL_ = "NORMAL"
MODE_FILENAME_ = "FILENAME"
@@ -113,7 +113,7 @@ class Lexer(object):
if not Lexer.RE_GLYPHCLASS.match(glyphclass):
raise FeatureLibError(
"Glyph class names must consist of letters, digits, "
- "underscore, or period", location)
+ "underscore, period or hyphen", location)
return (Lexer.GLYPHCLASS, glyphclass, location)
if cur_char in Lexer.CHAR_NAME_START_:
self.pos_ += 1
diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py
index 61d37111..e4971719 100644
--- a/Lib/fontTools/feaLib/parser.py
+++ b/Lib/fontTools/feaLib/parser.py
@@ -404,7 +404,9 @@ class Parser(object):
return ([], prefix, [None] * len(prefix), values, [], hasMarks)
else:
assert not any(values[:len(prefix)]), values
- values = values[len(prefix):][:len(glyphs)]
+ format1 = values[len(prefix):][:len(glyphs)]
+ format2 = values[(len(prefix) + len(glyphs)):][:len(suffix)]
+ values = format2 if format2 and isinstance(format2[0], self.ast.ValueRecord) else format1
return (prefix, glyphs, lookups, values, suffix, hasMarks)
def parse_chain_context_(self):
diff --git a/Lib/fontTools/subset/cff.py b/Lib/fontTools/subset/cff.py
index 96dc3210..f01dfe67 100644
--- a/Lib/fontTools/subset/cff.py
+++ b/Lib/fontTools/subset/cff.py
@@ -352,28 +352,44 @@ class _DehintingT2Decompiler(psCharStrings.T2WidthExtractor):
if subr_hints.status == 0:
hints.last_hint = index
else:
- hints.last_hint = index - 2 # Leave the subr call in
+ hints.last_hint = index - 2 # Leave the subr call in
elif subr_hints.status == 0:
hints.deletions.append(index)
hints.status = max(hints.status, subr_hints.status)
+class StopHintCountEvent(Exception):
+ pass
+
+
+
+
class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler):
+ stop_hintcount_ops = ("op_hstem", "op_vstem", "op_rmoveto", "op_hmoveto",
+ "op_vmoveto")
def __init__(self, localSubrs, globalSubrs, private=None):
- psCharStrings.SimpleT2Decompiler.__init__(self,
- localSubrs,
- globalSubrs, private)
+ psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs,
+ private)
def execute(self, charString):
+ self.need_hintcount = True # until proven otherwise
+ for op_name in self.stop_hintcount_ops:
+ setattr(self, op_name, self.stop_hint_count)
+
if hasattr(charString, '_desubroutinized'):
+ if self.need_hintcount and self.callingStack:
+ try:
+ psCharStrings.SimpleT2Decompiler.execute(self, charString)
+ except StopHintCountEvent:
+ del self.callingStack[-1]
return
charString._patches = []
psCharStrings.SimpleT2Decompiler.execute(self, charString)
desubroutinized = charString.program[:]
- for idx,expansion in reversed (charString._patches):
+ for idx, expansion in reversed(charString._patches):
assert idx >= 2
assert desubroutinized[idx - 1] in ['callsubr', 'callgsubr'], desubroutinized[idx - 1]
assert type(desubroutinized[idx - 2]) == int
@@ -401,9 +417,23 @@ class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler):
psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
self.processSubr(index, subr)
+ def stop_hint_count(self, *args):
+ self.need_hintcount = False
+ for op_name in self.stop_hintcount_ops:
+ setattr(self, op_name, None)
+ cs = self.callingStack[-1]
+ if hasattr(cs, '_desubroutinized'):
+ raise StopHintCountEvent()
+
+ def op_hintmask(self, index):
+ psCharStrings.SimpleT2Decompiler.op_hintmask(self, index)
+ if self.need_hintcount:
+ self.stop_hint_count()
+
def processSubr(self, index, subr):
cs = self.callingStack[-1]
- cs._patches.append((index, subr._desubroutinized))
+ if not hasattr(cs, '_desubroutinized'):
+ cs._patches.append((index, subr._desubroutinized))
@_add_method(ttLib.getTableClass('CFF '))
@@ -425,16 +455,12 @@ def prune_post_subset(self, ttfFont, options):
# Desubroutinize if asked for
if options.desubroutinize:
self.desubroutinize()
- else:
- for fontname in cff.keys():
- font = cff[fontname]
- self.remove_unused_subroutines()
# Drop hints if not needed
if not options.hinting:
self.remove_hints()
-
-
+ elif not options.desubroutinize:
+ self.remove_unused_subroutines()
return True
@@ -458,9 +484,7 @@ def desubroutinize(self):
decompiler.execute(c)
c.program = c._desubroutinized
del c._desubroutinized
- # Delete All the Subrs!!!
- if font.GlobalSubrs:
- del font.GlobalSubrs
+ # Delete all the local subrs
if hasattr(font, 'FDArray'):
for fd in font.FDArray:
pd = fd.Private
@@ -468,7 +492,14 @@ def desubroutinize(self):
del pd.Subrs
if 'Subrs' in pd.rawDict:
del pd.rawDict['Subrs']
- self.remove_unused_subroutines()
+ else:
+ pd = font.Private
+ if hasattr(pd, 'Subrs'):
+ del pd.Subrs
+ if 'Subrs' in pd.rawDict:
+ del pd.rawDict['Subrs']
+ # as well as the global subrs
+ cff.GlobalSubrs.clear()
@_add_method(ttLib.getTableClass('CFF '))
@@ -506,7 +537,7 @@ def remove_hints(self):
for charstring in css:
charstring.drop_hints()
del css
-
+
# Drop font-wide hinting values
all_privs = []
if hasattr(font, 'FDArray'):
@@ -590,4 +621,3 @@ def remove_unused_subroutines(self):
# Cleanup
for subrs in all_subrs:
del subrs._used, subrs._old_bias, subrs._new_bias
-
diff --git a/Lib/fontTools/svgLib/path/__init__.py b/Lib/fontTools/svgLib/path/__init__.py
index 690475f2..017ff57e 100644
--- a/Lib/fontTools/svgLib/path/__init__.py
+++ b/Lib/fontTools/svgLib/path/__init__.py
@@ -5,6 +5,7 @@ from fontTools.misc.py23 import *
from fontTools.pens.transformPen import TransformPen
from fontTools.misc import etree
from .parser import parse_path
+from .shapes import PathBuilder
__all__ = [tostr(s) for s in ("SVGPath", "parse_path")]
@@ -50,5 +51,10 @@ class SVGPath(object):
def draw(self, pen):
if self.transform:
pen = TransformPen(pen, self.transform)
- for el in self.root.findall(".//{http://www.w3.org/2000/svg}path[@d]"):
- parse_path(el.get("d"), pen)
+ pb = PathBuilder()
+ # xpath | doesn't seem to reliable work so just walk it
+ for el in self.root.iter():
+ pb.add_path_from_element(el)
+ for path in pb.paths:
+ parse_path(path, pen)
+
diff --git a/Lib/fontTools/svgLib/path/shapes.py b/Lib/fontTools/svgLib/path/shapes.py
new file mode 100644
index 00000000..a83274e4
--- /dev/null
+++ b/Lib/fontTools/svgLib/path/shapes.py
@@ -0,0 +1,125 @@
+def _prefer_non_zero(*args):
+ for arg in args:
+ if arg != 0:
+ return arg
+ return 0.
+
+
+def _ntos(n):
+ # %f likes to add unnecessary 0's, %g isn't consistent about # decimals
+ return ('%.3f' % n).rstrip('0').rstrip('.')
+
+
+def _strip_xml_ns(tag):
+ # ElementTree API doesn't provide a way to ignore XML namespaces in tags
+ # so we here strip them ourselves: cf. https://bugs.python.org/issue18304
+ return tag.split('}', 1)[1] if '}' in tag else tag
+
+
+class PathBuilder(object):
+ def __init__(self):
+ self.paths = []
+
+ def _start_path(self, initial_path=''):
+ self.paths.append(initial_path)
+
+ def _end_path(self):
+ self._add('z')
+
+ def _add(self, path_snippet):
+ path = self.paths[-1]
+ if path:
+ path += ' ' + path_snippet
+ else:
+ path = path_snippet
+ self.paths[-1] = path
+
+ def _move(self, c, x, y):
+ self._add('%s%s,%s' % (c, _ntos(x), _ntos(y)))
+
+ def M(self, x, y):
+ self._move('M', x, y)
+
+ def m(self, x, y):
+ self._move('m', x, y)
+
+ def _arc(self, c, rx, ry, x, y, large_arc):
+ self._add('%s%s,%s 0 %d 1 %s,%s' % (c, _ntos(rx), _ntos(ry), large_arc,
+ _ntos(x), _ntos(y)))
+
+ def A(self, rx, ry, x, y, large_arc=0):
+ self._arc('A', rx, ry, x, y, large_arc)
+
+ def a(self, rx, ry, x, y, large_arc=0):
+ self._arc('a', rx, ry, x, y, large_arc)
+
+ def _vhline(self, c, x):
+ self._add('%s%s' % (c, _ntos(x)))
+
+ def H(self, x):
+ self._vhline('H', x)
+
+ def h(self, x):
+ self._vhline('h', x)
+
+ def V(self, y):
+ self._vhline('V', y)
+
+ def v(self, y):
+ self._vhline('v', y)
+
+ def _parse_rect(self, rect):
+ x = float(rect.attrib.get('x', 0))
+ y = float(rect.attrib.get('y', 0))
+ w = float(rect.attrib.get('width'))
+ h = float(rect.attrib.get('height'))
+ rx = float(rect.attrib.get('rx', 0))
+ ry = float(rect.attrib.get('ry', 0))
+
+ rx = _prefer_non_zero(rx, ry)
+ ry = _prefer_non_zero(ry, rx)
+ # TODO there are more rules for adjusting rx, ry
+
+ self._start_path()
+ self.M(x + rx, y)
+ self.H(x + w - rx)
+ if rx > 0:
+ self.A(rx, ry, x + w, y + ry)
+ self.V(y + h - ry)
+ if rx > 0:
+ self.A(rx, ry, x + w - rx, y + h)
+ self.H(x + rx)
+ if rx > 0:
+ self.A(rx, ry, x, y + h - ry)
+ self.V(y + ry)
+ if rx > 0:
+ self.A(rx, ry, x + rx, y)
+ self._end_path()
+
+ def _parse_path(self, path):
+ if 'd' in path.attrib:
+ self._start_path(initial_path=path.attrib['d'])
+
+ def _parse_polygon(self, poly):
+ if 'points' in poly.attrib:
+ self._start_path('M' + poly.attrib['points'])
+ self._end_path()
+
+ def _parse_circle(self, circle):
+ cx = float(circle.attrib.get('cx', 0))
+ cy = float(circle.attrib.get('cy', 0))
+ r = float(circle.attrib.get('r'))
+
+ # arc doesn't seem to like being a complete shape, draw two halves
+ self._start_path()
+ self.M(cx - r, cy)
+ self.A(r, r, cx + r, cy, large_arc=1)
+ self.A(r, r, cx - r, cy, large_arc=1)
+
+ def add_path_from_element(self, el):
+ tag = _strip_xml_ns(el.tag)
+ parse_fn = getattr(self, '_parse_%s' % tag.lower(), None)
+ if not callable(parse_fn):
+ return False
+ parse_fn(el)
+ return True
diff --git a/Lib/fontTools/ttLib/tables/C_P_A_L_.py b/Lib/fontTools/ttLib/tables/C_P_A_L_.py
index c687c7a1..f9d0c643 100644
--- a/Lib/fontTools/ttLib/tables/C_P_A_L_.py
+++ b/Lib/fontTools/ttLib/tables/C_P_A_L_.py
@@ -7,6 +7,7 @@ from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import array
+from collections import namedtuple
import struct
import sys
@@ -206,8 +207,8 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
for element in content:
if isinstance(element, basestring):
continue
- color = Color()
- color.fromXML(element[0], element[1], element[2], ttFont)
+ attrs = element[1]
+ color = Color.fromHex(attrs["value"])
palette.append(color)
self.palettes.append(palette)
elif name == "paletteEntryLabels":
@@ -230,13 +231,7 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
self.paletteEntryLabels = [0] * self.numPaletteEntries
-class Color(object):
-
- def __init__(self, blue=None, green=None, red=None, alpha=None):
- self.blue = blue
- self.green = green
- self.red = red
- self.alpha = alpha
+class Color(namedtuple("Color", "blue green red alpha")):
def hex(self):
return "#%02X%02X%02X%02X" % (self.red, self.green, self.blue, self.alpha)
@@ -248,11 +243,12 @@ class Color(object):
writer.simpletag("color", value=self.hex(), index=index)
writer.newline()
- def fromXML(self, eltname, attrs, content, ttFont):
- value = attrs["value"]
+ @classmethod
+ def fromHex(cls, value):
if value[0] == '#':
value = value[1:]
- self.red = int(value[0:2], 16)
- self.green = int(value[2:4], 16)
- self.blue = int(value[4:6], 16)
- self.alpha = int(value[6:8], 16) if len (value) >= 8 else 0xFF
+ red = int(value[0:2], 16)
+ green = int(value[2:4], 16)
+ blue = int(value[4:6], 16)
+ alpha = int(value[6:8], 16) if len (value) >= 8 else 0xFF
+ return cls(red=red, green=green, blue=blue, alpha=alpha)
diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py
index 3a89592f..dca0b515 100644
--- a/Lib/fontTools/ttLib/ttFont.py
+++ b/Lib/fontTools/ttLib/ttFont.py
@@ -774,8 +774,8 @@ class _TTGlyphGlyf(_TTGlyph):
glyph.draw(pen, glyfTable, offset)
def drawPoints(self, pen):
- """Draw the glyph onto PointPen. See ufoLib.pointPen for details
- how that works.
+ """Draw the glyph onto PointPen. See fontTools.pens.pointPen
+ for details how that works.
"""
glyfTable = self._glyphset._glyphs
glyph = self._glyph
diff --git a/Lib/fontTools/ufoLib/glifLib.py b/Lib/fontTools/ufoLib/glifLib.py
index e36c3c7f..99dd2cfa 100755
--- a/Lib/fontTools/ufoLib/glifLib.py
+++ b/Lib/fontTools/ufoLib/glifLib.py
@@ -21,8 +21,8 @@ import fs.osfs
import fs.path
from fontTools.misc.py23 import basestring, unicode, tobytes, tounicode
from fontTools.misc import plistlib
+from fontTools.pens.pointPen import AbstractPointPen, PointToSegmentPen
from fontTools.ufoLib.errors import GlifLibError
-from fontTools.ufoLib.pointPen import AbstractPointPen, PointToSegmentPen
from fontTools.ufoLib.filenames import userNameToFileName
from fontTools.ufoLib.validators import (
genericTypeValidator,
diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py
index 2dd5718e..6ec4113f 100644
--- a/Lib/fontTools/varLib/__init__.py
+++ b/Lib/fontTools/varLib/__init__.py
@@ -521,7 +521,11 @@ def _add_MVAR(font, masterModel, master_ttfs, axisTags):
# the minimum FWord (int16) value, was chosen for its unlikelyhood to appear
# in real-world underline position/thickness values.
specialTags = {"unds": -0x8000, "undo": -0x8000}
+
for tag, (tableTag, itemName) in sorted(MVAR_ENTRIES.items(), key=lambda kv: kv[1]):
+ # For each tag, fetch the associated table from all fonts (or not when we are
+ # still looking at a tag from the same tables) and set up the variation model
+ # for them.
if tableTag != lastTableTag:
tables = fontTable = None
if tableTag in font:
@@ -535,15 +539,15 @@ def _add_MVAR(font, masterModel, master_ttfs, axisTags):
tables.append(None)
else:
tables.append(master[tableTag])
+ model, tables = masterModel.getSubModel(tables)
+ store_builder.setModel(model)
lastTableTag = tableTag
- if tables is None:
+
+ if tables is None: # Tag not applicable to the master font.
continue
# TODO support gasp entries
- model, tables = masterModel.getSubModel(tables)
- store_builder.setModel(model)
-
master_values = [getattr(table, itemName) for table in tables]
if models.allEqual(master_values):
base, varIdx = master_values[0], None
@@ -886,7 +890,7 @@ def load_masters(designspace, master_finder=lambda s: s):
# 2. A SourceDescriptor's path might point an OpenType binary, a
# TTX file, or another source file (e.g. UFO), in which case we
# resolve the path using 'master_finder' function
- font = _open_font(master.path, master_finder)
+ master.font = font = _open_font(master.path, master_finder)
master_fonts.append(font)
return master_fonts
diff --git a/Lib/fontTools/voltLib/ast.py b/Lib/fontTools/voltLib/ast.py
index de626bae..1ce47819 100644
--- a/Lib/fontTools/voltLib/ast.py
+++ b/Lib/fontTools/voltLib/ast.py
@@ -96,15 +96,6 @@ class Enum(Expression):
for e in self.glyphSet():
yield e
- def __len__(self):
- return len(self.enum)
-
- def __eq__(self, other):
- return self.glyphSet() == other.glyphSet()
-
- def __hash__(self):
- return hash(self.glyphSet())
-
def glyphSet(self, groups=None):
glyphs = []
for element in self.enum:
diff --git a/Lib/fontTools/voltLib/parser.py b/Lib/fontTools/voltLib/parser.py
index a452b9a4..4fe10a0e 100644
--- a/Lib/fontTools/voltLib/parser.py
+++ b/Lib/fontTools/voltLib/parser.py
@@ -96,7 +96,6 @@ class Parser(object):
name = self.expect_string_()
enum = None
if self.next_token_ == "ENUM":
- self.expect_keyword_("ENUM")
enum = self.parse_enum_()
self.expect_keyword_("END_GROUP")
if self.groups_.resolve(name) is not None:
@@ -499,8 +498,9 @@ class Parser(object):
return unicode_values if unicode_values != [] else None
def parse_enum_(self):
- assert self.is_cur_keyword_("ENUM")
- enum = self.parse_coverage_()
+ self.expect_keyword_("ENUM")
+ location = self.cur_token_location_
+ enum = ast.Enum(self.parse_coverage_(), location=location)
self.expect_keyword_("END_ENUM")
return enum
@@ -509,7 +509,6 @@ class Parser(object):
location = self.cur_token_location_
while self.next_token_ in ("GLYPH", "GROUP", "RANGE", "ENUM"):
if self.next_token_ == "ENUM":
- self.advance_lexer_()
enum = self.parse_enum_()
coverage.append(enum)
elif self.next_token_ == "GLYPH":
@@ -526,7 +525,7 @@ class Parser(object):
self.expect_keyword_("TO")
end = self.expect_string_()
coverage.append(ast.Range(start, end, self, location=location))
- return ast.Enum(coverage, location=location)
+ return tuple(coverage)
def resolve_group(self, group_name):
return self.groups_.resolve(group_name)
diff --git a/Lib/fonttools.egg-info/PKG-INFO b/Lib/fonttools.egg-info/PKG-INFO
index 28c4c034..fc57c8dc 100644
--- a/Lib/fonttools.egg-info/PKG-INFO
+++ b/Lib/fonttools.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: fonttools
-Version: 3.37.0
+Version: 3.38.0
Summary: Tools to manipulate font files
Home-page: http://github.com/fonttools/fonttools
Author: Just van Rossum
@@ -430,6 +430,46 @@ Description: |Travis Build Status| |Appveyor Build status| |Health| |Coverage St
Changelog
~~~~~~~~~
+ 3.38.0 (released 2019-02-18)
+ ----------------------------
+
+ - [cffLib] Fixed RecursionError when unpickling or deepcopying TTFont with
+ CFF table (#1488, 649dc49).
+ - [subset] Fixed AttributeError when using --desubroutinize option (#1490).
+ Also, fixed desubroutinizing bug when subrs contain hints (#1499).
+ - [CPAL] Make Color a subclass of namedtuple (173a0f5).
+ - [feaLib] Allow hyphen in glyph class names.
+ - [feaLib] Added 'tables' option to __main__.py (#1497).
+ - [feaLib] Add support for special-case contextual positioning formatting
+ (#1501).
+ - [svgLib] Support converting SVG basic shapes (rect, circle, etc.) into
+ equivalent SVG paths (#1500, #1508).
+ - [Snippets] Added name-viewer.ipynb Jupyter notebook.
+
+
+ 3.37.3 (released 2019-02-05)
+ ----------------------------
+
+ - The previous release accidentally changed several files from Unix to DOS
+ line-endings. Fix that.
+
+ 3.37.2 (released 2019-02-05)
+ ----------------------------
+
+ - [varLib] Temporarily revert the fix to ``load_masters()``, which caused a
+ crash in ``interpolate_layout()`` when ``deepcopy``-ing OTFs.
+
+ 3.37.1 (released 2019-02-05)
+ ----------------------------
+
+ - [varLib] ``load_masters()`` now actually assigns the fonts it loads to the
+ source.font attributes.
+ - [varLib] Fixed an MVAR table generation crash when sparse masters were
+ involved.
+ - [voltLib] ``parse_coverage_()`` returns a tuple instead of an ast.Enum.
+ - [feaLib] A MarkClassDefinition inside a block is no longer doubly indented
+ compared to the rest of the block.
+
3.37.0 (released 2019-01-28)
----------------------------
@@ -1630,13 +1670,13 @@ Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Text Processing :: Fonts
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
-Provides-Extra: unicode
-Provides-Extra: interpolatable
-Provides-Extra: plot
+Provides-Extra: woff
Provides-Extra: type1
+Provides-Extra: interpolatable
Provides-Extra: symfont
-Provides-Extra: ufo
-Provides-Extra: all
Provides-Extra: graphite
+Provides-Extra: all
+Provides-Extra: ufo
+Provides-Extra: unicode
Provides-Extra: lxml
-Provides-Extra: woff
+Provides-Extra: plot
diff --git a/Lib/fonttools.egg-info/SOURCES.txt b/Lib/fonttools.egg-info/SOURCES.txt
index c71e28f7..b07edaec 100644
--- a/Lib/fonttools.egg-info/SOURCES.txt
+++ b/Lib/fonttools.egg-info/SOURCES.txt
@@ -167,6 +167,7 @@ Lib/fontTools/svgLib/__init__.py
Lib/fontTools/svgLib/path/__init__.py
Lib/fontTools/svgLib/path/arc.py
Lib/fontTools/svgLib/path/parser.py
+Lib/fontTools/svgLib/path/shapes.py
Lib/fontTools/t1Lib/__init__.py
Lib/fontTools/ttLib/__init__.py
Lib/fontTools/ttLib/macUtils.py
@@ -342,6 +343,7 @@ Tests/designspaceLib/designspace_test.py
Tests/designspaceLib/data/test.designspace
Tests/encodings/codecs_test.py
Tests/feaLib/__init__.py
+Tests/feaLib/ast_test.py
Tests/feaLib/builder_test.py
Tests/feaLib/error_test.py
Tests/feaLib/lexer_test.py
@@ -672,9 +674,12 @@ Tests/subset/data/expect_opbd_1.ttx
Tests/subset/data/expect_prop_0.ttx
Tests/subset/data/expect_prop_1.ttx
Tests/subset/data/google_color.ttx
+Tests/subset/data/test_hinted_subrs_CFF.desub.ttx
+Tests/subset/data/test_hinted_subrs_CFF.ttx
Tests/svgLib/path/__init__.py
Tests/svgLib/path/parser_test.py
Tests/svgLib/path/path_test.py
+Tests/svgLib/path/shapes_test.py
Tests/t1Lib/t1Lib_test.py
Tests/t1Lib/data/TestT1-Regular.lwfn
Tests/t1Lib/data/TestT1-Regular.pfa
diff --git a/METADATA b/METADATA
index 4da9b169..e3f3b934 100644
--- a/METADATA
+++ b/METADATA
@@ -7,12 +7,12 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://github.com/fonttools/fonttools/releases/download/3.37.0/fonttools-3.37.0.zip"
+ value: "https://github.com/fonttools/fonttools/releases/download/3.38.0/fonttools-3.38.0.zip"
}
- version: "3.37.0"
+ version: "3.38.0"
last_upgrade_date {
year: 2019
month: 2
- day: 1
+ day: 19
}
}
diff --git a/NEWS.rst b/NEWS.rst
index 8ebf4be6..b3b0adb3 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -1,3 +1,43 @@
+3.38.0 (released 2019-02-18)
+----------------------------
+
+- [cffLib] Fixed RecursionError when unpickling or deepcopying TTFont with
+ CFF table (#1488, 649dc49).
+- [subset] Fixed AttributeError when using --desubroutinize option (#1490).
+ Also, fixed desubroutinizing bug when subrs contain hints (#1499).
+- [CPAL] Make Color a subclass of namedtuple (173a0f5).
+- [feaLib] Allow hyphen in glyph class names.
+- [feaLib] Added 'tables' option to __main__.py (#1497).
+- [feaLib] Add support for special-case contextual positioning formatting
+ (#1501).
+- [svgLib] Support converting SVG basic shapes (rect, circle, etc.) into
+ equivalent SVG paths (#1500, #1508).
+- [Snippets] Added name-viewer.ipynb Jupyter notebook.
+
+
+3.37.3 (released 2019-02-05)
+----------------------------
+
+- The previous release accidentally changed several files from Unix to DOS
+ line-endings. Fix that.
+
+3.37.2 (released 2019-02-05)
+----------------------------
+
+- [varLib] Temporarily revert the fix to ``load_masters()``, which caused a
+ crash in ``interpolate_layout()`` when ``deepcopy``-ing OTFs.
+
+3.37.1 (released 2019-02-05)
+----------------------------
+
+- [varLib] ``load_masters()`` now actually assigns the fonts it loads to the
+ source.font attributes.
+- [varLib] Fixed an MVAR table generation crash when sparse masters were
+ involved.
+- [voltLib] ``parse_coverage_()`` returns a tuple instead of an ast.Enum.
+- [feaLib] A MarkClassDefinition inside a block is no longer doubly indented
+ compared to the rest of the block.
+
3.37.0 (released 2019-01-28)
----------------------------
diff --git a/PKG-INFO b/PKG-INFO
index 28c4c034..fc57c8dc 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: fonttools
-Version: 3.37.0
+Version: 3.38.0
Summary: Tools to manipulate font files
Home-page: http://github.com/fonttools/fonttools
Author: Just van Rossum
@@ -430,6 +430,46 @@ Description: |Travis Build Status| |Appveyor Build status| |Health| |Coverage St
Changelog
~~~~~~~~~
+ 3.38.0 (released 2019-02-18)
+ ----------------------------
+
+ - [cffLib] Fixed RecursionError when unpickling or deepcopying TTFont with
+ CFF table (#1488, 649dc49).
+ - [subset] Fixed AttributeError when using --desubroutinize option (#1490).
+ Also, fixed desubroutinizing bug when subrs contain hints (#1499).
+ - [CPAL] Make Color a subclass of namedtuple (173a0f5).
+ - [feaLib] Allow hyphen in glyph class names.
+ - [feaLib] Added 'tables' option to __main__.py (#1497).
+ - [feaLib] Add support for special-case contextual positioning formatting
+ (#1501).
+ - [svgLib] Support converting SVG basic shapes (rect, circle, etc.) into
+ equivalent SVG paths (#1500, #1508).
+ - [Snippets] Added name-viewer.ipynb Jupyter notebook.
+
+
+ 3.37.3 (released 2019-02-05)
+ ----------------------------
+
+ - The previous release accidentally changed several files from Unix to DOS
+ line-endings. Fix that.
+
+ 3.37.2 (released 2019-02-05)
+ ----------------------------
+
+ - [varLib] Temporarily revert the fix to ``load_masters()``, which caused a
+ crash in ``interpolate_layout()`` when ``deepcopy``-ing OTFs.
+
+ 3.37.1 (released 2019-02-05)
+ ----------------------------
+
+ - [varLib] ``load_masters()`` now actually assigns the fonts it loads to the
+ source.font attributes.
+ - [varLib] Fixed an MVAR table generation crash when sparse masters were
+ involved.
+ - [voltLib] ``parse_coverage_()`` returns a tuple instead of an ast.Enum.
+ - [feaLib] A MarkClassDefinition inside a block is no longer doubly indented
+ compared to the rest of the block.
+
3.37.0 (released 2019-01-28)
----------------------------
@@ -1630,13 +1670,13 @@ Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Text Processing :: Fonts
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
-Provides-Extra: unicode
-Provides-Extra: interpolatable
-Provides-Extra: plot
+Provides-Extra: woff
Provides-Extra: type1
+Provides-Extra: interpolatable
Provides-Extra: symfont
-Provides-Extra: ufo
-Provides-Extra: all
Provides-Extra: graphite
+Provides-Extra: all
+Provides-Extra: ufo
+Provides-Extra: unicode
Provides-Extra: lxml
-Provides-Extra: woff
+Provides-Extra: plot
diff --git a/Tests/cffLib/cffLib_test.py b/Tests/cffLib/cffLib_test.py
index 1d169c9a..ce73b344 100644
--- a/Tests/cffLib/cffLib_test.py
+++ b/Tests/cffLib/cffLib_test.py
@@ -2,6 +2,8 @@ from __future__ import print_function, division, absolute_import
from fontTools.cffLib import TopDict, PrivateDict, CharStrings
from fontTools.misc.testTools import parseXML, DataFilesHandler
from fontTools.ttLib import TTFont
+import copy
+import os
import sys
import unittest
@@ -59,6 +61,21 @@ class CffLibTest(DataFilesHandler):
topDict2 = font2["CFF "].cff.topDictIndex[0]
self.assertEqual(topDict2.Encoding[32], "space")
+ def test_CFF_deepcopy(self):
+ """Test that deepcopying a TTFont with a CFF table does not recurse
+ infinitely."""
+ ttx_path = os.path.join(
+ os.path.dirname(__file__),
+ "..",
+ "varLib",
+ "data",
+ "master_ttx_interpolatable_otf",
+ "TestFamily2-Master0.ttx",
+ )
+ font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
+ font.importXML(ttx_path)
+ copy.deepcopy(font)
+
if __name__ == "__main__":
sys.exit(unittest.main())
diff --git a/Tests/feaLib/ast_test.py b/Tests/feaLib/ast_test.py
new file mode 100644
index 00000000..7a43d7bf
--- /dev/null
+++ b/Tests/feaLib/ast_test.py
@@ -0,0 +1,17 @@
+from __future__ import print_function, division, absolute_import
+from __future__ import unicode_literals
+from fontTools.feaLib import ast
+import unittest
+
+
+class AstTest(unittest.TestCase):
+ def test_glyphname_escape(self):
+ statement = ast.GlyphClass()
+ for name in ("BASE", "NULL", "foo", "a"):
+ statement.append(ast.GlyphName(name))
+ self.assertEqual(statement.asFea(), r"[\BASE \NULL foo a]")
+
+
+if __name__ == "__main__":
+ import sys
+ sys.exit(unittest.main())
diff --git a/Tests/feaLib/lexer_test.py b/Tests/feaLib/lexer_test.py
index 27e2da68..1b28fa09 100644
--- a/Tests/feaLib/lexer_test.py
+++ b/Tests/feaLib/lexer_test.py
@@ -39,6 +39,7 @@ class LexerTest(unittest.TestCase):
def test_glyphclass(self):
self.assertEqual(lex("@Vowel.sc"), [(Lexer.GLYPHCLASS, "Vowel.sc")])
+ self.assertEqual(lex("@Vowel-sc"), [(Lexer.GLYPHCLASS, "Vowel-sc")])
self.assertRaisesRegex(FeatureLibError,
"Expected glyph class", lex, "@(a)")
self.assertRaisesRegex(FeatureLibError,
diff --git a/Tests/feaLib/parser_test.py b/Tests/feaLib/parser_test.py
index 6636b413..bf260235 100644
--- a/Tests/feaLib/parser_test.py
+++ b/Tests/feaLib/parser_test.py
@@ -778,6 +778,26 @@ class ParserTest(unittest.TestCase):
self.assertEqual(glyphstr(pos.prefix), "[A B]")
self.assertEqual(glyphstr(pos.suffix), "comma")
+ def test_gpos_type_1_chained_special_kern_format_valuerecord_format_a(self):
+ doc = self.parse("feature kern {pos [A B] [T Y]' comma 20;} kern;")
+ pos = doc.statements[0].statements[0]
+ self.assertIsInstance(pos, ast.SinglePosStatement)
+ [(glyphs, value)] = pos.pos
+ self.assertEqual(glyphstr([glyphs]), "[T Y]")
+ self.assertEqual(value.asFea(), "20")
+ self.assertEqual(glyphstr(pos.prefix), "[A B]")
+ self.assertEqual(glyphstr(pos.suffix), "comma")
+
+ def test_gpos_type_1_chained_special_kern_format_valuerecord_format_b(self):
+ doc = self.parse("feature kern {pos [A B] [T Y]' comma <0 0 0 0>;} kern;")
+ pos = doc.statements[0].statements[0]
+ self.assertIsInstance(pos, ast.SinglePosStatement)
+ [(glyphs, value)] = pos.pos
+ self.assertEqual(glyphstr([glyphs]), "[T Y]")
+ self.assertEqual(value.asFea(), "<0 0 0 0>")
+ self.assertEqual(glyphstr(pos.prefix), "[A B]")
+ self.assertEqual(glyphstr(pos.suffix), "comma")
+
def test_gpos_type_2_format_a(self):
doc = self.parse("feature kern {"
" pos [T V] -60 [a b c] <1 2 3 4>;"
diff --git a/Tests/subset/data/expect_no_hinting_desubroutinize_CFF.ttx b/Tests/subset/data/expect_no_hinting_desubroutinize_CFF.ttx
index 7f44c90f..7896626e 100644
--- a/Tests/subset/data/expect_no_hinting_desubroutinize_CFF.ttx
+++ b/Tests/subset/data/expect_no_hinting_desubroutinize_CFF.ttx
@@ -1,16 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
-<ttFont sfntVersion="OTTO" ttLibVersion="3.5">
+<ttFont sfntVersion="OTTO" ttLibVersion="3.37">
<CFF>
<major value="1"/>
<minor value="0"/>
- <CFFFont name="Lobster1.4">
- <version value="001.001"/>
- <Notice value="Copyright (c) 2010 by Pablo Impallari. www.impallari.com. All rights reserved."/>
- <Copyright value="Copyright (c) 2010 by Pablo Impallari. All rights reserved."/>
- <FullName value="Lobster 1.4"/>
- <FamilyName value="Lobster 1.4"/>
- <Weight value="Regular"/>
+ <CFFFont name="SourceSerifPro-Regular">
+ <version value="1.0"/>
+ <Notice value="Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries."/>
+ <Copyright value="Copyright 2014 Adobe Systems Incorporated. All Rights Reserved."/>
+ <FamilyName value="Source Serif Pro"/>
<isFixedPitch value="0"/>
<ItalicAngle value="0"/>
<UnderlinePosition value="-100"/>
@@ -18,7 +16,7 @@
<PaintType value="0"/>
<CharstringType value="2"/>
<FontMatrix value="0.001 0 0 0.001 0 0"/>
- <FontBBox value="-209 -250 1186 1000"/>
+ <FontBBox value="0 -249 560 758"/>
<StrokeWidth value="0"/>
<!-- charset is dumped separately as the 'GlyphOrder' element -->
<Encoding name="StandardEncoding"/>
@@ -30,165 +28,62 @@
<LanguageGroup value="0"/>
<ExpansionFactor value="0.06"/>
<initialRandomSeed value="0"/>
- <defaultWidthX value="267"/>
- <nominalWidthX value="448"/>
+ <defaultWidthX value="0"/>
+ <nominalWidthX value="604"/>
</Private>
<CharStrings>
<CharString name=".notdef">
- -63 endchar
- </CharString>
- <CharString name="A">
- 220 535 hmoveto
- 157 736 rlineto
- 10 -24 -32 4 -23 hhcurveto
- -117 -130 -135 -160 -101 hvcurveto
- 2 -21 -17 1 -14 hhcurveto
- -118 -86 -55 -68 -39 28 -19 34 31 25 15 24 14 -8 17 -5 hvcurveto
- 13 34 42 14 62 4 rrcurveto
- -87 -153 -60 -164 -90 vvcurveto
- -104 80 -2 54 vhcurveto
- -6 9 -8 15 32 vvcurveto
- 104 55 190 75 163 vhcurveto
- 44 -4 39 -9 51 -23 -77 -363 rcurveline
- 86 407 rmoveto
- -39 16 -43 11 -40 8 56 112 64 93 60 32 rrcurveto
- endchar
- </CharString>
- <CharString name="A.salt">
- 142 459 hmoveto
- 157 736 rlineto
- 12 -30 -26 3 -24 hhcurveto
- -238 -290 -563 -189 -106 65 -2 69 -4 hvcurveto
- -1 9 -13 -4 51 vvcurveto
- 97 42 172 64 154 vhcurveto
- 158 hlineto
- -77 -366 rlineto
- -59 418 rmoveto
- 58 126 72 106 73 32 -56 -264 rcurveline
- endchar
- </CharString>
- <CharString name="B">
- 187 230 636 rmoveto
- -136 -636 rlineto
- 144 hlineto
- 82 383 rlineto
- 2 18 20 1 8 hhcurveto
- 73 22 -57 -70 hvcurveto
- -76 -26 -104 -73 -23 -19 10 26 -25 vhcurveto
- -9 -23 -4 -19 -16 vvcurveto
- -61 56 -13 43 167 52 192 96 75 -33 69 -85 17 vhcurveto
- 65 37 35 63 59 vvcurveto
- 82 -66 77 -147 -189 -174 -127 -138 -67 41 -25 66 vhcurveto
- -1 9 -13 8 51 vvcurveto
- 165 133 78 117 95 37 -51 -57 -75 -64 -87 -80 vhcurveto
- -6 hlineto
- 47 222 rlineto
- endchar
- </CharString>
- <CharString name="B.salt">
- 185 230 636 rmoveto
- -136 -636 rlineto
- 144 hlineto
- 6 30 rlineto
- -41 39 41 -17 39 hhcurveto
- 125 110 175 136 72 -32 62 -82 15 hvcurveto
- 64 38 36 61 58 vvcurveto
- 83 -74 78 -144 -183 -177 -126 -139 -67 41 -25 66 vhcurveto
- -1 9 -13 8 51 vvcurveto
- 152 116 91 138 101 25 -49 -53 -81 -59 -87 -83 vhcurveto
- -6 hlineto
- 47 222 rlineto
- -59 -592 rmoveto
- -20 -21 8 21 -20 hvcurveto
- 62 290 rlineto
- 2 18 20 1 7 hhcurveto
- 63 21 -49 -57 -96 -58 -120 -72 hvcurveto
- endchar
- </CharString>
- <CharString name="I">
- -73 397 748 rmoveto
- 1 -13 -13 1 -14 hhcurveto
- -167 -184 -127 -133 -72 38 -25 69 hvcurveto
- -1 9 -13 8 51 vvcurveto
- 107 53 75 87 36 vhcurveto
- -145 -679 rlineto
- 144 hlineto
- endchar
- </CharString>
- <CharString name="IJ">
- 215 397 748 rmoveto
- 1 -13 -13 1 -14 hhcurveto
- -167 -184 -127 -133 -72 38 -25 69 hvcurveto
- -1 9 -13 8 51 vvcurveto
- 107 53 75 87 36 vhcurveto
- -145 -679 rlineto
- 34 hlineto
- -11 -20 -5 -23 -27 vvcurveto
- -79 48 -58 113 155 66 109 138 29 vhcurveto
- 150 710 -150 -33 -164 -751 rlineto
- -100 -22 -30 -23 -40 hhcurveto
- -44 -27 29 39 40 29 33 36 16 17 -7 -16 16 hvcurveto
- 4 11 3 11 11 vvcurveto
- 34 -26 24 -41 6 vhcurveto
- endchar
- </CharString>
- <CharString name="J">
- 88 538 750 rmoveto
- -167 -184 -127 -133 -72 38 -25 69 hvcurveto
- -1 9 -13 8 51 vvcurveto
- 107 54 76 87 36 vhcurveto
- -157 -714 rlineto
- -103 -23 -27 -20 -45 hhcurveto
- -29 -39 18 52 37 24 37 46 20 15 -5 -21 25 hvcurveto
- 4 15 2 14 11 vvcurveto
- 64 -58 3 -40 -79 -43 -66 -68 -83 53 -58 95 164 67 94 153 32 vhcurveto
- 150 710 rlineto
- endchar
- </CharString>
- <CharString name="one">
- -131 324 748 rmoveto
- -72 -121 -78 -6 -55 hhcurveto
- -12 -46 rlineto
- 95 hlineto
- -132 -624 rlineto
- 144 hlineto
- endchar
- </CharString>
- <CharString name="three">
- 66 205 257 rmoveto
- 38 -8 -33 13 -37 hhcurveto
- -80 -41 -60 -83 -154 141 -16 58 171 111 136 121 71 -38 65 -88 29 hvcurveto
- 92 46 45 74 66 vvcurveto
- 78 -63 68 -123 vhcurveto
- -116 -91 -61 -91 -54 32 -31 40 24 27 11 23 25 hvcurveto
- -28 8 -10 36 27 vvcurveto
- 47 31 31 48 51 25 -36 -46 -70 -58 -94 -113 -31 vhcurveto
- 93 -33 40 -80 -76 vvcurveto
- -87 -53 -82 -86 -37 -39 13 76 40 10 62 78 6 vhcurveto
+ 36 80 hmoveto
+ 480 669 -480 hlineto
+ 240 -286 rmoveto
+ -148 236 rlineto
+ 296 hlineto
+ 32 -523 rmoveto
+ -149 239 149 238 rlineto
+ -360 -477 rmoveto
+ 477 vlineto
+ 150 -238 rlineto
+ -118 -285 rmoveto
+ 148 236 148 -236 rlineto
endchar
</CharString>
- <CharString name="two">
- 44 111 132 rmoveto
- -5 hlineto
- 83 135 273 98 223 vvcurveto
- 97 -53 64 -137 -151 -55 -79 -68 -58 31 -32 41 24 26 11 23 26 vhcurveto
- -28 8 -10 37 23 vvcurveto
- 50 14 31 67 29 32 -33 -49 vhcurveto
- -266 -329 -98 -219 vvcurveto
- -11 0 -11 2 -11 vhcurveto
- 7 20 36 21 23 hhcurveto
- 102 37 -36 109 hhcurveto
- 99 20 52 98 14 0 14 -1 16 hvcurveto
- -44 -47 -17 -25 -70 hhcurveto
- -75 -57 18 -59 hhcurveto
+ <CharString name="y">
+ -92 92 -249 rmoveto
+ 82 56 75 177 67 hvcurveto
+ 161 425 54 11 rlineto
+ 36 -195 vlineto
+ -36 vlineto
+ 87 -12 -123 -340 rlineto
+ -125 341 88 10 rlineto
+ 37 -240 vlineto
+ -36 vlineto
+ 55 -8 181 -457 -4 -12 -19 -54 -29 -54 -42 -36 rlinecurve
+ -5 5 rlineto
+ 28 -27 -21 10 -26 hhcurveto
+ -31 -29 -15 -29 -7 hvcurveto
+ -39 42 -27 50 vhcurveto
endchar
</CharString>
- <CharString name="zero">
- 98 377 750 rmoveto
- -215 -132 -223 -273 -166 35 -97 172 205 113 299 199 168 -53 93 -125 hvcurveto
- -189 -425 rmoveto
- 225 17 105 148 60 hhcurveto
- 47 7 -63 -82 -232 -68 -246 -114 -48 -11 77 74 37 3 35 2 27 hvcurveto
+ <CharString name="yacute">
+ -92 92 -249 rmoveto
+ 82 56 75 177 67 hvcurveto
+ 161 425 54 11 rlineto
+ 36 -195 vlineto
+ -36 vlineto
+ 87 -12 -123 -340 rlineto
+ -125 341 88 10 rlineto
+ 37 -240 vlineto
+ -36 vlineto
+ 55 -8 181 -457 -4 -12 -19 -54 -29 -54 -42 -36 rlinecurve
+ -5 5 rlineto
+ 28 -27 -21 10 -26 hhcurveto
+ -31 -29 -15 -29 -7 hvcurveto
+ -39 42 -27 50 vhcurveto
+ 155 825 rmoveto
+ 26 -19 41 36 39 35 41 37 rlinecurve
+ 28 26 6 15 14 vvcurveto
+ 26 -19 12 -19 -18 -17 -10 -31 -19 vhcurveto
+ -32 -48 -29 -46 -28 -47 rrcurveto
endchar
</CharString>
</CharStrings>
diff --git a/Tests/subset/data/test_hinted_subrs_CFF.desub.ttx b/Tests/subset/data/test_hinted_subrs_CFF.desub.ttx
new file mode 100644
index 00000000..50374b36
--- /dev/null
+++ b/Tests/subset/data/test_hinted_subrs_CFF.desub.ttx
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="3.37">
+
+ <CFF>
+ <major value="1"/>
+ <minor value="0"/>
+ <CFFFont name="SourceSerifPro-Regular">
+ <version value="1.0"/>
+ <Notice value="Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries."/>
+ <Copyright value="Copyright 2014 Adobe Systems Incorporated. All Rights Reserved."/>
+ <FamilyName value="Source Serif Pro"/>
+ <isFixedPitch value="0"/>
+ <ItalicAngle value="0"/>
+ <UnderlinePosition value="-100"/>
+ <UnderlineThickness value="50"/>
+ <PaintType value="0"/>
+ <CharstringType value="2"/>
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FontBBox value="0 -249 560 758"/>
+ <StrokeWidth value="0"/>
+ <!-- charset is dumped separately as the 'GlyphOrder' element -->
+ <Encoding name="StandardEncoding"/>
+ <Private>
+ <BlueValues value="-15 0 475 488 527 540 549 563 646 659 669 684 729 749"/>
+ <OtherBlues value="-249 -239"/>
+ <FamilyBlues value="-15 0 475 488 527 540 549 563 646 659 669 684 729 749"/>
+ <FamilyOtherBlues value="-249 -239"/>
+ <BlueScale value="0.0375"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="0"/>
+ <StdHW value="56"/>
+ <StdVW value="85"/>
+ <StemSnapH value="41 56"/>
+ <StemSnapV value="85 95"/>
+ <ForceBold value="0"/>
+ <LanguageGroup value="0"/>
+ <ExpansionFactor value="0.06"/>
+ <initialRandomSeed value="0"/>
+ <defaultWidthX value="0"/>
+ <nominalWidthX value="604"/>
+ </Private>
+ <CharStrings>
+ <CharString name=".notdef">
+ 36 0 50 569 50 hstem
+ 80 60 360 60 vstem
+ 80 hmoveto
+ 480 669 -480 hlineto
+ 240 -286 rmoveto
+ -148 236 rlineto
+ 296 hlineto
+ 32 -523 rmoveto
+ -149 239 149 238 rlineto
+ -360 -477 rmoveto
+ 477 vlineto
+ 150 -238 rlineto
+ -118 -285 rmoveto
+ 148 236 148 -236 rlineto
+ endchar
+ </CharString>
+ <CharString name="y">
+ -92 -249 110 572 42 -40 40 -36 36 0 1 hstemhm
+ 0 512 -195 195 vstemhm
+ 92 -249 rmoveto
+ 82 56 75 177 67 hvcurveto
+ 161 425 54 11 rlineto
+ 36 -195 vlineto
+ -36 vlineto
+ 87 -12 -123 -340 rlineto
+ -125 341 88 10 rlineto
+ 37 -240 vlineto
+ -36 vlineto
+ 55 -8 181 -457 -4 -12 -19 -54 -29 -54 -42 -36 rlinecurve
+ -5 5 rlineto
+ 28 -27 -21 10 -26 hhcurveto
+ -31 -29 -15 -29 -7 hvcurveto
+ -39 42 -27 50 vhcurveto
+ hintmask 11001000
+ endchar
+ </CharString>
+ <CharString name="yacute">
+ -92 -249 110 572 42 -40 40 -36 36 10 5 5 4 3 2 2 1 1 0 hstemhm
+ 0 512 -195 195 vstemhm
+ 92 -249 rmoveto
+ 82 56 75 177 67 hvcurveto
+ 161 425 54 11 rlineto
+ 36 -195 vlineto
+ -36 vlineto
+ 87 -12 -123 -340 rlineto
+ -125 341 88 10 rlineto
+ 37 -240 vlineto
+ -36 vlineto
+ 55 -8 181 -457 -4 -12 -19 -54 -29 -54 -42 -36 rlinecurve
+ -5 5 rlineto
+ 28 -27 -21 10 -26 hhcurveto
+ -31 -29 -15 -29 -7 hvcurveto
+ -39 42 -27 50 vhcurveto
+ hintmask 1100100010000000
+ 155 825 rmoveto
+ 26 -19 41 36 39 35 41 37 rlinecurve
+ 28 26 6 15 14 vvcurveto
+ 26 -19 12 -19 -18 -17 -10 -31 -19 vhcurveto
+ -32 -48 -29 -46 -28 -47 rrcurveto
+ endchar
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF>
+
+</ttFont>
diff --git a/Tests/subset/data/test_hinted_subrs_CFF.ttx b/Tests/subset/data/test_hinted_subrs_CFF.ttx
new file mode 100644
index 00000000..daf9f0ea
--- /dev/null
+++ b/Tests/subset/data/test_hinted_subrs_CFF.ttx
@@ -0,0 +1,351 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="3.37">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="y"/>
+ <GlyphID id="2" name="yacute"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="2.0"/>
+ <checkSumAdjustment value="0x30fffb39"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jan 4 11:55:59 2017"/>
+ <modified value="Sat Feb 9 07:43:13 2019"/>
+ <xMin value="0"/>
+ <yMin value="-249"/>
+ <xMax value="560"/>
+ <yMax value="758"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1036"/>
+ <descent value="-335"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="640"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="560"/>
+ <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="2"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="3"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="554"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="285"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="4"/>
+ <bWeight value="6"/>
+ <bProportion value="3"/>
+ <bContrast value="5"/>
+ <bStrokeVariation value="4"/>
+ <bArmStyle value="5"/>
+ <bLetterForm value="2"/>
+ <bMidline value="2"/>
+ <bXHeight value="4"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000011"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="ADBO"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="121"/>
+ <usLastCharIndex value="253"/>
+ <sTypoAscender value="730"/>
+ <sTypoDescender value="-270"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="1036"/>
+ <usWinDescent value="335"/>
+ <ulCodePageRange1 value="00100000 00000000 00000001 10011111"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="475"/>
+ <sCapHeight value="670"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="3"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="0" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Copyright 2014, 2015, 2016 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'.
+ </namerecord>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Serif Pro
+ </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">
+ 2.000;ADBO;SourceSerifPro-Regular;ADOBE
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source Serif Pro
+ </namerecord>
+ <namerecord nameID="5" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Version 2.000;PS 1.0;hotconv 16.6.51;makeotf.lib2.5.65220
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SourceSerifPro-Regular
+ </namerecord>
+ <namerecord nameID="7" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
+ </namerecord>
+ <namerecord nameID="8" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Adobe Systems Incorporated
+ </namerecord>
+ <namerecord nameID="9" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Frank Grießhammer
+ </namerecord>
+ <namerecord nameID="11" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ http://www.adobe.com/type
+ </namerecord>
+ <namerecord nameID="13" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.&#13;
+&#13;
+This Font Software is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the SIL Open Font License for the specific language, permissions and limitations governing your use of this Font Software.
+ </namerecord>
+ <namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ http://scripts.sil.org/OFL
+ </namerecord>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright 2014, 2015, 2016 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'.
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Source Serif Pro
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 2.000;ADBO;SourceSerifPro-Regular;ADOBE
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Source Serif Pro
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 2.000;PS 1.0;hotconv 16.6.51;makeotf.lib2.5.65220
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SourceSerifPro-Regular
+ </namerecord>
+ <namerecord nameID="7" platformID="3" platEncID="1" langID="0x409">
+ Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
+ </namerecord>
+ <namerecord nameID="8" platformID="3" platEncID="1" langID="0x409">
+ Adobe Systems Incorporated
+ </namerecord>
+ <namerecord nameID="9" platformID="3" platEncID="1" langID="0x409">
+ Frank Grießhammer
+ </namerecord>
+ <namerecord nameID="11" platformID="3" platEncID="1" langID="0x409">
+ http://www.adobe.com/type
+ </namerecord>
+ <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.&#13;
+&#13;
+This Font Software is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the SIL Open Font License for the specific language, permissions and limitations governing your use of this Font Software.
+ </namerecord>
+ <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+ http://scripts.sil.org/OFL
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x79" name="y"/><!-- LATIN SMALL LETTER Y -->
+ <map code="0xfd" name="yacute"/><!-- LATIN SMALL LETTER Y WITH ACUTE -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x79" name="y"/><!-- LATIN SMALL LETTER Y -->
+ <map code="0xfd" name="yacute"/><!-- LATIN SMALL LETTER Y WITH ACUTE -->
+ </cmap_format_4>
+ <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="40" language="0" nGroups="2">
+ <map code="0x79" name="y"/><!-- LATIN SMALL LETTER Y -->
+ <map code="0xfd" name="yacute"/><!-- LATIN SMALL LETTER Y WITH ACUTE -->
+ </cmap_format_12>
+ </cmap>
+
+ <post>
+ <formatType value="3.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"/>
+ </post>
+
+ <CFF>
+ <major value="1"/>
+ <minor value="0"/>
+ <CFFFont name="SourceSerifPro-Regular">
+ <version value="1.0"/>
+ <Notice value="Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries."/>
+ <Copyright value="Copyright 2014 Adobe Systems Incorporated. All Rights Reserved."/>
+ <FamilyName value="Source Serif Pro"/>
+ <isFixedPitch value="0"/>
+ <ItalicAngle value="0"/>
+ <UnderlinePosition value="-100"/>
+ <UnderlineThickness value="50"/>
+ <PaintType value="0"/>
+ <CharstringType value="2"/>
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FontBBox value="0 -249 560 758"/>
+ <StrokeWidth value="0"/>
+ <!-- charset is dumped separately as the 'GlyphOrder' element -->
+ <Encoding name="StandardEncoding"/>
+ <Private>
+ <BlueValues value="-15 0 475 488 527 540 549 563 646 659 669 684 729 749"/>
+ <OtherBlues value="-249 -239"/>
+ <FamilyBlues value="-15 0 475 488 527 540 549 563 646 659 669 684 729 749"/>
+ <FamilyOtherBlues value="-249 -239"/>
+ <BlueScale value="0.0375"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="0"/>
+ <StdHW value="56"/>
+ <StdVW value="85"/>
+ <StemSnapH value="41 56"/>
+ <StemSnapV value="85 95"/>
+ <ForceBold value="0"/>
+ <LanguageGroup value="0"/>
+ <ExpansionFactor value="0.06"/>
+ <initialRandomSeed value="0"/>
+ <defaultWidthX value="0"/>
+ <nominalWidthX value="604"/>
+ <Subrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ <CharString index="0">
+ rmoveto
+ 26 -19 41 36 39 35 41 37 rlinecurve
+ 28 26 6 15 14 vvcurveto
+ 26 -19 12 -19 -18 -17 -10 -31 -19 vhcurveto
+ -32 -48 -29 -46 -28 -47 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="1">
+ 195 vstemhm
+ -107 callgsubr
+ 161 425 54 11 rlineto
+ 36 -195 vlineto
+ -36 vlineto
+ 87 -12 -123 -340 rlineto
+ -125 341 88 10 rlineto
+ 37 -240 vlineto
+ -105 callsubr
+ -39 42 -27 50 vhcurveto
+ return
+ </CharString>
+ <CharString index="2">
+ -36 vlineto
+ 55 -8 181 -457 -4 -12 -19 -54 -29 -54 -42 -36 rlinecurve
+ -5 5 rlineto
+ 28 -27 -21 10 -26 hhcurveto
+ -31 -29 -15 -29 -7 hvcurveto
+ return
+ </CharString>
+ <CharString index="3">
+ -92 -249 110 572 42 -40 40 -36 36 return
+ </CharString>
+ <CharString index="4">
+ hstemhm
+ return
+ </CharString>
+ </Subrs>
+ </Private>
+ <CharStrings>
+ <CharString name=".notdef">
+ 36 0 50 569 50 hstem
+ 80 60 360 60 vstem
+ 80 hmoveto
+ 480 669 -480 hlineto
+ 240 -286 rmoveto
+ -148 236 rlineto
+ 296 hlineto
+ 32 -523 rmoveto
+ -149 239 149 238 rlineto
+ -360 -477 rmoveto
+ 477 vlineto
+ 150 -238 rlineto
+ -118 -285 rmoveto
+ 148 236 148 -236 rlineto
+ endchar
+ </CharString>
+ <CharString name="y">
+ -104 callsubr
+ 0 1 -103 callsubr
+ 0 512 -195 -106 callsubr
+ hintmask 11001000
+ endchar
+ </CharString>
+ <CharString name="yacute">
+ -104 callsubr
+ 10 5 5 4 3 2 2 1 1 0 -103 callsubr
+ 0 512 -195 -106 callsubr
+ hintmask 1100100010000000
+ 155 825 -107 callsubr
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ <CharString index="0">
+ 92 -249 rmoveto
+ 82 56 75 177 67 hvcurveto
+ return
+ </CharString>
+ </GlobalSubrs>
+ </CFF>
+
+ <hmtx>
+ <mtx name=".notdef" width="640" lsb="80"/>
+ <mtx name="y" width="512" lsb="0"/>
+ <mtx name="yacute" width="512" lsb="0"/>
+ </hmtx>
+
+</ttFont>
diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py
index 72fee41b..d78c496b 100644
--- a/Tests/subset/subset_test.py
+++ b/Tests/subset/subset_test.py
@@ -416,8 +416,18 @@ class SubsetTest(unittest.TestCase):
self.expect_ttx(subsetfont, self.getpath(
"expect_desubroutinize_CFF.ttx"), ["CFF "])
+ def test_desubroutinize_hinted_subrs_CFF(self):
+ ttxpath = self.getpath("test_hinted_subrs_CFF.ttx")
+ _, fontpath = self.compile_font(ttxpath, ".otf")
+ subsetpath = self.temp_path(".otf")
+ subset.main([fontpath, "--desubroutinize", "--notdef-outline",
+ "--output-file=%s" % subsetpath, "*"])
+ subsetfont = TTFont(subsetpath)
+ self.expect_ttx(subsetfont, self.getpath(
+ "test_hinted_subrs_CFF.desub.ttx"), ["CFF "])
+
def test_no_hinting_desubroutinize_CFF(self):
- ttxpath = self.getpath("Lobster.subset.ttx")
+ ttxpath = self.getpath("test_hinted_subrs_CFF.ttx")
_, fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
subset.main([fontpath, "--no-hinting", "--desubroutinize", "--notdef-outline",
diff --git a/Tests/svgLib/path/shapes_test.py b/Tests/svgLib/path/shapes_test.py
new file mode 100644
index 00000000..ee9ddeae
--- /dev/null
+++ b/Tests/svgLib/path/shapes_test.py
@@ -0,0 +1,68 @@
+from __future__ import print_function, absolute_import, division
+
+from fontTools.misc.py23 import *
+from fontTools.pens.recordingPen import RecordingPen
+from fontTools.svgLib.path import shapes
+from fontTools.misc import etree
+import pytest
+
+
+@pytest.mark.parametrize(
+ "svg_xml, expected_path",
+ [
+ # path: direct passthrough
+ (
+ "<path d='I love kittens'/>",
+ "I love kittens"
+ ),
+ # path no @d
+ (
+ "<path duck='Mallard'/>",
+ None
+ ),
+ # rect: minimal valid example
+ (
+ "<rect width='1' height='1'/>",
+ "M0,0 H1 V1 H0 V0 z",
+ ),
+ # rect: sharp corners
+ (
+ "<rect x='10' y='11' width='17' height='11'/>",
+ "M10,11 H27 V22 H10 V11 z",
+ ),
+ # rect: round corners
+ (
+ "<rect x='9' y='9' width='11' height='7' rx='2'/>",
+ "M11,9 H18 A2,2 0 0 1 20,11 V14 A2,2 0 0 1 18,16 H11"
+ " A2,2 0 0 1 9,14 V11 A2,2 0 0 1 11,9 z",
+ ),
+ # polygon
+ (
+ "<polygon points='30,10 50,30 10,30'/>",
+ "M30,10 50,30 10,30 z"
+ ),
+ # circle, minimal valid example
+ (
+ "<circle r='1'/>",
+ "M-1,0 A1,1 0 1 1 1,0 A1,1 0 1 1 -1,0"
+ ),
+ # circle
+ (
+ "<circle cx='600' cy='200' r='100'/>",
+ "M500,200 A100,100 0 1 1 700,200 A100,100 0 1 1 500,200"
+ ),
+ # circle, decimal positioning
+ (
+ "<circle cx='12' cy='6.5' r='1.5'></circle>",
+ "M10.5,6.5 A1.5,1.5 0 1 1 13.5,6.5 A1.5,1.5 0 1 1 10.5,6.5"
+ )
+ ]
+)
+def test_el_to_path(svg_xml, expected_path):
+ pb = shapes.PathBuilder()
+ pb.add_path_from_element(etree.fromstring(svg_xml))
+ if expected_path:
+ expected = [expected_path]
+ else:
+ expected = []
+ assert pb.paths == expected
diff --git a/Tests/varLib/interpolate_layout_test.py b/Tests/varLib/interpolate_layout_test.py
index 00039295..b858a3c9 100644
--- a/Tests/varLib/interpolate_layout_test.py
+++ b/Tests/varLib/interpolate_layout_test.py
@@ -4,7 +4,7 @@ from fontTools.ttLib import TTFont
from fontTools.varLib import build
from fontTools.varLib.interpolate_layout import interpolate_layout
from fontTools.varLib.interpolate_layout import main as interpolate_layout_main
-from fontTools.designspaceLib import DesignSpaceDocumentError
+from fontTools.designspaceLib import DesignSpaceDocument, DesignSpaceDocumentError
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
import difflib
import os
diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py
index 831d8b83..32e7ab5e 100644
--- a/Tests/varLib/varLib_test.py
+++ b/Tests/varLib/varLib_test.py
@@ -1,6 +1,6 @@
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
-from fontTools.ttLib import TTFont
+from fontTools.ttLib import TTFont, newTable
from fontTools.varLib import build
from fontTools.varLib import main as varLib_main, load_masters
from fontTools.designspaceLib import (
@@ -361,6 +361,87 @@ class BuildTest(unittest.TestCase):
tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
self.expect_ttx(varfont, expected_ttx_path, tables)
+ def test_varlib_build_sparse_masters_MVAR(self):
+ import fontTools.varLib.mvar
+
+ ds_path = self.get_test_input("SparseMasters.designspace")
+ ds = DesignSpaceDocument.fromfile(ds_path)
+ load_masters(ds)
+
+ # Trigger MVAR generation so varLib is forced to create deltas with a
+ # sparse master inbetween.
+ font_0_os2 = ds.sources[0].font["OS/2"]
+ font_0_os2.sTypoAscender = 1
+ font_0_os2.sTypoDescender = 1
+ font_0_os2.sTypoLineGap = 1
+ font_0_os2.usWinAscent = 1
+ font_0_os2.usWinDescent = 1
+ font_0_os2.sxHeight = 1
+ font_0_os2.sCapHeight = 1
+ font_0_os2.ySubscriptXSize = 1
+ font_0_os2.ySubscriptYSize = 1
+ font_0_os2.ySubscriptXOffset = 1
+ font_0_os2.ySubscriptYOffset = 1
+ font_0_os2.ySuperscriptXSize = 1
+ font_0_os2.ySuperscriptYSize = 1
+ font_0_os2.ySuperscriptXOffset = 1
+ font_0_os2.ySuperscriptYOffset = 1
+ font_0_os2.yStrikeoutSize = 1
+ font_0_os2.yStrikeoutPosition = 1
+ font_0_vhea = newTable("vhea")
+ font_0_vhea.ascent = 1
+ font_0_vhea.descent = 1
+ font_0_vhea.lineGap = 1
+ font_0_vhea.caretSlopeRise = 1
+ font_0_vhea.caretSlopeRun = 1
+ font_0_vhea.caretOffset = 1
+ ds.sources[0].font["vhea"] = font_0_vhea
+ font_0_hhea = ds.sources[0].font["hhea"]
+ font_0_hhea.caretSlopeRise = 1
+ font_0_hhea.caretSlopeRun = 1
+ font_0_hhea.caretOffset = 1
+ font_0_post = ds.sources[0].font["post"]
+ font_0_post.underlineThickness = 1
+ font_0_post.underlinePosition = 1
+
+ font_2_os2 = ds.sources[2].font["OS/2"]
+ font_2_os2.sTypoAscender = 800
+ font_2_os2.sTypoDescender = 800
+ font_2_os2.sTypoLineGap = 800
+ font_2_os2.usWinAscent = 800
+ font_2_os2.usWinDescent = 800
+ font_2_os2.sxHeight = 800
+ font_2_os2.sCapHeight = 800
+ font_2_os2.ySubscriptXSize = 800
+ font_2_os2.ySubscriptYSize = 800
+ font_2_os2.ySubscriptXOffset = 800
+ font_2_os2.ySubscriptYOffset = 800
+ font_2_os2.ySuperscriptXSize = 800
+ font_2_os2.ySuperscriptYSize = 800
+ font_2_os2.ySuperscriptXOffset = 800
+ font_2_os2.ySuperscriptYOffset = 800
+ font_2_os2.yStrikeoutSize = 800
+ font_2_os2.yStrikeoutPosition = 800
+ font_2_vhea = newTable("vhea")
+ font_2_vhea.ascent = 800
+ font_2_vhea.descent = 800
+ font_2_vhea.lineGap = 800
+ font_2_vhea.caretSlopeRise = 800
+ font_2_vhea.caretSlopeRun = 800
+ font_2_vhea.caretOffset = 800
+ ds.sources[2].font["vhea"] = font_2_vhea
+ font_2_hhea = ds.sources[2].font["hhea"]
+ font_2_hhea.caretSlopeRise = 800
+ font_2_hhea.caretSlopeRun = 800
+ font_2_hhea.caretOffset = 800
+ font_2_post = ds.sources[2].font["post"]
+ font_2_post.underlineThickness = 800
+ font_2_post.underlinePosition = 800
+
+ varfont, _, _ = build(ds)
+ mvar_tags = [vr.ValueTag for vr in varfont["MVAR"].table.ValueRecord]
+ assert all(tag in mvar_tags for tag in fontTools.varLib.mvar.MVAR_ENTRIES)
+
def test_load_masters_layerName_without_required_font():
ds = DesignSpaceDocument()
diff --git a/Tests/voltLib/parser_test.py b/Tests/voltLib/parser_test.py
index 51a65fc8..a532b375 100644
--- a/Tests/voltLib/parser_test.py
+++ b/Tests/voltLib/parser_test.py
@@ -18,6 +18,13 @@ class ParserTest(unittest.TestCase):
if not hasattr(self, "assertRaisesRegex"):
self.assertRaisesRegex = self.assertRaisesRegexp
+ def assertSubEqual(self, sub, glyph_ref, replacement_ref):
+ glyphs = [[g.glyph for g in v] for v in sub.mapping.keys()]
+ replacement = [[g.glyph for g in v] for v in sub.mapping.values()]
+
+ self.assertEqual(glyphs, glyph_ref)
+ self.assertEqual(replacement, replacement_ref)
+
def test_def_glyph_base(self):
[def_glyph] = self.parse(
'DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH'
@@ -130,7 +137,7 @@ class ParserTest(unittest.TestCase):
"atilde")))
def test_def_group_groups(self):
- parser = self.parser(
+ [group1, group2, test_group] = self.parse(
'DEF_GROUP "Group1"\n'
'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n'
'END_GROUP\n'
@@ -140,16 +147,14 @@ class ParserTest(unittest.TestCase):
'DEF_GROUP "TestGroup"\n'
'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
'END_GROUP\n'
- )
- [group1, group2, test_group] = parser.parse().statements
- self.assertEqual(
- (test_group.name, test_group.enum),
- ("TestGroup",
- ast.Enum([ast.GroupName("Group1", parser),
- ast.GroupName("Group2", parser)])))
+ ).statements
+ groups = [g.group for g in test_group.enum.enum]
+ self.assertEqual((test_group.name, groups),
+ ("TestGroup", ["Group1", "Group2"]))
def test_def_group_groups_not_yet_defined(self):
- parser = self.parser(
+ [group1, test_group1, test_group2, test_group3, group2] = \
+ self.parse(
'DEF_GROUP "Group1"\n'
'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n'
'END_GROUP\n'
@@ -165,23 +170,19 @@ class ParserTest(unittest.TestCase):
'DEF_GROUP "Group2"\n'
'ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n'
'END_GROUP\n'
- )
- [group1, test_group1, test_group2, test_group3, group2] = \
- parser.parse().statements
+ ).statements
+ groups = [g.group for g in test_group1.enum.enum]
self.assertEqual(
- (test_group1.name, test_group1.enum),
- ("TestGroup1",
- ast.Enum([ast.GroupName("Group1", parser),
- ast.GroupName("Group2", parser)])))
+ (test_group1.name, groups),
+ ("TestGroup1", ["Group1", "Group2"]))
+ groups = [g.group for g in test_group2.enum.enum]
self.assertEqual(
- (test_group2.name, test_group2.enum),
- ("TestGroup2",
- ast.Enum([ast.GroupName("Group2", parser)])))
+ (test_group2.name, groups),
+ ("TestGroup2", ["Group2"]))
+ groups = [g.group for g in test_group3.enum.enum]
self.assertEqual(
- (test_group3.name, test_group3.enum),
- ("TestGroup3",
- ast.Enum([ast.GroupName("Group2", parser),
- ast.GroupName("Group1", parser)])))
+ (test_group3.name, groups),
+ ("TestGroup3", ["Group2", "Group1"]))
# def test_def_group_groups_undefined(self):
# with self.assertRaisesRegex(
@@ -556,12 +557,11 @@ class ParserTest(unittest.TestCase):
'END_SUB\n'
'END_SUBSTITUTION'
).statements
- self.assertEqual((lookup.name, list(lookup.sub.mapping.items())),
- ("smcp", [(self.enum(["a"]), self.enum(["a.sc"])),
- (self.enum(["b"]), self.enum(["b.sc"]))]))
+ self.assertEqual(lookup.name, "smcp")
+ self.assertSubEqual(lookup.sub, [["a"], ["b"]], [["a.sc"], ["b.sc"]])
def test_substitution_single_in_context(self):
- parser = self.parser(
+ [group, lookup] = self.parse(
'DEF_GROUP "Denominators" ENUM GLYPH "one.dnom" GLYPH "two.dnom" '
'END_ENUM END_GROUP\n'
'DEF_LOOKUP "fracdnom" PROCESS_BASE PROCESS_MARKS ALL '
@@ -577,22 +577,22 @@ class ParserTest(unittest.TestCase):
'WITH GLYPH "two.dnom"\n'
'END_SUB\n'
'END_SUBSTITUTION'
- )
- [group, lookup] = parser.parse().statements
+ ).statements
context = lookup.context[0]
- self.assertEqual(
- (lookup.name, list(lookup.sub.mapping.items()),
- context.ex_or_in, context.left, context.right),
- ("fracdnom",
- [(self.enum(["one"]), self.enum(["one.dnom"])),
- (self.enum(["two"]), self.enum(["two.dnom"]))],
- "IN_CONTEXT", [ast.Enum([
- ast.GroupName("Denominators", parser=parser),
- ast.GlyphName("fraction")])], [])
- )
+
+ self.assertEqual(lookup.name, "fracdnom")
+ self.assertEqual(context.ex_or_in, "IN_CONTEXT")
+ self.assertEqual(len(context.left), 1)
+ self.assertEqual(len(context.left[0]), 1)
+ self.assertEqual(len(context.left[0][0].enum), 2)
+ self.assertEqual(context.left[0][0].enum[0].group, "Denominators")
+ self.assertEqual(context.left[0][0].enum[1].glyph, "fraction")
+ self.assertEqual(context.right, [])
+ self.assertSubEqual(lookup.sub, [["one"], ["two"]],
+ [["one.dnom"], ["two.dnom"]])
def test_substitution_single_in_contexts(self):
- parser = self.parser(
+ [group, lookup] = self.parse(
'DEF_GROUP "Hebrew" ENUM GLYPH "uni05D0" GLYPH "uni05D1" '
'END_ENUM END_GROUP\n'
'DEF_LOOKUP "HebrewCurrency" PROCESS_BASE PROCESS_MARKS ALL '
@@ -610,19 +610,27 @@ class ParserTest(unittest.TestCase):
'WITH GLYPH "dollar.Hebr"\n'
'END_SUB\n'
'END_SUBSTITUTION'
- )
- [group, lookup] = parser.parse().statements
+ ).statements
context1 = lookup.context[0]
context2 = lookup.context[1]
- self.assertEqual(
- (lookup.name, context1.ex_or_in, context1.left,
- context1.right, context2.ex_or_in,
- context2.left, context2.right),
- ("HebrewCurrency", "IN_CONTEXT", [],
- [ast.Enum([ast.GroupName("Hebrew", parser)]),
- self.enum(["one.Hebr"])], "IN_CONTEXT",
- [ast.Enum([ast.GroupName("Hebrew", parser)]),
- self.enum(["one.Hebr"])], []))
+
+ self.assertEqual(lookup.name, "HebrewCurrency")
+
+ self.assertEqual(context1.ex_or_in, "IN_CONTEXT")
+ self.assertEqual(context1.left, [])
+ self.assertEqual(len(context1.right), 2)
+ self.assertEqual(len(context1.right[0]), 1)
+ self.assertEqual(len(context1.right[1]), 1)
+ self.assertEqual(context1.right[0][0].group, "Hebrew")
+ self.assertEqual(context1.right[1][0].glyph, "one.Hebr")
+
+ self.assertEqual(context2.ex_or_in, "IN_CONTEXT")
+ self.assertEqual(len(context2.left), 2)
+ self.assertEqual(len(context2.left[0]), 1)
+ self.assertEqual(len(context2.left[1]), 1)
+ self.assertEqual(context2.left[0][0].group, "Hebrew")
+ self.assertEqual(context2.left[1][0].glyph, "one.Hebr")
+ self.assertEqual(context2.right, [])
def test_substitution_skip_base(self):
[group, lookup] = self.parse(
@@ -787,11 +795,9 @@ class ParserTest(unittest.TestCase):
'END_SUB\n'
'END_SUBSTITUTION'
).statements
- self.assertEqual((lookup.name, list(lookup.sub.mapping.items())),
- ("ccmp",
- [(self.enum(["aacute"]), self.enum(["a", "acutecomb"])),
- (self.enum(["agrave"]), self.enum(["a", "gravecomb"]))]
- ))
+ self.assertEqual(lookup.name, "ccmp")
+ self.assertSubEqual(lookup.sub, [["aacute"], ["agrave"]],
+ [["a", "acutecomb"], ["a", "gravecomb"]])
def test_substitution_multiple_to_single(self):
[lookup] = self.parse(
@@ -808,33 +814,12 @@ class ParserTest(unittest.TestCase):
'END_SUB\n'
'END_SUBSTITUTION'
).statements
- self.assertEqual((lookup.name, list(lookup.sub.mapping.items())),
- ("liga",
- [(self.enum(["f", "i"]), self.enum(["f_i"])),
- (self.enum(["f", "t"]), self.enum(["f_t"]))]))
+ self.assertEqual(lookup.name, "liga")
+ self.assertSubEqual(lookup.sub, [["f", "i"], ["f", "t"]],
+ [["f_i"], ["f_t"]])
def test_substitution_reverse_chaining_single(self):
- parser = self.parser(
- 'DEF_GLYPH "zero" ID 1 UNICODE 48 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "one" ID 2 UNICODE 49 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "two" ID 3 UNICODE 50 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "three" ID 4 UNICODE 51 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "four" ID 5 UNICODE 52 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "five" ID 6 UNICODE 53 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "six" ID 7 UNICODE 54 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "seven" ID 8 UNICODE 55 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "eight" ID 9 UNICODE 56 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "nine" ID 10 UNICODE 57 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "zero.numr" ID 11 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "one.numr" ID 12 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "two.numr" ID 13 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "three.numr" ID 14 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "four.numr" ID 15 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "five.numr" ID 16 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "six.numr" ID 17 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "seven.numr" ID 18 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "eight.numr" ID 19 TYPE BASE END_GLYPH\n'
- 'DEF_GLYPH "nine.numr" ID 20 TYPE BASE END_GLYPH\n'
+ [lookup] = self.parse(
'DEF_LOOKUP "numr" PROCESS_BASE PROCESS_MARKS ALL '
'DIRECTION LTR REVERSAL\n'
'IN_CONTEXT\n'
@@ -848,16 +833,23 @@ class ParserTest(unittest.TestCase):
'WITH RANGE "zero.numr" TO "nine.numr"\n'
'END_SUB\n'
'END_SUBSTITUTION'
- )
- lookup = parser.parse().statements[-1]
- self.assertEqual(
- (lookup.name, lookup.context[0].right,
- list(lookup.sub.mapping.items())),
- ("numr",
- [(ast.Enum([ast.GlyphName("fraction"),
- ast.Range("zero.numr", "nine.numr", parser)]))],
- [(ast.Enum([ast.Range("zero", "nine", parser)]),
- ast.Enum([ast.Range("zero.numr", "nine.numr", parser)]))]))
+ ).statements
+
+ mapping = lookup.sub.mapping
+ glyphs = [[(r.start, r.end) for r in v] for v in mapping.keys()]
+ replacement = [[(r.start, r.end) for r in v] for v in mapping.values()]
+
+ self.assertEqual(lookup.name, "numr")
+ self.assertEqual(glyphs, [[('zero', 'nine')]])
+ self.assertEqual(replacement, [[('zero.numr', 'nine.numr')]])
+
+ self.assertEqual(len(lookup.context[0].right), 1)
+ self.assertEqual(len(lookup.context[0].right[0]), 1)
+ enum = lookup.context[0].right[0][0]
+ self.assertEqual(len(enum.enum), 2)
+ self.assertEqual(enum.enum[0].glyph, "fraction")
+ self.assertEqual((enum.enum[1].start, enum.enum[1].end),
+ ('zero.numr', 'nine.numr'))
# GPOS
# ATTACH_CURSIVE
@@ -899,11 +891,13 @@ class ParserTest(unittest.TestCase):
'DEF_ANCHOR "top" ON 35 GLYPH e COMPONENT 1 '
'AT POS DX 215 DY 450 END_POS END_ANCHOR\n'
).statements
+ pos = lookup.pos
+ coverage = [g.glyph for g in pos.coverage]
+ coverage_to = [[[g.glyph for g in e], a] for (e, a) in pos.coverage_to]
self.assertEqual(
- (lookup.name, lookup.pos.coverage, lookup.pos.coverage_to),
- ("anchor_top", self.enum(["a", "e"]),
- [(self.enum(["acutecomb"]), "top"),
- (self.enum(["gravecomb"]), "top")])
+ (lookup.name, coverage, coverage_to),
+ ("anchor_top", ["a", "e"],
+ [[["acutecomb"], "top"], [["gravecomb"], "top"]])
)
self.assertEqual(
(anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component,
@@ -939,11 +933,11 @@ class ParserTest(unittest.TestCase):
'END_ATTACH\n'
'END_POSITION\n'
).statements
+ exit = [[g.glyph for g in v] for v in lookup.pos.coverages_exit]
+ enter = [[g.glyph for g in v] for v in lookup.pos.coverages_enter]
self.assertEqual(
- (lookup.name,
- lookup.pos.coverages_exit, lookup.pos.coverages_enter),
- ("SomeLookup",
- [self.enum(["a", "b"])], [self.enum(["c"])])
+ (lookup.name, exit, enter),
+ ("SomeLookup", [["a", "b"]], [["c"]])
)
def test_position_adjust_pair(self):
@@ -961,10 +955,12 @@ class ParserTest(unittest.TestCase):
'END_ADJUST\n'
'END_POSITION\n'
).statements
+ coverages_1 = [[g.glyph for g in v] for v in lookup.pos.coverages_1]
+ coverages_2 = [[g.glyph for g in v] for v in lookup.pos.coverages_2]
self.assertEqual(
- (lookup.name, lookup.pos.coverages_1, lookup.pos.coverages_2,
+ (lookup.name, coverages_1, coverages_2,
lookup.pos.adjust_pair),
- ("kern1", [self.enum(["A"])], [self.enum(["V"])],
+ ("kern1", [["A"]], [["V"]],
{(1, 2): ((-30, None, None, {}, {}, {}),
(None, None, None, {}, {}, {})),
(2, 1): ((-30, None, None, {}, {}, {}),
@@ -986,11 +982,13 @@ class ParserTest(unittest.TestCase):
'END_ADJUST\n'
'END_POSITION\n'
).statements
+ pos = lookup.pos
+ adjust = [[[g.glyph for g in a], b] for (a, b) in pos.adjust_single]
self.assertEqual(
- (lookup.name, lookup.pos.adjust_single),
+ (lookup.name, adjust),
("TestLookup",
- [(self.enum(["glyph1"]), (0, 123, None, {}, {}, {})),
- (self.enum(["glyph2"]), (0, 456, None, {}, {}, {}))])
+ [[["glyph1"], (0, 123, None, {}, {}, {})],
+ [["glyph2"], (0, 456, None, {}, {}, {})]])
)
def test_def_anchor(self):
@@ -1129,20 +1127,14 @@ class ParserTest(unittest.TestCase):
if self.tempdir:
shutil.rmtree(self.tempdir)
- def parser(self, text):
+ def parse(self, text):
if not self.tempdir:
self.tempdir = tempfile.mkdtemp()
self.num_tempfiles += 1
path = os.path.join(self.tempdir, "tmp%d.vtp" % self.num_tempfiles)
with open(path, "w") as outfile:
outfile.write(text)
- return Parser(path)
-
- def parse(self, text):
- return self.parser(text).parse()
-
- def enum(self, glyphs):
- return ast.Enum([ast.GlyphName(g) for g in glyphs])
+ return Parser(path).parse()
if __name__ == "__main__":
import sys
diff --git a/requirements.txt b/requirements.txt
index b910fea7..acfd0fd4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,7 @@
brotli==1.0.7; platform_python_implementation != "PyPy"
brotlipy==0.7.0; platform_python_implementation == "PyPy"
unicodedata2==11.0.0; python_version < '3.7' and platform_python_implementation != "PyPy"
-scipy==1.2.0; platform_python_implementation != "PyPy"
+scipy==1.2.1; platform_python_implementation != "PyPy"
munkres==1.0.12; platform_python_implementation == "PyPy"
zopfli==0.1.6
-fs==2.2.1
+fs==2.3.1
diff --git a/setup.cfg b/setup.cfg
index 97d51aef..9fe169ed 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 3.37.0
+current_version = 3.38.0
commit = True
tag = False
tag_name = {new_version}
diff --git a/setup.py b/setup.py
index f22309da..273c585c 100755
--- a/setup.py
+++ b/setup.py
@@ -352,7 +352,7 @@ def find_data_files(manpath="share/man"):
setup(
name="fonttools",
- version="3.37.0",
+ version="3.38.0",
description="Tools to manipulate font files",
author="Just van Rossum",
author_email="just@letterror.com",