fonttools 4.55.4__cp313-cp313-musllinux_1_2_aarch64.whl → 4.61.1__cp313-cp313-musllinux_1_2_aarch64.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.
Files changed (140) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/annotations.py +30 -0
  3. fontTools/cffLib/CFF2ToCFF.py +65 -10
  4. fontTools/cffLib/__init__.py +61 -26
  5. fontTools/cffLib/specializer.py +4 -1
  6. fontTools/cffLib/transforms.py +11 -6
  7. fontTools/config/__init__.py +15 -0
  8. fontTools/cu2qu/cu2qu.c +6567 -5579
  9. fontTools/cu2qu/cu2qu.cpython-313-aarch64-linux-musl.so +0 -0
  10. fontTools/cu2qu/cu2qu.py +36 -4
  11. fontTools/cu2qu/ufo.py +14 -0
  12. fontTools/designspaceLib/__init__.py +8 -3
  13. fontTools/designspaceLib/statNames.py +14 -7
  14. fontTools/feaLib/ast.py +24 -15
  15. fontTools/feaLib/builder.py +139 -66
  16. fontTools/feaLib/error.py +1 -1
  17. fontTools/feaLib/lexer.c +7038 -7995
  18. fontTools/feaLib/lexer.cpython-313-aarch64-linux-musl.so +0 -0
  19. fontTools/feaLib/parser.py +75 -40
  20. fontTools/feaLib/variableScalar.py +6 -1
  21. fontTools/fontBuilder.py +50 -44
  22. fontTools/merge/__init__.py +1 -1
  23. fontTools/merge/cmap.py +33 -1
  24. fontTools/merge/tables.py +12 -1
  25. fontTools/misc/bezierTools.c +14913 -17013
  26. fontTools/misc/bezierTools.cpython-313-aarch64-linux-musl.so +0 -0
  27. fontTools/misc/bezierTools.py +4 -1
  28. fontTools/misc/configTools.py +3 -1
  29. fontTools/misc/enumTools.py +23 -0
  30. fontTools/misc/etree.py +4 -27
  31. fontTools/misc/filesystem/__init__.py +68 -0
  32. fontTools/misc/filesystem/_base.py +134 -0
  33. fontTools/misc/filesystem/_copy.py +45 -0
  34. fontTools/misc/filesystem/_errors.py +54 -0
  35. fontTools/misc/filesystem/_info.py +75 -0
  36. fontTools/misc/filesystem/_osfs.py +164 -0
  37. fontTools/misc/filesystem/_path.py +67 -0
  38. fontTools/misc/filesystem/_subfs.py +92 -0
  39. fontTools/misc/filesystem/_tempfs.py +34 -0
  40. fontTools/misc/filesystem/_tools.py +34 -0
  41. fontTools/misc/filesystem/_walk.py +55 -0
  42. fontTools/misc/filesystem/_zipfs.py +204 -0
  43. fontTools/misc/fixedTools.py +1 -1
  44. fontTools/misc/loggingTools.py +1 -1
  45. fontTools/misc/psCharStrings.py +17 -2
  46. fontTools/misc/sstruct.py +2 -6
  47. fontTools/misc/symfont.py +6 -8
  48. fontTools/misc/testTools.py +5 -1
  49. fontTools/misc/textTools.py +4 -2
  50. fontTools/misc/visitor.py +32 -16
  51. fontTools/misc/xmlWriter.py +44 -8
  52. fontTools/mtiLib/__init__.py +1 -3
  53. fontTools/otlLib/builder.py +402 -155
  54. fontTools/otlLib/optimize/gpos.py +49 -63
  55. fontTools/pens/filterPen.py +218 -26
  56. fontTools/pens/momentsPen.c +5514 -5584
  57. fontTools/pens/momentsPen.cpython-313-aarch64-linux-musl.so +0 -0
  58. fontTools/pens/pointPen.py +61 -18
  59. fontTools/pens/roundingPen.py +2 -2
  60. fontTools/pens/t2CharStringPen.py +31 -11
  61. fontTools/qu2cu/qu2cu.c +6581 -6168
  62. fontTools/qu2cu/qu2cu.cpython-313-aarch64-linux-musl.so +0 -0
  63. fontTools/subset/__init__.py +283 -25
  64. fontTools/subset/svg.py +2 -3
  65. fontTools/ttLib/__init__.py +4 -0
  66. fontTools/ttLib/__main__.py +47 -8
  67. fontTools/ttLib/removeOverlaps.py +7 -5
  68. fontTools/ttLib/reorderGlyphs.py +8 -7
  69. fontTools/ttLib/sfnt.py +11 -9
  70. fontTools/ttLib/tables/D__e_b_g.py +20 -2
  71. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  72. fontTools/ttLib/tables/S__i_l_f.py +2 -2
  73. fontTools/ttLib/tables/T_S_I__0.py +14 -3
  74. fontTools/ttLib/tables/T_S_I__1.py +2 -5
  75. fontTools/ttLib/tables/T_S_I__5.py +18 -7
  76. fontTools/ttLib/tables/__init__.py +1 -0
  77. fontTools/ttLib/tables/_a_v_a_r.py +12 -3
  78. fontTools/ttLib/tables/_c_m_a_p.py +20 -7
  79. fontTools/ttLib/tables/_c_v_t.py +3 -2
  80. fontTools/ttLib/tables/_f_p_g_m.py +3 -1
  81. fontTools/ttLib/tables/_g_l_y_f.py +45 -21
  82. fontTools/ttLib/tables/_g_v_a_r.py +67 -19
  83. fontTools/ttLib/tables/_h_d_m_x.py +4 -4
  84. fontTools/ttLib/tables/_h_m_t_x.py +7 -3
  85. fontTools/ttLib/tables/_l_o_c_a.py +2 -2
  86. fontTools/ttLib/tables/_n_a_m_e.py +11 -6
  87. fontTools/ttLib/tables/_p_o_s_t.py +9 -7
  88. fontTools/ttLib/tables/otBase.py +5 -12
  89. fontTools/ttLib/tables/otConverters.py +5 -2
  90. fontTools/ttLib/tables/otData.py +1 -1
  91. fontTools/ttLib/tables/otTables.py +33 -30
  92. fontTools/ttLib/tables/otTraverse.py +2 -1
  93. fontTools/ttLib/tables/sbixStrike.py +3 -3
  94. fontTools/ttLib/ttFont.py +666 -120
  95. fontTools/ttLib/ttGlyphSet.py +0 -10
  96. fontTools/ttLib/woff2.py +10 -13
  97. fontTools/ttx.py +13 -1
  98. fontTools/ufoLib/__init__.py +300 -202
  99. fontTools/ufoLib/converters.py +103 -30
  100. fontTools/ufoLib/errors.py +8 -0
  101. fontTools/ufoLib/etree.py +1 -1
  102. fontTools/ufoLib/filenames.py +171 -106
  103. fontTools/ufoLib/glifLib.py +303 -205
  104. fontTools/ufoLib/kerning.py +98 -48
  105. fontTools/ufoLib/utils.py +46 -15
  106. fontTools/ufoLib/validators.py +121 -99
  107. fontTools/unicodedata/Blocks.py +35 -20
  108. fontTools/unicodedata/Mirrored.py +446 -0
  109. fontTools/unicodedata/ScriptExtensions.py +63 -37
  110. fontTools/unicodedata/Scripts.py +173 -152
  111. fontTools/unicodedata/__init__.py +10 -2
  112. fontTools/varLib/__init__.py +198 -109
  113. fontTools/varLib/avar/__init__.py +0 -0
  114. fontTools/varLib/avar/__main__.py +72 -0
  115. fontTools/varLib/avar/build.py +79 -0
  116. fontTools/varLib/avar/map.py +108 -0
  117. fontTools/varLib/avar/plan.py +1004 -0
  118. fontTools/varLib/{avar.py → avar/unbuild.py} +70 -59
  119. fontTools/varLib/avarPlanner.py +3 -999
  120. fontTools/varLib/featureVars.py +21 -7
  121. fontTools/varLib/hvar.py +113 -0
  122. fontTools/varLib/instancer/__init__.py +180 -65
  123. fontTools/varLib/interpolatableHelpers.py +3 -0
  124. fontTools/varLib/iup.c +7564 -6903
  125. fontTools/varLib/iup.cpython-313-aarch64-linux-musl.so +0 -0
  126. fontTools/varLib/models.py +17 -2
  127. fontTools/varLib/mutator.py +11 -0
  128. fontTools/varLib/varStore.py +10 -38
  129. fontTools/voltLib/__main__.py +206 -0
  130. fontTools/voltLib/ast.py +4 -0
  131. fontTools/voltLib/parser.py +16 -8
  132. fontTools/voltLib/voltToFea.py +347 -166
  133. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/METADATA +269 -1410
  134. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/RECORD +318 -294
  135. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/WHEEL +1 -1
  136. fonttools-4.61.1.dist-info/licenses/LICENSE.external +388 -0
  137. {fonttools-4.55.4.data → fonttools-4.61.1.data}/data/share/man/man1/ttx.1 +0 -0
  138. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/entry_points.txt +0 -0
  139. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info/licenses}/LICENSE +0 -0
  140. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/top_level.txt +0 -0
@@ -887,6 +887,11 @@ class Parser(object):
887
887
 
888
888
  is_deletion = False
889
889
  if len(new) == 1 and isinstance(new[0], ast.NullGlyph):
890
+ if reverse:
891
+ raise FeatureLibError(
892
+ "Reverse chaining substitutions do not support glyph deletion",
893
+ location,
894
+ )
890
895
  new = [] # Deletion
891
896
  is_deletion = True
892
897
 
@@ -1286,6 +1291,19 @@ class Parser(object):
1286
1291
  n = match.group(0)[1:]
1287
1292
  return bytechr(int(n, 16)).decode(encoding)
1288
1293
 
1294
+ def find_previous(self, statements, class_):
1295
+ for previous in reversed(statements):
1296
+ if isinstance(previous, self.ast.Comment):
1297
+ continue
1298
+ elif isinstance(previous, class_):
1299
+ return previous
1300
+ else:
1301
+ # If we find something that doesn't match what we're looking
1302
+ # for, and isn't a comment, fail
1303
+ return None
1304
+ # Out of statements to look at
1305
+ return None
1306
+
1289
1307
  def parse_table_BASE_(self, table):
1290
1308
  statements = table.statements
1291
1309
  while self.next_token_ != "}" or self.cur_comments_:
@@ -1306,6 +1324,19 @@ class Parser(object):
1306
1324
  location=self.cur_token_location_,
1307
1325
  )
1308
1326
  )
1327
+ elif self.is_cur_keyword_("HorizAxis.MinMax"):
1328
+ base_script_list = self.find_previous(statements, ast.BaseAxis)
1329
+ if base_script_list is None:
1330
+ raise FeatureLibError(
1331
+ "MinMax must be preceded by BaseScriptList",
1332
+ self.cur_token_location_,
1333
+ )
1334
+ if base_script_list.vertical:
1335
+ raise FeatureLibError(
1336
+ "HorizAxis.MinMax must be preceded by HorizAxis statements",
1337
+ self.cur_token_location_,
1338
+ )
1339
+ base_script_list.minmax.append(self.parse_base_minmax_())
1309
1340
  elif self.is_cur_keyword_("VertAxis.BaseTagList"):
1310
1341
  vert_bases = self.parse_base_tag_list_()
1311
1342
  elif self.is_cur_keyword_("VertAxis.BaseScriptList"):
@@ -1318,6 +1349,19 @@ class Parser(object):
1318
1349
  location=self.cur_token_location_,
1319
1350
  )
1320
1351
  )
1352
+ elif self.is_cur_keyword_("VertAxis.MinMax"):
1353
+ base_script_list = self.find_previous(statements, ast.BaseAxis)
1354
+ if base_script_list is None:
1355
+ raise FeatureLibError(
1356
+ "MinMax must be preceded by BaseScriptList",
1357
+ self.cur_token_location_,
1358
+ )
1359
+ if not base_script_list.vertical:
1360
+ raise FeatureLibError(
1361
+ "VertAxis.MinMax must be preceded by VertAxis statements",
1362
+ self.cur_token_location_,
1363
+ )
1364
+ base_script_list.minmax.append(self.parse_base_minmax_())
1321
1365
  elif self.cur_token_ == ";":
1322
1366
  continue
1323
1367
 
@@ -1574,7 +1618,7 @@ class Parser(object):
1574
1618
  "HorizAxis.BaseScriptList",
1575
1619
  "VertAxis.BaseScriptList",
1576
1620
  ), self.cur_token_
1577
- scripts = [(self.parse_base_script_record_(count))]
1621
+ scripts = [self.parse_base_script_record_(count)]
1578
1622
  while self.next_token_ == ",":
1579
1623
  self.expect_symbol_(",")
1580
1624
  scripts.append(self.parse_base_script_record_(count))
@@ -1587,6 +1631,25 @@ class Parser(object):
1587
1631
  coords = [self.expect_number_() for i in range(count)]
1588
1632
  return script_tag, base_tag, coords
1589
1633
 
1634
+ def parse_base_minmax_(self):
1635
+ script_tag = self.expect_script_tag_()
1636
+ language = self.expect_language_tag_()
1637
+ min_coord = self.expect_number_()
1638
+ self.advance_lexer_()
1639
+ if not (self.cur_token_type_ is Lexer.SYMBOL and self.cur_token_ == ","):
1640
+ raise FeatureLibError(
1641
+ "Expected a comma between min and max coordinates",
1642
+ self.cur_token_location_,
1643
+ )
1644
+ max_coord = self.expect_number_()
1645
+ if self.next_token_ == ",": # feature tag...
1646
+ raise FeatureLibError(
1647
+ "Feature tags are not yet supported in BASE table",
1648
+ self.cur_token_location_,
1649
+ )
1650
+
1651
+ return script_tag, language, min_coord, max_coord
1652
+
1590
1653
  def parse_device_(self):
1591
1654
  result = None
1592
1655
  self.expect_symbol_("<")
@@ -2004,44 +2067,6 @@ class Parser(object):
2004
2067
  )
2005
2068
  self.expect_symbol_(";")
2006
2069
 
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
2070
  def is_cur_keyword_(self, k):
2046
2071
  if self.cur_token_type_ is Lexer.NAME:
2047
2072
  if isinstance(k, type("")): # basestring is gone in Python3
@@ -2178,7 +2203,7 @@ class Parser(object):
2178
2203
  raise FeatureLibError(
2179
2204
  "Expected an equals sign", self.cur_token_location_
2180
2205
  )
2181
- value = self.expect_number_()
2206
+ value = self.expect_integer_or_float_()
2182
2207
  location[axis] = value
2183
2208
  if self.next_token_type_ is Lexer.NAME and self.next_token_[0] == ":":
2184
2209
  # Lexer has just read the value as a glyph name. We'll correct it later
@@ -2210,6 +2235,16 @@ class Parser(object):
2210
2235
  "Expected a floating-point number", self.cur_token_location_
2211
2236
  )
2212
2237
 
2238
+ def expect_integer_or_float_(self):
2239
+ if self.next_token_type_ == Lexer.FLOAT:
2240
+ return self.expect_float_()
2241
+ elif self.next_token_type_ is Lexer.NUMBER:
2242
+ return self.expect_number_()
2243
+ else:
2244
+ raise FeatureLibError(
2245
+ "Expected an integer or floating-point number", self.cur_token_location_
2246
+ )
2247
+
2213
2248
  def expect_decipoint_(self):
2214
2249
  if self.next_token_type_ == Lexer.FLOAT:
2215
2250
  return self.expect_float_()
@@ -17,7 +17,12 @@ class VariableScalar:
17
17
  def __repr__(self):
18
18
  items = []
19
19
  for location, value in self.values.items():
20
- loc = ",".join(["%s=%i" % (ax, loc) for ax, loc in location])
20
+ loc = ",".join(
21
+ [
22
+ f"{ax}={int(coord) if float(coord).is_integer() else coord}"
23
+ for ax, coord in location
24
+ ]
25
+ )
21
26
  items.append("%s:%i" % (loc, value))
22
27
  return "(" + (" ".join(items)) + ")"
23
28
 
fontTools/fontBuilder.py CHANGED
@@ -264,49 +264,49 @@ _nameIDs = dict(
264
264
  # to insert in setupNameTable doc string:
265
265
  # print("\n".join(("%s (nameID %s)" % (k, v)) for k, v in sorted(_nameIDs.items(), key=lambda x: x[1])))
266
266
 
267
- _panoseDefaults = Panose()
268
-
269
- _OS2Defaults = dict(
270
- version=3,
271
- xAvgCharWidth=0,
272
- usWeightClass=400,
273
- usWidthClass=5,
274
- fsType=0x0004, # default: Preview & Print embedding
275
- ySubscriptXSize=0,
276
- ySubscriptYSize=0,
277
- ySubscriptXOffset=0,
278
- ySubscriptYOffset=0,
279
- ySuperscriptXSize=0,
280
- ySuperscriptYSize=0,
281
- ySuperscriptXOffset=0,
282
- ySuperscriptYOffset=0,
283
- yStrikeoutSize=0,
284
- yStrikeoutPosition=0,
285
- sFamilyClass=0,
286
- panose=_panoseDefaults,
287
- ulUnicodeRange1=0,
288
- ulUnicodeRange2=0,
289
- ulUnicodeRange3=0,
290
- ulUnicodeRange4=0,
291
- achVendID="????",
292
- fsSelection=0,
293
- usFirstCharIndex=0,
294
- usLastCharIndex=0,
295
- sTypoAscender=0,
296
- sTypoDescender=0,
297
- sTypoLineGap=0,
298
- usWinAscent=0,
299
- usWinDescent=0,
300
- ulCodePageRange1=0,
301
- ulCodePageRange2=0,
302
- sxHeight=0,
303
- sCapHeight=0,
304
- usDefaultChar=0, # .notdef
305
- usBreakChar=32, # space
306
- usMaxContext=0,
307
- usLowerOpticalPointSize=0,
308
- usUpperOpticalPointSize=0,
309
- )
267
+
268
+ def _getOS2Defaults():
269
+ return dict(
270
+ version=3,
271
+ xAvgCharWidth=0,
272
+ usWeightClass=400,
273
+ usWidthClass=5,
274
+ fsType=0x0004, # default: Preview & Print embedding
275
+ ySubscriptXSize=0,
276
+ ySubscriptYSize=0,
277
+ ySubscriptXOffset=0,
278
+ ySubscriptYOffset=0,
279
+ ySuperscriptXSize=0,
280
+ ySuperscriptYSize=0,
281
+ ySuperscriptXOffset=0,
282
+ ySuperscriptYOffset=0,
283
+ yStrikeoutSize=0,
284
+ yStrikeoutPosition=0,
285
+ sFamilyClass=0,
286
+ panose=Panose(),
287
+ ulUnicodeRange1=0,
288
+ ulUnicodeRange2=0,
289
+ ulUnicodeRange3=0,
290
+ ulUnicodeRange4=0,
291
+ achVendID="????",
292
+ fsSelection=0,
293
+ usFirstCharIndex=0,
294
+ usLastCharIndex=0,
295
+ sTypoAscender=0,
296
+ sTypoDescender=0,
297
+ sTypoLineGap=0,
298
+ usWinAscent=0,
299
+ usWinDescent=0,
300
+ ulCodePageRange1=0,
301
+ ulCodePageRange2=0,
302
+ sxHeight=0,
303
+ sCapHeight=0,
304
+ usDefaultChar=0, # .notdef
305
+ usBreakChar=32, # space
306
+ usMaxContext=0,
307
+ usLowerOpticalPointSize=0,
308
+ usUpperOpticalPointSize=0,
309
+ )
310
310
 
311
311
 
312
312
  class FontBuilder(object):
@@ -493,7 +493,7 @@ class FontBuilder(object):
493
493
  """Create a new `OS/2` table and initialize it with default values,
494
494
  which can be overridden by keyword arguments.
495
495
  """
496
- self._initTableWithValues("OS/2", _OS2Defaults, values)
496
+ self._initTableWithValues("OS/2", _getOS2Defaults(), values)
497
497
  if "xAvgCharWidth" not in values:
498
498
  assert (
499
499
  "hmtx" in self.font
@@ -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.
@@ -196,7 +196,7 @@ def main(args=None):
196
196
 
197
197
  if len(fontfiles) < 1:
198
198
  print(
199
- "usage: pyftmerge [font1 ... fontN] [--input-file=filelist.txt] [--output-file=merged.ttf] [--import-file=tables.ttx]",
199
+ "usage: fonttools merge [font1 ... fontN] [--input-file=filelist.txt] [--output-file=merged.ttf] [--import-file=tables.ttx]",
200
200
  file=sys.stderr,
201
201
  )
202
202
  print(
fontTools/merge/cmap.py CHANGED
@@ -54,6 +54,28 @@ def _glyphsAreSame(
54
54
  return True
55
55
 
56
56
 
57
+ def computeMegaUvs(merger, uvsTables):
58
+ """Returns merged UVS subtable (cmap format=14)."""
59
+ uvsDict = {}
60
+ cmap = merger.cmap
61
+ for table in uvsTables:
62
+ for variationSelector, uvsMapping in table.uvsDict.items():
63
+ if variationSelector not in uvsDict:
64
+ uvsDict[variationSelector] = {}
65
+ for unicodeValue, glyphName in uvsMapping:
66
+ if cmap.get(unicodeValue) == glyphName:
67
+ # this is a default variation
68
+ glyphName = None
69
+ # prefer previous glyph id if both fonts defined UVS
70
+ if unicodeValue not in uvsDict[variationSelector]:
71
+ uvsDict[variationSelector][unicodeValue] = glyphName
72
+
73
+ for variationSelector in uvsDict:
74
+ uvsDict[variationSelector] = [*uvsDict[variationSelector].items()]
75
+
76
+ return uvsDict
77
+
78
+
57
79
  # Valid (format, platformID, platEncID) triplets for cmap subtables containing
58
80
  # Unicode BMP-only and Unicode Full Repertoire semantics.
59
81
  # Cf. OpenType spec for "Platform specific encodings":
@@ -61,24 +83,29 @@ def _glyphsAreSame(
61
83
  class _CmapUnicodePlatEncodings:
62
84
  BMP = {(4, 3, 1), (4, 0, 3), (4, 0, 4), (4, 0, 6)}
63
85
  FullRepertoire = {(12, 3, 10), (12, 0, 4), (12, 0, 6)}
86
+ UVS = {(14, 0, 5)}
64
87
 
65
88
 
66
89
  def computeMegaCmap(merger, cmapTables):
67
- """Sets merger.cmap and merger.glyphOrder."""
90
+ """Sets merger.cmap and merger.uvsDict."""
68
91
 
69
92
  # TODO Handle format=14.
70
93
  # Only merge format 4 and 12 Unicode subtables, ignores all other subtables
71
94
  # If there is a format 12 table for a font, ignore the format 4 table of it
72
95
  chosenCmapTables = []
96
+ chosenUvsTables = []
73
97
  for fontIdx, table in enumerate(cmapTables):
74
98
  format4 = None
75
99
  format12 = None
100
+ format14 = None
76
101
  for subtable in table.tables:
77
102
  properties = (subtable.format, subtable.platformID, subtable.platEncID)
78
103
  if properties in _CmapUnicodePlatEncodings.BMP:
79
104
  format4 = subtable
80
105
  elif properties in _CmapUnicodePlatEncodings.FullRepertoire:
81
106
  format12 = subtable
107
+ elif properties in _CmapUnicodePlatEncodings.UVS:
108
+ format14 = subtable
82
109
  else:
83
110
  log.warning(
84
111
  "Dropped cmap subtable from font '%s':\t"
@@ -93,6 +120,9 @@ def computeMegaCmap(merger, cmapTables):
93
120
  elif format4 is not None:
94
121
  chosenCmapTables.append((format4, fontIdx))
95
122
 
123
+ if format14 is not None:
124
+ chosenUvsTables.append(format14)
125
+
96
126
  # Build the unicode mapping
97
127
  merger.cmap = cmap = {}
98
128
  fontIndexForGlyph = {}
@@ -127,6 +157,8 @@ def computeMegaCmap(merger, cmapTables):
127
157
  "Dropped mapping from codepoint %#06X to glyphId '%s'", uni, gid
128
158
  )
129
159
 
160
+ merger.uvsDict = computeMegaUvs(merger, chosenUvsTables)
161
+
130
162
 
131
163
  def renameCFFCharStrings(merger, glyphOrder, cffTable):
132
164
  """Rename topDictIndex charStrings based on glyphOrder."""
fontTools/merge/tables.py CHANGED
@@ -312,7 +312,6 @@ def merge(self, m, tables):
312
312
 
313
313
  @add_method(ttLib.getTableClass("cmap"))
314
314
  def merge(self, m, tables):
315
- # TODO Handle format=14.
316
315
  if not hasattr(m, "cmap"):
317
316
  computeMegaCmap(m, tables)
318
317
  cmap = m.cmap
@@ -336,6 +335,18 @@ def merge(self, m, tables):
336
335
  cmapTable.cmap = cmapBmpOnly
337
336
  # ordered by platform then encoding
338
337
  self.tables.insert(0, cmapTable)
338
+
339
+ uvsDict = m.uvsDict
340
+ if uvsDict:
341
+ # format-14
342
+ uvsTable = module.cmap_classes[14](14)
343
+ uvsTable.platformID = 0
344
+ uvsTable.platEncID = 5
345
+ uvsTable.language = 0
346
+ uvsTable.cmap = {}
347
+ uvsTable.uvsDict = uvsDict
348
+ # ordered by platform then encoding
349
+ self.tables.insert(0, uvsTable)
339
350
  self.tableVersion = 0
340
351
  self.numSubTables = len(self.tables)
341
352
  return self