Merge branch 'master' into partial-instancer
This commit is contained in:
commit
2ee528e2fd
@ -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.
|
||||
|
@ -8,7 +8,6 @@ fontTools Docs
|
||||
agl
|
||||
cffLib
|
||||
designspaceLib/index
|
||||
inspect
|
||||
encodings
|
||||
feaLib
|
||||
merge
|
||||
|
@ -1,7 +0,0 @@
|
||||
#######
|
||||
inspect
|
||||
#######
|
||||
|
||||
.. automodule:: fontTools.inspect
|
||||
:members:
|
||||
:undoc-members:
|
@ -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"]
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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())
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -1798,6 +1798,7 @@ def subset_glyphs(self, s):
|
||||
used = set()
|
||||
|
||||
if table.AdvWidthMap:
|
||||
if not s.options.retain_gids:
|
||||
table.AdvWidthMap.mapping = _dict_subset(table.AdvWidthMap.mapping, s.glyphs)
|
||||
used.update(table.AdvWidthMap.mapping.values())
|
||||
else:
|
||||
@ -1805,9 +1806,11 @@ def subset_glyphs(self, s):
|
||||
used.update(s.reverseOrigGlyphMap.values())
|
||||
|
||||
if table.LsbMap:
|
||||
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:
|
||||
if not s.options.retain_gids:
|
||||
table.RsbMap.mapping = _dict_subset(table.RsbMap.mapping, s.glyphs)
|
||||
used.update(table.RsbMap.mapping.values())
|
||||
|
||||
|
@ -1007,7 +1007,10 @@ class Glyph(object):
|
||||
allEndPts = []
|
||||
for compo in self.components:
|
||||
g = glyfTable[compo.glyphName]
|
||||
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,6 +1083,10 @@ class Glyph(object):
|
||||
expanding it."""
|
||||
if not hasattr(self, "data"):
|
||||
if remove_hinting:
|
||||
if self.isComposite():
|
||||
if hasattr(self, "program"):
|
||||
del self.program
|
||||
else:
|
||||
self.program = ttProgram.Program()
|
||||
self.program.fromBytecode([])
|
||||
# No padding to trim.
|
||||
|
30
NEWS.rst
30
NEWS.rst
@ -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)
|
||||
----------------------------
|
||||
|
14
README.rst
14
README.rst
@ -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
|
||||
~~~~~~~
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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(
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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}
|
||||
|
3
setup.py
3
setup.py
@ -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={
|
||||
|
Loading…
x
Reference in New Issue
Block a user