fonttools 4.57.0__cp312-cp312-win_amd64.whl → 4.58.1__cp312-cp312-win_amd64.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 (67) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/cffLib/__init__.py +61 -26
  3. fontTools/cffLib/specializer.py +4 -1
  4. fontTools/cu2qu/cu2qu.c +15357 -14829
  5. fontTools/cu2qu/cu2qu.cp312-win_amd64.pyd +0 -0
  6. fontTools/designspaceLib/statNames.py +14 -7
  7. fontTools/feaLib/ast.py +12 -9
  8. fontTools/feaLib/builder.py +75 -49
  9. fontTools/feaLib/lexer.c +17160 -17986
  10. fontTools/feaLib/lexer.cp312-win_amd64.pyd +0 -0
  11. fontTools/feaLib/parser.py +1 -39
  12. fontTools/fontBuilder.py +6 -0
  13. fontTools/merge/cmap.py +33 -1
  14. fontTools/merge/tables.py +12 -1
  15. fontTools/misc/bezierTools.c +39948 -41831
  16. fontTools/misc/bezierTools.cp312-win_amd64.pyd +0 -0
  17. fontTools/misc/etree.py +4 -27
  18. fontTools/misc/loggingTools.py +1 -1
  19. fontTools/misc/symfont.py +6 -8
  20. fontTools/mtiLib/__init__.py +1 -3
  21. fontTools/otlLib/builder.py +359 -145
  22. fontTools/otlLib/optimize/gpos.py +42 -62
  23. fontTools/pens/momentsPen.c +13283 -13448
  24. fontTools/pens/momentsPen.cp312-win_amd64.pyd +0 -0
  25. fontTools/pens/pointPen.py +21 -12
  26. fontTools/pens/t2CharStringPen.py +31 -11
  27. fontTools/qu2cu/qu2cu.c +16550 -16269
  28. fontTools/qu2cu/qu2cu.cp312-win_amd64.pyd +0 -0
  29. fontTools/subset/__init__.py +12 -1
  30. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  31. fontTools/ttLib/tables/T_S_I__0.py +14 -3
  32. fontTools/ttLib/tables/T_S_I__5.py +16 -5
  33. fontTools/ttLib/tables/__init__.py +1 -0
  34. fontTools/ttLib/tables/_c_v_t.py +2 -0
  35. fontTools/ttLib/tables/_f_p_g_m.py +3 -1
  36. fontTools/ttLib/tables/_g_l_y_f.py +2 -6
  37. fontTools/ttLib/tables/_g_v_a_r.py +58 -15
  38. fontTools/ttLib/tables/_p_o_s_t.py +5 -2
  39. fontTools/ttLib/tables/otBase.py +1 -0
  40. fontTools/ufoLib/__init__.py +3 -3
  41. fontTools/ufoLib/converters.py +89 -25
  42. fontTools/ufoLib/errors.py +8 -0
  43. fontTools/ufoLib/etree.py +1 -1
  44. fontTools/ufoLib/filenames.py +155 -100
  45. fontTools/ufoLib/glifLib.py +9 -2
  46. fontTools/ufoLib/kerning.py +66 -36
  47. fontTools/ufoLib/utils.py +5 -2
  48. fontTools/unicodedata/Mirrored.py +446 -0
  49. fontTools/unicodedata/__init__.py +6 -2
  50. fontTools/varLib/__init__.py +20 -6
  51. fontTools/varLib/featureVars.py +13 -7
  52. fontTools/varLib/hvar.py +1 -1
  53. fontTools/varLib/instancer/__init__.py +14 -5
  54. fontTools/varLib/iup.c +19642 -19154
  55. fontTools/varLib/iup.cp312-win_amd64.pyd +0 -0
  56. fontTools/voltLib/__main__.py +206 -0
  57. fontTools/voltLib/ast.py +4 -0
  58. fontTools/voltLib/parser.py +16 -8
  59. fontTools/voltLib/voltToFea.py +347 -166
  60. {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/METADATA +64 -11
  61. {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/RECORD +67 -63
  62. {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/WHEEL +1 -1
  63. fonttools-4.58.1.dist-info/licenses/LICENSE.external +359 -0
  64. {fonttools-4.57.0.data → fonttools-4.58.1.data}/data/share/man/man1/ttx.1 +0 -0
  65. {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/entry_points.txt +0 -0
  66. {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/licenses/LICENSE +0 -0
  67. {fonttools-4.57.0.dist-info → fonttools-4.58.1.dist-info}/top_level.txt +0 -0
Binary file
@@ -16,6 +16,7 @@ from fontTools.subset.cff import *
16
16
  from fontTools.subset.svg import *
17
17
  from fontTools.varLib import varStore, multiVarStore # For monkey-patching
18
18
  from fontTools.ttLib.tables._n_a_m_e import NameRecordVisitor
19
+ from fontTools.unicodedata import mirrored
19
20
  import sys
20
21
  import struct
21
22
  import array
@@ -2870,6 +2871,15 @@ def prune_post_subset(self, font, options):
2870
2871
  def closure_glyphs(self, s):
2871
2872
  tables = [t for t in self.tables if t.isUnicode()]
2872
2873
 
2874
+ # Closure unicodes, which for now is pulling in bidi mirrored variants
2875
+ if s.options.bidi_closure:
2876
+ additional_unicodes = set()
2877
+ for u in s.unicodes_requested:
2878
+ mirror_u = mirrored(u)
2879
+ if mirror_u is not None:
2880
+ additional_unicodes.add(mirror_u)
2881
+ s.unicodes_requested.update(additional_unicodes)
2882
+
2873
2883
  # Close glyphs
2874
2884
  for table in tables:
2875
2885
  if table.format == 14:
@@ -3191,6 +3201,7 @@ class Options(object):
3191
3201
  self.font_number = -1
3192
3202
  self.pretty_svg = False
3193
3203
  self.lazy = True
3204
+ self.bidi_closure = True
3194
3205
 
3195
3206
  self.set(**kwargs)
3196
3207
 
@@ -3746,7 +3757,7 @@ def main(args=None):
3746
3757
  text += g[7:]
3747
3758
  continue
3748
3759
  if g.startswith("--text-file="):
3749
- with open(g[12:], encoding="utf-8") as f:
3760
+ with open(g[12:], encoding="utf-8-sig") as f:
3750
3761
  text += f.read().replace("\n", "")
3751
3762
  continue
3752
3763
  if g.startswith("--unicodes="):
@@ -0,0 +1,5 @@
1
+ from ._g_v_a_r import table__g_v_a_r
2
+
3
+
4
+ class table_G_V_A_R_(table__g_v_a_r):
5
+ gid_size = 3
@@ -1,4 +1,4 @@
1
- """ TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
1
+ """TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
2
2
  tool to store its hinting source data.
3
3
 
4
4
  TSI0 is the index table containing the lengths and offsets for the glyph
@@ -8,9 +8,13 @@ in the TSI1 table.
8
8
  See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
9
9
  """
10
10
 
11
- from . import DefaultTable
11
+ import logging
12
12
  import struct
13
13
 
14
+ from . import DefaultTable
15
+
16
+ log = logging.getLogger(__name__)
17
+
14
18
  tsi0Format = ">HHL"
15
19
 
16
20
 
@@ -25,7 +29,14 @@ class table_T_S_I__0(DefaultTable.DefaultTable):
25
29
  numGlyphs = ttFont["maxp"].numGlyphs
26
30
  indices = []
27
31
  size = struct.calcsize(tsi0Format)
28
- for i in range(numGlyphs + 5):
32
+ numEntries = len(data) // size
33
+ if numEntries != numGlyphs + 5:
34
+ diff = numEntries - numGlyphs - 5
35
+ log.warning(
36
+ "Number of glyphPrograms differs from the number of glyphs in the font "
37
+ f"by {abs(diff)} ({numEntries - 5} programs vs. {numGlyphs} glyphs)."
38
+ )
39
+ for _ in range(numEntries):
29
40
  glyphID, textLength, textOffset = fixlongs(
30
41
  *struct.unpack(tsi0Format, data[:size])
31
42
  )
@@ -1,4 +1,4 @@
1
- """ TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
1
+ """TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
2
2
  tool to store its hinting source data.
3
3
 
4
4
  TSI5 contains the VTT character groups.
@@ -6,22 +6,33 @@ TSI5 contains the VTT character groups.
6
6
  See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
7
7
  """
8
8
 
9
+ import array
10
+ import logging
11
+ import sys
12
+
9
13
  from fontTools.misc.textTools import safeEval
14
+
10
15
  from . import DefaultTable
11
- import sys
12
- import array
16
+
17
+ log = logging.getLogger(__name__)
13
18
 
14
19
 
15
20
  class table_T_S_I__5(DefaultTable.DefaultTable):
16
21
  def decompile(self, data, ttFont):
17
22
  numGlyphs = ttFont["maxp"].numGlyphs
18
- assert len(data) == 2 * numGlyphs
19
23
  a = array.array("H")
20
24
  a.frombytes(data)
21
25
  if sys.byteorder != "big":
22
26
  a.byteswap()
23
27
  self.glyphGrouping = {}
24
- for i in range(numGlyphs):
28
+ numEntries = len(data) // 2
29
+ if numEntries != numGlyphs:
30
+ diff = numEntries - numGlyphs
31
+ log.warning(
32
+ "Number of entries differs from the number of glyphs in the font "
33
+ f"by {abs(diff)} ({numEntries} entries vs. {numGlyphs} glyphs)."
34
+ )
35
+ for i in range(numEntries):
25
36
  self.glyphGrouping[ttFont.getGlyphName(i)] = a[i]
26
37
 
27
38
  def compile(self, ttFont):
@@ -23,6 +23,7 @@ def _moduleFinderHint():
23
23
  from . import G_P_K_G_
24
24
  from . import G_P_O_S_
25
25
  from . import G_S_U_B_
26
+ from . import G_V_A_R_
26
27
  from . import G__l_a_t
27
28
  from . import G__l_o_c
28
29
  from . import H_V_A_R_
@@ -21,6 +21,8 @@ class table__c_v_t(DefaultTable.DefaultTable):
21
21
  self.values = values
22
22
 
23
23
  def compile(self, ttFont):
24
+ if not hasattr(self, "values"):
25
+ return b""
24
26
  values = self.values[:]
25
27
  if sys.byteorder != "big":
26
28
  values.byteswap()
@@ -20,7 +20,9 @@ class table__f_p_g_m(DefaultTable.DefaultTable):
20
20
  self.program = program
21
21
 
22
22
  def compile(self, ttFont):
23
- return self.program.getBytecode()
23
+ if hasattr(self, "program"):
24
+ return self.program.getBytecode()
25
+ return b""
24
26
 
25
27
  def toXML(self, writer, ttFont):
26
28
  self.program.toXML(writer, ttFont)
@@ -1225,7 +1225,7 @@ class Glyph(object):
1225
1225
  if boundsDone is not None:
1226
1226
  boundsDone.add(glyphName)
1227
1227
  # empty components shouldn't update the bounds of the parent glyph
1228
- if g.numberOfContours == 0:
1228
+ if g.yMin == g.yMax and g.xMin == g.xMax:
1229
1229
  continue
1230
1230
 
1231
1231
  x, y = compo.x, compo.y
@@ -1285,11 +1285,7 @@ class Glyph(object):
1285
1285
  # however, if the referenced component glyph is another composite, we
1286
1286
  # must not round here but only at the end, after all the nested
1287
1287
  # transforms have been applied, or else rounding errors will compound.
1288
- if (
1289
- round is not noRound
1290
- and g.numberOfContours > 0
1291
- and not compo._hasOnlyIntegerTranslate()
1292
- ):
1288
+ if round is not noRound and g.numberOfContours > 0:
1293
1289
  coordinates.toInt(round=round)
1294
1290
  if hasattr(compo, "firstPt"):
1295
1291
  # component uses two reference points: we apply the transform _before_
@@ -24,19 +24,24 @@ log = logging.getLogger(__name__)
24
24
  # FreeType2 source code for parsing 'gvar':
25
25
  # http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/src/truetype/ttgxvar.c
26
26
 
27
- GVAR_HEADER_FORMAT = """
27
+ GVAR_HEADER_FORMAT_HEAD = """
28
28
  > # big endian
29
29
  version: H
30
30
  reserved: H
31
31
  axisCount: H
32
32
  sharedTupleCount: H
33
33
  offsetToSharedTuples: I
34
- 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
35
39
  flags: H
36
40
  offsetToGlyphVariationData: I
37
41
  """
38
42
 
39
- 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)
40
45
 
41
46
 
42
47
  class table__g_v_a_r(DefaultTable.DefaultTable):
@@ -51,6 +56,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
51
56
  """
52
57
 
53
58
  dependencies = ["fvar", "glyf"]
59
+ gid_size = 2
54
60
 
55
61
  def __init__(self, tag=None):
56
62
  DefaultTable.DefaultTable.__init__(self, tag)
@@ -74,20 +80,25 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
74
80
  offsets.append(offset)
75
81
  compiledOffsets, tableFormat = self.compileOffsets_(offsets)
76
82
 
83
+ GVAR_HEADER_SIZE = GVAR_HEADER_SIZE_HEAD + self.gid_size + GVAR_HEADER_SIZE_TAIL
77
84
  header = {}
78
85
  header["version"] = self.version
79
86
  header["reserved"] = self.reserved
80
87
  header["axisCount"] = len(axisTags)
81
88
  header["sharedTupleCount"] = len(sharedTuples)
82
89
  header["offsetToSharedTuples"] = GVAR_HEADER_SIZE + len(compiledOffsets)
83
- header["glyphCount"] = len(compiledGlyphs)
84
90
  header["flags"] = tableFormat
85
91
  header["offsetToGlyphVariationData"] = (
86
92
  header["offsetToSharedTuples"] + sharedTupleSize
87
93
  )
88
- compiledHeader = sstruct.pack(GVAR_HEADER_FORMAT, header)
89
94
 
90
- 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)
91
102
  result.extend(sharedTuples)
92
103
  result.extend(compiledGlyphs)
93
104
  return b"".join(result)
@@ -104,6 +115,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
104
115
  pointCountUnused = 0 # pointCount is actually unused by compileGlyph
105
116
  result.append(
106
117
  compileGlyph_(
118
+ self.gid_size,
107
119
  variations,
108
120
  pointCountUnused,
109
121
  axisTags,
@@ -116,7 +128,19 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
116
128
  def decompile(self, data, ttFont):
117
129
  axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
118
130
  glyphs = ttFont.getGlyphOrder()
119
- 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
+
120
144
  assert len(glyphs) == self.glyphCount
121
145
  assert len(axisTags) == self.axisCount
122
146
  sharedCoords = tv.decompileSharedTuples(
@@ -146,7 +170,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
146
170
  glyph = glyf[glyphName]
147
171
  numPointsInGlyph = self.getNumPoints_(glyph)
148
172
  return decompileGlyph_(
149
- numPointsInGlyph, sharedCoords, axisTags, gvarData
173
+ self.gid_size, numPointsInGlyph, sharedCoords, axisTags, gvarData
150
174
  )
151
175
 
152
176
  return read_item
@@ -264,23 +288,42 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
264
288
 
265
289
 
266
290
  def compileGlyph_(
267
- variations, pointCount, axisTags, sharedCoordIndices, *, optimizeSize=True
291
+ dataOffsetSize,
292
+ variations,
293
+ pointCount,
294
+ axisTags,
295
+ sharedCoordIndices,
296
+ *,
297
+ optimizeSize=True,
268
298
  ):
299
+ assert dataOffsetSize in (2, 3)
269
300
  tupleVariationCount, tuples, data = tv.compileTupleVariationStore(
270
301
  variations, pointCount, axisTags, sharedCoordIndices, optimizeSize=optimizeSize
271
302
  )
272
303
  if tupleVariationCount == 0:
273
304
  return b""
274
- result = [struct.pack(">HH", tupleVariationCount, 4 + len(tuples)), tuples, data]
275
- 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:
276
315
  result.append(b"\0") # padding
277
316
  return b"".join(result)
278
317
 
279
318
 
280
- def decompileGlyph_(pointCount, sharedTuples, axisTags, data):
281
- if len(data) < 4:
319
+ def decompileGlyph_(dataOffsetSize, pointCount, sharedTuples, axisTags, data):
320
+ assert dataOffsetSize in (2, 3)
321
+ if len(data) < 2 + dataOffsetSize:
282
322
  return []
283
- 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
+
284
327
  dataPos = offsetToData
285
328
  return tv.decompileTupleVariationStore(
286
329
  "gvar",
@@ -289,6 +332,6 @@ def decompileGlyph_(pointCount, sharedTuples, axisTags, data):
289
332
  pointCount,
290
333
  sharedTuples,
291
334
  data,
292
- 4,
335
+ 2 + dataOffsetSize,
293
336
  offsetToData,
294
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()
@@ -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
 
@@ -654,7 +654,7 @@ class UFOReader(_UFOBaseIO):
654
654
  The returned string is empty if the file is missing.
655
655
  """
656
656
  try:
657
- with self.fs.open(FEATURES_FILENAME, "r", encoding="utf-8") as f:
657
+ with self.fs.open(FEATURES_FILENAME, "r", encoding="utf-8-sig") as f:
658
658
  return f.read()
659
659
  except fs.errors.ResourceNotFound:
660
660
  return ""
@@ -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