fonttools 4.56.0__py3-none-any.whl → 4.58.0__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 (59) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/cffLib/__init__.py +61 -26
  3. fontTools/config/__init__.py +15 -0
  4. fontTools/designspaceLib/statNames.py +14 -7
  5. fontTools/feaLib/ast.py +92 -13
  6. fontTools/feaLib/builder.py +52 -13
  7. fontTools/feaLib/parser.py +59 -39
  8. fontTools/fontBuilder.py +6 -0
  9. fontTools/misc/etree.py +4 -27
  10. fontTools/misc/testTools.py +2 -1
  11. fontTools/mtiLib/__init__.py +0 -2
  12. fontTools/otlLib/builder.py +195 -145
  13. fontTools/otlLib/optimize/gpos.py +49 -63
  14. fontTools/pens/pointPen.py +21 -12
  15. fontTools/subset/__init__.py +11 -0
  16. fontTools/ttLib/__init__.py +4 -0
  17. fontTools/ttLib/__main__.py +47 -8
  18. fontTools/ttLib/tables/D__e_b_g.py +20 -2
  19. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  20. fontTools/ttLib/tables/T_S_I__0.py +14 -3
  21. fontTools/ttLib/tables/T_S_I__5.py +16 -5
  22. fontTools/ttLib/tables/__init__.py +1 -0
  23. fontTools/ttLib/tables/_c_m_a_p.py +19 -6
  24. fontTools/ttLib/tables/_c_v_t.py +2 -0
  25. fontTools/ttLib/tables/_f_p_g_m.py +3 -1
  26. fontTools/ttLib/tables/_g_l_y_f.py +11 -10
  27. fontTools/ttLib/tables/_g_v_a_r.py +62 -17
  28. fontTools/ttLib/tables/_p_o_s_t.py +5 -2
  29. fontTools/ttLib/tables/otBase.py +1 -0
  30. fontTools/ttLib/tables/otConverters.py +5 -2
  31. fontTools/ttLib/tables/otTables.py +5 -1
  32. fontTools/ttLib/ttFont.py +3 -5
  33. fontTools/ttLib/ttGlyphSet.py +0 -10
  34. fontTools/ttx.py +13 -1
  35. fontTools/ufoLib/__init__.py +2 -2
  36. fontTools/ufoLib/converters.py +89 -25
  37. fontTools/ufoLib/errors.py +8 -0
  38. fontTools/ufoLib/etree.py +1 -1
  39. fontTools/ufoLib/filenames.py +155 -100
  40. fontTools/ufoLib/glifLib.py +9 -2
  41. fontTools/ufoLib/kerning.py +66 -36
  42. fontTools/ufoLib/utils.py +5 -2
  43. fontTools/unicodedata/Mirrored.py +446 -0
  44. fontTools/unicodedata/__init__.py +6 -2
  45. fontTools/varLib/__init__.py +94 -89
  46. fontTools/varLib/hvar.py +113 -0
  47. fontTools/varLib/varStore.py +1 -1
  48. fontTools/voltLib/__main__.py +206 -0
  49. fontTools/voltLib/ast.py +4 -0
  50. fontTools/voltLib/parser.py +16 -8
  51. fontTools/voltLib/voltToFea.py +347 -166
  52. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/METADATA +60 -12
  53. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/RECORD +59 -54
  54. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/WHEEL +1 -1
  55. fonttools-4.58.0.dist-info/licenses/LICENSE.external +359 -0
  56. {fonttools-4.56.0.data → fonttools-4.58.0.data}/data/share/man/man1/ttx.1 +0 -0
  57. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/entry_points.txt +0 -0
  58. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info/licenses}/LICENSE +0 -0
  59. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/top_level.txt +0 -0
@@ -1286,6 +1286,19 @@ class Parser(object):
1286
1286
  n = match.group(0)[1:]
1287
1287
  return bytechr(int(n, 16)).decode(encoding)
1288
1288
 
1289
+ def find_previous(self, statements, class_):
1290
+ for previous in reversed(statements):
1291
+ if isinstance(previous, self.ast.Comment):
1292
+ continue
1293
+ elif isinstance(previous, class_):
1294
+ return previous
1295
+ else:
1296
+ # If we find something that doesn't match what we're looking
1297
+ # for, and isn't a comment, fail
1298
+ return None
1299
+ # Out of statements to look at
1300
+ return None
1301
+
1289
1302
  def parse_table_BASE_(self, table):
1290
1303
  statements = table.statements
1291
1304
  while self.next_token_ != "}" or self.cur_comments_:
@@ -1306,6 +1319,19 @@ class Parser(object):
1306
1319
  location=self.cur_token_location_,
1307
1320
  )
1308
1321
  )
1322
+ elif self.is_cur_keyword_("HorizAxis.MinMax"):
1323
+ base_script_list = self.find_previous(statements, ast.BaseAxis)
1324
+ if base_script_list is None:
1325
+ raise FeatureLibError(
1326
+ "MinMax must be preceded by BaseScriptList",
1327
+ self.cur_token_location_,
1328
+ )
1329
+ if base_script_list.vertical:
1330
+ raise FeatureLibError(
1331
+ "HorizAxis.MinMax must be preceded by HorizAxis statements",
1332
+ self.cur_token_location_,
1333
+ )
1334
+ base_script_list.minmax.append(self.parse_base_minmax_())
1309
1335
  elif self.is_cur_keyword_("VertAxis.BaseTagList"):
1310
1336
  vert_bases = self.parse_base_tag_list_()
1311
1337
  elif self.is_cur_keyword_("VertAxis.BaseScriptList"):
@@ -1318,6 +1344,19 @@ class Parser(object):
1318
1344
  location=self.cur_token_location_,
1319
1345
  )
1320
1346
  )
1347
+ elif self.is_cur_keyword_("VertAxis.MinMax"):
1348
+ base_script_list = self.find_previous(statements, ast.BaseAxis)
1349
+ if base_script_list is None:
1350
+ raise FeatureLibError(
1351
+ "MinMax must be preceded by BaseScriptList",
1352
+ self.cur_token_location_,
1353
+ )
1354
+ if not base_script_list.vertical:
1355
+ raise FeatureLibError(
1356
+ "VertAxis.MinMax must be preceded by VertAxis statements",
1357
+ self.cur_token_location_,
1358
+ )
1359
+ base_script_list.minmax.append(self.parse_base_minmax_())
1321
1360
  elif self.cur_token_ == ";":
1322
1361
  continue
1323
1362
 
@@ -1574,7 +1613,7 @@ class Parser(object):
1574
1613
  "HorizAxis.BaseScriptList",
1575
1614
  "VertAxis.BaseScriptList",
1576
1615
  ), self.cur_token_
1577
- scripts = [(self.parse_base_script_record_(count))]
1616
+ scripts = [self.parse_base_script_record_(count)]
1578
1617
  while self.next_token_ == ",":
1579
1618
  self.expect_symbol_(",")
1580
1619
  scripts.append(self.parse_base_script_record_(count))
@@ -1587,6 +1626,25 @@ class Parser(object):
1587
1626
  coords = [self.expect_number_() for i in range(count)]
1588
1627
  return script_tag, base_tag, coords
1589
1628
 
1629
+ def parse_base_minmax_(self):
1630
+ script_tag = self.expect_script_tag_()
1631
+ language = self.expect_language_tag_()
1632
+ min_coord = self.expect_number_()
1633
+ self.advance_lexer_()
1634
+ if not (self.cur_token_type_ is Lexer.SYMBOL and self.cur_token_ == ","):
1635
+ raise FeatureLibError(
1636
+ "Expected a comma between min and max coordinates",
1637
+ self.cur_token_location_,
1638
+ )
1639
+ max_coord = self.expect_number_()
1640
+ if self.next_token_ == ",": # feature tag...
1641
+ raise FeatureLibError(
1642
+ "Feature tags are not yet supported in BASE table",
1643
+ self.cur_token_location_,
1644
+ )
1645
+
1646
+ return script_tag, language, min_coord, max_coord
1647
+
1590
1648
  def parse_device_(self):
1591
1649
  result = None
1592
1650
  self.expect_symbol_("<")
@@ -2004,44 +2062,6 @@ class Parser(object):
2004
2062
  )
2005
2063
  self.expect_symbol_(";")
2006
2064
 
2007
- # A multiple substitution may have a single destination, in which case
2008
- # it will look just like a single substitution. So if there are both
2009
- # multiple and single substitutions, upgrade all the single ones to
2010
- # multiple substitutions.
2011
-
2012
- # Check if we have a mix of non-contextual singles and multiples.
2013
- has_single = False
2014
- has_multiple = False
2015
- for s in statements:
2016
- if isinstance(s, self.ast.SingleSubstStatement):
2017
- has_single = not any([s.prefix, s.suffix, s.forceChain])
2018
- elif isinstance(s, self.ast.MultipleSubstStatement):
2019
- has_multiple = not any([s.prefix, s.suffix, s.forceChain])
2020
-
2021
- # Upgrade all single substitutions to multiple substitutions.
2022
- if has_single and has_multiple:
2023
- statements = []
2024
- for s in block.statements:
2025
- if isinstance(s, self.ast.SingleSubstStatement):
2026
- glyphs = s.glyphs[0].glyphSet()
2027
- replacements = s.replacements[0].glyphSet()
2028
- if len(replacements) == 1:
2029
- replacements *= len(glyphs)
2030
- for i, glyph in enumerate(glyphs):
2031
- statements.append(
2032
- self.ast.MultipleSubstStatement(
2033
- s.prefix,
2034
- glyph,
2035
- s.suffix,
2036
- [replacements[i]],
2037
- s.forceChain,
2038
- location=s.location,
2039
- )
2040
- )
2041
- else:
2042
- statements.append(s)
2043
- block.statements = statements
2044
-
2045
2065
  def is_cur_keyword_(self, k):
2046
2066
  if self.cur_token_type_ is Lexer.NAME:
2047
2067
  if isinstance(k, type("")): # basestring is gone in Python3
fontTools/fontBuilder.py CHANGED
@@ -714,6 +714,12 @@ class FontBuilder(object):
714
714
  gvar.reserved = 0
715
715
  gvar.variations = variations
716
716
 
717
+ def setupGVAR(self, variations):
718
+ gvar = self.font["GVAR"] = newTable("GVAR")
719
+ gvar.version = 1
720
+ gvar.reserved = 0
721
+ gvar.variations = variations
722
+
717
723
  def calcGlyphBounds(self):
718
724
  """Calculate the bounding boxes of all glyphs in the `glyf` table.
719
725
  This is usually not called explicitly by client code.
fontTools/misc/etree.py CHANGED
@@ -56,21 +56,7 @@ except ImportError:
56
56
  from xml.etree.ElementTree import *
57
57
  _have_lxml = False
58
58
 
59
- import sys
60
-
61
- # dict is always ordered in python >= 3.6 and on pypy
62
- PY36 = sys.version_info >= (3, 6)
63
- try:
64
- import __pypy__
65
- except ImportError:
66
- __pypy__ = None
67
- _dict_is_ordered = bool(PY36 or __pypy__)
68
- del PY36, __pypy__
69
-
70
- if _dict_is_ordered:
71
- _Attrib = dict
72
- else:
73
- from collections import OrderedDict as _Attrib
59
+ _Attrib = dict
74
60
 
75
61
  if isinstance(Element, type):
76
62
  _Element = Element
@@ -221,18 +207,9 @@ except ImportError:
221
207
  # characters, the surrogate blocks, FFFE, and FFFF:
222
208
  # Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
223
209
  # Here we reversed the pattern to match only the invalid characters.
224
- # For the 'narrow' python builds supporting only UCS-2, which represent
225
- # characters beyond BMP as UTF-16 surrogate pairs, we need to pass through
226
- # the surrogate block. I haven't found a more elegant solution...
227
- UCS2 = sys.maxunicode < 0x10FFFF
228
- if UCS2:
229
- _invalid_xml_string = re.compile(
230
- "[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uFFFE-\uFFFF]"
231
- )
232
- else:
233
- _invalid_xml_string = re.compile(
234
- "[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE-\uFFFF]"
235
- )
210
+ _invalid_xml_string = re.compile(
211
+ "[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE-\uFFFF]"
212
+ )
236
213
 
237
214
  def _tounicode(s):
238
215
  """Test if a string is valid user input and decode it to unicode string
@@ -46,7 +46,8 @@ def parseXmlInto(font, parseInto, xmlSnippet):
46
46
  parsed_xml = [e for e in parseXML(xmlSnippet.strip()) if not isinstance(e, str)]
47
47
  for name, attrs, content in parsed_xml:
48
48
  parseInto.fromXML(name, attrs, content, font)
49
- parseInto.populateDefaults()
49
+ if hasattr(parseInto, "populateDefaults"):
50
+ parseInto.populateDefaults()
50
51
  return parseInto
51
52
 
52
53
 
@@ -1,5 +1,3 @@
1
- #!/usr/bin/python
2
-
3
1
  # FontDame-to-FontTools for OpenType Layout tables
4
2
  #
5
3
  # Source language spec is available at: