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
@@ -3,6 +3,7 @@ from functools import partial
3
3
  from fontTools.misc import sstruct
4
4
  from fontTools.misc.textTools import safeEval
5
5
  from fontTools.misc.lazyTools import LazyDict
6
+ from fontTools.ttLib import OPTIMIZE_FONT_SPEED
6
7
  from fontTools.ttLib.tables.TupleVariation import TupleVariation
7
8
  from . import DefaultTable
8
9
  import array
@@ -23,19 +24,24 @@ log = logging.getLogger(__name__)
23
24
  # FreeType2 source code for parsing 'gvar':
24
25
  # http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/src/truetype/ttgxvar.c
25
26
 
26
- GVAR_HEADER_FORMAT = """
27
+ GVAR_HEADER_FORMAT_HEAD = """
27
28
  > # big endian
28
29
  version: H
29
30
  reserved: H
30
31
  axisCount: H
31
32
  sharedTupleCount: H
32
33
  offsetToSharedTuples: I
33
- glyphCount: H
34
+ """
35
+ # In between the HEAD and TAIL lies the glyphCount, which is
36
+ # of different size: 2 bytes for gvar, and 3 bytes for GVAR.
37
+ GVAR_HEADER_FORMAT_TAIL = """
38
+ > # big endian
34
39
  flags: H
35
40
  offsetToGlyphVariationData: I
36
41
  """
37
42
 
38
- GVAR_HEADER_SIZE = sstruct.calcsize(GVAR_HEADER_FORMAT)
43
+ GVAR_HEADER_SIZE_HEAD = sstruct.calcsize(GVAR_HEADER_FORMAT_HEAD)
44
+ GVAR_HEADER_SIZE_TAIL = sstruct.calcsize(GVAR_HEADER_FORMAT_TAIL)
39
45
 
40
46
 
41
47
  class table__g_v_a_r(DefaultTable.DefaultTable):
@@ -50,6 +56,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
50
56
  """
51
57
 
52
58
  dependencies = ["fvar", "glyf"]
59
+ gid_size = 2
53
60
 
54
61
  def __init__(self, tag=None):
55
62
  DefaultTable.DefaultTable.__init__(self, tag)
@@ -57,6 +64,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
57
64
  self.variations = {}
58
65
 
59
66
  def compile(self, ttFont):
67
+
60
68
  axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
61
69
  sharedTuples = tv.compileSharedTuples(
62
70
  axisTags, itertools.chain(*self.variations.values())
@@ -72,28 +80,33 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
72
80
  offsets.append(offset)
73
81
  compiledOffsets, tableFormat = self.compileOffsets_(offsets)
74
82
 
83
+ GVAR_HEADER_SIZE = GVAR_HEADER_SIZE_HEAD + self.gid_size + GVAR_HEADER_SIZE_TAIL
75
84
  header = {}
76
85
  header["version"] = self.version
77
86
  header["reserved"] = self.reserved
78
87
  header["axisCount"] = len(axisTags)
79
88
  header["sharedTupleCount"] = len(sharedTuples)
80
89
  header["offsetToSharedTuples"] = GVAR_HEADER_SIZE + len(compiledOffsets)
81
- header["glyphCount"] = len(compiledGlyphs)
82
90
  header["flags"] = tableFormat
83
91
  header["offsetToGlyphVariationData"] = (
84
92
  header["offsetToSharedTuples"] + sharedTupleSize
85
93
  )
86
- compiledHeader = sstruct.pack(GVAR_HEADER_FORMAT, header)
87
94
 
88
- result = [compiledHeader, compiledOffsets]
95
+ result = [
96
+ sstruct.pack(GVAR_HEADER_FORMAT_HEAD, header),
97
+ len(compiledGlyphs).to_bytes(self.gid_size, "big"),
98
+ sstruct.pack(GVAR_HEADER_FORMAT_TAIL, header),
99
+ ]
100
+
101
+ result.append(compiledOffsets)
89
102
  result.extend(sharedTuples)
90
103
  result.extend(compiledGlyphs)
91
104
  return b"".join(result)
92
105
 
93
106
  def compileGlyphs_(self, ttFont, axisTags, sharedCoordIndices):
107
+ optimizeSpeed = ttFont.cfg[OPTIMIZE_FONT_SPEED]
94
108
  result = []
95
109
  glyf = ttFont["glyf"]
96
- optimizeSize = getattr(self, "optimizeSize", True)
97
110
  for glyphName in ttFont.getGlyphOrder():
98
111
  variations = self.variations.get(glyphName, [])
99
112
  if not variations:
@@ -102,11 +115,12 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
102
115
  pointCountUnused = 0 # pointCount is actually unused by compileGlyph
103
116
  result.append(
104
117
  compileGlyph_(
118
+ self.gid_size,
105
119
  variations,
106
120
  pointCountUnused,
107
121
  axisTags,
108
122
  sharedCoordIndices,
109
- optimizeSize=optimizeSize,
123
+ optimizeSize=not optimizeSpeed,
110
124
  )
111
125
  )
112
126
  return result
@@ -114,7 +128,19 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
114
128
  def decompile(self, data, ttFont):
115
129
  axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
116
130
  glyphs = ttFont.getGlyphOrder()
117
- sstruct.unpack(GVAR_HEADER_FORMAT, data[0:GVAR_HEADER_SIZE], self)
131
+
132
+ # Parse the header
133
+ GVAR_HEADER_SIZE = GVAR_HEADER_SIZE_HEAD + self.gid_size + GVAR_HEADER_SIZE_TAIL
134
+ sstruct.unpack(GVAR_HEADER_FORMAT_HEAD, data[:GVAR_HEADER_SIZE_HEAD], self)
135
+ self.glyphCount = int.from_bytes(
136
+ data[GVAR_HEADER_SIZE_HEAD : GVAR_HEADER_SIZE_HEAD + self.gid_size], "big"
137
+ )
138
+ sstruct.unpack(
139
+ GVAR_HEADER_FORMAT_TAIL,
140
+ data[GVAR_HEADER_SIZE_HEAD + self.gid_size : GVAR_HEADER_SIZE],
141
+ self,
142
+ )
143
+
118
144
  assert len(glyphs) == self.glyphCount
119
145
  assert len(axisTags) == self.axisCount
120
146
  sharedCoords = tv.decompileSharedTuples(
@@ -144,7 +170,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
144
170
  glyph = glyf[glyphName]
145
171
  numPointsInGlyph = self.getNumPoints_(glyph)
146
172
  return decompileGlyph_(
147
- numPointsInGlyph, sharedCoords, axisTags, gvarData
173
+ self.gid_size, numPointsInGlyph, sharedCoords, axisTags, gvarData
148
174
  )
149
175
 
150
176
  return read_item
@@ -262,23 +288,42 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
262
288
 
263
289
 
264
290
  def compileGlyph_(
265
- variations, pointCount, axisTags, sharedCoordIndices, *, optimizeSize=True
291
+ dataOffsetSize,
292
+ variations,
293
+ pointCount,
294
+ axisTags,
295
+ sharedCoordIndices,
296
+ *,
297
+ optimizeSize=True,
266
298
  ):
299
+ assert dataOffsetSize in (2, 3)
267
300
  tupleVariationCount, tuples, data = tv.compileTupleVariationStore(
268
301
  variations, pointCount, axisTags, sharedCoordIndices, optimizeSize=optimizeSize
269
302
  )
270
303
  if tupleVariationCount == 0:
271
304
  return b""
272
- result = [struct.pack(">HH", tupleVariationCount, 4 + len(tuples)), tuples, data]
273
- if (len(tuples) + len(data)) % 2 != 0:
305
+
306
+ offsetToData = 2 + dataOffsetSize + len(tuples)
307
+
308
+ result = [
309
+ tupleVariationCount.to_bytes(2, "big"),
310
+ offsetToData.to_bytes(dataOffsetSize, "big"),
311
+ tuples,
312
+ data,
313
+ ]
314
+ if (offsetToData + len(data)) % 2 != 0:
274
315
  result.append(b"\0") # padding
275
316
  return b"".join(result)
276
317
 
277
318
 
278
- def decompileGlyph_(pointCount, sharedTuples, axisTags, data):
279
- if len(data) < 4:
319
+ def decompileGlyph_(dataOffsetSize, pointCount, sharedTuples, axisTags, data):
320
+ assert dataOffsetSize in (2, 3)
321
+ if len(data) < 2 + dataOffsetSize:
280
322
  return []
281
- tupleVariationCount, offsetToData = struct.unpack(">HH", data[:4])
323
+
324
+ tupleVariationCount = int.from_bytes(data[:2], "big")
325
+ offsetToData = int.from_bytes(data[2 : 2 + dataOffsetSize], "big")
326
+
282
327
  dataPos = offsetToData
283
328
  return tv.decompileTupleVariationStore(
284
329
  "gvar",
@@ -287,6 +332,6 @@ def decompileGlyph_(pointCount, sharedTuples, axisTags, data):
287
332
  pointCount,
288
333
  sharedTuples,
289
334
  data,
290
- 4,
335
+ 2 + dataOffsetSize,
291
336
  offsetToData,
292
337
  )
@@ -122,13 +122,16 @@ class table__p_o_s_t(DefaultTable.DefaultTable):
122
122
  glyphName = psName = self.glyphOrder[i]
123
123
  if glyphName == "":
124
124
  glyphName = "glyph%.5d" % i
125
+
125
126
  if glyphName in allNames:
126
127
  # make up a new glyphName that's unique
127
128
  n = allNames[glyphName]
128
- while (glyphName + "#" + str(n)) in allNames:
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:
129
132
  n += 1
130
133
  allNames[glyphName] = n + 1
131
- glyphName = glyphName + "#" + str(n)
134
+ glyphName = glyphName + "." + str(n)
132
135
 
133
136
  self.glyphOrder[i] = glyphName
134
137
  allNames[glyphName] = 1
@@ -398,6 +398,7 @@ class OTTableWriter(object):
398
398
  self.localState = localState
399
399
  self.tableTag = tableTag
400
400
  self.parent = None
401
+ self.name = "<none>"
401
402
 
402
403
  def __setitem__(self, name, value):
403
404
  state = self.localState.copy() if self.localState else dict()
@@ -10,7 +10,7 @@ from fontTools.ttLib.tables.TupleVariation import TupleVariation
10
10
  from fontTools.misc.roundTools import nearestMultipleShortestRepr, otRound
11
11
  from fontTools.misc.textTools import bytesjoin, tobytes, tostr, pad, safeEval
12
12
  from fontTools.misc.lazyTools import LazyList
13
- from fontTools.ttLib import getSearchRange
13
+ from fontTools.ttLib import OPTIMIZE_FONT_SPEED, getSearchRange
14
14
  from .otBase import (
15
15
  CountReference,
16
16
  FormatSwitchingBaseTable,
@@ -1810,7 +1810,10 @@ class TupleValues:
1810
1810
  return TupleVariation.decompileDeltas_(None, data)[0]
1811
1811
 
1812
1812
  def write(self, writer, font, tableDict, values, repeatIndex=None):
1813
- return bytes(TupleVariation.compileDeltaValues_(values))
1813
+ optimizeSpeed = font.cfg[OPTIMIZE_FONT_SPEED]
1814
+ return bytes(
1815
+ TupleVariation.compileDeltaValues_(values, optimizeSize=not optimizeSpeed)
1816
+ )
1814
1817
 
1815
1818
  def xmlRead(self, attrs, content, font):
1816
1819
  return safeEval(attrs["value"])
@@ -11,6 +11,7 @@ from functools import reduce
11
11
  from math import radians
12
12
  import itertools
13
13
  from collections import defaultdict, namedtuple
14
+ from fontTools.ttLib import OPTIMIZE_FONT_SPEED
14
15
  from fontTools.ttLib.tables.TupleVariation import TupleVariation
15
16
  from fontTools.ttLib.tables.otTraverse import dfs_base_table
16
17
  from fontTools.misc.arrayTools import quantizeRect
@@ -236,6 +237,8 @@ class VarComponent:
236
237
  return data[i:]
237
238
 
238
239
  def compile(self, font):
240
+ optimizeSpeed = font.cfg[OPTIMIZE_FONT_SPEED]
241
+
239
242
  data = []
240
243
 
241
244
  flags = self.flags
@@ -259,7 +262,8 @@ class VarComponent:
259
262
  data.append(_write_uint32var(self.axisIndicesIndex))
260
263
  data.append(
261
264
  TupleVariation.compileDeltaValues_(
262
- [fl2fi(v, 14) for v in self.axisValues]
265
+ [fl2fi(v, 14) for v in self.axisValues],
266
+ optimizeSize=not optimizeSpeed,
263
267
  )
264
268
  )
265
269
  else:
fontTools/ttLib/ttFont.py CHANGED
@@ -593,10 +593,8 @@ class TTFont(object):
593
593
  # temporary cmap and by the real cmap in case we don't find a unicode
594
594
  # cmap.
595
595
  numGlyphs = int(self["maxp"].numGlyphs)
596
- glyphOrder = [None] * numGlyphs
596
+ glyphOrder = ["glyph%.5d" % i for i in range(numGlyphs)]
597
597
  glyphOrder[0] = ".notdef"
598
- for i in range(1, numGlyphs):
599
- glyphOrder[i] = "glyph%.5d" % i
600
598
  # Set the glyph order, so the cmap parser has something
601
599
  # to work with (so we don't get called recursively).
602
600
  self.glyphOrder = glyphOrder
@@ -606,7 +604,7 @@ class TTFont(object):
606
604
  # this naming table will usually not cover all glyphs in the font.
607
605
  # If the font has no Unicode cmap table, reversecmap will be empty.
608
606
  if "cmap" in self:
609
- reversecmap = self["cmap"].buildReversed()
607
+ reversecmap = self["cmap"].buildReversedMin()
610
608
  else:
611
609
  reversecmap = {}
612
610
  useCount = {}
@@ -616,7 +614,7 @@ class TTFont(object):
616
614
  # If a font maps both U+0041 LATIN CAPITAL LETTER A and
617
615
  # U+0391 GREEK CAPITAL LETTER ALPHA to the same glyph,
618
616
  # we prefer naming the glyph as "A".
619
- glyphName = self._makeGlyphName(min(reversecmap[tempName]))
617
+ glyphName = self._makeGlyphName(reversecmap[tempName])
620
618
  numUses = useCount[glyphName] = useCount.get(glyphName, 0) + 1
621
619
  if numUses > 1:
622
620
  glyphName = "%s.alt%d" % (glyphName, numUses - 1)
@@ -104,16 +104,6 @@ class _TTGlyphSetGlyf(_TTGlyphSet):
104
104
  return _TTGlyphGlyf(self, glyphName, recalcBounds=self.recalcBounds)
105
105
 
106
106
 
107
- class _TTGlyphSetGlyf(_TTGlyphSet):
108
- def __init__(self, font, location, recalcBounds=True):
109
- self.glyfTable = font["glyf"]
110
- super().__init__(font, location, self.glyfTable, recalcBounds=recalcBounds)
111
- self.gvarTable = font.get("gvar")
112
-
113
- def __getitem__(self, glyphName):
114
- return _TTGlyphGlyf(self, glyphName, recalcBounds=self.recalcBounds)
115
-
116
-
117
107
  class _TTGlyphSetCFF(_TTGlyphSet):
118
108
  def __init__(self, font, location):
119
109
  tableTag = "CFF2" if "CFF2" in font else "CFF "
fontTools/ttx.py CHANGED
@@ -101,9 +101,15 @@ Compile options
101
101
  --with-zopfli
102
102
  Use Zopfli instead of Zlib to compress WOFF. The Python
103
103
  extension is available at https://pypi.python.org/pypi/zopfli
104
+ --optimize-font-speed
105
+ Enable optimizations that prioritize speed over file size.
106
+ This mainly affects how glyf t able and gvar / VARC tables are
107
+ compiled. The produced fonts will be larger, but rendering
108
+ performance will be improved with HarfBuzz and other text
109
+ layout engines.
104
110
  """
105
111
 
106
- from fontTools.ttLib import TTFont, TTLibError
112
+ from fontTools.ttLib import OPTIMIZE_FONT_SPEED, TTFont, TTLibError
107
113
  from fontTools.misc.macCreatorType import getMacCreatorAndType
108
114
  from fontTools.unicode import setUnicodeData
109
115
  from fontTools.misc.textTools import Tag, tostr
@@ -141,6 +147,7 @@ class Options(object):
141
147
  recalcTimestamp = None
142
148
  flavor = None
143
149
  useZopfli = False
150
+ optimizeFontSpeed = False
144
151
 
145
152
  def __init__(self, rawOptions, numFiles):
146
153
  self.onlyTables = []
@@ -229,6 +236,8 @@ class Options(object):
229
236
  self.flavor = value
230
237
  elif option == "--with-zopfli":
231
238
  self.useZopfli = True
239
+ elif option == "--optimize-font-speed":
240
+ self.optimizeFontSpeed = True
232
241
  if self.verbose and self.quiet:
233
242
  raise getopt.GetoptError("-q and -v options are mutually exclusive")
234
243
  if self.verbose:
@@ -324,6 +333,8 @@ def ttCompile(input, output, options):
324
333
  recalcBBoxes=options.recalcBBoxes,
325
334
  recalcTimestamp=options.recalcTimestamp,
326
335
  )
336
+ if options.optimizeFontSpeed:
337
+ ttf.cfg[OPTIMIZE_FONT_SPEED] = options.optimizeFontSpeed
327
338
  ttf.importXML(input)
328
339
 
329
340
  if options.recalcTimestamp is None and "head" in ttf and input is not sys.stdin:
@@ -386,6 +397,7 @@ def parseOptions(args):
386
397
  "version",
387
398
  "with-zopfli",
388
399
  "newline=",
400
+ "optimize-font-speed",
389
401
  ],
390
402
  )
391
403
 
@@ -204,7 +204,7 @@ class UFOReader(_UFOBaseIO):
204
204
  """Read the various components of a .ufo.
205
205
 
206
206
  Attributes:
207
- path: An `os.PathLike` object pointing to the .ufo.
207
+ path: An :class:`os.PathLike` object pointing to the .ufo.
208
208
  validate: A boolean indicating if the data read should be
209
209
  validated. Defaults to `True`.
210
210
 
@@ -891,7 +891,7 @@ class UFOWriter(UFOReader):
891
891
  """Write the various components of a .ufo.
892
892
 
893
893
  Attributes:
894
- path: An `os.PathLike` object pointing to the .ufo.
894
+ path: An :class:`os.PathLike` object pointing to the .ufo.
895
895
  formatVersion: the UFO format version as a tuple of integers (major, minor),
896
896
  or as a single integer for the major digit only (minor is implied to be 0).
897
897
  By default, the latest formatVersion will be used; currently it is 3.0,
@@ -1,11 +1,33 @@
1
1
  """
2
- Conversion functions.
2
+ Functions for converting UFO1 or UFO2 files into UFO3 format.
3
+
4
+ Currently provides functionality for converting kerning rules
5
+ and kerning groups. Conversion is only supported _from_ UFO1
6
+ or UFO2, and _to_ UFO3.
3
7
  """
4
8
 
5
9
  # adapted from the UFO spec
6
10
 
7
11
 
8
12
  def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()):
13
+ """Convert kerning data in UFO1 or UFO2 syntax into UFO3 syntax.
14
+
15
+ Args:
16
+ kerning:
17
+ A dictionary containing the kerning rules defined in
18
+ the UFO font, as used in :class:`.UFOReader` objects.
19
+ groups:
20
+ A dictionary containing the groups defined in the UFO
21
+ font, as used in :class:`.UFOReader` objects.
22
+ glyphSet:
23
+ Optional; a set of glyph objects to skip (default: None).
24
+
25
+ Returns:
26
+ 1. A dictionary representing the converted kerning data.
27
+ 2. A copy of the groups dictionary, with all groups renamed to UFO3 syntax.
28
+ 3. A dictionary containing the mapping of old group names to new group names.
29
+
30
+ """
9
31
  # gather known kerning groups based on the prefixes
10
32
  firstReferencedGroups, secondReferencedGroups = findKnownKerningGroups(groups)
11
33
  # Make lists of groups referenced in kerning pairs.
@@ -63,35 +85,54 @@ def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()):
63
85
 
64
86
 
65
87
  def findKnownKerningGroups(groups):
66
- """
67
- This will find kerning groups with known prefixes.
68
- In some cases not all kerning groups will be referenced
69
- by the kerning pairs. The algorithm for locating groups
70
- in convertUFO1OrUFO2KerningToUFO3Kerning will miss these
71
- unreferenced groups. By scanning for known prefixes
88
+ """Find all kerning groups in a UFO1 or UFO2 font that use known prefixes.
89
+
90
+ In some cases, not all kerning groups will be referenced
91
+ by the kerning pairs in a UFO. The algorithm for locating
92
+ groups in :func:`convertUFO1OrUFO2KerningToUFO3Kerning` will
93
+ miss these unreferenced groups. By scanning for known prefixes,
72
94
  this function will catch all of the prefixed groups.
73
95
 
74
- These are the prefixes and sides that are handled:
96
+ The prefixes and sides by this function are:
97
+
75
98
  @MMK_L_ - side 1
76
99
  @MMK_R_ - side 2
77
100
 
78
- >>> testGroups = {
79
- ... "@MMK_L_1" : None,
80
- ... "@MMK_L_2" : None,
81
- ... "@MMK_L_3" : None,
82
- ... "@MMK_R_1" : None,
83
- ... "@MMK_R_2" : None,
84
- ... "@MMK_R_3" : None,
85
- ... "@MMK_l_1" : None,
86
- ... "@MMK_r_1" : None,
87
- ... "@MMK_X_1" : None,
88
- ... "foo" : None,
89
- ... }
90
- >>> first, second = findKnownKerningGroups(testGroups)
91
- >>> sorted(first) == ['@MMK_L_1', '@MMK_L_2', '@MMK_L_3']
92
- True
93
- >>> sorted(second) == ['@MMK_R_1', '@MMK_R_2', '@MMK_R_3']
94
- True
101
+ as defined in the UFO1 specification.
102
+
103
+ Args:
104
+ groups:
105
+ A dictionary containing the groups defined in the UFO
106
+ font, as read by :class:`.UFOReader`.
107
+
108
+ Returns:
109
+ Two sets; the first containing the names of all
110
+ first-side kerning groups identified in the ``groups``
111
+ dictionary, and the second containing the names of all
112
+ second-side kerning groups identified.
113
+
114
+ "First-side" and "second-side" are with respect to the
115
+ writing direction of the script.
116
+
117
+ Example::
118
+
119
+ >>> testGroups = {
120
+ ... "@MMK_L_1" : None,
121
+ ... "@MMK_L_2" : None,
122
+ ... "@MMK_L_3" : None,
123
+ ... "@MMK_R_1" : None,
124
+ ... "@MMK_R_2" : None,
125
+ ... "@MMK_R_3" : None,
126
+ ... "@MMK_l_1" : None,
127
+ ... "@MMK_r_1" : None,
128
+ ... "@MMK_X_1" : None,
129
+ ... "foo" : None,
130
+ ... }
131
+ >>> first, second = findKnownKerningGroups(testGroups)
132
+ >>> sorted(first) == ['@MMK_L_1', '@MMK_L_2', '@MMK_L_3']
133
+ True
134
+ >>> sorted(second) == ['@MMK_R_1', '@MMK_R_2', '@MMK_R_3']
135
+ True
95
136
  """
96
137
  knownFirstGroupPrefixes = ["@MMK_L_"]
97
138
  knownSecondGroupPrefixes = ["@MMK_R_"]
@@ -110,6 +151,27 @@ def findKnownKerningGroups(groups):
110
151
 
111
152
 
112
153
  def makeUniqueGroupName(name, groupNames, counter=0):
154
+ """Make a kerning group name that will be unique within the set of group names.
155
+
156
+ If the requested kerning group name already exists within the set, this
157
+ will return a new name by adding an incremented counter to the end
158
+ of the requested name.
159
+
160
+ Args:
161
+ name:
162
+ The requested kerning group name.
163
+ groupNames:
164
+ A list of the existing kerning group names.
165
+ counter:
166
+ Optional; a counter of group names already seen (default: 0). If
167
+ :attr:`.counter` is not provided, the function will recurse,
168
+ incrementing the value of :attr:`.counter` until it finds the
169
+ first unused ``name+counter`` combination, and return that result.
170
+
171
+ Returns:
172
+ A unique kerning group name composed of the requested name suffixed
173
+ by the smallest available integer counter.
174
+ """
113
175
  # Add a number to the name if the counter is higher than zero.
114
176
  newName = name
115
177
  if counter > 0:
@@ -123,6 +185,8 @@ def makeUniqueGroupName(name, groupNames, counter=0):
123
185
 
124
186
  def test():
125
187
  """
188
+ Tests for :func:`.convertUFO1OrUFO2KerningToUFO3Kerning`.
189
+
126
190
  No known prefixes.
127
191
 
128
192
  >>> testKerning = {
@@ -10,6 +10,14 @@ class UnsupportedUFOFormat(UFOLibError):
10
10
 
11
11
 
12
12
  class GlifLibError(UFOLibError):
13
+ """An error raised by glifLib.
14
+
15
+ This class is a loose backport of PEP 678, adding a :attr:`.note`
16
+ attribute that can hold additional context for errors encountered.
17
+
18
+ It will be maintained until only Python 3.11-and-later are supported.
19
+ """
20
+
13
21
  def _add_note(self, note: str) -> None:
14
22
  # Loose backport of PEP 678 until we only support Python 3.11+, used for
15
23
  # adding additional context to errors.
fontTools/ufoLib/etree.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """DEPRECATED - This module is kept here only as a backward compatibility shim
2
- for the old ufoLib.etree module, which was moved to fontTools.misc.etree.
2
+ for the old ufoLib.etree module, which was moved to :mod:`fontTools.misc.etree`.
3
3
  Please use the latter instead.
4
4
  """
5
5