Merge branch 'master' into partial-instancer

This commit is contained in:
Cosimo Lupo 2019-03-21 15:26:27 +00:00
commit 2ee528e2fd
26 changed files with 298 additions and 338 deletions

View File

@ -1044,9 +1044,24 @@ Recommendation for editors
- Use ``documentObject.updateFilenameFromPath()`` to explicitly set the
**filename** attributes for all instance and source descriptors.
.. 7-this-document:
.. 7-common-lib-key-registry:
7 This document
7 Common Lib Key Registry
=========================
public.skipExportGlyphs
-----------------------
This lib key works the same as the UFO lib key with the same name. The
difference is that applications using a Designspace as the corner stone of the
font compilation process should use the lib key in that Designspace instead of
any of the UFOs. If the lib key is empty or not present in the Designspace, all
glyphs should be exported, regardless of what the same lib key in any of the
UFOs says.
.. 8-this-document:
8 This document
===============
- The package is rather new and changes are to be expected.

View File

@ -8,7 +8,6 @@ fontTools Docs
agl
cffLib
designspaceLib/index
inspect
encodings
feaLib
merge

View File

@ -1,7 +0,0 @@
#######
inspect
#######
.. automodule:: fontTools.inspect
:members:
:undoc-members:

View File

@ -5,6 +5,6 @@ from fontTools.misc.loggingTools import configLogger
log = logging.getLogger(__name__)
version = __version__ = "3.38.1.dev0"
version = __version__ = "3.39.1.dev0"
__all__ = ["version", "log", "configLogger"]

View File

@ -1128,11 +1128,14 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
self.rules.append(ruleDescriptor)
def newDefaultLocation(self):
"""Return default location in design space."""
# Without OrderedDict, output XML would be non-deterministic.
# https://github.com/LettError/designSpaceDocument/issues/10
loc = collections.OrderedDict()
for axisDescriptor in self.axes:
loc[axisDescriptor.name] = axisDescriptor.default
loc[axisDescriptor.name] = axisDescriptor.map_forward(
axisDescriptor.default
)
return loc
def updateFilenameFromPath(self, masters=True, instances=True, force=False):
@ -1176,15 +1179,25 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
return None
def findDefault(self):
# new default finder
# take the sourcedescriptor with the location at all the defaults
# if we can't find it, return None, let someone else figure it out
"""Set and return SourceDescriptor at the default location or None.
The default location is the set of all `default` values in user space
of all axes.
"""
self.default = None
# Convert the default location from user space to design space before comparing
# it against the SourceDescriptor locations (always in design space).
default_location_design = {
axis.name: axis.map_forward(self.defaultLoc[axis.name])
for axis in self.axes
}
for sourceDescriptor in self.sources:
if sourceDescriptor.location == self.defaultLoc:
# we choose you!
if sourceDescriptor.location == default_location_design:
self.default = sourceDescriptor
return sourceDescriptor
return None
def normalizeLocation(self, location):

View File

@ -821,19 +821,20 @@ class LookupFlagStatement(Statement):
markAttach, markFilter)
def asFea(self, indent=""):
res = "lookupflag"
res = []
flags = ["RightToLeft", "IgnoreBaseGlyphs", "IgnoreLigatures", "IgnoreMarks"]
curr = 1
for i in range(len(flags)):
if self.value & curr != 0:
res += " " + flags[i]
res.append(flags[i])
curr = curr << 1
if self.markAttachment is not None:
res += " MarkAttachmentType {}".format(self.markAttachment.asFea())
res.append("MarkAttachmentType {}".format(self.markAttachment.asFea()))
if self.markFilteringSet is not None:
res += " UseMarkFilteringSet {}".format(self.markFilteringSet.asFea())
res += ";"
return res
res.append("UseMarkFilteringSet {}".format(self.markFilteringSet.asFea()))
if not res:
res = ["0"]
return "lookupflag {};".format(" ".join(res))
class LookupReferenceStatement(Statement):
@ -905,7 +906,9 @@ class MarkMarkPosStatement(Statement):
class MultipleSubstStatement(Statement):
def __init__(self, prefix, glyph, suffix, replacement, forceChain, location=None):
def __init__(
self, prefix, glyph, suffix, replacement, forceChain=False, location=None
):
Statement.__init__(self, location)
self.prefix, self.glyph, self.suffix = prefix, glyph, suffix
self.replacement = replacement

View File

@ -881,7 +881,7 @@ class Builder(object):
lookup.ligatures[g] = replacement
def add_multiple_subst(self, location,
prefix, glyph, suffix, replacements, forceChain):
prefix, glyph, suffix, replacements, forceChain=False):
if prefix or suffix or forceChain:
chain = self.get_lookup_(location, ChainContextSubstBuilder)
sub = self.get_chained_lookup_(location, MultipleSubstBuilder)

View File

@ -526,6 +526,7 @@ class Parser(object):
return self.ast.LookupFlagStatement(value, location=location)
# format A: "lookupflag RightToLeft MarkAttachmentType @M;"
value_seen = False
value, markAttachment, markFilteringSet = 0, None, None
flags = {
"RightToLeft": 1, "IgnoreBaseGlyphs": 2,
@ -545,12 +546,18 @@ class Parser(object):
self.expect_keyword_("UseMarkFilteringSet")
markFilteringSet = self.parse_class_name_()
elif self.next_token_ in flags:
value_seen = True
value = value | flags[self.expect_name_()]
else:
raise FeatureLibError(
'"%s" is not a recognized lookupflag' % self.next_token_,
self.next_token_location_)
self.expect_symbol_(";")
if not any([value_seen, markAttachment, markFilteringSet]):
raise FeatureLibError(
'lookupflag must have a value', self.next_token_location_)
return self.ast.LookupFlagStatement(value,
markAttachment=markAttachment,
markFilteringSet=markFilteringSet,

View File

@ -1,271 +0,0 @@
# Copyright 2013 Google, Inc. All Rights Reserved.
#
# Google Author(s): Behdad Esfahbod
"""GUI font inspector.
"""
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from fontTools import misc, ttLib, cffLib
try:
from gi import pygtkcompat
except ImportError:
pygtkcompat = None
if pygtkcompat is not None:
pygtkcompat.enable()
pygtkcompat.enable_gtk(version='3.0')
import gtk
import sys
class Row(object):
def __init__(self, parent, index, key, value, font):
self._parent = parent
self._index = index
self._key = key
self._value = value
self._font = font
if isinstance(value, ttLib.TTFont):
self._add_font(value)
return
if not isinstance(value, basestring):
# Try sequences
is_sequence = True
try:
len(value)
iter(value)
# It's hard to differentiate list-type sequences
# from dict-type ones. Try fetching item 0.
value[0]
except (TypeError, AttributeError, KeyError, IndexError):
is_sequence = False
if is_sequence:
self._add_list(key, value)
return
if hasattr(value, '__dict__'):
self._add_object(key, value)
return
if hasattr(value, 'items'):
self._add_dict(key, value)
return
if isinstance(value, basestring):
self._value_str = '"'+value+'"'
self._children = []
return
# Everything else
self._children = []
def _filter_items(self):
items = []
for k,v in self._items:
if isinstance(v, ttLib.TTFont):
continue
if k in ['reader', 'file', 'tableTag', 'compileStatus', 'recurse']:
continue
if isinstance(k, basestring) and k[0] == '_':
continue
items.append((k,v))
self._items = items
def _add_font(self, font):
self._items = [(tag,font[tag]) for tag in font.keys()]
def _add_object(self, key, value):
# Make sure item is decompiled
try:
value.asdf # Any better way?!
except (AttributeError, KeyError, TypeError, ttLib.TTLibError):
pass
if isinstance(value, ttLib.getTableModule('glyf').Glyph):
# Glyph type needs explicit expanding to be useful
value.expand(self._font['glyf'])
if isinstance(value, misc.psCharStrings.T2CharString):
try:
value.decompile()
except TypeError: # Subroutines can't be decompiled
pass
if isinstance(value, cffLib.BaseDict):
for k in value.rawDict.keys():
getattr(value, k)
if isinstance(value, cffLib.Index):
# Load all items
for i in range(len(value)):
value[i]
# Discard offsets as should not be needed anymore
if hasattr(value, 'offsets'):
del value.offsets
self._value_str = value.__class__.__name__
if isinstance(value, ttLib.tables.DefaultTable.DefaultTable):
self._value_str += ' (%d Bytes)' % self._font.reader.tables[key].length
self._items = sorted(value.__dict__.items())
self._filter_items()
def _add_dict(self, key, value):
self._value_str = '%s of %d items' % (value.__class__.__name__, len(value))
self._items = sorted(value.items())
def _add_list(self, key, value):
if len(value) and len(value) <= 32:
self._value_str = str(value)
else:
self._value_str = '%s of %d items' % (value.__class__.__name__, len(value))
self._items = list(enumerate(value))
def __len__(self):
if hasattr(self, '_children'):
return len(self._children)
if hasattr(self, '_items'):
return len(self._items)
assert False
def _ensure_children(self):
if hasattr(self, '_children'):
return
children = []
for i,(k,v) in enumerate(self._items):
children.append(Row(self, i, k, v, self._font))
self._children = children
del self._items
def __getitem__(self, n):
if n >= len(self):
return None
if not hasattr(self, '_children'):
self._children = [None] * len(self)
c = self._children[n]
if c is None:
k,v = self._items[n]
c = self._children[n] = Row(self, n, k, v, self._font)
self._items[n] = None
return c
def get_parent(self):
return self._parent
def get_index(self):
return self._index
def get_key(self):
return self._key
def get_value(self):
return self._value
def get_value_str(self):
if hasattr(self,'_value_str'):
return self._value_str
return str(self._value)
class FontTreeModel(gtk.GenericTreeModel):
__gtype_name__ = 'FontTreeModel'
def __init__(self, font):
super(FontTreeModel, self).__init__()
self._columns = (str, str)
self.font = font
self._root = Row(None, 0, "font", font, font)
def on_get_flags(self):
return 0
def on_get_n_columns(self):
return len(self._columns)
def on_get_column_type(self, index):
return self._columns[index]
def on_get_iter(self, path):
rowref = self._root
while path:
rowref = rowref[path[0]]
path = path[1:]
return rowref
def on_get_path(self, rowref):
path = []
while rowref != self._root:
path.append(rowref.get_index())
rowref = rowref.get_parent()
path.reverse()
return tuple(path)
def on_get_value(self, rowref, column):
if column == 0:
return rowref.get_key()
else:
return rowref.get_value_str()
def on_iter_next(self, rowref):
return rowref.get_parent()[rowref.get_index() + 1]
def on_iter_children(self, rowref):
return rowref[0]
def on_iter_has_child(self, rowref):
return bool(len(rowref))
def on_iter_n_children(self, rowref):
return len(rowref)
def on_iter_nth_child(self, rowref, n):
if not rowref: rowref = self._root
return rowref[n]
def on_iter_parent(self, rowref):
return rowref.get_parent()
class Inspect(object):
def _delete_event(self, widget, event, data=None):
gtk.main_quit()
return False
def __init__(self, fontfile):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_title("%s - pyftinspect" % fontfile)
self.window.connect("delete_event", self._delete_event)
self.window.set_size_request(400, 600)
self.scrolled_window = gtk.ScrolledWindow()
self.window.add(self.scrolled_window)
self.font = ttLib.TTFont(fontfile, lazy=True)
self.treemodel = FontTreeModel(self.font)
self.treeview = gtk.TreeView(self.treemodel)
#self.treeview.set_reorderable(True)
for i in range(2):
col_name = ('Key', 'Value')[i]
col = gtk.TreeViewColumn(col_name)
col.set_sort_column_id(-1)
self.treeview.append_column(col)
cell = gtk.CellRendererText()
col.pack_start(cell, True)
col.add_attribute(cell, 'text', i)
self.treeview.set_search_column(1)
self.scrolled_window.add(self.treeview)
self.window.show_all()
def main(args=None):
if args is None:
args = sys.argv[1:]
if len(args) < 1:
print("usage: pyftinspect font...", file=sys.stderr)
return 1
for arg in args:
Inspect(arg)
gtk.main()
if __name__ == "__main__":
sys.exit(main())

View File

@ -226,6 +226,23 @@ ttLib.getTableClass('hhea').mergeMap = {
'numberOfHMetrics': recalculate,
}
ttLib.getTableClass('vhea').mergeMap = {
'*': equal,
'tableTag': equal,
'tableVersion': max,
'ascent': max,
'descent': min,
'lineGap': max,
'advanceHeightMax': max,
'minTopSideBearing': min,
'minBottomSideBearing': min,
'yMaxExtent': max,
'caretSlopeRise': first,
'caretSlopeRun': first,
'caretOffset': first,
'numberOfVMetrics': recalculate,
}
os2FsTypeMergeBitMap = {
'size': 16,
'*': lambda bit: 0,
@ -363,10 +380,18 @@ def _glyphsAreSame(glyphSet1, glyphSet2, glyph1, glyph2):
g1.width == g2.width and
(not hasattr(g1, 'height') or g1.height == g2.height))
# Valid (format, platformID, platEncID) triplets for cmap subtables containing
# Unicode BMP-only and Unicode Full Repertoire semantics.
# Cf. OpenType spec for "Platform specific encodings":
# https://docs.microsoft.com/en-us/typography/opentype/spec/name
class CmapUnicodePlatEncodings:
BMP = {(4, 3, 1), (4, 0, 3), (4, 0, 4), (4, 0, 6)}
FullRepertoire = {(12, 3, 10), (12, 0, 4), (12, 0, 6)}
@_add_method(ttLib.getTableClass('cmap'))
def merge(self, m, tables):
# TODO Handle format=14.
# Only merges 4/3/1 and 12/3/10 subtables, ignores all other subtables
# Only merge format 4 and 12 Unicode subtables, ignores all other subtables
# If there is a format 12 table for the same font, ignore the format 4 table
cmapTables = []
for fontIdx,table in enumerate(tables):
@ -374,10 +399,16 @@ def merge(self, m, tables):
format12 = None
for subtable in table.tables:
properties = (subtable.format, subtable.platformID, subtable.platEncID)
if properties == (4,3,1):
if properties in CmapUnicodePlatEncodings.BMP:
format4 = subtable
elif properties == (12,3,10):
elif properties in CmapUnicodePlatEncodings.FullRepertoire:
format12 = subtable
else:
log.warning(
"Dropped cmap subtable from font [%s]:\t"
"format %2s, platformID %2s, platEncID %2s",
fontIdx, subtable.format, subtable.platformID, subtable.platEncID
)
if format12 is not None:
cmapTables.append((format12, fontIdx))
elif format4 is not None:

View File

@ -199,8 +199,7 @@ def getIntEncoder(format):
# distinguish anymore between small ints that were supposed to
# be small fixed numbers and small ints that were just small
# ints. Hence the warning.
import sys
sys.stderr.write("Warning: 4-byte T2 number got passed to the "
log.warning("4-byte T2 number got passed to the "
"IntType handler. This should happen only when reading in "
"old XML files.\n")
code = bytechr(255) + pack(">l", value)

View File

@ -7,7 +7,7 @@ import sys
__all__ = ['basestring', 'unicode', 'unichr', 'byteord', 'bytechr', 'BytesIO',
'StringIO', 'UnicodeIO', 'strjoin', 'bytesjoin', 'tobytes', 'tostr',
'tounicode', 'Tag', 'open', 'range', 'xrange', 'round', 'Py23Error',
'SimpleNamespace', 'zip']
'SimpleNamespace', 'zip', 'RecursionError']
class Py23Error(NotImplementedError):
@ -514,6 +514,12 @@ else:
_stream = "stderr"
try:
RecursionError = RecursionError
except NameError:
RecursionError = RuntimeError
if __name__ == "__main__":
import doctest, sys
sys.exit(doctest.testmod().failed)

View File

@ -1798,17 +1798,20 @@ def subset_glyphs(self, s):
used = set()
if table.AdvWidthMap:
table.AdvWidthMap.mapping = _dict_subset(table.AdvWidthMap.mapping, s.glyphs)
if not s.options.retain_gids:
table.AdvWidthMap.mapping = _dict_subset(table.AdvWidthMap.mapping, s.glyphs)
used.update(table.AdvWidthMap.mapping.values())
else:
assert table.LsbMap is None and table.RsbMap is None, "File a bug."
used.update(s.reverseOrigGlyphMap.values())
if table.LsbMap:
table.LsbMap.mapping = _dict_subset(table.LsbMap.mapping, s.glyphs)
if not s.options.retain_gids:
table.LsbMap.mapping = _dict_subset(table.LsbMap.mapping, s.glyphs)
used.update(table.LsbMap.mapping.values())
if table.RsbMap:
table.RsbMap.mapping = _dict_subset(table.RsbMap.mapping, s.glyphs)
if not s.options.retain_gids:
table.RsbMap.mapping = _dict_subset(table.RsbMap.mapping, s.glyphs)
used.update(table.RsbMap.mapping.values())
varidx_map = varStore.VarStore_subset_varidxes(table.VarStore, used)

View File

@ -1007,7 +1007,10 @@ class Glyph(object):
allEndPts = []
for compo in self.components:
g = glyfTable[compo.glyphName]
coordinates, endPts, flags = g.getCoordinates(glyfTable)
try:
coordinates, endPts, flags = g.getCoordinates(glyfTable)
except RecursionError:
raise ttLib.TTLibError("glyph '%s' contains a recursive component reference" % compo.glyphName)
if hasattr(compo, "firstPt"):
# move according to two reference points
x1,y1 = allCoords[compo.firstPt]
@ -1080,8 +1083,12 @@ class Glyph(object):
expanding it."""
if not hasattr(self, "data"):
if remove_hinting:
self.program = ttProgram.Program()
self.program.fromBytecode([])
if self.isComposite():
if hasattr(self, "program"):
del self.program
else:
self.program = ttProgram.Program()
self.program.fromBytecode([])
# No padding to trim.
return
if not self.data:

View File

@ -1,5 +1,31 @@
- [varLib] ``load_designspace()``: Provide a default English name for the ital
axis tag.
3.39.0 (released 2019-03-19)
----------------------------
- [ttLib/glyf] Raise more specific error when encountering recursive
component references (#1545, #1546).
- [Doc/designspaceLib] Defined new ``public.skipExportGlyphs`` lib key (#1534,
unified-font-object/ufo-spec#84).
- [varLib] Use ``vmtx`` to compute vertical phantom points; or ``hhea.ascent``
and ``head.unitsPerEM`` if ``vmtx`` is missing (#1528).
- [gvar/cvar] Sort XML element's min/value/max attributes in TupleVariation
toXML to improve readability of TTX dump (#1527).
- [varLib.plot] Added support for 2D plots with only 1 variation axis (#1522).
- [designspaceLib] Use axes maps when normalizing locations in
DesignSpaceDocument (#1226, #1521), and when finding default source (#1535).
- [mutator] Set ``OVERLAP_SIMPLE`` and ``OVERLAP_COMPOUND`` glyf flags by
default in ``instantiateVariableFont``. Added ``--no-overlap`` cli option
to disable this (#1518).
- [subset] Fixed subsetting ``VVAR`` table (#1516, #1517).
Fixed subsetting an ``HVAR`` table that has an ``AdvanceWidthMap`` when the
option ``--retain-gids`` is used.
- [feaLib] Added ``forceChained`` in MultipleSubstStatement (#1511).
Fixed double indentation of ``subtable`` statement (#1512).
Added support for ``subtable`` statement in more places than just PairPos
lookups (#1520).
Handle lookupflag 0 and lookupflag without a value (#1540).
- [varLib] In ``load_designspace``, provide a default English name for the
``ital`` axis tag.
- Remove pyftinspect because it is unmaintained and bitrotted.
3.38.0 (released 2019-02-18)
----------------------------

View File

@ -136,12 +136,10 @@ important, we maintain an ordered list of glyph names in the font.
Other Tools
~~~~~~~~~~~
Commands for inspecting, merging and subsetting fonts are also
available:
Commands for merging and subsetting fonts are also available:
.. code:: sh
pyftinspect
pyftmerge
pyftsubset
@ -300,16 +298,6 @@ are required to unlock the extra features named "ufo", etc.
* `reportlab <https://pypi.python.org/pypi/reportlab>`__: Python toolkit
for generating PDFs and graphics.
- ``Lib/fontTools/inspect.py``
A GUI font inspector, requires one of the following packages:
* `PyGTK <https://pypi.python.org/pypi/PyGTK>`__: Python bindings for
GTK  2.x (only works with Python 2).
* `PyGObject <https://wiki.gnome.org/action/show/Projects/PyGObject>`__ :
Python bindings for GTK 3.x and gobject-introspection libraries (also
compatible with Python 3).
Testing
~~~~~~~

View File

@ -5,9 +5,10 @@
<labelname xml:lang="en">Wéíght</labelname>
<labelname xml:lang="fa-IR">قطر</labelname>
</axis>
<axis tag="wdth" name="width" minimum="0" maximum="1000" default="20" hidden="1">
<axis tag="wdth" name="width" minimum="0" maximum="1000" default="15" hidden="1">
<labelname xml:lang="fr">Chasse</labelname>
<map input="0" output="10"/>
<map input="15" output="20"/>
<map input="401" output="66"/>
<map input="1000" output="990"/>
</axis>

View File

@ -66,10 +66,10 @@ def test_fill_document(tmpdir):
a2 = AxisDescriptor()
a2.minimum = 0
a2.maximum = 1000
a2.default = 20
a2.default = 15
a2.name = "width"
a2.tag = "wdth"
a2.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
a2.map = [(0.0, 10.0), (15.0, 20.0), (401.0, 66.0), (1000.0, 990.0)]
a2.hidden = True
a2.labelNames[u'fr'] = u"Chasse"
doc.addAxis(a2)
@ -847,3 +847,61 @@ def test_with_with_path_object(tmpdir):
doc = DesignSpaceDocument()
doc.write(dest)
assert dest.exists()
def test_findDefault_axis_mapping():
designspace_string = """\
<?xml version='1.0' encoding='UTF-8'?>
<designspace format="4.0">
<axes>
<axis tag="wght" name="Weight" minimum="100" maximum="800" default="400">
<map input="100" output="20"/>
<map input="300" output="40"/>
<map input="400" output="80"/>
<map input="700" output="126"/>
<map input="800" output="170"/>
</axis>
<axis tag="ital" name="Italic" minimum="0" maximum="1" default="1"/>
</axes>
<sources>
<source filename="Font-Light.ufo">
<location>
<dimension name="Weight" xvalue="20"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="Font-Regular.ufo">
<location>
<dimension name="Weight" xvalue="80"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="Font-Bold.ufo">
<location>
<dimension name="Weight" xvalue="170"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="Font-LightItalic.ufo">
<location>
<dimension name="Weight" xvalue="20"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="Font-Italic.ufo">
<location>
<dimension name="Weight" xvalue="80"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="Font-BoldItalic.ufo">
<location>
<dimension name="Weight" xvalue="170"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
</sources>
</designspace>
"""
designspace = DesignSpaceDocument.fromstring(designspace_string)
assert designspace.findDefault().filename == "Font-Italic.ufo"

View File

@ -677,6 +677,7 @@ class ParserTest(unittest.TestCase):
self.assertEqual(flag.value, 9)
self.assertIsNone(flag.markAttachment)
self.assertIsNone(flag.markFilteringSet)
self.assertEqual(flag.asFea(), "lookupflag RightToLeft IgnoreMarks;")
def test_lookupflag_format_A_MarkAttachmentType(self):
flag = self.parse_lookupflag_(
@ -688,6 +689,8 @@ class ParserTest(unittest.TestCase):
self.assertEqual(flag.markAttachment.glyphSet(),
("acute", "grave", "macron"))
self.assertIsNone(flag.markFilteringSet)
self.assertEqual(flag.asFea(),
"lookupflag RightToLeft MarkAttachmentType @TOP_MARKS;")
def test_lookupflag_format_A_UseMarkFilteringSet(self):
flag = self.parse_lookupflag_(
@ -699,6 +702,8 @@ class ParserTest(unittest.TestCase):
self.assertIsInstance(flag.markFilteringSet, ast.GlyphClassName)
self.assertEqual(flag.markFilteringSet.glyphSet(),
("cedilla", "ogonek"))
self.assertEqual(flag.asFea(),
"lookupflag IgnoreLigatures UseMarkFilteringSet @BOTTOM_MARKS;")
def test_lookupflag_format_B(self):
flag = self.parse_lookupflag_("lookupflag 7;")
@ -706,6 +711,23 @@ class ParserTest(unittest.TestCase):
self.assertEqual(flag.value, 7)
self.assertIsNone(flag.markAttachment)
self.assertIsNone(flag.markFilteringSet)
self.assertEqual(flag.asFea(),
"lookupflag RightToLeft IgnoreBaseGlyphs IgnoreLigatures;")
def test_lookupflag_format_B_zero(self):
flag = self.parse_lookupflag_("lookupflag 0;")
self.assertIsInstance(flag, ast.LookupFlagStatement)
self.assertEqual(flag.value, 0)
self.assertIsNone(flag.markAttachment)
self.assertIsNone(flag.markFilteringSet)
self.assertEqual(flag.asFea(), "lookupflag 0;")
def test_lookupflag_no_value(self):
self.assertRaisesRegex(
FeatureLibError,
'lookupflag must have a value',
self.parse,
"feature test {lookupflag;} test;")
def test_lookupflag_repeated(self):
self.assertRaisesRegex(

View File

@ -148,7 +148,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True">
https://github.com/fonttools/fonttools/blob/master/LICENSE.txt
https://github.com/fonttools/fonttools/blob/master/LICENSE
</namerecord>
<namerecord nameID="18" platformID="1" platEncID="0" langID="0x0" unicode="True">
Test TTF
@ -190,7 +190,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
https://github.com/fonttools/fonttools/blob/master/LICENSE.txt
https://github.com/fonttools/fonttools/blob/master/LICENSE
</namerecord>
</name>

View File

@ -468,7 +468,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True">
https://github.com/fonttools/fonttools/blob/master/LICENSE.txt
https://github.com/fonttools/fonttools/blob/master/LICENSE
</namerecord>
<namerecord nameID="18" platformID="1" platEncID="0" langID="0x0" unicode="True">
Test TTF
@ -510,7 +510,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
https://github.com/fonttools/fonttools/blob/master/LICENSE.txt
https://github.com/fonttools/fonttools/blob/master/LICENSE
</namerecord>
</name>

View File

@ -1,8 +1,10 @@
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from fontTools.misc.fixedTools import otRound
from fontTools.ttLib import TTFont, newTable
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.ttLib import TTFont, newTable, TTLibError
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
from fontTools.ttLib.tables import ttProgram
import sys
import array
import pytest
@ -180,6 +182,13 @@ def strip_ttLibVersion(string):
class glyfTableTest(unittest.TestCase):
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
# and fires deprecation warnings if a program uses the old name.
if not hasattr(self, "assertRaisesRegex"):
self.assertRaisesRegex = self.assertRaisesRegexp
@classmethod
def setUpClass(cls):
with open(GLYF_BIN, 'rb') as f:
@ -215,6 +224,58 @@ class glyfTableTest(unittest.TestCase):
glyfData = glyfTable.compile(font)
self.assertEqual(glyfData, self.glyfData)
def test_recursiveComponent(self):
glyphSet = {}
pen_dummy = TTGlyphPen(glyphSet)
glyph_dummy = pen_dummy.glyph()
glyphSet["A"] = glyph_dummy
glyphSet["B"] = glyph_dummy
pen_A = TTGlyphPen(glyphSet)
pen_A.addComponent("B", (1, 0, 0, 1, 0, 0))
pen_B = TTGlyphPen(glyphSet)
pen_B.addComponent("A", (1, 0, 0, 1, 0, 0))
glyph_A = pen_A.glyph()
glyph_B = pen_B.glyph()
glyphSet["A"] = glyph_A
glyphSet["B"] = glyph_B
with self.assertRaisesRegex(TTLibError, "glyph '.' contains a recursive component reference"):
glyph_A.getCoordinates(glyphSet)
def test_trim_remove_hinting_composite_glyph(self):
glyphSet = {"dummy": TTGlyphPen(None).glyph()}
pen = TTGlyphPen(glyphSet)
pen.addComponent("dummy", (1, 0, 0, 1, 0, 0))
composite = pen.glyph()
p = ttProgram.Program()
p.fromAssembly(['SVTCA[0]'])
composite.program = p
glyphSet["composite"] = composite
glyfTable = newTable("glyf")
glyfTable.glyphs = glyphSet
glyfTable.glyphOrder = sorted(glyphSet)
composite.compact(glyfTable)
self.assertTrue(hasattr(composite, "data"))
# remove hinting from the compacted composite glyph, without expanding it
composite.trim(remove_hinting=True)
# check that, after expanding the glyph, we have no instructions
composite.expand(glyfTable)
self.assertFalse(hasattr(composite, "program"))
# now remove hinting from expanded composite glyph
composite.program = p
composite.trim(remove_hinting=True)
# check we have no instructions
self.assertFalse(hasattr(composite, "program"))
composite.compact(glyfTable)
if __name__ == "__main__":
import sys

View File

@ -148,7 +148,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True">
https://github.com/fonttools/fonttools/blob/master/LICENSE.txt
https://github.com/fonttools/fonttools/blob/master/LICENSE
</namerecord>
<namerecord nameID="18" platformID="1" platEncID="0" langID="0x0" unicode="True">
Test TTF
@ -190,7 +190,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
https://github.com/fonttools/fonttools/blob/master/LICENSE.txt
https://github.com/fonttools/fonttools/blob/master/LICENSE
</namerecord>
</name>

View File

@ -468,7 +468,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True">
https://github.com/fonttools/fonttools/blob/master/LICENSE.txt
https://github.com/fonttools/fonttools/blob/master/LICENSE
</namerecord>
<namerecord nameID="18" platformID="1" platEncID="0" langID="0x0" unicode="True">
Test TTF
@ -510,7 +510,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
https://github.com/fonttools/fonttools/blob/master/LICENSE.txt
https://github.com/fonttools/fonttools/blob/master/LICENSE
</namerecord>
</name>

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 3.38.1.dev0
current_version = 3.39.1.dev0
commit = True
tag = False
tag_name = {new_version}

View File

@ -352,7 +352,7 @@ def find_data_files(manpath="share/man"):
setup(
name="fonttools",
version="3.38.1.dev0",
version="3.39.1.dev0",
description="Tools to manipulate font files",
author="Just van Rossum",
author_email="just@letterror.com",
@ -374,7 +374,6 @@ setup(
"ttx = fontTools.ttx:main",
"pyftsubset = fontTools.subset:main",
"pyftmerge = fontTools.merge:main",
"pyftinspect = fontTools.inspect:main"
]
},
cmdclass={