diff options
author | Haibo Huang <hhb@google.com> | 2019-03-26 08:57:41 -0700 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2019-03-26 08:57:41 -0700 |
commit | a7883b52840b4c2412f296e03722abaff64cab47 (patch) | |
tree | fd26782cbbf4c73650f8c5b42ebcb2b2e2b2be10 | |
parent | 1c9fe670c2f9e440bcf085f682803cd3956ecb72 (diff) | |
parent | c30bf2b15125e02d14893fb657b8a80e3c80cbf6 (diff) | |
download | fonttools-a7883b52840b4c2412f296e03722abaff64cab47.tar.gz |
Upgrade fonttools to 3.39.0 am: a2a985d834 am: 195ed3228f
am: c30bf2b151
Change-Id: I14f2d1dbafadb024cdceb2c5c0514ad0476644a8
90 files changed, 1470 insertions, 734 deletions
diff --git a/Doc/source/designspaceLib/readme.rst b/Doc/source/designspaceLib/readme.rst index 68a70091..2594084a 100644 --- a/Doc/source/designspaceLib/readme.rst +++ b/Doc/source/designspaceLib/readme.rst @@ -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. diff --git a/Doc/source/index.rst b/Doc/source/index.rst index 67ec88ee..b3d44cf0 100644 --- a/Doc/source/index.rst +++ b/Doc/source/index.rst @@ -8,7 +8,6 @@ fontTools Docs agl cffLib designspaceLib/index - inspect encodings feaLib merge diff --git a/Doc/source/inspect.rst b/Doc/source/inspect.rst deleted file mode 100644 index e0f65c2d..00000000 --- a/Doc/source/inspect.rst +++ /dev/null @@ -1,7 +0,0 @@ -####### -inspect -####### - -.. automodule:: fontTools.inspect - :members: - :undoc-members: diff --git a/Lib/fontTools/__init__.py b/Lib/fontTools/__init__.py index 99dc4e19..b0b99739 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.38.0" +version = __version__ = "3.39.0" __all__ = ["version", "log", "configLogger"] diff --git a/Lib/fontTools/designspaceLib/__init__.py b/Lib/fontTools/designspaceLib/__init__.py index b8b962fc..4c5be8b4 100644 --- a/Lib/fontTools/designspaceLib/__init__.py +++ b/Lib/fontTools/designspaceLib/__init__.py @@ -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,43 +1179,43 @@ 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): - # adapted from fontTools.varlib.models.normalizeLocation because: - # - this needs to work with axis names, not tags - # - this needs to accomodate anisotropic locations - # - the axes are stored differently here, it's just math + from fontTools.varLib.models import normalizeValue + new = {} for axis in self.axes: if axis.name not in location: # skipping this dimension it seems continue - v = location.get(axis.name, axis.default) - if type(v) == tuple: - v = v[0] - if v == axis.default: - v = 0.0 - elif v < axis.default: - if axis.default == axis.minimum: - v = 0.0 - else: - v = (max(v, axis.minimum) - axis.default) / (axis.default - axis.minimum) - else: - if axis.default == axis.maximum: - v = 0.0 - else: - v = (min(v, axis.maximum) - axis.default) / (axis.maximum - axis.default) - new[axis.name] = v + value = location[axis.name] + # 'anisotropic' location, take first coord only + if isinstance(value, tuple): + value = value[0] + triple = [ + axis.map_forward(v) for v in (axis.minimum, axis.default, axis.maximum) + ] + new[axis.name] = normalizeValue(value, triple) return new def normalize(self): diff --git a/Lib/fontTools/encodings/codecs.py b/Lib/fontTools/encodings/codecs.py index 30e46914..5e401f06 100644 --- a/Lib/fontTools/encodings/codecs.py +++ b/Lib/fontTools/encodings/codecs.py @@ -1,5 +1,5 @@ """Extend the Python codecs module with a few encodings that are used in OpenType (name table) -but missing from Python. See https://github.com/behdad/fonttools/issues/236 for details.""" +but missing from Python. See https://github.com/fonttools/fonttools/issues/236 for details.""" from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index 7bf64981..39dc4bcf 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -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,20 +906,24 @@ class MarkMarkPosStatement(Statement): class MultipleSubstStatement(Statement): - def __init__(self, prefix, glyph, suffix, replacement, 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 + self.forceChain = forceChain def build(self, builder): prefix = [p.glyphSet() for p in self.prefix] suffix = [s.glyphSet() for s in self.suffix] builder.add_multiple_subst( - self.location, prefix, self.glyph, suffix, self.replacement) + self.location, prefix, self.glyph, suffix, self.replacement, + self.forceChain) def asFea(self, indent=""): res = "sub " - if len(self.prefix) or len(self.suffix): + if len(self.prefix) or len(self.suffix) or self.forceChain: if len(self.prefix): res += " ".join(map(asFea, self.prefix)) + " " res += asFea(self.glyph) + "'" @@ -1088,7 +1093,7 @@ class SubtableStatement(Statement): builder.add_subtable_break(self.location) def asFea(self, indent=""): - return indent + "subtable;" + return "subtable;" class ValueRecord(Expression): diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index e521a0ed..456ae3cc 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -9,7 +9,7 @@ from fontTools.feaLib.ast import FeatureFile from fontTools.otlLib import builder as otl from fontTools.ttLib import newTable, getTableModule from fontTools.ttLib.tables import otBase, otTables -from collections import defaultdict +from collections import defaultdict, OrderedDict import itertools import logging @@ -545,7 +545,7 @@ class Builder(object): required_feature_indices = {} # ('latn', 'DEU') --> 23 scripts = {} # 'latn' --> {'DEU': [23, 24]} for feature #23,24 # Sort the feature table by feature tag: - # https://github.com/behdad/fonttools/issues/568 + # https://github.com/fonttools/fonttools/issues/568 sortFeatureTag = lambda f: (f[0][2], f[0][1], f[0][0], f[1]) for key, lookups in sorted(self.features_.items(), key=sortFeatureTag): script, lang, feature_tag = key @@ -881,8 +881,8 @@ class Builder(object): lookup.ligatures[g] = replacement def add_multiple_subst(self, location, - prefix, glyph, suffix, replacements): - if prefix or suffix: + 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) sub.mapping[glyph] = replacements @@ -918,19 +918,10 @@ class Builder(object): location) lookup.mapping[from_glyph] = to_glyph - def find_chainable_SingleSubst_(self, chain, glyphs): - """Helper for add_single_subst_chained_()""" - for _, _, _, substitutions in chain.substitutions: - for sub in substitutions: - if (isinstance(sub, SingleSubstBuilder) and - not any(g in glyphs for g in sub.mapping.keys())): - return sub - return None - def add_single_subst_chained_(self, location, prefix, suffix, mapping): - # https://github.com/behdad/fonttools/issues/512 + # https://github.com/fonttools/fonttools/issues/512 chain = self.get_lookup_(location, ChainContextSubstBuilder) - sub = self.find_chainable_SingleSubst_(chain, set(mapping.keys())) + sub = chain.find_chainable_single_subst(set(mapping.keys())) if sub is None: sub = self.get_chained_lookup_(location, SingleSubstBuilder) sub.mapping.update(mapping) @@ -996,14 +987,7 @@ class Builder(object): lookup.addClassPair(location, glyphclass1, value1, glyphclass2, value2) def add_subtable_break(self, location): - if type(self.cur_lookup_) is not PairPosBuilder: - raise FeatureLibError( - 'explicit "subtable" statement is intended for use with only ' - "Pair Adjustment Positioning Format 2 (i.e. pair class kerning)", - location - ) - lookup = self.get_lookup_(location, PairPosBuilder) - lookup.add_subtable_break(location) + self.cur_lookup_.add_subtable_break(location) def add_specific_pair_pos(self, location, glyph1, value1, glyph2, value2): lookup = self.get_lookup_(location, PairPosBuilder) @@ -1018,28 +1002,19 @@ class Builder(object): for glyph in glyphs: lookup.add_pos(location, glyph, value) - def find_chainable_SinglePos_(self, lookups, glyphs, value): - """Helper for add_single_pos_chained_()""" - for look in lookups: - if all(look.can_add(glyph, value) for glyph in glyphs): - return look - return None - def add_single_pos_chained_(self, location, prefix, suffix, pos): # https://github.com/fonttools/fonttools/issues/514 chain = self.get_lookup_(location, ChainContextPosBuilder) targets = [] for _, _, _, lookups in chain.rules: - for lookup in lookups: - if isinstance(lookup, SinglePosBuilder): - targets.append(lookup) + targets.extend(lookups) subs = [] for glyphs, value in pos: if value is None: subs.append(None) continue otValue, _ = makeOpenTypeValueRecord(value, pairPosContext=False) - sub = self.find_chainable_SinglePos_(targets, glyphs, otValue) + sub = chain.find_chainable_single_pos(targets, glyphs, otValue) if sub is None: sub = self.get_chained_lookup_(location, SinglePosBuilder) targets.append(sub) @@ -1129,6 +1104,8 @@ def makeOpenTypeValueRecord(v, pairPosContext): class LookupBuilder(object): + SUBTABLE_BREAK_ = "SUBTABLE_BREAK" + def __init__(self, font, location, table, lookup_type): self.font = font self.glyphMap = font.getReverseGlyphMap() @@ -1191,23 +1168,43 @@ class LookupBuilder(object): coverage = otl.buildCoverage(g, self.glyphMap) subtable.InputCoverage.append(coverage) + def build_subst_subtables(self, mapping, klass): + substitutions = [{}] + for key in mapping: + if key[0] == self.SUBTABLE_BREAK_: + substitutions.append({}) + else: + substitutions[-1][key] = mapping[key] + subtables = [klass(s) for s in substitutions] + return subtables + + def add_subtable_break(self, location): + log.warning(FeatureLibError( + 'unsupported "subtable" statement for lookup type', + location + )) + class AlternateSubstBuilder(LookupBuilder): def __init__(self, font, location): LookupBuilder.__init__(self, font, location, 'GSUB', 3) - self.alternates = {} + self.alternates = OrderedDict() def equals(self, other): return (LookupBuilder.equals(self, other) and self.alternates == other.alternates) def build(self): - subtable = otl.buildAlternateSubstSubtable(self.alternates) - return self.buildLookup_([subtable]) + subtables = self.build_subst_subtables(self.alternates, + otl.buildAlternateSubstSubtable) + return self.buildLookup_(subtables) def getAlternateGlyphs(self): return self.alternates + def add_subtable_break(self, location): + self.alternates[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_ + class ChainContextPosBuilder(LookupBuilder): def __init__(self, font, location): @@ -1221,6 +1218,8 @@ class ChainContextPosBuilder(LookupBuilder): def build(self): subtables = [] for (prefix, glyphs, suffix, lookups) in self.rules: + if prefix == self.SUBTABLE_BREAK_: + continue st = otTables.ChainContextPos() subtables.append(st) st.Format = 3 @@ -1238,6 +1237,21 @@ class ChainContextPosBuilder(LookupBuilder): st.PosLookupRecord.append(rec) return self.buildLookup_(subtables) + def find_chainable_single_pos(self, lookups, glyphs, value): + """Helper for add_single_pos_chained_()""" + res = None + for lookup in lookups[::-1]: + if lookup == self.SUBTABLE_BREAK_: + return res + if isinstance(lookup, SinglePosBuilder) and \ + all(lookup.can_add(glyph, value) for glyph in glyphs): + res = lookup + return res + + def add_subtable_break(self, location): + self.rules.append((self.SUBTABLE_BREAK_, self.SUBTABLE_BREAK_, + self.SUBTABLE_BREAK_, [self.SUBTABLE_BREAK_])) + class ChainContextSubstBuilder(LookupBuilder): def __init__(self, font, location): @@ -1251,6 +1265,8 @@ class ChainContextSubstBuilder(LookupBuilder): def build(self): subtables = [] for (prefix, input, suffix, lookups) in self.substitutions: + if prefix == self.SUBTABLE_BREAK_: + continue st = otTables.ChainContextSubst() subtables.append(st) st.Format = 3 @@ -1270,40 +1286,66 @@ class ChainContextSubstBuilder(LookupBuilder): def getAlternateGlyphs(self): result = {} - for (_prefix, _input, _suffix, lookups) in self.substitutions: + for (_, _, _, lookups) in self.substitutions: + if lookups == self.SUBTABLE_BREAK_: + continue for lookup in lookups: alts = lookup.getAlternateGlyphs() for glyph, replacements in alts.items(): result.setdefault(glyph, set()).update(replacements) return result + def find_chainable_single_subst(self, glyphs): + """Helper for add_single_subst_chained_()""" + res = None + for _, _, _, substitutions in self.substitutions[::-1]: + if substitutions == self.SUBTABLE_BREAK_: + return res + for sub in substitutions: + if (isinstance(sub, SingleSubstBuilder) and + not any(g in glyphs for g in sub.mapping.keys())): + res = sub + return res + + def add_subtable_break(self, location): + self.substitutions.append((self.SUBTABLE_BREAK_, self.SUBTABLE_BREAK_, + self.SUBTABLE_BREAK_, self.SUBTABLE_BREAK_)) + class LigatureSubstBuilder(LookupBuilder): def __init__(self, font, location): LookupBuilder.__init__(self, font, location, 'GSUB', 4) - self.ligatures = {} # {('f','f','i'): 'f_f_i'} + self.ligatures = OrderedDict() # {('f','f','i'): 'f_f_i'} def equals(self, other): return (LookupBuilder.equals(self, other) and self.ligatures == other.ligatures) def build(self): - subtable = otl.buildLigatureSubstSubtable(self.ligatures) - return self.buildLookup_([subtable]) + subtables = self.build_subst_subtables(self.ligatures, + otl.buildLigatureSubstSubtable) + return self.buildLookup_(subtables) + + def add_subtable_break(self, location): + self.ligatures[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_ class MultipleSubstBuilder(LookupBuilder): def __init__(self, font, location): LookupBuilder.__init__(self, font, location, 'GSUB', 2) - self.mapping = {} + self.mapping = OrderedDict() def equals(self, other): return (LookupBuilder.equals(self, other) and self.mapping == other.mapping) def build(self): - subtable = otl.buildMultipleSubstSubtable(self.mapping) - return self.buildLookup_([subtable]) + subtables = self.build_subst_subtables(self.mapping, + otl.buildMultipleSubstSubtable) + return self.buildLookup_(subtables) + + def add_subtable_break(self, location): + self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_ class CursivePosBuilder(LookupBuilder): @@ -1440,23 +1482,31 @@ class ReverseChainSingleSubstBuilder(LookupBuilder): subtables.append(st) return self.buildLookup_(subtables) + def add_subtable_break(self, location): + # Nothing to do here, each substitution is in its own subtable. + pass + class SingleSubstBuilder(LookupBuilder): def __init__(self, font, location): LookupBuilder.__init__(self, font, location, 'GSUB', 1) - self.mapping = {} + self.mapping = OrderedDict() def equals(self, other): return (LookupBuilder.equals(self, other) and self.mapping == other.mapping) def build(self): - subtable = otl.buildSingleSubstSubtable(self.mapping) - return self.buildLookup_([subtable]) + subtables = self.build_subst_subtables(self.mapping, + otl.buildSingleSubstSubtable) + return self.buildLookup_(subtables) def getAlternateGlyphs(self): return {glyph: set([repl]) for glyph, repl in self.mapping.items()} + def add_subtable_break(self, location): + self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_ + class ClassPairPosSubtableBuilder(object): def __init__(self, builder, valueFormat1, valueFormat2): @@ -1501,8 +1551,6 @@ class ClassPairPosSubtableBuilder(object): class PairPosBuilder(LookupBuilder): - SUBTABLE_BREAK_ = "SUBTABLE_BREAK" - def __init__(self, font, location): LookupBuilder.__init__(self, font, location, 'GPOS', 2) self.pairs = [] # [(gc1, value1, gc2, value2)*] diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index e4971719..9edfc46f 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -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, @@ -755,7 +762,8 @@ class Parser(object): num_lookups == 0): return self.ast.MultipleSubstStatement( old_prefix, tuple(old[0].glyphSet())[0], old_suffix, - tuple([list(n.glyphSet())[0] for n in new]), location=location) + tuple([list(n.glyphSet())[0] for n in new]), + forceChain=hasMarks, location=location) # GSUB lookup type 4: Ligature substitution. # Format: "substitute f f i by f_f_i;" @@ -1442,7 +1450,7 @@ class Parser(object): if isinstance(s, self.ast.SingleSubstStatement): has_single = not any([s.prefix, s.suffix, s.forceChain]) elif isinstance(s, self.ast.MultipleSubstStatement): - has_multiple = not any([s.prefix, s.suffix]) + has_multiple = not any([s.prefix, s.suffix, s.forceChain]) # Upgrade all single substitutions to multiple substitutions. if has_single and has_multiple: @@ -1451,7 +1459,7 @@ class Parser(object): statements[i] = self.ast.MultipleSubstStatement( s.prefix, s.glyphs[0].glyphSet()[0], s.suffix, [r.glyphSet()[0] for r in s.replacements], - location=s.location) + s.forceChain, location=s.location) def is_cur_keyword_(self, k): if self.cur_token_type_ is Lexer.NAME: diff --git a/Lib/fontTools/inspect.py b/Lib/fontTools/inspect.py deleted file mode 100644 index bdacadf3..00000000 --- a/Lib/fontTools/inspect.py +++ /dev/null @@ -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()) diff --git a/Lib/fontTools/merge.py b/Lib/fontTools/merge.py index 1005b859..36ac7565 100644 --- a/Lib/fontTools/merge.py +++ b/Lib/fontTools/merge.py @@ -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: diff --git a/Lib/fontTools/misc/encodingTools.py b/Lib/fontTools/misc/encodingTools.py index 275ae9fb..454ec9ac 100644 --- a/Lib/fontTools/misc/encodingTools.py +++ b/Lib/fontTools/misc/encodingTools.py @@ -18,7 +18,7 @@ _encodingMap = { }, 1: { # Macintosh # See - # https://github.com/behdad/fonttools/issues/236 + # https://github.com/fonttools/fonttools/issues/236 0: { # Macintosh, platEncID==0, keyed by langID 15: "mac_iceland", 17: "mac_turkish", diff --git a/Lib/fontTools/misc/psCharStrings.py b/Lib/fontTools/misc/psCharStrings.py index 34f21bb1..7fc7a26f 100644 --- a/Lib/fontTools/misc/psCharStrings.py +++ b/Lib/fontTools/misc/psCharStrings.py @@ -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) diff --git a/Lib/fontTools/misc/py23.py b/Lib/fontTools/misc/py23.py index 37020555..5cddfebc 100644 --- a/Lib/fontTools/misc/py23.py +++ b/Lib/fontTools/misc/py23.py @@ -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) diff --git a/Lib/fontTools/misc/timeTools.py b/Lib/fontTools/misc/timeTools.py index c30a3778..657d77f8 100644 --- a/Lib/fontTools/misc/timeTools.py +++ b/Lib/fontTools/misc/timeTools.py @@ -31,7 +31,7 @@ def asctime(t=None): In Python 3.x, the day of the month is right-justified, whereas on Windows Python 2.7 it is padded with zeros. - See https://github.com/behdad/fonttools/issues/455 + See https://github.com/fonttools/fonttools/issues/455 """ if t is None: t = time.localtime() diff --git a/Lib/fontTools/misc/xmlWriter.py b/Lib/fontTools/misc/xmlWriter.py index d625d3ae..ef51a79a 100644 --- a/Lib/fontTools/misc/xmlWriter.py +++ b/Lib/fontTools/misc/xmlWriter.py @@ -30,7 +30,7 @@ class XMLWriter(object): # Figure out if writer expects bytes or unicodes try: # The bytes check should be first. See: - # https://github.com/behdad/fonttools/pull/233 + # https://github.com/fonttools/fonttools/pull/233 self.file.write(b'') self.totype = tobytes except TypeError: diff --git a/Lib/fontTools/pens/pointPen.py b/Lib/fontTools/pens/pointPen.py index 415972f6..6299e863 100644 --- a/Lib/fontTools/pens/pointPen.py +++ b/Lib/fontTools/pens/pointPen.py @@ -200,7 +200,8 @@ class PointToSegmentPen(BasePointToSegmentPen): else: pen.endPath() - def addComponent(self, glyphName, transform, **kwargs): + def addComponent(self, glyphName, transform, identifier=None, **kwargs): + del identifier # unused self.pen.addComponent(glyphName, transform) diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py index 9403fc15..6e17cc33 100644 --- a/Lib/fontTools/subset/__init__.py +++ b/Lib/fontTools/subset/__init__.py @@ -208,7 +208,7 @@ Font table options: By default, the following tables are dropped: 'BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'SVG ', 'PCLT', 'LTSH' and Graphite tables: 'Feat', 'Glat', 'Gloc', 'Silf', 'Sill' - and color tables: 'CBLC', 'CBDT', 'sbix'. + and color tables: 'sbix'. The tool will attempt to subset the remaining tables. Examples: --drop-tables-='SVG ' @@ -266,7 +266,7 @@ Font naming options: --name-legacy Keep legacy (non-Unicode) 'name' table entries (0.x, 1.x etc.). XXX Note: This might be needed for some fonts that have no Unicode name - entires for English. See: https://github.com/behdad/fonttools/issues/146 + entires for English. See: https://github.com/fonttools/fonttools/issues/146 --no-name-legacy Drop legacy (non-Unicode) 'name' table entries [default] --name-languages[+|-]=<langID>[,<langID>] @@ -1377,7 +1377,7 @@ def subset_glyphs(self, s): return True -# CBDC will inherit it +# CBDT will inherit it @_add_method(ttLib.getTableClass('EBDT')) def subset_glyphs(self, s): self.strikeData = [{g: strike[g] for g in s.glyphs if g in strike} @@ -1425,7 +1425,7 @@ def subset_glyphs(self, s): @_add_method(ttLib.getTableClass('GSUB'), ttLib.getTableClass('GPOS')) def retain_empty_scripts(self): - # https://github.com/behdad/fonttools/issues/518 + # https://github.com/fonttools/fonttools/issues/518 # https://bugzilla.mozilla.org/show_bug.cgi?id=1080739#c15 return self.__class__ == ttLib.getTableClass('GSUB') @@ -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) @@ -1852,9 +1855,9 @@ def subset_glyphs(self, s): if table.TsbMap: table.TsbMap.mapping = {k:varidx_map[v] for k,v in table.TsbMap.mapping.items()} if table.BsbMap: - table.RsbMap.mapping = {k:varidx_map[v] for k,v in table.RsbMap.mapping.items()} + table.BsbMap.mapping = {k:varidx_map[v] for k,v in table.BsbMap.mapping.items()} if table.VOrgMap: - table.RsbMap.mapping = {k:varidx_map[v] for k,v in table.RsbMap.mapping.items()} + table.VOrgMap.mapping = {k:varidx_map[v] for k,v in table.VOrgMap.mapping.items()} # TODO Return emptiness... return True diff --git a/Lib/fontTools/subset/cff.py b/Lib/fontTools/subset/cff.py index f01dfe67..274071e5 100644 --- a/Lib/fontTools/subset/cff.py +++ b/Lib/fontTools/subset/cff.py @@ -101,7 +101,7 @@ def prune_pre_subset(self, font, options): # Clear useless Encoding for fontname in cff.keys(): font = cff[fontname] - # https://github.com/behdad/fonttools/issues/620 + # https://github.com/fonttools/fonttools/issues/620 font.Encoding = "StandardEncoding" return True # bool(cff.fontNames) diff --git a/Lib/fontTools/ttLib/tables/C_O_L_R_.py b/Lib/fontTools/ttLib/tables/C_O_L_R_.py index 743fa915..441242ee 100644 --- a/Lib/fontTools/ttLib/tables/C_O_L_R_.py +++ b/Lib/fontTools/ttLib/tables/C_O_L_R_.py @@ -6,7 +6,6 @@ from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * from fontTools.misc.textTools import safeEval from . import DefaultTable -import operator import struct @@ -44,12 +43,13 @@ class table_C_O_L_R_(DefaultTable.DefaultTable): self.ColorLayers = colorLayerLists = {} try: - names = list(map(operator.getitem, [glyphOrder]*numBaseGlyphRecords, gids)) + names = [glyphOrder[gid] for gid in gids] except IndexError: getGlyphName = self.getGlyphName - names = list(map(getGlyphName, gids )) + names = map(getGlyphName, gids) - list(map(operator.setitem, [colorLayerLists]*numBaseGlyphRecords, names, layerLists)) + for name, layerList in zip(names, layerLists): + colorLayerLists[name] = layerList def compile(self, ttFont): ordered = [] @@ -112,7 +112,7 @@ class table_C_O_L_R_(DefaultTable.DefaultTable): layer = LayerRecord() layer.fromXML(element[0], element[1], element[2], ttFont) layers.append (layer) - operator.setitem(self, glyphName, layers) + self[glyphName] = layers elif "value" in attrs: setattr(self, name, safeEval(attrs["value"])) diff --git a/Lib/fontTools/ttLib/tables/E_B_L_C_.py b/Lib/fontTools/ttLib/tables/E_B_L_C_.py index 0c53e7d5..8cf66007 100644 --- a/Lib/fontTools/ttLib/tables/E_B_L_C_.py +++ b/Lib/fontTools/ttLib/tables/E_B_L_C_.py @@ -115,7 +115,7 @@ class table_E_B_L_C_(DefaultTable.DefaultTable): indexSubTable.indexFormat = indexFormat indexSubTable.imageFormat = imageFormat indexSubTable.imageDataOffset = imageDataOffset - indexSubTable.decompile() # https://github.com/behdad/fonttools/issues/317 + indexSubTable.decompile() # https://github.com/fonttools/fonttools/issues/317 curStrike.indexSubTables.append(indexSubTable) def compile(self, ttFont): diff --git a/Lib/fontTools/ttLib/tables/G__l_a_t.py b/Lib/fontTools/ttLib/tables/G__l_a_t.py index 7d1f7350..b7e8281b 100644 --- a/Lib/fontTools/ttLib/tables/G__l_a_t.py +++ b/Lib/fontTools/ttLib/tables/G__l_a_t.py @@ -7,10 +7,6 @@ from functools import partial from . import DefaultTable from . import grUtils import struct, operator, warnings -try: - import lz4 -except: - lz4 = None Glat_format_0 = """ diff --git a/Lib/fontTools/ttLib/tables/TupleVariation.py b/Lib/fontTools/ttLib/tables/TupleVariation.py index 92d07a11..e2ceba4d 100644 --- a/Lib/fontTools/ttLib/tables/TupleVariation.py +++ b/Lib/fontTools/ttLib/tables/TupleVariation.py @@ -71,7 +71,13 @@ class TupleVariation(object): if minValue == defaultMinValue and maxValue == defaultMaxValue: writer.simpletag("coord", axis=axis, value=value) else: - writer.simpletag("coord", axis=axis, value=value, min=minValue, max=maxValue) + attrs = [ + ("axis", axis), + ("min", minValue), + ("value", value), + ("max", maxValue), + ] + writer.simpletag("coord", attrs) writer.newline() wrote_any_deltas = False for i, delta in enumerate(self.coordinates): diff --git a/Lib/fontTools/ttLib/tables/V_O_R_G_.py b/Lib/fontTools/ttLib/tables/V_O_R_G_.py index 8b2c317a..1b752940 100644 --- a/Lib/fontTools/ttLib/tables/V_O_R_G_.py +++ b/Lib/fontTools/ttLib/tables/V_O_R_G_.py @@ -30,27 +30,27 @@ class table_V_O_R_G_(DefaultTable.DefaultTable): self.VOriginRecords = vOrig = {} glyphOrder = ttFont.getGlyphOrder() try: - names = map(operator.getitem, [glyphOrder]*self.numVertOriginYMetrics, gids) + names = [glyphOrder[gid] for gid in gids] except IndexError: getGlyphName = self.getGlyphName - names = map(getGlyphName, gids ) + names = map(getGlyphName, gids) - list(map(operator.setitem, [vOrig]*self.numVertOriginYMetrics, names, vids)) + for name, vid in zip(names, vids): + vOrig[name] = vid def compile(self, ttFont): vorgs = list(self.VOriginRecords.values()) names = list(self.VOriginRecords.keys()) nameMap = ttFont.getReverseGlyphMap() - lenRecords = len(vorgs) try: - gids = map(operator.getitem, [nameMap]*lenRecords, names) + gids = [nameMap[name] for name in names] except KeyError: nameMap = ttFont.getReverseGlyphMap(rebuild=True) - gids = map(operator.getitem, [nameMap]*lenRecords, names) + gids = [nameMap[name] for name in names] vOriginTable = list(zip(gids, vorgs)) - self.numVertOriginYMetrics = lenRecords + self.numVertOriginYMetrics = len(vorgs) vOriginTable.sort() # must be in ascending GID order - dataList = [ struct.pack(">Hh", rec[0], rec[1]) for rec in vOriginTable] + dataList = [struct.pack(">Hh", rec[0], rec[1]) for rec in vOriginTable] header = struct.pack(">HHhH", self.majorVersion, self.minorVersion, self.defaultVertOriginY, self.numVertOriginYMetrics) dataList.insert(0, header) data = bytesjoin(dataList) diff --git a/Lib/fontTools/ttLib/tables/_c_m_a_p.py b/Lib/fontTools/ttLib/tables/_c_m_a_p.py index 1767ad4c..971a3163 100644 --- a/Lib/fontTools/ttLib/tables/_c_m_a_p.py +++ b/Lib/fontTools/ttLib/tables/_c_m_a_p.py @@ -8,7 +8,6 @@ from . import DefaultTable import sys import struct import array -import operator import logging @@ -442,13 +441,12 @@ class cmap_format_2(CmapSubtable): charCodes = [item[0] for item in items] names = [item[1] for item in items] nameMap = ttFont.getReverseGlyphMap() - lenCharCodes = len(charCodes) try: - gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) + gids = [nameMap[name] for name in names] except KeyError: nameMap = ttFont.getReverseGlyphMap(rebuild=True) try: - gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) + gids = [nameMap[name] for name in names] except KeyError: # allow virtual GIDs in format 2 tables gids = [] @@ -458,7 +456,7 @@ class cmap_format_2(CmapSubtable): except KeyError: try: if (name[:3] == 'gid'): - gid = eval(name[3:]) + gid = int(name[3:]) else: gid = ttFont.getGlyphID(name) except: @@ -744,20 +742,19 @@ class cmap_format_4(CmapSubtable): return struct.pack(">HHH", self.format, self.length, self.language) + self.data charCodes = list(self.cmap.keys()) - lenCharCodes = len(charCodes) - if lenCharCodes == 0: + if not charCodes: startCode = [0xffff] endCode = [0xffff] else: charCodes.sort() - names = list(map(operator.getitem, [self.cmap]*lenCharCodes, charCodes)) + names = [self.cmap[code] for code in charCodes] nameMap = ttFont.getReverseGlyphMap() try: - gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) + gids = [nameMap[name] for name in names] except KeyError: nameMap = ttFont.getReverseGlyphMap(rebuild=True) try: - gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) + gids = [nameMap[name] for name in names] except KeyError: # allow virtual GIDs in format 4 tables gids = [] @@ -767,7 +764,7 @@ class cmap_format_4(CmapSubtable): except KeyError: try: if (name[:3] == 'gid'): - gid = eval(name[3:]) + gid = int(name[3:]) else: gid = ttFont.getGlyphID(name) except: @@ -775,7 +772,8 @@ class cmap_format_4(CmapSubtable): gids.append(gid) cmap = {} # code:glyphID mapping - list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) + for code, gid in zip(charCodes, gids): + cmap[code] = gid # Build startCode and endCode lists. # Split the char codes in ranges of consecutive char codes, then split @@ -955,15 +953,14 @@ class cmap_format_12_or_13(CmapSubtable): if self.data: return struct.pack(">HHLLL", self.format, self.reserved, self.length, self.language, self.nGroups) + self.data charCodes = list(self.cmap.keys()) - lenCharCodes = len(charCodes) names = list(self.cmap.values()) nameMap = ttFont.getReverseGlyphMap() try: - gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) + gids = [nameMap[name] for name in names] except KeyError: nameMap = ttFont.getReverseGlyphMap(rebuild=True) try: - gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names)) + gids = [nameMap[name] for name in names] except KeyError: # allow virtual GIDs in format 12 tables gids = [] @@ -973,7 +970,7 @@ class cmap_format_12_or_13(CmapSubtable): except KeyError: try: if (name[:3] == 'gid'): - gid = eval(name[3:]) + gid = int(name[3:]) else: gid = ttFont.getGlyphID(name) except: @@ -982,7 +979,8 @@ class cmap_format_12_or_13(CmapSubtable): gids.append(gid) cmap = {} # code:glyphID mapping - list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids)) + for code, gid in zip(charCodes, gids): + cmap[code] = gid charCodes.sort() index = 0 diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py index 8b360504..b8020ca7 100644 --- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py +++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py @@ -841,7 +841,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] diff --git a/Lib/fontTools/ttLib/tables/_h_e_a_d.py b/Lib/fontTools/ttLib/tables/_h_e_a_d.py index 9275d410..4235acf4 100644 --- a/Lib/fontTools/ttLib/tables/_h_e_a_d.py +++ b/Lib/fontTools/ttLib/tables/_h_e_a_d.py @@ -46,7 +46,7 @@ class table__h_e_a_d(DefaultTable.DefaultTable): # bogus values there. Since till 2038 those bytes only can be zero, # ignore them. # - # https://github.com/behdad/fonttools/issues/99#issuecomment-66776810 + # https://github.com/fonttools/fonttools/issues/99#issuecomment-66776810 for stamp in 'created', 'modified': value = getattr(self, stamp) if value > 0xFFFFFFFF: diff --git a/Lib/fontTools/ttLib/tables/grUtils.py b/Lib/fontTools/ttLib/tables/grUtils.py index d11ac4b1..a60df234 100644 --- a/Lib/fontTools/ttLib/tables/grUtils.py +++ b/Lib/fontTools/ttLib/tables/grUtils.py @@ -1,8 +1,10 @@ import struct, warnings try: import lz4 -except: +except ImportError: lz4 = None +else: + import lz4.block #old scheme for VERSION < 0.9 otherwise use lz4.block diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py index c71cc8db..81ab354e 100644 --- a/Lib/fontTools/ttLib/tables/otBase.py +++ b/Lib/fontTools/ttLib/tables/otBase.py @@ -303,7 +303,7 @@ class OTTableWriter(object): # Certain versions of Uniscribe reject the font if the GSUB/GPOS top-level # arrays (ScriptList, FeatureList, LookupList) point to the same, possibly # empty, array. So, we don't share those. - # See: https://github.com/behdad/fonttools/issues/518 + # See: https://github.com/fonttools/fonttools/issues/518 dontShare = hasattr(self, 'DontShare') if isExtension: diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py index 4d2bc574..af2a7c6c 100644 --- a/Lib/fontTools/ttLib/tables/otTables.py +++ b/Lib/fontTools/ttLib/tables/otTables.py @@ -9,7 +9,6 @@ from __future__ import print_function, division, absolute_import, unicode_litera from fontTools.misc.py23 import * from fontTools.misc.textTools import pad, safeEval from .otBase import BaseTable, FormatSwitchingBaseTable, ValueRecord -import operator import logging import struct @@ -698,18 +697,19 @@ class SingleSubst(FormatSwitchingBaseTable): def postRead(self, rawTable, font): mapping = {} input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) - lenMapping = len(input) if self.Format == 1: delta = rawTable["DeltaGlyphID"] inputGIDS = [ font.getGlyphID(name) for name in input ] outGIDS = [ (glyphID + delta) % 65536 for glyphID in inputGIDS ] outNames = [ font.getGlyphName(glyphID) for glyphID in outGIDS ] - list(map(operator.setitem, [mapping]*lenMapping, input, outNames)) + for inp, out in zip(input, outNames): + mapping[inp] = out elif self.Format == 2: assert len(input) == rawTable["GlyphCount"], \ "invalid SingleSubstFormat2 table" subst = rawTable["Substitute"] - list(map(operator.setitem, [mapping]*lenMapping, input, subst)) + for inp, sub in zip(input, subst): + mapping[inp] = sub else: assert 0, "unknown format: %s" % self.Format self.mapping = mapping diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py index 6ec4113f..0543ee37 100644 --- a/Lib/fontTools/varLib/__init__.py +++ b/Lib/fontTools/varLib/__init__.py @@ -209,8 +209,40 @@ def _add_stat(font, axes): # TODO make this user-configurable via designspace document stat.ElidedFallbackNameID = 2 + +def _get_phantom_points(font, glyphName, defaultVerticalOrigin=None): + glyf = font["glyf"] + glyph = glyf[glyphName] + horizontalAdvanceWidth, leftSideBearing = font["hmtx"].metrics[glyphName] + if not hasattr(glyph, 'xMin'): + glyph.recalcBounds(glyf) + leftSideX = glyph.xMin - leftSideBearing + rightSideX = leftSideX + horizontalAdvanceWidth + if "vmtx" in font: + verticalAdvanceWidth, topSideBearing = font["vmtx"].metrics[glyphName] + topSideY = topSideBearing + glyph.yMax + else: + # without vmtx, use ascent as vertical origin and UPEM as vertical advance + # like HarfBuzz does + verticalAdvanceWidth = font["head"].unitsPerEm + try: + topSideY = font["hhea"].ascent + except KeyError: + # sparse masters may not contain an hhea table; use the ascent + # of the default master as the vertical origin + assert defaultVerticalOrigin is not None + topSideY = defaultVerticalOrigin + bottomSideY = topSideY - verticalAdvanceWidth + return [ + (leftSideX, 0), + (rightSideX, 0), + (0, topSideY), + (0, bottomSideY), + ] + + # TODO Move to glyf or gvar table proper -def _GetCoordinates(font, glyphName): +def _GetCoordinates(font, glyphName, defaultVerticalOrigin=None): """font, glyphName --> glyph coordinates as expected by "gvar" table The result includes four "phantom points" for the glyph metrics, @@ -228,19 +260,9 @@ def _GetCoordinates(font, glyphName): control = (glyph.numberOfContours,)+allData[1:] # Add phantom points for (left, right, top, bottom) positions. - horizontalAdvanceWidth, leftSideBearing = font["hmtx"].metrics[glyphName] - if not hasattr(glyph, 'xMin'): - glyph.recalcBounds(glyf) - leftSideX = glyph.xMin - leftSideBearing - rightSideX = leftSideX + horizontalAdvanceWidth - # XXX these are incorrect. Load vmtx and fix. - topSideY = glyph.yMax - bottomSideY = -glyph.yMin + phantomPoints = _get_phantom_points(font, glyphName, defaultVerticalOrigin) coord = coord.copy() - coord.extend([(leftSideX, 0), - (rightSideX, 0), - (0, topSideY), - (0, bottomSideY)]) + coord.extend(phantomPoints) return coord, control @@ -297,11 +319,16 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True): glyf = font['glyf'] + # use hhea.ascent of base master as default vertical origin when vmtx is missing + defaultVerticalOrigin = font['hhea'].ascent for glyph in font.getGlyphOrder(): isComposite = glyf[glyph].isComposite() - allData = [_GetCoordinates(m, glyph) for m in master_ttfs] + allData = [ + _GetCoordinates(m, glyph, defaultVerticalOrigin=defaultVerticalOrigin) + for m in master_ttfs + ] model, allData = masterModel.getSubModel(allData) allCoords = [d[0] for d in allData] @@ -688,6 +715,7 @@ def load_designspace(designspace): ('width', ('wdth', {'en': u'Width'})), ('slant', ('slnt', {'en': u'Slant'})), ('optical', ('opsz', {'en': u'Optical Size'})), + ('italic', ('ital', {'en': u'Italic'})), ]) # Setup axes diff --git a/Lib/fontTools/varLib/models.py b/Lib/fontTools/varLib/models.py index 207e30f3..9a990b7b 100644 --- a/Lib/fontTools/varLib/models.py +++ b/Lib/fontTools/varLib/models.py @@ -189,19 +189,22 @@ class VariationModel(object): 7: 0.6666666666666667}] """ - def __init__(self, locations, axisOrder=[]): + def __init__(self, locations, axisOrder=None): + if len(set(tuple(sorted(l.items())) for l in locations)) != len(locations): + raise ValueError("locations must be unique") + self.origLocations = locations - self.axisOrder = axisOrder + self.axisOrder = axisOrder if axisOrder is not None else [] locations = [{k:v for k,v in loc.items() if v != 0.} for loc in locations] - keyFunc = self.getMasterLocationsSortKeyFunc(locations, axisOrder=axisOrder) - axisPoints = keyFunc.axisPoints + keyFunc = self.getMasterLocationsSortKeyFunc(locations, axisOrder=self.axisOrder) self.locations = sorted(locations, key=keyFunc) - # TODO Assert that locations are unique. - self.mapping = [self.locations.index(l) for l in locations] # Mapping from user's master order to our master order - self.reverseMapping = [locations.index(l) for l in self.locations] # Reverse of above - self._computeMasterSupports(axisPoints, axisOrder) + # Mapping from user's master order to our master order + self.mapping = [self.locations.index(l) for l in locations] + self.reverseMapping = [locations.index(l) for l in self.locations] + + self._computeMasterSupports(keyFunc.axisPoints) self._subModels = {} def getSubModel(self, items): @@ -252,18 +255,6 @@ class VariationModel(object): ret.axisPoints = axisPoints return ret - @staticmethod - def lowerBound(value, lst): - if any(v < value for v in lst): - return max(v for v in lst if v < value) - else: - return value - @staticmethod - def upperBound(value, lst): - if any(v > value for v in lst): - return min(v for v in lst if v > value) - else: - return value def reorderMasters(self, master_list, mapping): # For changing the master data order without # recomputing supports and deltaWeights. @@ -276,7 +267,7 @@ class VariationModel(object): self._subModels = {} return new_list - def _computeMasterSupports(self, axisPoints, axisOrder): + def _computeMasterSupports(self, axisPoints): supports = [] deltaWeights = [] locations = self.locations @@ -444,6 +435,7 @@ def main(args): pprint(locs) doc.normalize() print("Normalized locations:") + locs = [s.location for s in doc.sources] pprint(locs) else: axes = [chr(c) for c in range(ord('A'), ord('Z')+1)] diff --git a/Lib/fontTools/varLib/mutator.py b/Lib/fontTools/varLib/mutator.py index 79a6f3d1..de612365 100644 --- a/Lib/fontTools/varLib/mutator.py +++ b/Lib/fontTools/varLib/mutator.py @@ -9,7 +9,7 @@ from fontTools.misc.fixedTools import floatToFixedToFloat, otRound, floatToFixed from fontTools.pens.boundsPen import BoundsPen from fontTools.ttLib import TTFont, newTable from fontTools.ttLib.tables import ttProgram -from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates +from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates, flagOverlapSimple, OVERLAP_COMPOUND from fontTools.varLib import _GetCoordinates, _SetCoordinates from fontTools.varLib.models import ( supportScalar, @@ -145,7 +145,7 @@ def interpolate_cff2_metrics(varfont, topDict, glyphOrder, loc): hmtx[gname] = tuple(entry) -def instantiateVariableFont(varfont, location, inplace=False): +def instantiateVariableFont(varfont, location, inplace=False, overlap=True): """ Generate a static instance from a variable TTFont and a dictionary defining the desired location along the variable font's axes. The location values must be specified as user-space coordinates, e.g.: @@ -154,6 +154,10 @@ def instantiateVariableFont(varfont, location, inplace=False): By default, a new TTFont object is returned. If ``inplace`` is True, the input varfont is modified and reduced to a static font. + + When the overlap parameter is defined as True, + OVERLAP_SIMPLE and OVERLAP_COMPOUND bits are set to 1. See + https://docs.microsoft.com/en-us/typography/opentype/spec/glyf """ if not inplace: # make a copy to leave input varfont unmodified @@ -308,6 +312,15 @@ def instantiateVariableFont(varfont, location, inplace=False): addidef = any(op.startswith("GETVARIATION") for op in instructions) if addidef: break + if overlap: + for glyph_name in glyf.keys(): + glyph = glyf[glyph_name] + # Set OVERLAP_COMPOUND bit for compound glyphs + if glyph.isComposite(): + glyph.components[0].flags |= OVERLAP_COMPOUND + # Set OVERLAP_SIMPLE bit for simple glyphs + elif glyph.numberOfContours > 0: + glyph.flags[0] |= flagOverlapSimple if addidef: log.info("Adding IDEF to fpgm table for GETVARIATION opcode") asm = [] @@ -404,6 +417,12 @@ def main(args=None): "-v", "--verbose", action="store_true", help="Run more verbosely.") logging_group.add_argument( "-q", "--quiet", action="store_true", help="Turn verbosity off.") + parser.add_argument( + "--no-overlap", + dest="overlap", + action="store_false", + help="Don't set OVERLAP_SIMPLE/OVERLAP_COMPOUND glyf flags." + ) options = parser.parse_args(args) varfilename = options.input @@ -428,7 +447,7 @@ def main(args=None): log.info("Loading variable font") varfont = TTFont(varfilename) - instantiateVariableFont(varfont, loc, inplace=True) + instantiateVariableFont(varfont, loc, inplace=True, overlap=options.overlap) log.info("Saving instance font %s", outfile) varfont.save(outfile) diff --git a/Lib/fontTools/varLib/plot.py b/Lib/fontTools/varLib/plot.py index b03744e9..6b134160 100644 --- a/Lib/fontTools/varLib/plot.py +++ b/Lib/fontTools/varLib/plot.py @@ -21,34 +21,84 @@ def stops(support, count=10): [b + (c - b) * i / count for i in range(count)] + \ [c] -def plotLocations(locations, axes, axis3D, **kwargs): - for loc,color in zip(locations, cycle(pyplot.cm.Set1.colors)): - axis3D.plot([loc.get(axes[0], 0)], - [loc.get(axes[1], 0)], - [1.], - 'o', - color=color, - **kwargs) - -def plotLocationsSurfaces(locations, fig, names=None, **kwargs): - - assert len(locations[0].keys()) == 2 - - if names is None: - names = [''] +def _plotLocationsDots(locations, axes, subplot, **kwargs): + for loc, color in zip(locations, cycle(pyplot.cm.Set1.colors)): + if len(axes) == 1: + subplot.plot( + [loc.get(axes[0], 0)], + [1.], + 'o', + color=color, + **kwargs + ) + elif len(axes) == 2: + subplot.plot( + [loc.get(axes[0], 0)], + [loc.get(axes[1], 0)], + [1.], + 'o', + color=color, + **kwargs + ) + else: + raise AssertionError(len(axes)) + + +def plotLocations(locations, fig, names=None, **kwargs): n = len(locations) cols = math.ceil(n**.5) rows = math.ceil(n / cols) + if names is None: + names = [None] * len(locations) + model = VariationModel(locations) names = [names[model.reverseMapping[i]] for i in range(len(names))] - ax1, ax2 = sorted(locations[0].keys()) - for i, (support,color, name) in enumerate(zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))): + axes = sorted(locations[0].keys()) + if len(axes) == 1: + _plotLocations2D( + model, axes[0], fig, cols, rows, names=names, **kwargs + ) + elif len(axes) == 2: + _plotLocations3D( + model, axes, fig, cols, rows, names=names, **kwargs + ) + else: + raise ValueError("Only 1 or 2 axes are supported") + + +def _plotLocations2D(model, axis, fig, cols, rows, names, **kwargs): + for i, (support, color, name) in enumerate( + zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names)) + ): + subplot = fig.add_subplot(rows, cols, i + 1) + if name is not None: + subplot.set_title(name) + subplot.set_xlabel(axis) + pyplot.xlim(-1.,+1.) + + Xs = support.get(axis, (-1.,0.,+1.)) + X, Y = [], [] + for x in stops(Xs): + y = supportScalar({axis:x}, support) + X.append(x) + Y.append(y) + subplot.plot(X, Y, color=color, **kwargs) + + _plotLocationsDots(model.locations, [axis], subplot) + + +def _plotLocations3D(model, axes, fig, rows, cols, names, **kwargs): + ax1, ax2 = axes + for i, (support, color, name) in enumerate( + zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names)) + ): axis3D = fig.add_subplot(rows, cols, i + 1, projection='3d') - axis3D.set_title(name) + if name is not None: + axis3D.set_title(name) axis3D.set_xlabel(ax1) axis3D.set_ylabel(ax2) pyplot.xlim(-1.,+1.) @@ -73,14 +123,14 @@ def plotLocationsSurfaces(locations, fig, names=None, **kwargs): Z.append(z) axis3D.plot(X, Y, Z, color=color, **kwargs) - plotLocations(model.locations, [ax1, ax2], axis3D) + _plotLocationsDots(model.locations, [ax1, ax2], axis3D) def plotDocument(doc, fig, **kwargs): doc.normalize() locations = [s.location for s in doc.sources] names = [s.name for s in doc.sources] - plotLocationsSurfaces(locations, fig, names, **kwargs) + plotLocations(locations, fig, names, **kwargs) def main(args=None): @@ -101,6 +151,7 @@ def main(args=None): sys.exit(1) fig = pyplot.figure() + fig.set_tight_layout(True) if len(args) == 1 and args[0].endswith('.designspace'): doc = DesignSpaceDocument() @@ -109,7 +160,7 @@ def main(args=None): else: axes = [chr(c) for c in range(ord('A'), ord('Z')+1)] locs = [dict(zip(axes, (float(v) for v in s.split(',')))) for s in args] - plotLocationsSurfaces(locs, fig) + plotLocations(locs, fig) pyplot.show() diff --git a/Lib/fonttools.egg-info/PKG-INFO b/Lib/fonttools.egg-info/PKG-INFO index fc57c8dc..d57498f3 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.38.0 +Version: 3.39.0 Summary: Tools to manipulate font files Home-page: http://github.com/fonttools/fonttools Author: Just van Rossum @@ -8,8 +8,7 @@ Author-email: just@letterror.com Maintainer: Behdad Esfahbod Maintainer-email: behdad@behdad.org License: MIT -Description: |Travis Build Status| |Appveyor Build status| |Health| |Coverage Status| - |PyPI| |Gitter Chat| +Description: |Travis Build Status| |Appveyor Build status| |Coverage Status| |PyPI| |Gitter Chat| What is this? ~~~~~~~~~~~~~ @@ -147,12 +146,10 @@ Description: |Travis Build Status| |Appveyor Build status| |Health| |Coverage St 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 @@ -311,16 +308,6 @@ Description: |Travis Build Status| |Appveyor Build status| |Health| |Coverage St * `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 ~~~~~~~ @@ -417,8 +404,6 @@ Description: |Travis Build Status| |Appveyor Build status| |Health| |Coverage St :target: https://travis-ci.org/fonttools/fonttools .. |Appveyor Build status| image:: https://ci.appveyor.com/api/projects/status/0f7fmee9as744sl7/branch/master?svg=true :target: https://ci.appveyor.com/project/fonttools/fonttools/branch/master - .. |Health| image:: https://landscape.io/github/behdad/fonttools/master/landscape.svg?style=flat - :target: https://landscape.io/github/behdad/fonttools/master .. |Coverage Status| image:: https://codecov.io/gh/fonttools/fonttools/branch/master/graph/badge.svg :target: https://codecov.io/gh/fonttools/fonttools .. |PyPI| image:: https://img.shields.io/pypi/v/fonttools.svg @@ -430,6 +415,35 @@ Description: |Travis Build Status| |Appveyor Build status| |Health| |Coverage St Changelog ~~~~~~~~~ + 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) ---------------------------- @@ -1670,13 +1684,13 @@ Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Text Processing :: Fonts Classifier: Topic :: Multimedia :: Graphics Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion -Provides-Extra: woff +Provides-Extra: symfont Provides-Extra: type1 +Provides-Extra: lxml +Provides-Extra: ufo Provides-Extra: interpolatable -Provides-Extra: symfont -Provides-Extra: graphite Provides-Extra: all -Provides-Extra: ufo -Provides-Extra: unicode -Provides-Extra: lxml +Provides-Extra: woff Provides-Extra: plot +Provides-Extra: unicode +Provides-Extra: graphite diff --git a/Lib/fonttools.egg-info/SOURCES.txt b/Lib/fonttools.egg-info/SOURCES.txt index b07edaec..3bb752fa 100644 --- a/Lib/fonttools.egg-info/SOURCES.txt +++ b/Lib/fonttools.egg-info/SOURCES.txt @@ -29,7 +29,6 @@ Doc/source/conf.py Doc/source/encodings.rst Doc/source/feaLib.rst Doc/source/index.rst -Doc/source/inspect.rst Doc/source/merge.rst Doc/source/subset.rst Doc/source/t1Lib.rst @@ -88,7 +87,6 @@ Lib/fontTools/__main__.py Lib/fontTools/afmLib.py Lib/fontTools/agl.py Lib/fontTools/fontBuilder.py -Lib/fontTools/inspect.py Lib/fontTools/merge.py Lib/fontTools/ttx.py Lib/fontTools/unicode.py @@ -348,8 +346,14 @@ Tests/feaLib/builder_test.py Tests/feaLib/error_test.py Tests/feaLib/lexer_test.py Tests/feaLib/parser_test.py +Tests/feaLib/data/AlternateSubtable.fea +Tests/feaLib/data/AlternateSubtable.ttx Tests/feaLib/data/Attach.fea Tests/feaLib/data/Attach.ttx +Tests/feaLib/data/ChainPosSubtable.fea +Tests/feaLib/data/ChainPosSubtable.ttx +Tests/feaLib/data/ChainSubstSubtable.fea +Tests/feaLib/data/ChainSubstSubtable.ttx Tests/feaLib/data/GPOS_1.fea Tests/feaLib/data/GPOS_1.ttx Tests/feaLib/data/GPOS_1_zero.fea @@ -382,8 +386,14 @@ Tests/feaLib/data/LigatureCaretByIndex.fea Tests/feaLib/data/LigatureCaretByIndex.ttx Tests/feaLib/data/LigatureCaretByPos.fea Tests/feaLib/data/LigatureCaretByPos.ttx +Tests/feaLib/data/LigatureSubtable.fea +Tests/feaLib/data/LigatureSubtable.ttx +Tests/feaLib/data/MultipleSubstSubtable.fea +Tests/feaLib/data/MultipleSubstSubtable.ttx Tests/feaLib/data/PairPosSubtable.fea Tests/feaLib/data/PairPosSubtable.ttx +Tests/feaLib/data/SingleSubstSubtable.fea +Tests/feaLib/data/SingleSubstSubtable.ttx Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.fea Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.ttx Tests/feaLib/data/ZeroValue_ChainSinglePos_vertical.fea diff --git a/Lib/fonttools.egg-info/entry_points.txt b/Lib/fonttools.egg-info/entry_points.txt index 6d707744..2c235cff 100644 --- a/Lib/fonttools.egg-info/entry_points.txt +++ b/Lib/fonttools.egg-info/entry_points.txt @@ -1,6 +1,5 @@ [console_scripts] fonttools = fontTools.__main__:main -pyftinspect = fontTools.inspect:main pyftmerge = fontTools.merge:main pyftsubset = fontTools.subset:main ttx = fontTools.ttx:main @@ -7,12 +7,12 @@ third_party { } url { type: ARCHIVE - value: "https://github.com/fonttools/fonttools/releases/download/3.38.0/fonttools-3.38.0.zip" + value: "https://github.com/fonttools/fonttools/releases/download/3.39.0/fonttools-3.39.0.zip" } - version: "3.38.0" + version: "3.39.0" last_upgrade_date { year: 2019 - month: 2 + month: 3 day: 19 } } @@ -1,3 +1,32 @@ +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) ---------------------------- @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: fonttools -Version: 3.38.0 +Version: 3.39.0 Summary: Tools to manipulate font files Home-page: http://github.com/fonttools/fonttools Author: Just van Rossum @@ -8,8 +8,7 @@ Author-email: just@letterror.com Maintainer: Behdad Esfahbod Maintainer-email: behdad@behdad.org License: MIT -Description: |Travis Build Status| |Appveyor Build status| |Health| |Coverage Status| - |PyPI| |Gitter Chat| +Description: |Travis Build Status| |Appveyor Build status| |Coverage Status| |PyPI| |Gitter Chat| What is this? ~~~~~~~~~~~~~ @@ -147,12 +146,10 @@ Description: |Travis Build Status| |Appveyor Build status| |Health| |Coverage St 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 @@ -311,16 +308,6 @@ Description: |Travis Build Status| |Appveyor Build status| |Health| |Coverage St * `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 ~~~~~~~ @@ -417,8 +404,6 @@ Description: |Travis Build Status| |Appveyor Build status| |Health| |Coverage St :target: https://travis-ci.org/fonttools/fonttools .. |Appveyor Build status| image:: https://ci.appveyor.com/api/projects/status/0f7fmee9as744sl7/branch/master?svg=true :target: https://ci.appveyor.com/project/fonttools/fonttools/branch/master - .. |Health| image:: https://landscape.io/github/behdad/fonttools/master/landscape.svg?style=flat - :target: https://landscape.io/github/behdad/fonttools/master .. |Coverage Status| image:: https://codecov.io/gh/fonttools/fonttools/branch/master/graph/badge.svg :target: https://codecov.io/gh/fonttools/fonttools .. |PyPI| image:: https://img.shields.io/pypi/v/fonttools.svg @@ -430,6 +415,35 @@ Description: |Travis Build Status| |Appveyor Build status| |Health| |Coverage St Changelog ~~~~~~~~~ + 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) ---------------------------- @@ -1670,13 +1684,13 @@ Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Text Processing :: Fonts Classifier: Topic :: Multimedia :: Graphics Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion -Provides-Extra: woff +Provides-Extra: symfont Provides-Extra: type1 +Provides-Extra: lxml +Provides-Extra: ufo Provides-Extra: interpolatable -Provides-Extra: symfont -Provides-Extra: graphite Provides-Extra: all -Provides-Extra: ufo -Provides-Extra: unicode -Provides-Extra: lxml +Provides-Extra: woff Provides-Extra: plot +Provides-Extra: unicode +Provides-Extra: graphite @@ -1,5 +1,4 @@ -|Travis Build Status| |Appveyor Build status| |Health| |Coverage Status| -|PyPI| |Gitter Chat| +|Travis Build Status| |Appveyor Build status| |Coverage Status| |PyPI| |Gitter Chat| What is this? ~~~~~~~~~~~~~ @@ -137,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 @@ -301,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 ~~~~~~~ @@ -407,8 +394,6 @@ Have fun! :target: https://travis-ci.org/fonttools/fonttools .. |Appveyor Build status| image:: https://ci.appveyor.com/api/projects/status/0f7fmee9as744sl7/branch/master?svg=true :target: https://ci.appveyor.com/project/fonttools/fonttools/branch/master -.. |Health| image:: https://landscape.io/github/behdad/fonttools/master/landscape.svg?style=flat - :target: https://landscape.io/github/behdad/fonttools/master .. |Coverage Status| image:: https://codecov.io/gh/fonttools/fonttools/branch/master/graph/badge.svg :target: https://codecov.io/gh/fonttools/fonttools .. |PyPI| image:: https://img.shields.io/pypi/v/fonttools.svg diff --git a/Snippets/svg2glif.py b/Snippets/svg2glif.py index 2dd64027..aed31005 100755 --- a/Snippets/svg2glif.py +++ b/Snippets/svg2glif.py @@ -8,8 +8,8 @@ __requires__ = ["FontTools", "ufoLib"] from fontTools.misc.py23 import SimpleNamespace from fontTools.svgLib import SVGPath -from ufoLib.pointPen import SegmentToPointPen -from ufoLib.glifLib import writeGlyphToString +from fontTools.pens.pointPen import SegmentToPointPen +from fontTools.ufoLib.glifLib import writeGlyphToString __all__ = ["svg2glif"] diff --git a/Tests/designspaceLib/data/test.designspace b/Tests/designspaceLib/data/test.designspace index 9f97ab57..901a0ee0 100644 --- a/Tests/designspaceLib/data/test.designspace +++ b/Tests/designspaceLib/data/test.designspace @@ -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> diff --git a/Tests/designspaceLib/designspace_test.py b/Tests/designspaceLib/designspace_test.py index 2044f00b..2d01c659 100644 --- a/Tests/designspaceLib/designspace_test.py +++ b/Tests/designspaceLib/designspace_test.py @@ -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) @@ -619,7 +619,7 @@ def test_normalise4(): for axis in doc.axes: r.append((axis.name, axis.map)) r.sort() - assert r == [('ddd', [(0, 0.1), (300, 0.5), (600, 0.5), (1000, 0.9)])] + assert r == [('ddd', [(0, 0.0), (300, 0.5), (600, 0.5), (1000, 1.0)])] def test_axisMapping(): # note: because designspance lib does not do any actual @@ -638,7 +638,7 @@ def test_axisMapping(): for axis in doc.axes: r.append((axis.name, axis.map)) r.sort() - assert r == [('ddd', [(0, 0.1), (300, 0.5), (600, 0.5), (1000, 0.9)])] + assert r == [('ddd', [(0, 0.0), (300, 0.5), (600, 0.5), (1000, 1.0)])] def test_rulesConditions(tmpdir): # tests of rules, conditionsets and conditions @@ -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" diff --git a/Tests/feaLib/builder_test.py b/Tests/feaLib/builder_test.py index fc1141a8..eb800782 100644 --- a/Tests/feaLib/builder_test.py +++ b/Tests/feaLib/builder_test.py @@ -70,7 +70,8 @@ class BuilderTest(unittest.TestCase): ZeroValue_SinglePos_horizontal ZeroValue_SinglePos_vertical ZeroValue_PairPos_horizontal ZeroValue_PairPos_vertical ZeroValue_ChainSinglePos_horizontal ZeroValue_ChainSinglePos_vertical - PairPosSubtable + PairPosSubtable ChainSubstSubtable ChainPosSubtable LigatureSubtable + AlternateSubtable MultipleSubstSubtable SingleSubstSubtable """.split() def __init__(self, methodName): @@ -512,17 +513,20 @@ class BuilderTest(unittest.TestCase): addOpenTypeFeatures(font, tree) assert "GSUB" in font + @unittest.skipIf(sys.version_info[0:2] < (3, 4), + "assertLogs() was introduced in 3.4") def test_unsupported_subtable_break(self): - self.assertRaisesRegex( - FeatureLibError, - 'explicit "subtable" statement is intended for .* class kerning', - self.build, - "feature liga {" - " sub f f by f_f;" - " subtable;" - " sub f i by f_i;" - "} liga;" - ) + with self.assertLogs(level='WARNING') as logs: + self.build( + "feature test {" + " pos a 10;" + " subtable;" + " pos b 10;" + "} test;" + ) + self.assertEqual(logs.output, + ['WARNING:fontTools.feaLib.builder:<features>:1:32: ' + 'unsupported "subtable" statement for lookup type']) def test_skip_featureNames_if_no_name_table(self): features = ( diff --git a/Tests/feaLib/data/AlternateSubtable.fea b/Tests/feaLib/data/AlternateSubtable.fea new file mode 100644 index 00000000..c4bce3cc --- /dev/null +++ b/Tests/feaLib/data/AlternateSubtable.fea @@ -0,0 +1,5 @@ +feature test { + sub A from [A.alt1 A.alt2]; + subtable; + sub B from [B.alt1 B.alt2]; +} test; diff --git a/Tests/feaLib/data/AlternateSubtable.ttx b/Tests/feaLib/data/AlternateSubtable.ttx new file mode 100644 index 00000000..e8119ba2 --- /dev/null +++ b/Tests/feaLib/data/AlternateSubtable.ttx @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ttFont> + + <GSUB> + <Version value="0x00010000"/> + <ScriptList> + <!-- ScriptCount=1 --> + <ScriptRecord index="0"> + <ScriptTag value="DFLT"/> + <Script> + <DefaultLangSys> + <ReqFeatureIndex value="65535"/> + <!-- FeatureCount=1 --> + <FeatureIndex index="0" value="0"/> + </DefaultLangSys> + <!-- LangSysCount=0 --> + </Script> + </ScriptRecord> + </ScriptList> + <FeatureList> + <!-- FeatureCount=1 --> + <FeatureRecord index="0"> + <FeatureTag value="test"/> + <Feature> + <!-- LookupCount=1 --> + <LookupListIndex index="0" value="0"/> + </Feature> + </FeatureRecord> + </FeatureList> + <LookupList> + <!-- LookupCount=1 --> + <Lookup index="0"> + <LookupType value="3"/> + <LookupFlag value="0"/> + <!-- SubTableCount=2 --> + <AlternateSubst index="0"> + <AlternateSet glyph="A"> + <Alternate glyph="A.alt1"/> + <Alternate glyph="A.alt2"/> + </AlternateSet> + </AlternateSubst> + <AlternateSubst index="1"> + <AlternateSet glyph="B"> + <Alternate glyph="B.alt1"/> + <Alternate glyph="B.alt2"/> + </AlternateSet> + </AlternateSubst> + </Lookup> + </LookupList> + </GSUB> + +</ttFont> diff --git a/Tests/feaLib/data/ChainPosSubtable.fea b/Tests/feaLib/data/ChainPosSubtable.fea new file mode 100644 index 00000000..46506229 --- /dev/null +++ b/Tests/feaLib/data/ChainPosSubtable.fea @@ -0,0 +1,7 @@ +feature test { + pos X [A-B]' -40 B' -40 A' -40 Y; + subtable; + pos X A' -111 Y; + subtable; + pos X B' -40 A' -111 [A-C]' -40 Y; +} test; diff --git a/Tests/feaLib/data/ChainPosSubtable.ttx b/Tests/feaLib/data/ChainPosSubtable.ttx new file mode 100644 index 00000000..a780ccb7 --- /dev/null +++ b/Tests/feaLib/data/ChainPosSubtable.ttx @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ttFont> + + <GPOS> + <Version value="0x00010000"/> + <ScriptList> + <!-- ScriptCount=1 --> + <ScriptRecord index="0"> + <ScriptTag value="DFLT"/> + <Script> + <DefaultLangSys> + <ReqFeatureIndex value="65535"/> + <!-- FeatureCount=1 --> + <FeatureIndex index="0" value="0"/> + </DefaultLangSys> + <!-- LangSysCount=0 --> + </Script> + </ScriptRecord> + </ScriptList> + <FeatureList> + <!-- FeatureCount=1 --> + <FeatureRecord index="0"> + <FeatureTag value="test"/> + <Feature> + <!-- LookupCount=1 --> + <LookupListIndex index="0" value="0"/> + </Feature> + </FeatureRecord> + </FeatureList> + <LookupList> + <!-- LookupCount=5 --> + <Lookup index="0"> + <LookupType value="8"/> + <LookupFlag value="0"/> + <!-- SubTableCount=3 --> + <ChainContextPos index="0" Format="3"> + <!-- BacktrackGlyphCount=1 --> + <BacktrackCoverage index="0"> + <Glyph value="X"/> + </BacktrackCoverage> + <!-- InputGlyphCount=3 --> + <InputCoverage index="0"> + <Glyph value="A"/> + <Glyph value="B"/> + </InputCoverage> + <InputCoverage index="1"> + <Glyph value="B"/> + </InputCoverage> + <InputCoverage index="2"> + <Glyph value="A"/> + </InputCoverage> + <!-- LookAheadGlyphCount=1 --> + <LookAheadCoverage index="0"> + <Glyph value="Y"/> + </LookAheadCoverage> + <!-- PosCount=3 --> + <PosLookupRecord index="0"> + <SequenceIndex value="0"/> + <LookupListIndex value="1"/> + </PosLookupRecord> + <PosLookupRecord index="1"> + <SequenceIndex value="1"/> + <LookupListIndex value="1"/> + </PosLookupRecord> + <PosLookupRecord index="2"> + <SequenceIndex value="2"/> + <LookupListIndex value="1"/> + </PosLookupRecord> + </ChainContextPos> + <ChainContextPos index="1" Format="3"> + <!-- BacktrackGlyphCount=1 --> + <BacktrackCoverage index="0"> + <Glyph value="X"/> + </BacktrackCoverage> + <!-- InputGlyphCount=1 --> + <InputCoverage index="0"> + <Glyph value="A"/> + </InputCoverage> + <!-- LookAheadGlyphCount=1 --> + <LookAheadCoverage index="0"> + <Glyph value="Y"/> + </LookAheadCoverage> + <!-- PosCount=1 --> + <PosLookupRecord index="0"> + <SequenceIndex value="0"/> + <LookupListIndex value="2"/> + </PosLookupRecord> + </ChainContextPos> + <ChainContextPos index="2" Format="3"> + <!-- BacktrackGlyphCount=1 --> + <BacktrackCoverage index="0"> + <Glyph value="X"/> + </BacktrackCoverage> + <!-- InputGlyphCount=3 --> + <InputCoverage index="0"> + <Glyph value="B"/> + </InputCoverage> + <InputCoverage index="1"> + <Glyph value="A"/> + </InputCoverage> + <InputCoverage index="2"> + <Glyph value="A"/> + <Glyph value="B"/> + <Glyph value="C"/> + </InputCoverage> + <!-- LookAheadGlyphCount=1 --> + <LookAheadCoverage index="0"> + <Glyph value="Y"/> + </LookAheadCoverage> + <!-- PosCount=3 --> + <PosLookupRecord index="0"> + <SequenceIndex value="0"/> + <LookupListIndex value="3"/> + </PosLookupRecord> + <PosLookupRecord index="1"> + <SequenceIndex value="1"/> + <LookupListIndex value="3"/> + </PosLookupRecord> + <PosLookupRecord index="2"> + <SequenceIndex value="2"/> + <LookupListIndex value="4"/> + </PosLookupRecord> + </ChainContextPos> + </Lookup> + <Lookup index="1"> + <LookupType value="1"/> + <LookupFlag value="0"/> + <!-- SubTableCount=1 --> + <SinglePos index="0" Format="1"> + <Coverage> + <Glyph value="A"/> + <Glyph value="B"/> + </Coverage> + <ValueFormat value="4"/> + <Value XAdvance="-40"/> + </SinglePos> + </Lookup> + <Lookup index="2"> + <LookupType value="1"/> + <LookupFlag value="0"/> + <!-- SubTableCount=1 --> + <SinglePos index="0" Format="1"> + <Coverage> + <Glyph value="A"/> + </Coverage> + <ValueFormat value="4"/> + <Value XAdvance="-111"/> + </SinglePos> + </Lookup> + <Lookup index="3"> + <LookupType value="1"/> + <LookupFlag value="0"/> + <!-- SubTableCount=1 --> + <SinglePos index="0" Format="2"> + <Coverage> + <Glyph value="A"/> + <Glyph value="B"/> + </Coverage> + <ValueFormat value="4"/> + <!-- ValueCount=2 --> + <Value index="0" XAdvance="-111"/> + <Value index="1" XAdvance="-40"/> + </SinglePos> + </Lookup> + <Lookup index="4"> + <LookupType value="1"/> + <LookupFlag value="0"/> + <!-- SubTableCount=1 --> + <SinglePos index="0" Format="1"> + <Coverage> + <Glyph value="A"/> + <Glyph value="B"/> + <Glyph value="C"/> + </Coverage> + <ValueFormat value="4"/> + <Value XAdvance="-40"/> + </SinglePos> + </Lookup> + </LookupList> + </GPOS> + +</ttFont> diff --git a/Tests/feaLib/data/ChainSubstSubtable.fea b/Tests/feaLib/data/ChainSubstSubtable.fea new file mode 100644 index 00000000..b6e959a2 --- /dev/null +++ b/Tests/feaLib/data/ChainSubstSubtable.fea @@ -0,0 +1,9 @@ +feature test { + sub G' by G.swash; + subtable; + sub H' by H.swash; + subtable; + sub G' by g; + subtable; + sub H' by H.swash; +} test; diff --git a/Tests/feaLib/data/ChainSubstSubtable.ttx b/Tests/feaLib/data/ChainSubstSubtable.ttx new file mode 100644 index 00000000..f7a09c7f --- /dev/null +++ b/Tests/feaLib/data/ChainSubstSubtable.ttx @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ttFont> + + <GSUB> + <Version value="0x00010000"/> + <ScriptList> + <!-- ScriptCount=1 --> + <ScriptRecord index="0"> + <ScriptTag value="DFLT"/> + <Script> + <DefaultLangSys> + <ReqFeatureIndex value="65535"/> + <!-- FeatureCount=1 --> + <FeatureIndex index="0" value="0"/> + </DefaultLangSys> + <!-- LangSysCount=0 --> + </Script> + </ScriptRecord> + </ScriptList> + <FeatureList> + <!-- FeatureCount=1 --> + <FeatureRecord index="0"> + <FeatureTag value="test"/> + <Feature> + <!-- LookupCount=1 --> + <LookupListIndex index="0" value="0"/> + </Feature> + </FeatureRecord> + </FeatureList> + <LookupList> + <!-- LookupCount=5 --> + <Lookup index="0"> + <LookupType value="6"/> + <LookupFlag value="0"/> + <!-- SubTableCount=4 --> + <ChainContextSubst index="0" Format="3"> + <!-- BacktrackGlyphCount=0 --> + <!-- InputGlyphCount=1 --> + <InputCoverage index="0"> + <Glyph value="G"/> + </InputCoverage> + <!-- LookAheadGlyphCount=0 --> + <!-- SubstCount=1 --> + <SubstLookupRecord index="0"> + <SequenceIndex value="0"/> + <LookupListIndex value="1"/> + </SubstLookupRecord> + </ChainContextSubst> + <ChainContextSubst index="1" Format="3"> + <!-- BacktrackGlyphCount=0 --> + <!-- InputGlyphCount=1 --> + <InputCoverage index="0"> + <Glyph value="H"/> + </InputCoverage> + <!-- LookAheadGlyphCount=0 --> + <!-- SubstCount=1 --> + <SubstLookupRecord index="0"> + <SequenceIndex value="0"/> + <LookupListIndex value="2"/> + </SubstLookupRecord> + </ChainContextSubst> + <ChainContextSubst index="2" Format="3"> + <!-- BacktrackGlyphCount=0 --> + <!-- InputGlyphCount=1 --> + <InputCoverage index="0"> + <Glyph value="G"/> + </InputCoverage> + <!-- LookAheadGlyphCount=0 --> + <!-- SubstCount=1 --> + <SubstLookupRecord index="0"> + <SequenceIndex value="0"/> + <LookupListIndex value="3"/> + </SubstLookupRecord> + </ChainContextSubst> + <ChainContextSubst index="3" Format="3"> + <!-- BacktrackGlyphCount=0 --> + <!-- InputGlyphCount=1 --> + <InputCoverage index="0"> + <Glyph value="H"/> + </InputCoverage> + <!-- LookAheadGlyphCount=0 --> + <!-- SubstCount=1 --> + <SubstLookupRecord index="0"> + <SequenceIndex value="0"/> + <LookupListIndex value="4"/> + </SubstLookupRecord> + </ChainContextSubst> + </Lookup> + <Lookup index="1"> + <LookupType value="1"/> + <LookupFlag value="0"/> + <!-- SubTableCount=1 --> + <SingleSubst index="0"> + <Substitution in="G" out="G.swash"/> + </SingleSubst> + </Lookup> + <Lookup index="2"> + <LookupType value="1"/> + <LookupFlag value="0"/> + <!-- SubTableCount=1 --> + <SingleSubst index="0"> + <Substitution in="H" out="H.swash"/> + </SingleSubst> + </Lookup> + <Lookup index="3"> + <LookupType value="1"/> + <LookupFlag value="0"/> + <!-- SubTableCount=1 --> + <SingleSubst index="0"> + <Substitution in="G" out="g"/> + </SingleSubst> + </Lookup> + <Lookup index="4"> + <LookupType value="1"/> + <LookupFlag value="0"/> + <!-- SubTableCount=1 --> + <SingleSubst index="0"> + <Substitution in="H" out="H.swash"/> + </SingleSubst> + </Lookup> + </LookupList> + </GSUB> + +</ttFont> diff --git a/Tests/feaLib/data/GPOS_1_zero.fea b/Tests/feaLib/data/GPOS_1_zero.fea index bfb51db6..2e738e2b 100644 --- a/Tests/feaLib/data/GPOS_1_zero.fea +++ b/Tests/feaLib/data/GPOS_1_zero.fea @@ -1,4 +1,4 @@ -# https://github.com/behdad/fonttools/issues/471 +# https://github.com/fonttools/fonttools/issues/471 feature test { pos zero 0; pos four 500; diff --git a/Tests/feaLib/data/GPOS_2.fea b/Tests/feaLib/data/GPOS_2.fea index b1f26d15..2948148b 100644 --- a/Tests/feaLib/data/GPOS_2.fea +++ b/Tests/feaLib/data/GPOS_2.fea @@ -1,7 +1,7 @@ languagesystem DFLT dflt; # Mixes kerning between single glyphs, and class-based kerning. -# https://github.com/behdad/fonttools/issues/456 +# https://github.com/fonttools/fonttools/issues/456 lookup MixedKerning { pos v v 14; pos [D O Q] [T V W] -26; diff --git a/Tests/feaLib/data/LigatureSubtable.fea b/Tests/feaLib/data/LigatureSubtable.fea new file mode 100644 index 00000000..aa4ec265 --- /dev/null +++ b/Tests/feaLib/data/LigatureSubtable.fea @@ -0,0 +1,5 @@ +feature test { + sub f f by f_f; + subtable; + sub f i by f_i; +} test; diff --git a/Tests/feaLib/data/LigatureSubtable.ttx b/Tests/feaLib/data/LigatureSubtable.ttx new file mode 100644 index 00000000..e13451a2 --- /dev/null +++ b/Tests/feaLib/data/LigatureSubtable.ttx @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ttFont> + + <GSUB> + <Version value="0x00010000"/> + <ScriptList> + <!-- ScriptCount=1 --> + <ScriptRecord index="0"> + <ScriptTag value="DFLT"/> + <Script> + <DefaultLangSys> + <ReqFeatureIndex value="65535"/> + <!-- FeatureCount=1 --> + <FeatureIndex index="0" value="0"/> + </DefaultLangSys> + <!-- LangSysCount=0 --> + </Script> + </ScriptRecord> + </ScriptList> + <FeatureList> + <!-- FeatureCount=1 --> + <FeatureRecord index="0"> + <FeatureTag value="test"/> + <Feature> + <!-- LookupCount=1 --> + <LookupListIndex index="0" value="0"/> + </Feature> + </FeatureRecord> + </FeatureList> + <LookupList> + <!-- LookupCount=1 --> + <Lookup index="0"> + <LookupType value="4"/> + <LookupFlag value="0"/> + <!-- SubTableCount=2 --> + <LigatureSubst index="0"> + <LigatureSet glyph="f"> + <Ligature components="f" glyph="f_f"/> + </LigatureSet> + </LigatureSubst> + <LigatureSubst index="1"> + <LigatureSet glyph="f"> + <Ligature components="i" glyph="f_i"/> + </LigatureSet> + </LigatureSubst> + </Lookup> + </LookupList> + </GSUB> + +</ttFont> diff --git a/Tests/feaLib/data/MultipleSubstSubtable.fea b/Tests/feaLib/data/MultipleSubstSubtable.fea new file mode 100644 index 00000000..4964037f --- /dev/null +++ b/Tests/feaLib/data/MultipleSubstSubtable.fea @@ -0,0 +1,5 @@ +feature test { + sub c_t by c t; + subtable; + sub f_i by f i; +} test; diff --git a/Tests/feaLib/data/MultipleSubstSubtable.ttx b/Tests/feaLib/data/MultipleSubstSubtable.ttx new file mode 100644 index 00000000..6b3afd43 --- /dev/null +++ b/Tests/feaLib/data/MultipleSubstSubtable.ttx @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ttFont> + + <GSUB> + <Version value="0x00010000"/> + <ScriptList> + <!-- ScriptCount=1 --> + <ScriptRecord index="0"> + <ScriptTag value="DFLT"/> + <Script> + <DefaultLangSys> + <ReqFeatureIndex value="65535"/> + <!-- FeatureCount=1 --> + <FeatureIndex index="0" value="0"/> + </DefaultLangSys> + <!-- LangSysCount=0 --> + </Script> + </ScriptRecord> + </ScriptList> + <FeatureList> + <!-- FeatureCount=1 --> + <FeatureRecord index="0"> + <FeatureTag value="test"/> + <Feature> + <!-- LookupCount=1 --> + <LookupListIndex index="0" value="0"/> + </Feature> + </FeatureRecord> + </FeatureList> + <LookupList> + <!-- LookupCount=1 --> + <Lookup index="0"> + <LookupType value="2"/> + <LookupFlag value="0"/> + <!-- SubTableCount=2 --> + <MultipleSubst index="0"> + <Substitution in="c_t" out="c,t"/> + </MultipleSubst> + <MultipleSubst index="1"> + <Substitution in="f_i" out="f,i"/> + </MultipleSubst> + </Lookup> + </LookupList> + </GSUB> + +</ttFont> diff --git a/Tests/feaLib/data/SingleSubstSubtable.fea b/Tests/feaLib/data/SingleSubstSubtable.fea new file mode 100644 index 00000000..f5a6da31 --- /dev/null +++ b/Tests/feaLib/data/SingleSubstSubtable.fea @@ -0,0 +1,5 @@ +feature test { + sub a by b; + subtable; + sub c by d; +} test; diff --git a/Tests/feaLib/data/SingleSubstSubtable.ttx b/Tests/feaLib/data/SingleSubstSubtable.ttx new file mode 100644 index 00000000..5030b738 --- /dev/null +++ b/Tests/feaLib/data/SingleSubstSubtable.ttx @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ttFont> + + <GSUB> + <Version value="0x00010000"/> + <ScriptList> + <!-- ScriptCount=1 --> + <ScriptRecord index="0"> + <ScriptTag value="DFLT"/> + <Script> + <DefaultLangSys> + <ReqFeatureIndex value="65535"/> + <!-- FeatureCount=1 --> + <FeatureIndex index="0" value="0"/> + </DefaultLangSys> + <!-- LangSysCount=0 --> + </Script> + </ScriptRecord> + </ScriptList> + <FeatureList> + <!-- FeatureCount=1 --> + <FeatureRecord index="0"> + <FeatureTag value="test"/> + <Feature> + <!-- LookupCount=1 --> + <LookupListIndex index="0" value="0"/> + </Feature> + </FeatureRecord> + </FeatureList> + <LookupList> + <!-- LookupCount=1 --> + <Lookup index="0"> + <LookupType value="1"/> + <LookupFlag value="0"/> + <!-- SubTableCount=2 --> + <SingleSubst index="0"> + <Substitution in="a" out="b"/> + </SingleSubst> + <SingleSubst index="1"> + <Substitution in="c" out="d"/> + </SingleSubst> + </Lookup> + </LookupList> + </GSUB> + +</ttFont> diff --git a/Tests/feaLib/data/bug453.fea b/Tests/feaLib/data/bug453.fea index fc5072e7..486632ee 100644 --- a/Tests/feaLib/data/bug453.fea +++ b/Tests/feaLib/data/bug453.fea @@ -1,4 +1,4 @@ -# https://github.com/behdad/fonttools/issues/453 +# https://github.com/fonttools/fonttools/issues/453 feature mark { lookup mark1 { markClass [acute] <anchor 150 -10> @TOP_MARKS; diff --git a/Tests/feaLib/data/bug463.fea b/Tests/feaLib/data/bug463.fea index e7e21af0..4b1910c1 100644 --- a/Tests/feaLib/data/bug463.fea +++ b/Tests/feaLib/data/bug463.fea @@ -1,4 +1,4 @@ -# https://github.com/behdad/fonttools/issues/463 +# https://github.com/fonttools/fonttools/issues/463 feature ordn { @DIGIT = [zero one two three four five six seven eight nine]; sub @DIGIT [A a]' by ordfeminine; diff --git a/Tests/feaLib/data/bug501.fea b/Tests/feaLib/data/bug501.fea index 58a4081f..06d91dda 100644 --- a/Tests/feaLib/data/bug501.fea +++ b/Tests/feaLib/data/bug501.fea @@ -1,4 +1,4 @@ -# https://github.com/behdad/fonttools/issues/501 +# https://github.com/fonttools/fonttools/issues/501 languagesystem DFLT dflt; feature test { lookup L { diff --git a/Tests/feaLib/data/bug502.fea b/Tests/feaLib/data/bug502.fea index b8130c42..3fcb94e2 100644 --- a/Tests/feaLib/data/bug502.fea +++ b/Tests/feaLib/data/bug502.fea @@ -1,4 +1,4 @@ -# https://github.com/behdad/fonttools/issues/502 +# https://github.com/fonttools/fonttools/issues/502 feature aalt { sub A by A.alt1; sub Eng by Eng.alt1; diff --git a/Tests/feaLib/data/bug504.fea b/Tests/feaLib/data/bug504.fea index 64d05baf..e6920b2b 100644 --- a/Tests/feaLib/data/bug504.fea +++ b/Tests/feaLib/data/bug504.fea @@ -1,4 +1,4 @@ -# https://github.com/behdad/fonttools/issues/504 +# https://github.com/fonttools/fonttools/issues/504 feature test { sub [a b c d] by [T E S T]; diff --git a/Tests/feaLib/data/bug505.fea b/Tests/feaLib/data/bug505.fea index 016ed1f7..742ea118 100644 --- a/Tests/feaLib/data/bug505.fea +++ b/Tests/feaLib/data/bug505.fea @@ -1,4 +1,4 @@ -# https://github.com/behdad/fonttools/issues/505 +# https://github.com/fonttools/fonttools/issues/505 languagesystem armn dflt; languagesystem avst dflt; diff --git a/Tests/feaLib/data/bug506.fea b/Tests/feaLib/data/bug506.fea index 62e4bbb9..2e1e97de 100644 --- a/Tests/feaLib/data/bug506.fea +++ b/Tests/feaLib/data/bug506.fea @@ -1,4 +1,4 @@ -# https://github.com/behdad/fonttools/issues/506 +# https://github.com/fonttools/fonttools/issues/506 feature test { sub f' i' by f_i; } test; diff --git a/Tests/feaLib/data/bug568.fea b/Tests/feaLib/data/bug568.fea index 3e879b19..d9f56f95 100644 --- a/Tests/feaLib/data/bug568.fea +++ b/Tests/feaLib/data/bug568.fea @@ -1,4 +1,4 @@ -# https://github.com/behdad/fonttools/issues/568 +# https://github.com/fonttools/fonttools/issues/568 feature tst1 { script latn; diff --git a/Tests/feaLib/parser_test.py b/Tests/feaLib/parser_test.py index bf260235..98ac1014 100644 --- a/Tests/feaLib/parser_test.py +++ b/Tests/feaLib/parser_test.py @@ -408,7 +408,7 @@ class ParserTest(unittest.TestCase): self.assertEqual(smcp.statements[0].glyphSet(), ("a", "b", "s")) def test_glyphclass_scoping_bug496(self): - # https://github.com/behdad/fonttools/issues/496 + # https://github.com/fonttools/fonttools/issues/496 f1, f2 = self.parse( "feature F1 { lookup L { @GLYPHCLASS = [A B C];} L; } F1;" "feature F2 { sub @GLYPHCLASS by D; } F2;" @@ -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( @@ -1289,6 +1311,14 @@ class ParserTest(unittest.TestCase): self.assertEqual(sub.glyph, "f_f_i") self.assertEqual(sub.replacement, ("f", "f", "i")) + def test_substitute_multiple_force_chained(self): + doc = self.parse("lookup L {sub f_f_i' by f f i;} L;") + sub = doc.statements[0].statements[0] + self.assertIsInstance(sub, ast.MultipleSubstStatement) + self.assertEqual(sub.glyph, "f_f_i") + self.assertEqual(sub.replacement, ("f", "f", "i")) + self.assertEqual(sub.asFea(), "sub f_f_i' by f f i;") + def test_substitute_multiple_by_mutliple(self): self.assertRaisesRegex( FeatureLibError, diff --git a/Tests/misc/xmlWriter_test.py b/Tests/misc/xmlWriter_test.py index 0930e1ce..ac8a789e 100644 --- a/Tests/misc/xmlWriter_test.py +++ b/Tests/misc/xmlWriter_test.py @@ -26,19 +26,19 @@ class TestXMLWriter(unittest.TestCase): writer.file.getvalue()) def test_encoding_utf8(self): - # https://github.com/behdad/fonttools/issues/246 + # https://github.com/fonttools/fonttools/issues/246 writer = XMLWriter(BytesIO(), encoding="utf8") self.assertEqual(b'<?xml version="1.0" encoding="UTF-8"?>' + linesep, writer.file.getvalue()) def test_encoding_UTF_8(self): - # https://github.com/behdad/fonttools/issues/246 + # https://github.com/fonttools/fonttools/issues/246 writer = XMLWriter(BytesIO(), encoding="UTF-8") self.assertEqual(b'<?xml version="1.0" encoding="UTF-8"?>' + linesep, writer.file.getvalue()) def test_encoding_UTF8(self): - # https://github.com/behdad/fonttools/issues/246 + # https://github.com/fonttools/fonttools/issues/246 writer = XMLWriter(BytesIO(), encoding="UTF8") self.assertEqual(b'<?xml version="1.0" encoding="UTF-8"?>' + linesep, writer.file.getvalue()) diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py index d78c496b..2fc3b8af 100644 --- a/Tests/subset/subset_test.py +++ b/Tests/subset/subset_test.py @@ -298,7 +298,7 @@ class SubsetTest(unittest.TestCase): self.expect_ttx(subsetfont, self.getpath("expect_prop_1.ttx"), ["prop"]) def test_options(self): - # https://github.com/behdad/fonttools/issues/413 + # https://github.com/fonttools/fonttools/issues/413 opt1 = subset.Options() self.assertTrue('Xyz-' not in opt1.layout_features) opt2 = subset.Options() diff --git a/Tests/ttLib/data/TestOTF-Regular.otx b/Tests/ttLib/data/TestOTF-Regular.otx index 573fe24c..92e0b2f6 100644 --- a/Tests/ttLib/data/TestOTF-Regular.otx +++ b/Tests/ttLib/data/TestOTF-Regular.otx @@ -142,13 +142,13 @@ FontTools </namerecord> <namerecord nameID="11" platformID="1" platEncID="0" langID="0x0" unicode="True"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="12" platformID="1" platEncID="0" langID="0x0" unicode="True"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True"> - https://github.com/behdad/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 @@ -184,13 +184,13 @@ FontTools </namerecord> <namerecord nameID="11" platformID="3" platEncID="1" langID="0x409"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="12" platformID="3" platEncID="1" langID="0x409"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409"> - https://github.com/behdad/fonttools/blob/master/LICENSE.txt + https://github.com/fonttools/fonttools/blob/master/LICENSE </namerecord> </name> diff --git a/Tests/ttLib/data/TestTTF-Regular.ttx b/Tests/ttLib/data/TestTTF-Regular.ttx index 8ffcefed..1f1dd2b4 100644 --- a/Tests/ttLib/data/TestTTF-Regular.ttx +++ b/Tests/ttLib/data/TestTTF-Regular.ttx @@ -462,13 +462,13 @@ FontTools </namerecord> <namerecord nameID="11" platformID="1" platEncID="0" langID="0x0" unicode="True"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="12" platformID="1" platEncID="0" langID="0x0" unicode="True"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True"> - https://github.com/behdad/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 @@ -504,13 +504,13 @@ FontTools </namerecord> <namerecord nameID="11" platformID="3" platEncID="1" langID="0x409"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="12" platformID="3" platEncID="1" langID="0x409"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409"> - https://github.com/behdad/fonttools/blob/master/LICENSE.txt + https://github.com/fonttools/fonttools/blob/master/LICENSE </namerecord> </name> diff --git a/Tests/ttLib/tables/TupleVariation_test.py b/Tests/ttLib/tables/TupleVariation_test.py index 6986d5b6..aab4cba0 100644 --- a/Tests/ttLib/tables/TupleVariation_test.py +++ b/Tests/ttLib/tables/TupleVariation_test.py @@ -107,7 +107,7 @@ class TupleVariationTest(unittest.TestCase): self.assertIn("bad delta format", [r.msg for r in captor.records]) self.assertEqual([ '<tuple>', - '<coord axis="wdth" max="0.5" min="0.3" value="0.4"/>', + '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>', '<!-- bad delta #0 -->', '</tuple>', ], TupleVariationTest.xml_lines(writer)) @@ -118,7 +118,7 @@ class TupleVariationTest(unittest.TestCase): g.toXML(writer, ["wdth", "wght", "opsz"]) self.assertEqual([ '<tuple>', - '<coord axis="wdth" max="0.5" min="0.3" value="0.4"/>', + '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>', '<coord axis="wght" value="1.0"/>', '<coord axis="opsz" value="-0.7"/>', '<delta cvt="0" value="42"/>', @@ -134,7 +134,7 @@ class TupleVariationTest(unittest.TestCase): g.toXML(writer, ["wdth", "wght", "opsz"]) self.assertEqual([ '<tuple>', - '<coord axis="wdth" max="0.5" min="0.3" value="0.4"/>', + '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>', '<coord axis="wght" value="1.0"/>', '<coord axis="opsz" value="-0.7"/>', '<delta pt="0" x="9" y="8"/>', @@ -370,7 +370,7 @@ class TupleVariationTest(unittest.TestCase): self.assertEqual(({"wght": -1.0, "wdth": 0.5}, 6), decompileCoord(["wght", "wdth"], data, 2)) def test_decompileCoord_roundTrip(self): - # Make sure we are not affected by https://github.com/behdad/fonttools/issues/286 + # Make sure we are not affected by https://github.com/fonttools/fonttools/issues/286 data = deHexStr("7F B9 80 35") values, _ = TupleVariation.decompileCoord_(["wght", "wdth"], data, 0) axisValues = {axis:(val, val, val) for axis, val in values.items()} diff --git a/Tests/ttLib/tables/_c_m_a_p_test.py b/Tests/ttLib/tables/_c_m_a_p_test.py index 306d0489..233cd14f 100644 --- a/Tests/ttLib/tables/_c_m_a_p_test.py +++ b/Tests/ttLib/tables/_c_m_a_p_test.py @@ -51,6 +51,47 @@ class CmapSubtableTest(unittest.TestCase): self.assertEqual(subtable.getEncoding("ascii"), "ascii") self.assertEqual(subtable.getEncoding(default="xyz"), "xyz") + def test_compile_2(self): + subtable = self.makeSubtable(2, 1, 2, 0) + subtable.cmap = {c: "cid%05d" % c for c in range(32, 8192)} + font = ttLib.TTFont() + font.setGlyphOrder([".notdef"] + list(subtable.cmap.values())) + data = subtable.compile(font) + + subtable2 = CmapSubtable.newSubtable(2) + subtable2.decompile(data, font) + self.assertEqual(subtable2.cmap, subtable.cmap) + + def test_compile_2_rebuild_rev_glyph_order(self): + for fmt in [2, 4, 12]: + subtable = self.makeSubtable(fmt, 1, 2, 0) + subtable.cmap = {c: "cid%05d" % c for c in range(32, 8192)} + font = ttLib.TTFont() + font.setGlyphOrder([".notdef"] + list(subtable.cmap.values())) + font._reverseGlyphOrderDict = {} # force first KeyError branch in subtable.compile() + data = subtable.compile(font) + subtable2 = CmapSubtable.newSubtable(fmt) + subtable2.decompile(data, font) + self.assertEqual(subtable2.cmap, subtable.cmap, str(fmt)) + + def test_compile_2_gids(self): + for fmt in [2, 4, 12]: + subtable = self.makeSubtable(fmt, 1, 3, 0) + subtable.cmap = {0x0041:'gid001', 0x0042:'gid002'} + font = ttLib.TTFont() + font.setGlyphOrder([".notdef"]) + data = subtable.compile(font) + + def test_compile_decompile_4_empty(self): + subtable = self.makeSubtable(4, 3, 1, 0) + subtable.cmap = {} + font = ttLib.TTFont() + font.setGlyphOrder([]) + data = subtable.compile(font) + subtable2 = CmapSubtable.newSubtable(4) + subtable2.decompile(data, font) + self.assertEqual(subtable2.cmap, {}) + def test_decompile_4(self): subtable = CmapSubtable.newSubtable(4) font = ttLib.TTFont() diff --git a/Tests/ttLib/tables/_g_l_y_f_test.py b/Tests/ttLib/tables/_g_l_y_f_test.py index d0786924..00c74bcf 100644 --- a/Tests/ttLib/tables/_g_l_y_f_test.py +++ b/Tests/ttLib/tables/_g_l_y_f_test.py @@ -1,7 +1,8 @@ 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 import sys import array @@ -180,6 +181,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 +223,23 @@ 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) + if __name__ == "__main__": import sys diff --git a/Tests/ttLib/tables/_g_v_a_r_test.py b/Tests/ttLib/tables/_g_v_a_r_test.py index 64adbb61..ebae5806 100644 --- a/Tests/ttLib/tables/_g_v_a_r_test.py +++ b/Tests/ttLib/tables/_g_v_a_r_test.py @@ -93,7 +93,7 @@ GVAR_XML = [ '</glyphVariations>', '<glyphVariations glyph="I">', ' <tuple>', - ' <coord axis="wght" max="1.0" min="0.0" value="0.5"/>', + ' <coord axis="wght" min="0.0" value="0.5" max="1.0"/>', ' <delta pt="0" x="3" y="3"/>', ' <delta pt="1" x="1" y="1"/>', ' <delta pt="2" x="4" y="4"/>', diff --git a/Tests/ttLib/tables/_n_a_m_e_test.py b/Tests/ttLib/tables/_n_a_m_e_test.py index fde14bc8..136b63f3 100644 --- a/Tests/ttLib/tables/_n_a_m_e_test.py +++ b/Tests/ttLib/tables/_n_a_m_e_test.py @@ -171,7 +171,7 @@ class NameTableTest(unittest.TestCase): captor.assertRegex("cannot store language la into 'ltag' table") def test_decompile_badOffset(self): - # https://github.com/behdad/fonttools/issues/525 + # https://github.com/fonttools/fonttools/issues/525 table = table__n_a_m_e() badRecord = { "platformID": 1, diff --git a/Tests/ttLib/tables/otTables_test.py b/Tests/ttLib/tables/otTables_test.py index 5a771e61..f39e27c2 100644 --- a/Tests/ttLib/tables/otTables_test.py +++ b/Tests/ttLib/tables/otTables_test.py @@ -166,7 +166,7 @@ class MultipleSubstTest(unittest.TestCase): {'c_t': ['c', 't'], 'f_f_i': ['f', 'f', 'i']}) def test_fromXML_oldFormat_bug385(self): - # https://github.com/behdad/fonttools/issues/385 + # https://github.com/fonttools/fonttools/issues/385 table = otTables.MultipleSubst() table.Format = 1 for name, attrs, content in parseXML( diff --git a/Tests/ttx/data/TestOTF.ttx b/Tests/ttx/data/TestOTF.ttx index 852dacfa..96f18449 100644 --- a/Tests/ttx/data/TestOTF.ttx +++ b/Tests/ttx/data/TestOTF.ttx @@ -142,13 +142,13 @@ FontTools </namerecord> <namerecord nameID="11" platformID="1" platEncID="0" langID="0x0" unicode="True"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="12" platformID="1" platEncID="0" langID="0x0" unicode="True"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True"> - https://github.com/behdad/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 @@ -184,13 +184,13 @@ FontTools </namerecord> <namerecord nameID="11" platformID="3" platEncID="1" langID="0x409"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="12" platformID="3" platEncID="1" langID="0x409"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409"> - https://github.com/behdad/fonttools/blob/master/LICENSE.txt + https://github.com/fonttools/fonttools/blob/master/LICENSE </namerecord> </name> diff --git a/Tests/ttx/data/TestTTF.ttx b/Tests/ttx/data/TestTTF.ttx index c283a295..66caf6ce 100644 --- a/Tests/ttx/data/TestTTF.ttx +++ b/Tests/ttx/data/TestTTF.ttx @@ -462,13 +462,13 @@ FontTools </namerecord> <namerecord nameID="11" platformID="1" platEncID="0" langID="0x0" unicode="True"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="12" platformID="1" platEncID="0" langID="0x0" unicode="True"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True"> - https://github.com/behdad/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 @@ -504,13 +504,13 @@ FontTools </namerecord> <namerecord nameID="11" platformID="3" platEncID="1" langID="0x409"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="12" platformID="3" platEncID="1" langID="0x409"> - https://github.com/behdad/fonttools + https://github.com/fonttools/fonttools </namerecord> <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409"> - https://github.com/behdad/fonttools/blob/master/LICENSE.txt + https://github.com/fonttools/fonttools/blob/master/LICENSE </namerecord> </name> diff --git a/Tests/varLib/data/test_results/Build.ttx b/Tests/varLib/data/test_results/Build.ttx index 5d665e35..1bc1880b 100644 --- a/Tests/varLib/data/test_results/Build.ttx +++ b/Tests/varLib/data/test_results/Build.ttx @@ -281,7 +281,7 @@ <delta pt="15" x="0" y="0"/> <delta pt="16" x="0" y="0"/> <delta pt="17" x="0" y="0"/> - <delta pt="18" x="0" y="7"/> + <delta pt="18" x="0" y="0"/> <delta pt="19" x="0" y="0"/> </tuple> <tuple> @@ -304,7 +304,7 @@ <delta pt="15" x="0" y="0"/> <delta pt="16" x="0" y="0"/> <delta pt="17" x="0" y="0"/> - <delta pt="18" x="0" y="-18"/> + <delta pt="18" x="0" y="0"/> <delta pt="19" x="0" y="0"/> </tuple> <tuple> @@ -424,7 +424,7 @@ <delta pt="23" x="-2" y="0"/> <delta pt="24" x="0" y="0"/> <delta pt="25" x="-10" y="0"/> - <delta pt="26" x="0" y="9"/> + <delta pt="26" x="0" y="0"/> <delta pt="27" x="0" y="0"/> </tuple> <tuple> @@ -455,7 +455,7 @@ <delta pt="23" x="12" y="0"/> <delta pt="24" x="0" y="0"/> <delta pt="25" x="17" y="0"/> - <delta pt="26" x="0" y="-23"/> + <delta pt="26" x="0" y="0"/> <delta pt="27" x="0" y="0"/> </tuple> <tuple> @@ -620,7 +620,7 @@ <delta pt="60" x="33" y="-1"/> <delta pt="61" x="0" y="0"/> <delta pt="62" x="-3" y="0"/> - <delta pt="63" x="0" y="-4"/> + <delta pt="63" x="0" y="0"/> <delta pt="64" x="0" y="0"/> </tuple> <tuple> @@ -688,8 +688,8 @@ <delta pt="60" x="-25" y="-13"/> <delta pt="61" x="0" y="0"/> <delta pt="62" x="32" y="0"/> - <delta pt="63" x="0" y="16"/> - <delta pt="64" x="0" y="3"/> + <delta pt="63" x="0" y="0"/> + <delta pt="64" x="0" y="0"/> </tuple> <tuple> <coord axis="cntr" value="1.0"/> @@ -965,7 +965,7 @@ <delta pt="61" x="-15" y="0"/> <delta pt="62" x="0" y="0"/> <delta pt="63" x="-7" y="0"/> - <delta pt="64" x="0" y="12"/> + <delta pt="64" x="0" y="0"/> <delta pt="65" x="0" y="0"/> </tuple> <tuple> @@ -1034,7 +1034,7 @@ <delta pt="61" x="39" y="0"/> <delta pt="62" x="0" y="0"/> <delta pt="63" x="63" y="0"/> - <delta pt="64" x="0" y="-19"/> + <delta pt="64" x="0" y="0"/> <delta pt="65" x="0" y="0"/> </tuple> <tuple> @@ -1314,7 +1314,7 @@ <delta pt="61" x="-15" y="0"/> <delta pt="62" x="0" y="0"/> <delta pt="63" x="-7" y="0"/> - <delta pt="64" x="0" y="12"/> + <delta pt="64" x="0" y="0"/> <delta pt="65" x="0" y="0"/> </tuple> <tuple> @@ -1383,7 +1383,7 @@ <delta pt="61" x="49" y="0"/> <delta pt="62" x="0" y="0"/> <delta pt="63" x="63" y="0"/> - <delta pt="64" x="0" y="-19"/> + <delta pt="64" x="0" y="0"/> <delta pt="65" x="0" y="0"/> </tuple> <tuple> diff --git a/Tests/varLib/data/test_results/BuildMain.ttx b/Tests/varLib/data/test_results/BuildMain.ttx index 84a75736..6875c712 100644 --- a/Tests/varLib/data/test_results/BuildMain.ttx +++ b/Tests/varLib/data/test_results/BuildMain.ttx @@ -927,7 +927,7 @@ <delta pt="15" x="0" y="0"/> <delta pt="16" x="0" y="0"/> <delta pt="17" x="0" y="0"/> - <delta pt="18" x="0" y="7"/> + <delta pt="18" x="0" y="0"/> <delta pt="19" x="0" y="0"/> </tuple> <tuple> @@ -950,7 +950,7 @@ <delta pt="15" x="0" y="0"/> <delta pt="16" x="0" y="0"/> <delta pt="17" x="0" y="0"/> - <delta pt="18" x="0" y="-18"/> + <delta pt="18" x="0" y="0"/> <delta pt="19" x="0" y="0"/> </tuple> <tuple> @@ -1070,7 +1070,7 @@ <delta pt="23" x="-2" y="0"/> <delta pt="24" x="0" y="0"/> <delta pt="25" x="-10" y="0"/> - <delta pt="26" x="0" y="9"/> + <delta pt="26" x="0" y="0"/> <delta pt="27" x="0" y="0"/> </tuple> <tuple> @@ -1101,7 +1101,7 @@ <delta pt="23" x="12" y="0"/> <delta pt="24" x="0" y="0"/> <delta pt="25" x="17" y="0"/> - <delta pt="26" x="0" y="-23"/> + <delta pt="26" x="0" y="0"/> <delta pt="27" x="0" y="0"/> </tuple> <tuple> @@ -1266,7 +1266,7 @@ <delta pt="60" x="33" y="-1"/> <delta pt="61" x="0" y="0"/> <delta pt="62" x="-3" y="0"/> - <delta pt="63" x="0" y="-4"/> + <delta pt="63" x="0" y="0"/> <delta pt="64" x="0" y="0"/> </tuple> <tuple> @@ -1334,8 +1334,8 @@ <delta pt="60" x="-25" y="-13"/> <delta pt="61" x="0" y="0"/> <delta pt="62" x="32" y="0"/> - <delta pt="63" x="0" y="16"/> - <delta pt="64" x="0" y="3"/> + <delta pt="63" x="0" y="0"/> + <delta pt="64" x="0" y="0"/> </tuple> <tuple> <coord axis="cntr" value="1.0"/> @@ -1611,7 +1611,7 @@ <delta pt="61" x="-15" y="0"/> <delta pt="62" x="0" y="0"/> <delta pt="63" x="-7" y="0"/> - <delta pt="64" x="0" y="12"/> + <delta pt="64" x="0" y="0"/> <delta pt="65" x="0" y="0"/> </tuple> <tuple> @@ -1680,7 +1680,7 @@ <delta pt="61" x="39" y="0"/> <delta pt="62" x="0" y="0"/> <delta pt="63" x="63" y="0"/> - <delta pt="64" x="0" y="-19"/> + <delta pt="64" x="0" y="0"/> <delta pt="65" x="0" y="0"/> </tuple> <tuple> @@ -1960,7 +1960,7 @@ <delta pt="61" x="-15" y="0"/> <delta pt="62" x="0" y="0"/> <delta pt="63" x="-7" y="0"/> - <delta pt="64" x="0" y="12"/> + <delta pt="64" x="0" y="0"/> <delta pt="65" x="0" y="0"/> </tuple> <tuple> @@ -2029,7 +2029,7 @@ <delta pt="61" x="49" y="0"/> <delta pt="62" x="0" y="0"/> <delta pt="63" x="63" y="0"/> - <delta pt="64" x="0" y="-19"/> + <delta pt="64" x="0" y="0"/> <delta pt="65" x="0" y="0"/> </tuple> <tuple> diff --git a/Tests/varLib/data/test_results/Mutator.ttx b/Tests/varLib/data/test_results/Mutator.ttx index a106d213..fac0997a 100644 --- a/Tests/varLib/data/test_results/Mutator.ttx +++ b/Tests/varLib/data/test_results/Mutator.ttx @@ -154,7 +154,7 @@ <TTGlyph name=".notdef" xMin="80" yMin="0" xMax="560" yMax="666"> <contour> - <pt x="83" y="0" on="1"/> + <pt x="83" y="0" on="1" overlap="1"/> <pt x="503" y="666" on="1"/> <pt x="557" y="666" on="1"/> <pt x="137" y="0" on="1"/> @@ -190,7 +190,7 @@ <TTGlyph name="uni0024" xMin="51" yMin="-115" xMax="474" yMax="746"> <contour> - <pt x="251" y="31" on="1"/> + <pt x="251" y="31" on="1" overlap="1"/> <pt x="309" y="31" on="0"/> <pt x="379" y="92" on="0"/> <pt x="379" y="144" on="1"/> @@ -262,7 +262,7 @@ <TTGlyph name="uni0024.nostroke" xMin="51" yMin="-115" xMax="474" yMax="746"> <contour> - <pt x="251" y="31" on="1"/> + <pt x="251" y="31" on="1" overlap="1"/> <pt x="308" y="31" on="0"/> <pt x="377" y="90" on="0"/> <pt x="377" y="142" on="1"/> @@ -334,7 +334,7 @@ <TTGlyph name="uni0041" xMin="7" yMin="0" xMax="656" yMax="670"> <contour> - <pt x="7" y="0" on="1"/> + <pt x="7" y="0" on="1" overlap="1"/> <pt x="7" y="38" on="1"/> <pt x="104" y="53" on="1"/> <pt x="124" y="53" on="1"/> @@ -370,7 +370,7 @@ <TTGlyph name="uni0061" xMin="42" yMin="-14" xMax="511" yMax="490"> <contour> - <pt x="42" y="110" on="1"/> + <pt x="42" y="110" on="1" overlap="1"/> <pt x="42" y="157" on="0"/> <pt x="110" y="229" on="0"/> <pt x="214" y="265" on="1"/> diff --git a/Tests/varLib/data/test_results/Mutator_Getvar-instance.ttx b/Tests/varLib/data/test_results/Mutator_Getvar-instance.ttx index a20f0e4f..28e0766a 100755 --- a/Tests/varLib/data/test_results/Mutator_Getvar-instance.ttx +++ b/Tests/varLib/data/test_results/Mutator_Getvar-instance.ttx @@ -168,7 +168,7 @@ <TTGlyph name="a" xMin="38" yMin="-12" xMax="388" yMax="468"> <contour> - <pt x="312" y="0" on="1"/> + <pt x="312" y="0" on="1" overlap="1"/> <pt x="312" y="64" on="1"/> <pt x="244" y="-12" on="1"/> <pt x="180" y="-12" on="1"/> @@ -200,7 +200,7 @@ <TTGlyph name="b" xMin="76" yMin="-12" xMax="426" yMax="628"> <contour> - <pt x="218" y="468" on="1"/> + <pt x="218" y="468" on="1" overlap="1"/> <pt x="284" y="468" on="1"/> <pt x="426" y="316" on="1"/> <pt x="426" y="140" on="1"/> @@ -229,7 +229,7 @@ <TTGlyph name="nonmarkingreturn"/><!-- contains no outline data --> <TTGlyph name="q" xMin="38" yMin="-172" xMax="388" yMax="468"> - <component glyphName="b" x="464" y="456" scale="-0.99994" flags="0x4"/> + <component glyphName="b" x="464" y="456" scale="-0.99994" flags="0x404"/> </TTGlyph> <TTGlyph name="space"/><!-- contains no outline data --> diff --git a/Tests/varLib/data/test_results/Mutator_IUP-instance.ttx b/Tests/varLib/data/test_results/Mutator_IUP-instance.ttx index 1800479b..1f7b651f 100755 --- a/Tests/varLib/data/test_results/Mutator_IUP-instance.ttx +++ b/Tests/varLib/data/test_results/Mutator_IUP-instance.ttx @@ -157,7 +157,7 @@ <TTGlyph name="a" xMin="38" yMin="-12" xMax="388" yMax="468"> <contour> - <pt x="312" y="0" on="1"/> + <pt x="312" y="0" on="1" overlap="1"/> <pt x="312" y="64" on="1"/> <pt x="244" y="-12" on="1"/> <pt x="180" y="-12" on="1"/> @@ -185,7 +185,7 @@ <TTGlyph name="b" xMin="76" yMin="-12" xMax="426" yMax="628"> <contour> - <pt x="218" y="468" on="1"/> + <pt x="218" y="468" on="1" overlap="1"/> <pt x="284" y="468" on="1"/> <pt x="426" y="316" on="1"/> <pt x="426" y="140" on="1"/> @@ -214,7 +214,7 @@ <TTGlyph name="nonmarkingreturn"/><!-- contains no outline data --> <TTGlyph name="q" xMin="38" yMin="-172" xMax="388" yMax="468"> - <component glyphName="b" x="464" y="456" scale="-0.99994" flags="0x4"/> + <component glyphName="b" x="464" y="456" scale="-0.99994" flags="0x404"/> </TTGlyph> <TTGlyph name="space"/><!-- contains no outline data --> diff --git a/Tests/varLib/data/test_results/SparseMasters.ttx b/Tests/varLib/data/test_results/SparseMasters.ttx index 99a80bfb..06e58c92 100644 --- a/Tests/varLib/data/test_results/SparseMasters.ttx +++ b/Tests/varLib/data/test_results/SparseMasters.ttx @@ -574,7 +574,7 @@ </glyphVariations> <glyphVariations glyph="e"> <tuple> - <coord axis="wght" max="1.0" min="0.0" value="0.36365"/> + <coord axis="wght" min="0.0" value="0.36365" max="1.0"/> <delta pt="0" x="-1" y="-25"/> <delta pt="1" x="0" y="0"/> <delta pt="2" x="-84" y="5"/> @@ -594,7 +594,7 @@ <delta pt="16" x="0" y="0"/> </tuple> <tuple> - <coord axis="wght" max="1.0" min="0.36365" value="1.0"/> + <coord axis="wght" min="0.36365" value="1.0" max="1.0"/> <delta pt="0" x="70" y="1"/> <delta pt="1" x="70" y="1"/> <delta pt="2" x="-76" y="1"/> @@ -610,7 +610,7 @@ <delta pt="12" x="25" y="-1"/> <delta pt="13" x="0" y="0"/> <delta pt="14" x="0" y="0"/> - <delta pt="15" x="0" y="35"/> + <delta pt="15" x="0" y="0"/> <delta pt="16" x="0" y="0"/> </tuple> </glyphVariations> @@ -632,7 +632,7 @@ <delta pt="12" x="0" y="0"/> <delta pt="13" x="0" y="0"/> <delta pt="14" x="0" y="0"/> - <delta pt="15" x="0" y="45"/> + <delta pt="15" x="0" y="0"/> </tuple> </glyphVariations> <glyphVariations glyph="dotabovecomb"> @@ -644,15 +644,14 @@ <delta pt="3" x="-27" y="-20"/> <delta pt="4" x="0" y="0"/> <delta pt="5" x="0" y="0"/> - <delta pt="6" x="0" y="28"/> - <delta pt="7" x="0" y="18"/> + <delta pt="6" x="0" y="0"/> + <delta pt="7" x="0" y="0"/> </tuple> </glyphVariations> <glyphVariations glyph="edotabove"> <tuple> <coord axis="wght" value="1.0"/> <delta pt="1" x="-6" y="91"/> - <delta pt="4" x="0" y="119"/> </tuple> </glyphVariations> </gvar> diff --git a/Tests/varLib/models_test.py b/Tests/varLib/models_test.py index c5c2a9a6..ae67e0e9 100644 --- a/Tests/varLib/models_test.py +++ b/Tests/varLib/models_test.py @@ -2,6 +2,7 @@ from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * from fontTools.varLib.models import ( normalizeLocation, supportScalar, VariationModel) +import pytest def test_normalizeLocation(): @@ -35,65 +36,121 @@ def test_supportScalar(): assert supportScalar({'wght':2.5}, {'wght':(0,2,4)}) == 0.75 -def test_VariationModel(): - locations = [ - {'wght':100}, - {'wght':-100}, - {'wght':-180}, - {'wdth':+.3}, - {'wght':+120,'wdth':.3}, - {'wght':+120,'wdth':.2}, - {}, - {'wght':+180,'wdth':.3}, - {'wght':+180}, - ] - model = VariationModel(locations, axisOrder=['wght']) +class VariationModelTest(object): - assert model.locations == [ - {}, - {'wght': -100}, - {'wght': -180}, - {'wght': 100}, - {'wght': 180}, - {'wdth': 0.3}, - {'wdth': 0.3, 'wght': 180}, - {'wdth': 0.3, 'wght': 120}, - {'wdth': 0.2, 'wght': 120}] + @pytest.mark.parametrize( + "locations, axisOrder, sortedLocs, supports, deltaWeights", + [ + ( + [ + {'wght': 0.55, 'wdth': 0.0}, + {'wght': -0.55, 'wdth': 0.0}, + {'wght': -1.0, 'wdth': 0.0}, + {'wght': 0.0, 'wdth': 1.0}, + {'wght': 0.66, 'wdth': 1.0}, + {'wght': 0.66, 'wdth': 0.66}, + {'wght': 0.0, 'wdth': 0.0}, + {'wght': 1.0, 'wdth': 1.0}, + {'wght': 1.0, 'wdth': 0.0}, + ], + ["wght"], + [ + {}, + {'wght': -0.55}, + {'wght': -1.0}, + {'wght': 0.55}, + {'wght': 1.0}, + {'wdth': 1.0}, + {'wdth': 1.0, 'wght': 1.0}, + {'wdth': 1.0, 'wght': 0.66}, + {'wdth': 0.66, 'wght': 0.66} + ], + [ + {}, + {'wght': (-1.0, -0.55, 0)}, + {'wght': (-1.0, -1.0, -0.55)}, + {'wght': (0, 0.55, 1.0)}, + {'wght': (0.55, 1.0, 1.0)}, + {'wdth': (0, 1.0, 1.0)}, + {'wdth': (0, 1.0, 1.0), 'wght': (0, 1.0, 1.0)}, + {'wdth': (0, 1.0, 1.0), 'wght': (0, 0.66, 1.0)}, + {'wdth': (0, 0.66, 1.0), 'wght': (0, 0.66, 1.0)} + ], + [ + {}, + {0: 1.0}, + {0: 1.0}, + {0: 1.0}, + {0: 1.0}, + {0: 1.0}, + {0: 1.0, + 4: 1.0, + 5: 1.0}, + {0: 1.0, + 3: 0.7555555555555555, + 4: 0.24444444444444444, + 5: 1.0, + 6: 0.66}, + {0: 1.0, + 3: 0.7555555555555555, + 4: 0.24444444444444444, + 5: 0.66, + 6: 0.43560000000000006, + 7: 0.66} + ] + ), + ( + [ + {}, + {'bar': 0.5}, + {'bar': 1.0}, + {'foo': 1.0}, + {'bar': 0.5, 'foo': 1.0}, + {'bar': 1.0, 'foo': 1.0}, + ], + None, + [ + {}, + {'bar': 0.5}, + {'bar': 1.0}, + {'foo': 1.0}, + {'bar': 0.5, 'foo': 1.0}, + {'bar': 1.0, 'foo': 1.0}, + ], + [ + {}, + {'bar': (0, 0.5, 1.0)}, + {'bar': (0.5, 1.0, 1.0)}, + {'foo': (0, 1.0, 1.0)}, + {'bar': (0, 0.5, 1.0), 'foo': (0, 1.0, 1.0)}, + {'bar': (0.5, 1.0, 1.0), 'foo': (0, 1.0, 1.0)}, + ], + [ + {}, + {0: 1.0}, + {0: 1.0}, + {0: 1.0}, + {0: 1.0, 1: 1.0, 3: 1.0}, + {0: 1.0, 2: 1.0, 3: 1.0}, + ], + ) + ] + ) + def test_init( + self, locations, axisOrder, sortedLocs, supports, deltaWeights + ): + model = VariationModel(locations, axisOrder=axisOrder) - assert model.deltaWeights == [ - {}, - {0: 1.0}, - {0: 1.0}, - {0: 1.0}, - {0: 1.0}, - {0: 1.0}, - {0: 1.0, 4: 1.0, 5: 1.0}, - {0: 1.0, 3: 0.75, 4: 0.25, 5: 1.0, 6: 0.6666666666666666}, - {0: 1.0, - 3: 0.75, - 4: 0.25, - 5: 0.6666666666666667, - 6: 0.4444444444444445, - 7: 0.6666666666666667}] + assert model.locations == sortedLocs + assert model.supports == supports + assert model.deltaWeights == deltaWeights -def test_VariationModel(): - locations = [ - {}, - {'bar': 0.5}, - {'bar': 1.0}, - {'foo': 1.0}, - {'bar': 0.5, 'foo': 1.0}, - {'bar': 1.0, 'foo': 1.0}, - ] - model = VariationModel(locations) - - assert model.locations == locations - - assert model.supports == [ - {}, - {'bar': (0, 0.5, 1.0)}, - {'bar': (0.5, 1.0, 1.0)}, - {'foo': (0, 1.0, 1.0)}, - {'bar': (0, 0.5, 1.0), 'foo': (0, 1.0, 1.0)}, - {'bar': (0.5, 1.0, 1.0), 'foo': (0, 1.0, 1.0)}, - ] + def test_init_duplicate_locations(self): + with pytest.raises(ValueError, match="locations must be unique"): + VariationModel( + [ + {"foo": 0.0, "bar": 0.0}, + {"foo": 1.0, "bar": 1.0}, + {"bar": 1.0, "foo": 1.0}, + ] + ) diff --git a/requirements.txt b/requirements.txt index acfd0fd4..8532edd5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,6 @@ 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.1; platform_python_implementation != "PyPy" -munkres==1.0.12; platform_python_implementation == "PyPy" +munkres==1.0.12; platform_python_implementation == "PyPy" # pyup: ignore zopfli==0.1.6 -fs==2.3.1 +fs==2.4.4 @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.38.0 +current_version = 3.39.0 commit = True tag = False tag_name = {new_version} @@ -352,7 +352,7 @@ def find_data_files(manpath="share/man"): setup( name="fonttools", - version="3.38.0", + version="3.39.0", 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={ |