fonttools 4.59.2__py3-none-any.whl → 4.60.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fonttools might be problematic. Click here for more details.

Files changed (32) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/annotations.py +30 -0
  3. fontTools/cu2qu/cu2qu.py +19 -2
  4. fontTools/misc/enumTools.py +23 -0
  5. fontTools/misc/visitor.py +24 -16
  6. fontTools/pens/filterPen.py +218 -26
  7. fontTools/pens/pointPen.py +40 -6
  8. fontTools/subset/__init__.py +178 -12
  9. fontTools/ttLib/tables/_p_o_s_t.py +5 -5
  10. fontTools/ufoLib/__init__.py +278 -175
  11. fontTools/ufoLib/converters.py +14 -5
  12. fontTools/ufoLib/filenames.py +16 -6
  13. fontTools/ufoLib/glifLib.py +286 -190
  14. fontTools/ufoLib/kerning.py +32 -12
  15. fontTools/ufoLib/utils.py +41 -13
  16. fontTools/ufoLib/validators.py +121 -97
  17. fontTools/varLib/avar/__init__.py +0 -0
  18. fontTools/varLib/avar/__main__.py +72 -0
  19. fontTools/varLib/avar/build.py +79 -0
  20. fontTools/varLib/avar/map.py +108 -0
  21. fontTools/varLib/avar/plan.py +1004 -0
  22. fontTools/varLib/{avar.py → avar/unbuild.py} +70 -59
  23. fontTools/varLib/avarPlanner.py +3 -999
  24. fontTools/varLib/interpolatableHelpers.py +3 -0
  25. {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/METADATA +41 -2
  26. {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/RECORD +32 -25
  27. {fonttools-4.59.2.data → fonttools-4.60.1.data}/data/share/man/man1/ttx.1 +0 -0
  28. {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/WHEEL +0 -0
  29. {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/entry_points.txt +0 -0
  30. {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/licenses/LICENSE +0 -0
  31. {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/licenses/LICENSE.external +0 -0
  32. {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/top_level.txt +0 -0
@@ -272,7 +272,7 @@ Font table options
272
272
  Specify (=), add to (+=) or exclude from (-=) the comma-separated
273
273
  set of tables that will be be dropped.
274
274
  By default, the following tables are dropped:
275
- 'BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'PCLT', 'LTSH'
275
+ 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'PCLT', 'LTSH'
276
276
  and Graphite tables: 'Feat', 'Glat', 'Gloc', 'Silf', 'Sill'.
277
277
  The tool will attempt to subset the remaining tables.
278
278
 
@@ -827,13 +827,26 @@ def subset_glyphs(self, s):
827
827
  self.MarkArray.MarkRecord, mark_indices
828
828
  )
829
829
  self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
830
- base_indices = self.BaseCoverage.subset(s.glyphs)
830
+ class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
831
+
832
+ intersect_base_indices = self.BaseCoverage.intersect(s.glyphs)
833
+ base_records = self.BaseArray.BaseRecord
834
+ num_base_records = len(base_records)
835
+ base_indices = [
836
+ i
837
+ for i in intersect_base_indices
838
+ if i < num_base_records
839
+ and any(base_records[i].BaseAnchor[j] is not None for j in class_indices)
840
+ ]
841
+ if not base_indices:
842
+ return False
843
+
844
+ self.BaseCoverage.remap(base_indices)
831
845
  self.BaseArray.BaseRecord = _list_subset(
832
846
  self.BaseArray.BaseRecord, base_indices
833
847
  )
834
848
  self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord)
835
849
  # Prune empty classes
836
- class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
837
850
  self.ClassCount = len(class_indices)
838
851
  for m in self.MarkArray.MarkRecord:
839
852
  m.Class = class_indices.index(m.Class)
@@ -867,13 +880,31 @@ def subset_glyphs(self, s):
867
880
  self.MarkArray.MarkRecord, mark_indices
868
881
  )
869
882
  self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
870
- ligature_indices = self.LigatureCoverage.subset(s.glyphs)
883
+ class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
884
+
885
+ intersect_ligature_indices = self.LigatureCoverage.intersect(s.glyphs)
886
+ ligature_array = self.LigatureArray.LigatureAttach
887
+ num_ligatures = self.LigatureArray.LigatureCount
888
+
889
+ ligature_indices = [
890
+ i
891
+ for i in intersect_ligature_indices
892
+ if i < num_ligatures
893
+ and any(
894
+ any(component.LigatureAnchor[j] is not None for j in class_indices)
895
+ for component in ligature_array[i].ComponentRecord
896
+ )
897
+ ]
898
+
899
+ if not ligature_indices:
900
+ return False
901
+
902
+ self.LigatureCoverage.remap(ligature_indices)
871
903
  self.LigatureArray.LigatureAttach = _list_subset(
872
904
  self.LigatureArray.LigatureAttach, ligature_indices
873
905
  )
874
906
  self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach)
875
907
  # Prune empty classes
876
- class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
877
908
  self.ClassCount = len(class_indices)
878
909
  for m in self.MarkArray.MarkRecord:
879
910
  m.Class = class_indices.index(m.Class)
@@ -915,13 +946,26 @@ def subset_glyphs(self, s):
915
946
  self.Mark1Array.MarkRecord, mark1_indices
916
947
  )
917
948
  self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord)
918
- mark2_indices = self.Mark2Coverage.subset(s.glyphs)
949
+ class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
950
+
951
+ intersect_mark2_indices = self.Mark2Coverage.intersect(s.glyphs)
952
+ mark2_records = self.Mark2Array.Mark2Record
953
+ num_mark2_records = len(mark2_records)
954
+ mark2_indices = [
955
+ i
956
+ for i in intersect_mark2_indices
957
+ if i < num_mark2_records
958
+ and any(mark2_records[i].Mark2Anchor[j] is not None for j in class_indices)
959
+ ]
960
+ if not mark2_indices:
961
+ return False
962
+
963
+ self.Mark2Coverage.remap(mark2_indices)
919
964
  self.Mark2Array.Mark2Record = _list_subset(
920
965
  self.Mark2Array.Mark2Record, mark2_indices
921
966
  )
922
967
  self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record)
923
968
  # Prune empty classes
924
- class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
925
969
  self.ClassCount = len(class_indices)
926
970
  for m in self.Mark1Array.MarkRecord:
927
971
  m.Class = class_indices.index(m.Class)
@@ -1717,6 +1761,19 @@ def subset_features(self, feature_indices):
1717
1761
  return bool(self.SubstitutionCount)
1718
1762
 
1719
1763
 
1764
+ @_add_method(otTables.FeatureTableSubstitution)
1765
+ def prune_features(self, feature_index_map):
1766
+ self.ensureDecompiled()
1767
+ self.SubstitutionRecord = [
1768
+ r for r in self.SubstitutionRecord if r.FeatureIndex in feature_index_map.keys()
1769
+ ]
1770
+ # remap feature indices
1771
+ for r in self.SubstitutionRecord:
1772
+ r.FeatureIndex = feature_index_map[r.FeatureIndex]
1773
+ self.SubstitutionCount = len(self.SubstitutionRecord)
1774
+ return bool(self.SubstitutionCount)
1775
+
1776
+
1720
1777
  @_add_method(otTables.FeatureVariations)
1721
1778
  def subset_features(self, feature_indices):
1722
1779
  self.ensureDecompiled()
@@ -1735,6 +1792,24 @@ def subset_features(self, feature_indices):
1735
1792
  return bool(self.FeatureVariationCount)
1736
1793
 
1737
1794
 
1795
+ @_add_method(otTables.FeatureVariations)
1796
+ def prune_features(self, feature_index_map):
1797
+ self.ensureDecompiled()
1798
+ for r in self.FeatureVariationRecord:
1799
+ r.FeatureTableSubstitution.prune_features(feature_index_map)
1800
+ # Prune empty records at the end only
1801
+ # https://github.com/fonttools/fonttools/issues/1881
1802
+ while (
1803
+ self.FeatureVariationRecord
1804
+ and not self.FeatureVariationRecord[
1805
+ -1
1806
+ ].FeatureTableSubstitution.SubstitutionCount
1807
+ ):
1808
+ self.FeatureVariationRecord.pop()
1809
+ self.FeatureVariationCount = len(self.FeatureVariationRecord)
1810
+ return bool(self.FeatureVariationCount)
1811
+
1812
+
1738
1813
  @_add_method(otTables.DefaultLangSys, otTables.LangSys)
1739
1814
  def subset_features(self, feature_indices):
1740
1815
  if self.ReqFeatureIndex in feature_indices:
@@ -1750,6 +1825,16 @@ def subset_features(self, feature_indices):
1750
1825
  return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
1751
1826
 
1752
1827
 
1828
+ @_add_method(otTables.DefaultLangSys, otTables.LangSys)
1829
+ def prune_features(self, feature_index_map):
1830
+ self.ReqFeatureIndex = feature_index_map.get(self.ReqFeatureIndex, 65535)
1831
+ self.FeatureIndex = [
1832
+ feature_index_map[f] for f in self.FeatureIndex if f in feature_index_map.keys()
1833
+ ]
1834
+ self.FeatureCount = len(self.FeatureIndex)
1835
+ return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
1836
+
1837
+
1753
1838
  @_add_method(otTables.DefaultLangSys, otTables.LangSys)
1754
1839
  def collect_features(self):
1755
1840
  feature_indices = self.FeatureIndex[:]
@@ -1773,6 +1858,21 @@ def subset_features(self, feature_indices, keepEmptyDefaultLangSys=False):
1773
1858
  return bool(self.LangSysCount or self.DefaultLangSys)
1774
1859
 
1775
1860
 
1861
+ @_add_method(otTables.Script)
1862
+ def prune_features(self, feature_index_map, keepEmptyDefaultLangSys=False):
1863
+ if (
1864
+ self.DefaultLangSys
1865
+ and not self.DefaultLangSys.prune_features(feature_index_map)
1866
+ and not keepEmptyDefaultLangSys
1867
+ ):
1868
+ self.DefaultLangSys = None
1869
+ self.LangSysRecord = [
1870
+ l for l in self.LangSysRecord if l.LangSys.prune_features(feature_index_map)
1871
+ ]
1872
+ self.LangSysCount = len(self.LangSysRecord)
1873
+ return bool(self.LangSysCount or self.DefaultLangSys)
1874
+
1875
+
1776
1876
  @_add_method(otTables.Script)
1777
1877
  def collect_features(self):
1778
1878
  feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord]
@@ -1794,6 +1894,19 @@ def subset_features(self, feature_indices, retain_empty):
1794
1894
  return bool(self.ScriptCount)
1795
1895
 
1796
1896
 
1897
+ @_add_method(otTables.ScriptList)
1898
+ def prune_features(self, feature_index_map, retain_empty):
1899
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=1331737#c32
1900
+ self.ScriptRecord = [
1901
+ s
1902
+ for s in self.ScriptRecord
1903
+ if s.Script.prune_features(feature_index_map, s.ScriptTag == "DFLT")
1904
+ or retain_empty
1905
+ ]
1906
+ self.ScriptCount = len(self.ScriptRecord)
1907
+ return bool(self.ScriptCount)
1908
+
1909
+
1797
1910
  @_add_method(otTables.ScriptList)
1798
1911
  def collect_features(self):
1799
1912
  return _uniq_sort(sum((s.Script.collect_features() for s in self.ScriptRecord), []))
@@ -1981,21 +2094,74 @@ def subset_script_tags(self, tags):
1981
2094
 
1982
2095
  @_add_method(ttLib.getTableClass("GSUB"), ttLib.getTableClass("GPOS"))
1983
2096
  def prune_features(self):
1984
- """Remove unreferenced features"""
2097
+ """Remove unreferenced and duplicate features in FeatureList
2098
+ Remove unreferenced features and remap duplicate feature indices in ScriptList and FeatureVariations
2099
+ """
1985
2100
  if self.table.ScriptList:
1986
2101
  feature_indices = self.table.ScriptList.collect_features()
1987
2102
  else:
1988
2103
  feature_indices = []
2104
+ (feature_indices, feature_index_map) = self.remap_duplicate_features(
2105
+ feature_indices
2106
+ )
2107
+
1989
2108
  if self.table.FeatureList:
1990
2109
  self.table.FeatureList.subset_features(feature_indices)
1991
2110
  if getattr(self.table, "FeatureVariations", None):
1992
- self.table.FeatureVariations.subset_features(feature_indices)
2111
+ self.table.FeatureVariations.prune_features(feature_index_map)
1993
2112
  if self.table.ScriptList:
1994
- self.table.ScriptList.subset_features(
1995
- feature_indices, self.retain_empty_scripts()
2113
+ self.table.ScriptList.prune_features(
2114
+ feature_index_map, self.retain_empty_scripts()
1996
2115
  )
1997
2116
 
1998
2117
 
2118
+ @_add_method(ttLib.getTableClass("GSUB"), ttLib.getTableClass("GPOS"))
2119
+ def remap_duplicate_features(self, feature_indices):
2120
+ """Return retained feature indices(without duplicates) and remapped feature indices"""
2121
+ features = self.table.FeatureList.FeatureRecord
2122
+
2123
+ unique_features = {}
2124
+ duplicate_features = {}
2125
+ for i in feature_indices:
2126
+ f = features[i]
2127
+ tag = f.FeatureTag
2128
+
2129
+ same_tag_features = unique_features.get(tag)
2130
+ if same_tag_features is None:
2131
+ unique_features[tag] = set([i])
2132
+ duplicate_features[i] = i
2133
+ continue
2134
+
2135
+ found = False
2136
+ for other_i in same_tag_features:
2137
+ if features[other_i] == f:
2138
+ found = True
2139
+ duplicate_features[i] = other_i
2140
+ break
2141
+
2142
+ if not found:
2143
+ same_tag_features.add(i)
2144
+ duplicate_features[i] = i
2145
+
2146
+ ## remap retained feature indices
2147
+ feature_map = {}
2148
+ new_idx = 0
2149
+
2150
+ for i in feature_indices:
2151
+ unique_i = duplicate_features.get(i, i)
2152
+ v = feature_map.get(unique_i)
2153
+ if v is None:
2154
+ feature_map[i] = new_idx
2155
+ new_idx += 1
2156
+ else:
2157
+ feature_map[i] = v
2158
+
2159
+ retained_feature_indices = _uniq_sort(
2160
+ sum((list(s) for s in unique_features.values()), [])
2161
+ )
2162
+ return (retained_feature_indices, feature_map)
2163
+
2164
+
1999
2165
  @_add_method(ttLib.getTableClass("GSUB"), ttLib.getTableClass("GPOS"))
2000
2166
  def prune_pre_subset(self, font, options):
2001
2167
  # Drop undesired features
@@ -3145,7 +3311,6 @@ class Options(object):
3145
3311
 
3146
3312
  # spaces in tag names (e.g. "SVG ", "cvt ") are stripped by the argument parser
3147
3313
  _drop_tables_default = [
3148
- "BASE",
3149
3314
  "JSTF",
3150
3315
  "DSIG",
3151
3316
  "EBDT",
@@ -3157,6 +3322,7 @@ class Options(object):
3157
3322
  _drop_tables_default += ["Feat", "Glat", "Gloc", "Silf", "Sill"] # Graphite
3158
3323
  _no_subset_tables_default = [
3159
3324
  "avar",
3325
+ "BASE",
3160
3326
  "fvar",
3161
3327
  "gasp",
3162
3328
  "head",
@@ -118,6 +118,7 @@ class table__p_o_s_t(DefaultTable.DefaultTable):
118
118
  def build_psNameMapping(self, ttFont):
119
119
  mapping = {}
120
120
  allNames = {}
121
+ glyphOrderNames = set(self.glyphOrder)
121
122
  for i in range(ttFont["maxp"].numGlyphs):
122
123
  glyphName = psName = self.glyphOrder[i]
123
124
  if glyphName == "":
@@ -126,16 +127,15 @@ class table__p_o_s_t(DefaultTable.DefaultTable):
126
127
  if glyphName in allNames:
127
128
  # make up a new glyphName that's unique
128
129
  n = allNames[glyphName]
129
- # check if the exists in any of the seen names or later ones
130
- names = set(allNames.keys()) | set(self.glyphOrder)
131
- while (glyphName + "." + str(n)) in names:
130
+ # check if the glyph name exists in the glyph order
131
+ while f"{glyphName}.{n}" in glyphOrderNames:
132
132
  n += 1
133
133
  allNames[glyphName] = n + 1
134
- glyphName = glyphName + "." + str(n)
134
+ glyphName = f"{glyphName}.{n}"
135
135
 
136
- self.glyphOrder[i] = glyphName
137
136
  allNames[glyphName] = 1
138
137
  if glyphName != psName:
138
+ self.glyphOrder[i] = glyphName
139
139
  mapping[glyphName] = psName
140
140
 
141
141
  self.mapping = mapping