wahregesundheit commited on
Commit
0122985
·
verified ·
1 Parent(s): 218ada5

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/afmLib.py +439 -0
  2. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/__main__.py +6 -0
  3. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/__pycache__/__init__.cpython-310.pyc +0 -0
  4. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/__pycache__/__main__.cpython-310.pyc +0 -0
  5. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/__pycache__/split.cpython-310.pyc +0 -0
  6. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/__pycache__/statNames.cpython-310.pyc +0 -0
  7. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/__pycache__/types.cpython-310.pyc +0 -0
  8. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/split.py +475 -0
  9. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/statNames.py +253 -0
  10. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/__init__.py +1 -0
  11. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/__pycache__/cython.cpython-310.pyc +0 -0
  12. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/__pycache__/vector.cpython-310.pyc +0 -0
  13. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/__pycache__/visitor.cpython-310.pyc +0 -0
  14. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/arrayTools.py +424 -0
  15. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/bezierTools.c +0 -0
  16. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/bezierTools.py +1490 -0
  17. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/cliTools.py +53 -0
  18. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/configTools.py +349 -0
  19. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/cython.py +27 -0
  20. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/dictTools.py +83 -0
  21. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/eexec.py +119 -0
  22. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/encodingTools.py +72 -0
  23. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/etree.py +479 -0
  24. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/filenames.py +245 -0
  25. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/fixedTools.py +253 -0
  26. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/intTools.py +25 -0
  27. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/iterTools.py +12 -0
  28. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/loggingTools.py +543 -0
  29. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/macCreatorType.py +56 -0
  30. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/macRes.py +261 -0
  31. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/psCharStrings.py +1496 -0
  32. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/psLib.py +398 -0
  33. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/psOperators.py +572 -0
  34. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/py23.py +96 -0
  35. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/roundTools.py +110 -0
  36. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/sstruct.py +231 -0
  37. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/symfont.py +244 -0
  38. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/testTools.py +229 -0
  39. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/textTools.py +154 -0
  40. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/timeTools.py +88 -0
  41. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/transform.py +518 -0
  42. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/treeTools.py +45 -0
  43. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/vector.py +147 -0
  44. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/visitor.py +142 -0
  45. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/xmlReader.py +188 -0
  46. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/xmlWriter.py +204 -0
  47. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/otlLib/builder.py +0 -0
  48. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/subset/__init__.py +0 -0
  49. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/subset/__main__.py +6 -0
  50. SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/subset/__pycache__/__init__.cpython-310.pyc +0 -0
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/afmLib.py ADDED
@@ -0,0 +1,439 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module for reading and writing AFM (Adobe Font Metrics) files.
2
+
3
+ Note that this has been designed to read in AFM files generated by Fontographer
4
+ and has not been tested on many other files. In particular, it does not
5
+ implement the whole Adobe AFM specification [#f1]_ but, it should read most
6
+ "common" AFM files.
7
+
8
+ Here is an example of using `afmLib` to read, modify and write an AFM file:
9
+
10
+ >>> from fontTools.afmLib import AFM
11
+ >>> f = AFM("Tests/afmLib/data/TestAFM.afm")
12
+ >>>
13
+ >>> # Accessing a pair gets you the kern value
14
+ >>> f[("V","A")]
15
+ -60
16
+ >>>
17
+ >>> # Accessing a glyph name gets you metrics
18
+ >>> f["A"]
19
+ (65, 668, (8, -25, 660, 666))
20
+ >>> # (charnum, width, bounding box)
21
+ >>>
22
+ >>> # Accessing an attribute gets you metadata
23
+ >>> f.FontName
24
+ 'TestFont-Regular'
25
+ >>> f.FamilyName
26
+ 'TestFont'
27
+ >>> f.Weight
28
+ 'Regular'
29
+ >>> f.XHeight
30
+ 500
31
+ >>> f.Ascender
32
+ 750
33
+ >>>
34
+ >>> # Attributes and items can also be set
35
+ >>> f[("A","V")] = -150 # Tighten kerning
36
+ >>> f.FontName = "TestFont Squished"
37
+ >>>
38
+ >>> # And the font written out again (remove the # in front)
39
+ >>> #f.write("testfont-squished.afm")
40
+
41
+ .. rubric:: Footnotes
42
+
43
+ .. [#f1] `Adobe Technote 5004 <https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5004.AFM_Spec.pdf>`_,
44
+ Adobe Font Metrics File Format Specification.
45
+
46
+ """
47
+
48
+ import re
49
+
50
+ # every single line starts with a "word"
51
+ identifierRE = re.compile(r"^([A-Za-z]+).*")
52
+
53
+ # regular expression to parse char lines
54
+ charRE = re.compile(
55
+ r"(-?\d+)" # charnum
56
+ r"\s*;\s*WX\s+" # ; WX
57
+ r"(-?\d+)" # width
58
+ r"\s*;\s*N\s+" # ; N
59
+ r"([.A-Za-z0-9_]+)" # charname
60
+ r"\s*;\s*B\s+" # ; B
61
+ r"(-?\d+)" # left
62
+ r"\s+"
63
+ r"(-?\d+)" # bottom
64
+ r"\s+"
65
+ r"(-?\d+)" # right
66
+ r"\s+"
67
+ r"(-?\d+)" # top
68
+ r"\s*;\s*" # ;
69
+ )
70
+
71
+ # regular expression to parse kerning lines
72
+ kernRE = re.compile(
73
+ r"([.A-Za-z0-9_]+)" # leftchar
74
+ r"\s+"
75
+ r"([.A-Za-z0-9_]+)" # rightchar
76
+ r"\s+"
77
+ r"(-?\d+)" # value
78
+ r"\s*"
79
+ )
80
+
81
+ # regular expressions to parse composite info lines of the form:
82
+ # Aacute 2 ; PCC A 0 0 ; PCC acute 182 211 ;
83
+ compositeRE = re.compile(
84
+ r"([.A-Za-z0-9_]+)" # char name
85
+ r"\s+"
86
+ r"(\d+)" # number of parts
87
+ r"\s*;\s*"
88
+ )
89
+ componentRE = re.compile(
90
+ r"PCC\s+" # PPC
91
+ r"([.A-Za-z0-9_]+)" # base char name
92
+ r"\s+"
93
+ r"(-?\d+)" # x offset
94
+ r"\s+"
95
+ r"(-?\d+)" # y offset
96
+ r"\s*;\s*"
97
+ )
98
+
99
+ preferredAttributeOrder = [
100
+ "FontName",
101
+ "FullName",
102
+ "FamilyName",
103
+ "Weight",
104
+ "ItalicAngle",
105
+ "IsFixedPitch",
106
+ "FontBBox",
107
+ "UnderlinePosition",
108
+ "UnderlineThickness",
109
+ "Version",
110
+ "Notice",
111
+ "EncodingScheme",
112
+ "CapHeight",
113
+ "XHeight",
114
+ "Ascender",
115
+ "Descender",
116
+ ]
117
+
118
+
119
+ class error(Exception):
120
+ pass
121
+
122
+
123
+ class AFM(object):
124
+ _attrs = None
125
+
126
+ _keywords = [
127
+ "StartFontMetrics",
128
+ "EndFontMetrics",
129
+ "StartCharMetrics",
130
+ "EndCharMetrics",
131
+ "StartKernData",
132
+ "StartKernPairs",
133
+ "EndKernPairs",
134
+ "EndKernData",
135
+ "StartComposites",
136
+ "EndComposites",
137
+ ]
138
+
139
+ def __init__(self, path=None):
140
+ """AFM file reader.
141
+
142
+ Instantiating an object with a path name will cause the file to be opened,
143
+ read, and parsed. Alternatively the path can be left unspecified, and a
144
+ file can be parsed later with the :meth:`read` method."""
145
+ self._attrs = {}
146
+ self._chars = {}
147
+ self._kerning = {}
148
+ self._index = {}
149
+ self._comments = []
150
+ self._composites = {}
151
+ if path is not None:
152
+ self.read(path)
153
+
154
+ def read(self, path):
155
+ """Opens, reads and parses a file."""
156
+ lines = readlines(path)
157
+ for line in lines:
158
+ if not line.strip():
159
+ continue
160
+ m = identifierRE.match(line)
161
+ if m is None:
162
+ raise error("syntax error in AFM file: " + repr(line))
163
+
164
+ pos = m.regs[1][1]
165
+ word = line[:pos]
166
+ rest = line[pos:].strip()
167
+ if word in self._keywords:
168
+ continue
169
+ if word == "C":
170
+ self.parsechar(rest)
171
+ elif word == "KPX":
172
+ self.parsekernpair(rest)
173
+ elif word == "CC":
174
+ self.parsecomposite(rest)
175
+ else:
176
+ self.parseattr(word, rest)
177
+
178
+ def parsechar(self, rest):
179
+ m = charRE.match(rest)
180
+ if m is None:
181
+ raise error("syntax error in AFM file: " + repr(rest))
182
+ things = []
183
+ for fr, to in m.regs[1:]:
184
+ things.append(rest[fr:to])
185
+ charname = things[2]
186
+ del things[2]
187
+ charnum, width, l, b, r, t = (int(thing) for thing in things)
188
+ self._chars[charname] = charnum, width, (l, b, r, t)
189
+
190
+ def parsekernpair(self, rest):
191
+ m = kernRE.match(rest)
192
+ if m is None:
193
+ raise error("syntax error in AFM file: " + repr(rest))
194
+ things = []
195
+ for fr, to in m.regs[1:]:
196
+ things.append(rest[fr:to])
197
+ leftchar, rightchar, value = things
198
+ value = int(value)
199
+ self._kerning[(leftchar, rightchar)] = value
200
+
201
+ def parseattr(self, word, rest):
202
+ if word == "FontBBox":
203
+ l, b, r, t = [int(thing) for thing in rest.split()]
204
+ self._attrs[word] = l, b, r, t
205
+ elif word == "Comment":
206
+ self._comments.append(rest)
207
+ else:
208
+ try:
209
+ value = int(rest)
210
+ except (ValueError, OverflowError):
211
+ self._attrs[word] = rest
212
+ else:
213
+ self._attrs[word] = value
214
+
215
+ def parsecomposite(self, rest):
216
+ m = compositeRE.match(rest)
217
+ if m is None:
218
+ raise error("syntax error in AFM file: " + repr(rest))
219
+ charname = m.group(1)
220
+ ncomponents = int(m.group(2))
221
+ rest = rest[m.regs[0][1] :]
222
+ components = []
223
+ while True:
224
+ m = componentRE.match(rest)
225
+ if m is None:
226
+ raise error("syntax error in AFM file: " + repr(rest))
227
+ basechar = m.group(1)
228
+ xoffset = int(m.group(2))
229
+ yoffset = int(m.group(3))
230
+ components.append((basechar, xoffset, yoffset))
231
+ rest = rest[m.regs[0][1] :]
232
+ if not rest:
233
+ break
234
+ assert len(components) == ncomponents
235
+ self._composites[charname] = components
236
+
237
+ def write(self, path, sep="\r"):
238
+ """Writes out an AFM font to the given path."""
239
+ import time
240
+
241
+ lines = [
242
+ "StartFontMetrics 2.0",
243
+ "Comment Generated by afmLib; at %s"
244
+ % (time.strftime("%m/%d/%Y %H:%M:%S", time.localtime(time.time()))),
245
+ ]
246
+
247
+ # write comments, assuming (possibly wrongly!) they should
248
+ # all appear at the top
249
+ for comment in self._comments:
250
+ lines.append("Comment " + comment)
251
+
252
+ # write attributes, first the ones we know about, in
253
+ # a preferred order
254
+ attrs = self._attrs
255
+ for attr in preferredAttributeOrder:
256
+ if attr in attrs:
257
+ value = attrs[attr]
258
+ if attr == "FontBBox":
259
+ value = "%s %s %s %s" % value
260
+ lines.append(attr + " " + str(value))
261
+ # then write the attributes we don't know about,
262
+ # in alphabetical order
263
+ items = sorted(attrs.items())
264
+ for attr, value in items:
265
+ if attr in preferredAttributeOrder:
266
+ continue
267
+ lines.append(attr + " " + str(value))
268
+
269
+ # write char metrics
270
+ lines.append("StartCharMetrics " + repr(len(self._chars)))
271
+ items = [
272
+ (charnum, (charname, width, box))
273
+ for charname, (charnum, width, box) in self._chars.items()
274
+ ]
275
+
276
+ def myKey(a):
277
+ """Custom key function to make sure unencoded chars (-1)
278
+ end up at the end of the list after sorting."""
279
+ if a[0] == -1:
280
+ a = (0xFFFF,) + a[1:] # 0xffff is an arbitrary large number
281
+ return a
282
+
283
+ items.sort(key=myKey)
284
+
285
+ for charnum, (charname, width, (l, b, r, t)) in items:
286
+ lines.append(
287
+ "C %d ; WX %d ; N %s ; B %d %d %d %d ;"
288
+ % (charnum, width, charname, l, b, r, t)
289
+ )
290
+ lines.append("EndCharMetrics")
291
+
292
+ # write kerning info
293
+ lines.append("StartKernData")
294
+ lines.append("StartKernPairs " + repr(len(self._kerning)))
295
+ items = sorted(self._kerning.items())
296
+ for (leftchar, rightchar), value in items:
297
+ lines.append("KPX %s %s %d" % (leftchar, rightchar, value))
298
+ lines.append("EndKernPairs")
299
+ lines.append("EndKernData")
300
+
301
+ if self._composites:
302
+ composites = sorted(self._composites.items())
303
+ lines.append("StartComposites %s" % len(self._composites))
304
+ for charname, components in composites:
305
+ line = "CC %s %s ;" % (charname, len(components))
306
+ for basechar, xoffset, yoffset in components:
307
+ line = line + " PCC %s %s %s ;" % (basechar, xoffset, yoffset)
308
+ lines.append(line)
309
+ lines.append("EndComposites")
310
+
311
+ lines.append("EndFontMetrics")
312
+
313
+ writelines(path, lines, sep)
314
+
315
+ def has_kernpair(self, pair):
316
+ """Returns `True` if the given glyph pair (specified as a tuple) exists
317
+ in the kerning dictionary."""
318
+ return pair in self._kerning
319
+
320
+ def kernpairs(self):
321
+ """Returns a list of all kern pairs in the kerning dictionary."""
322
+ return list(self._kerning.keys())
323
+
324
+ def has_char(self, char):
325
+ """Returns `True` if the given glyph exists in the font."""
326
+ return char in self._chars
327
+
328
+ def chars(self):
329
+ """Returns a list of all glyph names in the font."""
330
+ return list(self._chars.keys())
331
+
332
+ def comments(self):
333
+ """Returns all comments from the file."""
334
+ return self._comments
335
+
336
+ def addComment(self, comment):
337
+ """Adds a new comment to the file."""
338
+ self._comments.append(comment)
339
+
340
+ def addComposite(self, glyphName, components):
341
+ """Specifies that the glyph `glyphName` is made up of the given components.
342
+ The components list should be of the following form::
343
+
344
+ [
345
+ (glyphname, xOffset, yOffset),
346
+ ...
347
+ ]
348
+
349
+ """
350
+ self._composites[glyphName] = components
351
+
352
+ def __getattr__(self, attr):
353
+ if attr in self._attrs:
354
+ return self._attrs[attr]
355
+ else:
356
+ raise AttributeError(attr)
357
+
358
+ def __setattr__(self, attr, value):
359
+ # all attrs *not* starting with "_" are consider to be AFM keywords
360
+ if attr[:1] == "_":
361
+ self.__dict__[attr] = value
362
+ else:
363
+ self._attrs[attr] = value
364
+
365
+ def __delattr__(self, attr):
366
+ # all attrs *not* starting with "_" are consider to be AFM keywords
367
+ if attr[:1] == "_":
368
+ try:
369
+ del self.__dict__[attr]
370
+ except KeyError:
371
+ raise AttributeError(attr)
372
+ else:
373
+ try:
374
+ del self._attrs[attr]
375
+ except KeyError:
376
+ raise AttributeError(attr)
377
+
378
+ def __getitem__(self, key):
379
+ if isinstance(key, tuple):
380
+ # key is a tuple, return the kernpair
381
+ return self._kerning[key]
382
+ else:
383
+ # return the metrics instead
384
+ return self._chars[key]
385
+
386
+ def __setitem__(self, key, value):
387
+ if isinstance(key, tuple):
388
+ # key is a tuple, set kernpair
389
+ self._kerning[key] = value
390
+ else:
391
+ # set char metrics
392
+ self._chars[key] = value
393
+
394
+ def __delitem__(self, key):
395
+ if isinstance(key, tuple):
396
+ # key is a tuple, del kernpair
397
+ del self._kerning[key]
398
+ else:
399
+ # del char metrics
400
+ del self._chars[key]
401
+
402
+ def __repr__(self):
403
+ if hasattr(self, "FullName"):
404
+ return "<AFM object for %s>" % self.FullName
405
+ else:
406
+ return "<AFM object at %x>" % id(self)
407
+
408
+
409
+ def readlines(path):
410
+ with open(path, "r", encoding="ascii") as f:
411
+ data = f.read()
412
+ return data.splitlines()
413
+
414
+
415
+ def writelines(path, lines, sep="\r"):
416
+ with open(path, "w", encoding="ascii", newline=sep) as f:
417
+ f.write("\n".join(lines) + "\n")
418
+
419
+
420
+ if __name__ == "__main__":
421
+ import EasyDialogs
422
+
423
+ path = EasyDialogs.AskFileForOpen()
424
+ if path:
425
+ afm = AFM(path)
426
+ char = "A"
427
+ if afm.has_char(char):
428
+ print(afm[char]) # print charnum, width and boundingbox
429
+ pair = ("A", "V")
430
+ if afm.has_kernpair(pair):
431
+ print(afm[pair]) # print kerning value for pair
432
+ print(afm.Version) # various other afm entries have become attributes
433
+ print(afm.Weight)
434
+ # afm.comments() returns a list of all Comment lines found in the AFM
435
+ print(afm.comments())
436
+ # print afm.chars()
437
+ # print afm.kernpairs()
438
+ print(afm)
439
+ afm.write(path + ".muck")
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/__main__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import sys
2
+ from fontTools.designspaceLib import main
3
+
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (81.5 kB). View file
 
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/__pycache__/__main__.cpython-310.pyc ADDED
Binary file (326 Bytes). View file
 
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/__pycache__/split.cpython-310.pyc ADDED
Binary file (11.5 kB). View file
 
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/__pycache__/statNames.cpython-310.pyc ADDED
Binary file (6.21 kB). View file
 
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/__pycache__/types.cpython-310.pyc ADDED
Binary file (3.75 kB). View file
 
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/split.py ADDED
@@ -0,0 +1,475 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Allows building all the variable fonts of a DesignSpace version 5 by
2
+ splitting the document into interpolable sub-space, then into each VF.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import itertools
8
+ import logging
9
+ import math
10
+ from typing import Any, Callable, Dict, Iterator, List, Tuple, cast
11
+
12
+ from fontTools.designspaceLib import (
13
+ AxisDescriptor,
14
+ AxisMappingDescriptor,
15
+ DesignSpaceDocument,
16
+ DiscreteAxisDescriptor,
17
+ InstanceDescriptor,
18
+ RuleDescriptor,
19
+ SimpleLocationDict,
20
+ SourceDescriptor,
21
+ VariableFontDescriptor,
22
+ )
23
+ from fontTools.designspaceLib.statNames import StatNames, getStatNames
24
+ from fontTools.designspaceLib.types import (
25
+ ConditionSet,
26
+ Range,
27
+ Region,
28
+ getVFUserRegion,
29
+ locationInRegion,
30
+ regionInRegion,
31
+ userRegionToDesignRegion,
32
+ )
33
+
34
+ LOGGER = logging.getLogger(__name__)
35
+
36
+ MakeInstanceFilenameCallable = Callable[
37
+ [DesignSpaceDocument, InstanceDescriptor, StatNames], str
38
+ ]
39
+
40
+
41
+ def defaultMakeInstanceFilename(
42
+ doc: DesignSpaceDocument, instance: InstanceDescriptor, statNames: StatNames
43
+ ) -> str:
44
+ """Default callable to synthesize an instance filename
45
+ when makeNames=True, for instances that don't specify an instance name
46
+ in the designspace. This part of the name generation can be overriden
47
+ because it's not specified by the STAT table.
48
+ """
49
+ familyName = instance.familyName or statNames.familyNames.get("en")
50
+ styleName = instance.styleName or statNames.styleNames.get("en")
51
+ return f"{familyName}-{styleName}.ttf"
52
+
53
+
54
+ def splitInterpolable(
55
+ doc: DesignSpaceDocument,
56
+ makeNames: bool = True,
57
+ expandLocations: bool = True,
58
+ makeInstanceFilename: MakeInstanceFilenameCallable = defaultMakeInstanceFilename,
59
+ ) -> Iterator[Tuple[SimpleLocationDict, DesignSpaceDocument]]:
60
+ """Split the given DS5 into several interpolable sub-designspaces.
61
+ There are as many interpolable sub-spaces as there are combinations of
62
+ discrete axis values.
63
+
64
+ E.g. with axes:
65
+ - italic (discrete) Upright or Italic
66
+ - style (discrete) Sans or Serif
67
+ - weight (continuous) 100 to 900
68
+
69
+ There are 4 sub-spaces in which the Weight axis should interpolate:
70
+ (Upright, Sans), (Upright, Serif), (Italic, Sans) and (Italic, Serif).
71
+
72
+ The sub-designspaces still include the full axis definitions and STAT data,
73
+ but the rules, sources, variable fonts, instances are trimmed down to only
74
+ keep what falls within the interpolable sub-space.
75
+
76
+ Args:
77
+ - ``makeNames``: Whether to compute the instance family and style
78
+ names using the STAT data.
79
+ - ``expandLocations``: Whether to turn all locations into "full"
80
+ locations, including implicit default axis values where missing.
81
+ - ``makeInstanceFilename``: Callable to synthesize an instance filename
82
+ when makeNames=True, for instances that don't specify an instance name
83
+ in the designspace. This part of the name generation can be overridden
84
+ because it's not specified by the STAT table.
85
+
86
+ .. versionadded:: 5.0
87
+ """
88
+ discreteAxes = []
89
+ interpolableUserRegion: Region = {}
90
+ for axis in doc.axes:
91
+ if hasattr(axis, "values"):
92
+ # Mypy doesn't support narrowing union types via hasattr()
93
+ # TODO(Python 3.10): use TypeGuard
94
+ # https://mypy.readthedocs.io/en/stable/type_narrowing.html
95
+ axis = cast(DiscreteAxisDescriptor, axis)
96
+ discreteAxes.append(axis)
97
+ else:
98
+ axis = cast(AxisDescriptor, axis)
99
+ interpolableUserRegion[axis.name] = Range(
100
+ axis.minimum,
101
+ axis.maximum,
102
+ axis.default,
103
+ )
104
+ valueCombinations = itertools.product(*[axis.values for axis in discreteAxes])
105
+ for values in valueCombinations:
106
+ discreteUserLocation = {
107
+ discreteAxis.name: value
108
+ for discreteAxis, value in zip(discreteAxes, values)
109
+ }
110
+ subDoc = _extractSubSpace(
111
+ doc,
112
+ {**interpolableUserRegion, **discreteUserLocation},
113
+ keepVFs=True,
114
+ makeNames=makeNames,
115
+ expandLocations=expandLocations,
116
+ makeInstanceFilename=makeInstanceFilename,
117
+ )
118
+ yield discreteUserLocation, subDoc
119
+
120
+
121
+ def splitVariableFonts(
122
+ doc: DesignSpaceDocument,
123
+ makeNames: bool = False,
124
+ expandLocations: bool = False,
125
+ makeInstanceFilename: MakeInstanceFilenameCallable = defaultMakeInstanceFilename,
126
+ ) -> Iterator[Tuple[str, DesignSpaceDocument]]:
127
+ """Convert each variable font listed in this document into a standalone
128
+ designspace. This can be used to compile all the variable fonts from a
129
+ format 5 designspace using tools that can only deal with 1 VF at a time.
130
+
131
+ Args:
132
+ - ``makeNames``: Whether to compute the instance family and style
133
+ names using the STAT data.
134
+ - ``expandLocations``: Whether to turn all locations into "full"
135
+ locations, including implicit default axis values where missing.
136
+ - ``makeInstanceFilename``: Callable to synthesize an instance filename
137
+ when makeNames=True, for instances that don't specify an instance name
138
+ in the designspace. This part of the name generation can be overridden
139
+ because it's not specified by the STAT table.
140
+
141
+ .. versionadded:: 5.0
142
+ """
143
+ # Make one DesignspaceDoc v5 for each variable font
144
+ for vf in doc.getVariableFonts():
145
+ vfUserRegion = getVFUserRegion(doc, vf)
146
+ vfDoc = _extractSubSpace(
147
+ doc,
148
+ vfUserRegion,
149
+ keepVFs=False,
150
+ makeNames=makeNames,
151
+ expandLocations=expandLocations,
152
+ makeInstanceFilename=makeInstanceFilename,
153
+ )
154
+ vfDoc.lib = {**vfDoc.lib, **vf.lib}
155
+ yield vf.name, vfDoc
156
+
157
+
158
+ def convert5to4(
159
+ doc: DesignSpaceDocument,
160
+ ) -> Dict[str, DesignSpaceDocument]:
161
+ """Convert each variable font listed in this document into a standalone
162
+ format 4 designspace. This can be used to compile all the variable fonts
163
+ from a format 5 designspace using tools that only know about format 4.
164
+
165
+ .. versionadded:: 5.0
166
+ """
167
+ vfs = {}
168
+ for _location, subDoc in splitInterpolable(doc):
169
+ for vfName, vfDoc in splitVariableFonts(subDoc):
170
+ vfDoc.formatVersion = "4.1"
171
+ vfs[vfName] = vfDoc
172
+ return vfs
173
+
174
+
175
+ def _extractSubSpace(
176
+ doc: DesignSpaceDocument,
177
+ userRegion: Region,
178
+ *,
179
+ keepVFs: bool,
180
+ makeNames: bool,
181
+ expandLocations: bool,
182
+ makeInstanceFilename: MakeInstanceFilenameCallable,
183
+ ) -> DesignSpaceDocument:
184
+ subDoc = DesignSpaceDocument()
185
+ # Don't include STAT info
186
+ # FIXME: (Jany) let's think about it. Not include = OK because the point of
187
+ # the splitting is to build VFs and we'll use the STAT data of the full
188
+ # document to generate the STAT of the VFs, so "no need" to have STAT data
189
+ # in sub-docs. Counterpoint: what if someone wants to split this DS for
190
+ # other purposes? Maybe for that it would be useful to also subset the STAT
191
+ # data?
192
+ # subDoc.elidedFallbackName = doc.elidedFallbackName
193
+
194
+ def maybeExpandDesignLocation(object):
195
+ if expandLocations:
196
+ return object.getFullDesignLocation(doc)
197
+ else:
198
+ return object.designLocation
199
+
200
+ for axis in doc.axes:
201
+ range = userRegion[axis.name]
202
+ if isinstance(range, Range) and hasattr(axis, "minimum"):
203
+ # Mypy doesn't support narrowing union types via hasattr()
204
+ # TODO(Python 3.10): use TypeGuard
205
+ # https://mypy.readthedocs.io/en/stable/type_narrowing.html
206
+ axis = cast(AxisDescriptor, axis)
207
+ subDoc.addAxis(
208
+ AxisDescriptor(
209
+ # Same info
210
+ tag=axis.tag,
211
+ name=axis.name,
212
+ labelNames=axis.labelNames,
213
+ hidden=axis.hidden,
214
+ # Subset range
215
+ minimum=max(range.minimum, axis.minimum),
216
+ default=range.default or axis.default,
217
+ maximum=min(range.maximum, axis.maximum),
218
+ map=[
219
+ (user, design)
220
+ for user, design in axis.map
221
+ if range.minimum <= user <= range.maximum
222
+ ],
223
+ # Don't include STAT info
224
+ axisOrdering=None,
225
+ axisLabels=None,
226
+ )
227
+ )
228
+
229
+ subDoc.axisMappings = mappings = []
230
+ subDocAxes = {axis.name for axis in subDoc.axes}
231
+ for mapping in doc.axisMappings:
232
+ if not all(axis in subDocAxes for axis in mapping.inputLocation.keys()):
233
+ continue
234
+ if not all(axis in subDocAxes for axis in mapping.outputLocation.keys()):
235
+ LOGGER.error(
236
+ "In axis mapping from input %s, some output axes are not in the variable-font: %s",
237
+ mapping.inputLocation,
238
+ mapping.outputLocation,
239
+ )
240
+ continue
241
+
242
+ mappingAxes = set()
243
+ mappingAxes.update(mapping.inputLocation.keys())
244
+ mappingAxes.update(mapping.outputLocation.keys())
245
+ for axis in doc.axes:
246
+ if axis.name not in mappingAxes:
247
+ continue
248
+ range = userRegion[axis.name]
249
+ if (
250
+ range.minimum != axis.minimum
251
+ or (range.default is not None and range.default != axis.default)
252
+ or range.maximum != axis.maximum
253
+ ):
254
+ LOGGER.error(
255
+ "Limiting axis ranges used in <mapping> elements not supported: %s",
256
+ axis.name,
257
+ )
258
+ continue
259
+
260
+ mappings.append(
261
+ AxisMappingDescriptor(
262
+ inputLocation=mapping.inputLocation,
263
+ outputLocation=mapping.outputLocation,
264
+ )
265
+ )
266
+
267
+ # Don't include STAT info
268
+ # subDoc.locationLabels = doc.locationLabels
269
+
270
+ # Rules: subset them based on conditions
271
+ designRegion = userRegionToDesignRegion(doc, userRegion)
272
+ subDoc.rules = _subsetRulesBasedOnConditions(doc.rules, designRegion)
273
+ subDoc.rulesProcessingLast = doc.rulesProcessingLast
274
+
275
+ # Sources: keep only the ones that fall within the kept axis ranges
276
+ for source in doc.sources:
277
+ if not locationInRegion(doc.map_backward(source.designLocation), userRegion):
278
+ continue
279
+
280
+ subDoc.addSource(
281
+ SourceDescriptor(
282
+ filename=source.filename,
283
+ path=source.path,
284
+ font=source.font,
285
+ name=source.name,
286
+ designLocation=_filterLocation(
287
+ userRegion, maybeExpandDesignLocation(source)
288
+ ),
289
+ layerName=source.layerName,
290
+ familyName=source.familyName,
291
+ styleName=source.styleName,
292
+ muteKerning=source.muteKerning,
293
+ muteInfo=source.muteInfo,
294
+ mutedGlyphNames=source.mutedGlyphNames,
295
+ )
296
+ )
297
+
298
+ # Copy family name translations from the old default source to the new default
299
+ vfDefault = subDoc.findDefault()
300
+ oldDefault = doc.findDefault()
301
+ if vfDefault is not None and oldDefault is not None:
302
+ vfDefault.localisedFamilyName = oldDefault.localisedFamilyName
303
+
304
+ # Variable fonts: keep only the ones that fall within the kept axis ranges
305
+ if keepVFs:
306
+ # Note: call getVariableFont() to make the implicit VFs explicit
307
+ for vf in doc.getVariableFonts():
308
+ vfUserRegion = getVFUserRegion(doc, vf)
309
+ if regionInRegion(vfUserRegion, userRegion):
310
+ subDoc.addVariableFont(
311
+ VariableFontDescriptor(
312
+ name=vf.name,
313
+ filename=vf.filename,
314
+ axisSubsets=[
315
+ axisSubset
316
+ for axisSubset in vf.axisSubsets
317
+ if isinstance(userRegion[axisSubset.name], Range)
318
+ ],
319
+ lib=vf.lib,
320
+ )
321
+ )
322
+
323
+ # Instances: same as Sources + compute missing names
324
+ for instance in doc.instances:
325
+ if not locationInRegion(instance.getFullUserLocation(doc), userRegion):
326
+ continue
327
+
328
+ if makeNames:
329
+ statNames = getStatNames(doc, instance.getFullUserLocation(doc))
330
+ familyName = instance.familyName or statNames.familyNames.get("en")
331
+ styleName = instance.styleName or statNames.styleNames.get("en")
332
+ subDoc.addInstance(
333
+ InstanceDescriptor(
334
+ filename=instance.filename
335
+ or makeInstanceFilename(doc, instance, statNames),
336
+ path=instance.path,
337
+ font=instance.font,
338
+ name=instance.name or f"{familyName} {styleName}",
339
+ userLocation={} if expandLocations else instance.userLocation,
340
+ designLocation=_filterLocation(
341
+ userRegion, maybeExpandDesignLocation(instance)
342
+ ),
343
+ familyName=familyName,
344
+ styleName=styleName,
345
+ postScriptFontName=instance.postScriptFontName
346
+ or statNames.postScriptFontName,
347
+ styleMapFamilyName=instance.styleMapFamilyName
348
+ or statNames.styleMapFamilyNames.get("en"),
349
+ styleMapStyleName=instance.styleMapStyleName
350
+ or statNames.styleMapStyleName,
351
+ localisedFamilyName=instance.localisedFamilyName
352
+ or statNames.familyNames,
353
+ localisedStyleName=instance.localisedStyleName
354
+ or statNames.styleNames,
355
+ localisedStyleMapFamilyName=instance.localisedStyleMapFamilyName
356
+ or statNames.styleMapFamilyNames,
357
+ localisedStyleMapStyleName=instance.localisedStyleMapStyleName
358
+ or {},
359
+ lib=instance.lib,
360
+ )
361
+ )
362
+ else:
363
+ subDoc.addInstance(
364
+ InstanceDescriptor(
365
+ filename=instance.filename,
366
+ path=instance.path,
367
+ font=instance.font,
368
+ name=instance.name,
369
+ userLocation={} if expandLocations else instance.userLocation,
370
+ designLocation=_filterLocation(
371
+ userRegion, maybeExpandDesignLocation(instance)
372
+ ),
373
+ familyName=instance.familyName,
374
+ styleName=instance.styleName,
375
+ postScriptFontName=instance.postScriptFontName,
376
+ styleMapFamilyName=instance.styleMapFamilyName,
377
+ styleMapStyleName=instance.styleMapStyleName,
378
+ localisedFamilyName=instance.localisedFamilyName,
379
+ localisedStyleName=instance.localisedStyleName,
380
+ localisedStyleMapFamilyName=instance.localisedStyleMapFamilyName,
381
+ localisedStyleMapStyleName=instance.localisedStyleMapStyleName,
382
+ lib=instance.lib,
383
+ )
384
+ )
385
+
386
+ subDoc.lib = doc.lib
387
+
388
+ return subDoc
389
+
390
+
391
+ def _conditionSetFrom(conditionSet: List[Dict[str, Any]]) -> ConditionSet:
392
+ c: Dict[str, Range] = {}
393
+ for condition in conditionSet:
394
+ minimum, maximum = condition.get("minimum"), condition.get("maximum")
395
+ c[condition["name"]] = Range(
396
+ minimum if minimum is not None else -math.inf,
397
+ maximum if maximum is not None else math.inf,
398
+ )
399
+ return c
400
+
401
+
402
+ def _subsetRulesBasedOnConditions(
403
+ rules: List[RuleDescriptor], designRegion: Region
404
+ ) -> List[RuleDescriptor]:
405
+ # What rules to keep:
406
+ # - Keep the rule if any conditionset is relevant.
407
+ # - A conditionset is relevant if all conditions are relevant or it is empty.
408
+ # - A condition is relevant if
409
+ # - axis is point (C-AP),
410
+ # - and point in condition's range (C-AP-in)
411
+ # (in this case remove the condition because it's always true)
412
+ # - else (C-AP-out) whole conditionset can be discarded (condition false
413
+ # => conditionset false)
414
+ # - axis is range (C-AR),
415
+ # - (C-AR-all) and axis range fully contained in condition range: we can
416
+ # scrap the condition because it's always true
417
+ # - (C-AR-inter) and intersection(axis range, condition range) not empty:
418
+ # keep the condition with the smaller range (= intersection)
419
+ # - (C-AR-none) else, whole conditionset can be discarded
420
+ newRules: List[RuleDescriptor] = []
421
+ for rule in rules:
422
+ newRule: RuleDescriptor = RuleDescriptor(
423
+ name=rule.name, conditionSets=[], subs=rule.subs
424
+ )
425
+ for conditionset in rule.conditionSets:
426
+ cs = _conditionSetFrom(conditionset)
427
+ newConditionset: List[Dict[str, Any]] = []
428
+ discardConditionset = False
429
+ for selectionName, selectionValue in designRegion.items():
430
+ # TODO: Ensure that all(key in conditionset for key in region.keys())?
431
+ if selectionName not in cs:
432
+ # raise Exception("Selection has different axes than the rules")
433
+ continue
434
+ if isinstance(selectionValue, (float, int)): # is point
435
+ # Case C-AP-in
436
+ if selectionValue in cs[selectionName]:
437
+ pass # always matches, conditionset can stay empty for this one.
438
+ # Case C-AP-out
439
+ else:
440
+ discardConditionset = True
441
+ else: # is range
442
+ # Case C-AR-all
443
+ if selectionValue in cs[selectionName]:
444
+ pass # always matches, conditionset can stay empty for this one.
445
+ else:
446
+ intersection = cs[selectionName].intersection(selectionValue)
447
+ # Case C-AR-inter
448
+ if intersection is not None:
449
+ newConditionset.append(
450
+ {
451
+ "name": selectionName,
452
+ "minimum": intersection.minimum,
453
+ "maximum": intersection.maximum,
454
+ }
455
+ )
456
+ # Case C-AR-none
457
+ else:
458
+ discardConditionset = True
459
+ if not discardConditionset:
460
+ newRule.conditionSets.append(newConditionset)
461
+ if newRule.conditionSets:
462
+ newRules.append(newRule)
463
+
464
+ return newRules
465
+
466
+
467
+ def _filterLocation(
468
+ userRegion: Region,
469
+ location: Dict[str, float],
470
+ ) -> Dict[str, float]:
471
+ return {
472
+ name: value
473
+ for name, value in location.items()
474
+ if name in userRegion and isinstance(userRegion[name], Range)
475
+ }
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/designspaceLib/statNames.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compute name information for a given location in user-space coordinates
2
+ using STAT data. This can be used to fill-in automatically the names of an
3
+ instance:
4
+
5
+ .. code:: python
6
+
7
+ instance = doc.instances[0]
8
+ names = getStatNames(doc, instance.getFullUserLocation(doc))
9
+ print(names.styleNames)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass
15
+ from typing import Dict, Optional, Tuple, Union
16
+ import logging
17
+
18
+ from fontTools.designspaceLib import (
19
+ AxisDescriptor,
20
+ AxisLabelDescriptor,
21
+ DesignSpaceDocument,
22
+ DesignSpaceDocumentError,
23
+ DiscreteAxisDescriptor,
24
+ SimpleLocationDict,
25
+ SourceDescriptor,
26
+ )
27
+
28
+ LOGGER = logging.getLogger(__name__)
29
+
30
+ # TODO(Python 3.8): use Literal
31
+ # RibbiStyleName = Union[Literal["regular"], Literal["bold"], Literal["italic"], Literal["bold italic"]]
32
+ RibbiStyle = str
33
+ BOLD_ITALIC_TO_RIBBI_STYLE = {
34
+ (False, False): "regular",
35
+ (False, True): "italic",
36
+ (True, False): "bold",
37
+ (True, True): "bold italic",
38
+ }
39
+
40
+
41
+ @dataclass
42
+ class StatNames:
43
+ """Name data generated from the STAT table information."""
44
+
45
+ familyNames: Dict[str, str]
46
+ styleNames: Dict[str, str]
47
+ postScriptFontName: Optional[str]
48
+ styleMapFamilyNames: Dict[str, str]
49
+ styleMapStyleName: Optional[RibbiStyle]
50
+
51
+
52
+ def getStatNames(
53
+ doc: DesignSpaceDocument, userLocation: SimpleLocationDict
54
+ ) -> StatNames:
55
+ """Compute the family, style, PostScript names of the given ``userLocation``
56
+ using the document's STAT information.
57
+
58
+ Also computes localizations.
59
+
60
+ If not enough STAT data is available for a given name, either its dict of
61
+ localized names will be empty (family and style names), or the name will be
62
+ None (PostScript name).
63
+
64
+ .. versionadded:: 5.0
65
+ """
66
+ familyNames: Dict[str, str] = {}
67
+ defaultSource: Optional[SourceDescriptor] = doc.findDefault()
68
+ if defaultSource is None:
69
+ LOGGER.warning("Cannot determine default source to look up family name.")
70
+ elif defaultSource.familyName is None:
71
+ LOGGER.warning(
72
+ "Cannot look up family name, assign the 'familyname' attribute to the default source."
73
+ )
74
+ else:
75
+ familyNames = {
76
+ "en": defaultSource.familyName,
77
+ **defaultSource.localisedFamilyName,
78
+ }
79
+
80
+ styleNames: Dict[str, str] = {}
81
+ # If a free-standing label matches the location, use it for name generation.
82
+ label = doc.labelForUserLocation(userLocation)
83
+ if label is not None:
84
+ styleNames = {"en": label.name, **label.labelNames}
85
+ # Otherwise, scour the axis labels for matches.
86
+ else:
87
+ # Gather all languages in which at least one translation is provided
88
+ # Then build names for all these languages, but fallback to English
89
+ # whenever a translation is missing.
90
+ labels = _getAxisLabelsForUserLocation(doc.axes, userLocation)
91
+ if labels:
92
+ languages = set(
93
+ language for label in labels for language in label.labelNames
94
+ )
95
+ languages.add("en")
96
+ for language in languages:
97
+ styleName = " ".join(
98
+ label.labelNames.get(language, label.defaultName)
99
+ for label in labels
100
+ if not label.elidable
101
+ )
102
+ if not styleName and doc.elidedFallbackName is not None:
103
+ styleName = doc.elidedFallbackName
104
+ styleNames[language] = styleName
105
+
106
+ if "en" not in familyNames or "en" not in styleNames:
107
+ # Not enough information to compute PS names of styleMap names
108
+ return StatNames(
109
+ familyNames=familyNames,
110
+ styleNames=styleNames,
111
+ postScriptFontName=None,
112
+ styleMapFamilyNames={},
113
+ styleMapStyleName=None,
114
+ )
115
+
116
+ postScriptFontName = f"{familyNames['en']}-{styleNames['en']}".replace(" ", "")
117
+
118
+ styleMapStyleName, regularUserLocation = _getRibbiStyle(doc, userLocation)
119
+
120
+ styleNamesForStyleMap = styleNames
121
+ if regularUserLocation != userLocation:
122
+ regularStatNames = getStatNames(doc, regularUserLocation)
123
+ styleNamesForStyleMap = regularStatNames.styleNames
124
+
125
+ styleMapFamilyNames = {}
126
+ for language in set(familyNames).union(styleNames.keys()):
127
+ familyName = familyNames.get(language, familyNames["en"])
128
+ styleName = styleNamesForStyleMap.get(language, styleNamesForStyleMap["en"])
129
+ styleMapFamilyNames[language] = (familyName + " " + styleName).strip()
130
+
131
+ return StatNames(
132
+ familyNames=familyNames,
133
+ styleNames=styleNames,
134
+ postScriptFontName=postScriptFontName,
135
+ styleMapFamilyNames=styleMapFamilyNames,
136
+ styleMapStyleName=styleMapStyleName,
137
+ )
138
+
139
+
140
+ def _getSortedAxisLabels(
141
+ axes: list[Union[AxisDescriptor, DiscreteAxisDescriptor]],
142
+ ) -> Dict[str, list[AxisLabelDescriptor]]:
143
+ """Returns axis labels sorted by their ordering, with unordered ones appended as
144
+ they are listed."""
145
+
146
+ # First, get the axis labels with explicit ordering...
147
+ sortedAxes = sorted(
148
+ (axis for axis in axes if axis.axisOrdering is not None),
149
+ key=lambda a: a.axisOrdering,
150
+ )
151
+ sortedLabels: Dict[str, list[AxisLabelDescriptor]] = {
152
+ axis.name: axis.axisLabels for axis in sortedAxes
153
+ }
154
+
155
+ # ... then append the others in the order they appear.
156
+ # NOTE: This relies on Python 3.7+ dict's preserved insertion order.
157
+ for axis in axes:
158
+ if axis.axisOrdering is None:
159
+ sortedLabels[axis.name] = axis.axisLabels
160
+
161
+ return sortedLabels
162
+
163
+
164
+ def _getAxisLabelsForUserLocation(
165
+ axes: list[Union[AxisDescriptor, DiscreteAxisDescriptor]],
166
+ userLocation: SimpleLocationDict,
167
+ ) -> list[AxisLabelDescriptor]:
168
+ labels: list[AxisLabelDescriptor] = []
169
+
170
+ allAxisLabels = _getSortedAxisLabels(axes)
171
+ if allAxisLabels.keys() != userLocation.keys():
172
+ LOGGER.warning(
173
+ f"Mismatch between user location '{userLocation.keys()}' and available "
174
+ f"labels for '{allAxisLabels.keys()}'."
175
+ )
176
+
177
+ for axisName, axisLabels in allAxisLabels.items():
178
+ userValue = userLocation[axisName]
179
+ label: Optional[AxisLabelDescriptor] = next(
180
+ (
181
+ l
182
+ for l in axisLabels
183
+ if l.userValue == userValue
184
+ or (
185
+ l.userMinimum is not None
186
+ and l.userMaximum is not None
187
+ and l.userMinimum <= userValue <= l.userMaximum
188
+ )
189
+ ),
190
+ None,
191
+ )
192
+ if label is None:
193
+ LOGGER.debug(
194
+ f"Document needs a label for axis '{axisName}', user value '{userValue}'."
195
+ )
196
+ else:
197
+ labels.append(label)
198
+
199
+ return labels
200
+
201
+
202
+ def _getRibbiStyle(
203
+ self: DesignSpaceDocument, userLocation: SimpleLocationDict
204
+ ) -> Tuple[RibbiStyle, SimpleLocationDict]:
205
+ """Compute the RIBBI style name of the given user location,
206
+ return the location of the matching Regular in the RIBBI group.
207
+
208
+ .. versionadded:: 5.0
209
+ """
210
+ regularUserLocation = {}
211
+ axes_by_tag = {axis.tag: axis for axis in self.axes}
212
+
213
+ bold: bool = False
214
+ italic: bool = False
215
+
216
+ axis = axes_by_tag.get("wght")
217
+ if axis is not None:
218
+ for regular_label in axis.axisLabels:
219
+ if (
220
+ regular_label.linkedUserValue == userLocation[axis.name]
221
+ # In the "recursive" case where both the Regular has
222
+ # linkedUserValue pointing the Bold, and the Bold has
223
+ # linkedUserValue pointing to the Regular, only consider the
224
+ # first case: Regular (e.g. 400) has linkedUserValue pointing to
225
+ # Bold (e.g. 700, higher than Regular)
226
+ and regular_label.userValue < regular_label.linkedUserValue
227
+ ):
228
+ regularUserLocation[axis.name] = regular_label.userValue
229
+ bold = True
230
+ break
231
+
232
+ axis = axes_by_tag.get("ital") or axes_by_tag.get("slnt")
233
+ if axis is not None:
234
+ for upright_label in axis.axisLabels:
235
+ if (
236
+ upright_label.linkedUserValue == userLocation[axis.name]
237
+ # In the "recursive" case where both the Upright has
238
+ # linkedUserValue pointing the Italic, and the Italic has
239
+ # linkedUserValue pointing to the Upright, only consider the
240
+ # first case: Upright (e.g. ital=0, slant=0) has
241
+ # linkedUserValue pointing to Italic (e.g ital=1, slant=-12 or
242
+ # slant=12 for backwards italics, in any case higher than
243
+ # Upright in absolute value, hence the abs() below.
244
+ and abs(upright_label.userValue) < abs(upright_label.linkedUserValue)
245
+ ):
246
+ regularUserLocation[axis.name] = upright_label.userValue
247
+ italic = True
248
+ break
249
+
250
+ return BOLD_ITALIC_TO_RIBBI_STYLE[bold, italic], {
251
+ **userLocation,
252
+ **regularUserLocation,
253
+ }
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Empty __init__.py file to signal Python this directory is a package."""
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/__pycache__/cython.cpython-310.pyc ADDED
Binary file (1.05 kB). View file
 
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/__pycache__/vector.cpython-310.pyc ADDED
Binary file (6.34 kB). View file
 
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/__pycache__/visitor.cpython-310.pyc ADDED
Binary file (5.08 kB). View file
 
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/arrayTools.py ADDED
@@ -0,0 +1,424 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Routines for calculating bounding boxes, point in rectangle calculations and
2
+ so on.
3
+ """
4
+
5
+ from fontTools.misc.roundTools import otRound
6
+ from fontTools.misc.vector import Vector as _Vector
7
+ import math
8
+ import warnings
9
+
10
+
11
+ def calcBounds(array):
12
+ """Calculate the bounding rectangle of a 2D points array.
13
+
14
+ Args:
15
+ array: A sequence of 2D tuples.
16
+
17
+ Returns:
18
+ A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.
19
+ """
20
+ if not array:
21
+ return 0, 0, 0, 0
22
+ xs = [x for x, y in array]
23
+ ys = [y for x, y in array]
24
+ return min(xs), min(ys), max(xs), max(ys)
25
+
26
+
27
+ def calcIntBounds(array, round=otRound):
28
+ """Calculate the integer bounding rectangle of a 2D points array.
29
+
30
+ Values are rounded to closest integer towards ``+Infinity`` using the
31
+ :func:`fontTools.misc.fixedTools.otRound` function by default, unless
32
+ an optional ``round`` function is passed.
33
+
34
+ Args:
35
+ array: A sequence of 2D tuples.
36
+ round: A rounding function of type ``f(x: float) -> int``.
37
+
38
+ Returns:
39
+ A four-item tuple of integers representing the bounding rectangle:
40
+ ``(xMin, yMin, xMax, yMax)``.
41
+ """
42
+ return tuple(round(v) for v in calcBounds(array))
43
+
44
+
45
+ def updateBounds(bounds, p, min=min, max=max):
46
+ """Add a point to a bounding rectangle.
47
+
48
+ Args:
49
+ bounds: A bounding rectangle expressed as a tuple
50
+ ``(xMin, yMin, xMax, yMax), or None``.
51
+ p: A 2D tuple representing a point.
52
+ min,max: functions to compute the minimum and maximum.
53
+
54
+ Returns:
55
+ The updated bounding rectangle ``(xMin, yMin, xMax, yMax)``.
56
+ """
57
+ (x, y) = p
58
+ if bounds is None:
59
+ return x, y, x, y
60
+ xMin, yMin, xMax, yMax = bounds
61
+ return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)
62
+
63
+
64
+ def pointInRect(p, rect):
65
+ """Test if a point is inside a bounding rectangle.
66
+
67
+ Args:
68
+ p: A 2D tuple representing a point.
69
+ rect: A bounding rectangle expressed as a tuple
70
+ ``(xMin, yMin, xMax, yMax)``.
71
+
72
+ Returns:
73
+ ``True`` if the point is inside the rectangle, ``False`` otherwise.
74
+ """
75
+ (x, y) = p
76
+ xMin, yMin, xMax, yMax = rect
77
+ return (xMin <= x <= xMax) and (yMin <= y <= yMax)
78
+
79
+
80
+ def pointsInRect(array, rect):
81
+ """Determine which points are inside a bounding rectangle.
82
+
83
+ Args:
84
+ array: A sequence of 2D tuples.
85
+ rect: A bounding rectangle expressed as a tuple
86
+ ``(xMin, yMin, xMax, yMax)``.
87
+
88
+ Returns:
89
+ A list containing the points inside the rectangle.
90
+ """
91
+ if len(array) < 1:
92
+ return []
93
+ xMin, yMin, xMax, yMax = rect
94
+ return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array]
95
+
96
+
97
+ def vectorLength(vector):
98
+ """Calculate the length of the given vector.
99
+
100
+ Args:
101
+ vector: A 2D tuple.
102
+
103
+ Returns:
104
+ The Euclidean length of the vector.
105
+ """
106
+ x, y = vector
107
+ return math.sqrt(x**2 + y**2)
108
+
109
+
110
+ def asInt16(array):
111
+ """Round a list of floats to 16-bit signed integers.
112
+
113
+ Args:
114
+ array: List of float values.
115
+
116
+ Returns:
117
+ A list of rounded integers.
118
+ """
119
+ return [int(math.floor(i + 0.5)) for i in array]
120
+
121
+
122
+ def normRect(rect):
123
+ """Normalize a bounding box rectangle.
124
+
125
+ This function "turns the rectangle the right way up", so that the following
126
+ holds::
127
+
128
+ xMin <= xMax and yMin <= yMax
129
+
130
+ Args:
131
+ rect: A bounding rectangle expressed as a tuple
132
+ ``(xMin, yMin, xMax, yMax)``.
133
+
134
+ Returns:
135
+ A normalized bounding rectangle.
136
+ """
137
+ (xMin, yMin, xMax, yMax) = rect
138
+ return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax)
139
+
140
+
141
+ def scaleRect(rect, x, y):
142
+ """Scale a bounding box rectangle.
143
+
144
+ Args:
145
+ rect: A bounding rectangle expressed as a tuple
146
+ ``(xMin, yMin, xMax, yMax)``.
147
+ x: Factor to scale the rectangle along the X axis.
148
+ Y: Factor to scale the rectangle along the Y axis.
149
+
150
+ Returns:
151
+ A scaled bounding rectangle.
152
+ """
153
+ (xMin, yMin, xMax, yMax) = rect
154
+ return xMin * x, yMin * y, xMax * x, yMax * y
155
+
156
+
157
+ def offsetRect(rect, dx, dy):
158
+ """Offset a bounding box rectangle.
159
+
160
+ Args:
161
+ rect: A bounding rectangle expressed as a tuple
162
+ ``(xMin, yMin, xMax, yMax)``.
163
+ dx: Amount to offset the rectangle along the X axis.
164
+ dY: Amount to offset the rectangle along the Y axis.
165
+
166
+ Returns:
167
+ An offset bounding rectangle.
168
+ """
169
+ (xMin, yMin, xMax, yMax) = rect
170
+ return xMin + dx, yMin + dy, xMax + dx, yMax + dy
171
+
172
+
173
+ def insetRect(rect, dx, dy):
174
+ """Inset a bounding box rectangle on all sides.
175
+
176
+ Args:
177
+ rect: A bounding rectangle expressed as a tuple
178
+ ``(xMin, yMin, xMax, yMax)``.
179
+ dx: Amount to inset the rectangle along the X axis.
180
+ dY: Amount to inset the rectangle along the Y axis.
181
+
182
+ Returns:
183
+ An inset bounding rectangle.
184
+ """
185
+ (xMin, yMin, xMax, yMax) = rect
186
+ return xMin + dx, yMin + dy, xMax - dx, yMax - dy
187
+
188
+
189
+ def sectRect(rect1, rect2):
190
+ """Test for rectangle-rectangle intersection.
191
+
192
+ Args:
193
+ rect1: First bounding rectangle, expressed as tuples
194
+ ``(xMin, yMin, xMax, yMax)``.
195
+ rect2: Second bounding rectangle.
196
+
197
+ Returns:
198
+ A boolean and a rectangle.
199
+ If the input rectangles intersect, returns ``True`` and the intersecting
200
+ rectangle. Returns ``False`` and ``(0, 0, 0, 0)`` if the input
201
+ rectangles don't intersect.
202
+ """
203
+ (xMin1, yMin1, xMax1, yMax1) = rect1
204
+ (xMin2, yMin2, xMax2, yMax2) = rect2
205
+ xMin, yMin, xMax, yMax = (
206
+ max(xMin1, xMin2),
207
+ max(yMin1, yMin2),
208
+ min(xMax1, xMax2),
209
+ min(yMax1, yMax2),
210
+ )
211
+ if xMin >= xMax or yMin >= yMax:
212
+ return False, (0, 0, 0, 0)
213
+ return True, (xMin, yMin, xMax, yMax)
214
+
215
+
216
+ def unionRect(rect1, rect2):
217
+ """Determine union of bounding rectangles.
218
+
219
+ Args:
220
+ rect1: First bounding rectangle, expressed as tuples
221
+ ``(xMin, yMin, xMax, yMax)``.
222
+ rect2: Second bounding rectangle.
223
+
224
+ Returns:
225
+ The smallest rectangle in which both input rectangles are fully
226
+ enclosed.
227
+ """
228
+ (xMin1, yMin1, xMax1, yMax1) = rect1
229
+ (xMin2, yMin2, xMax2, yMax2) = rect2
230
+ xMin, yMin, xMax, yMax = (
231
+ min(xMin1, xMin2),
232
+ min(yMin1, yMin2),
233
+ max(xMax1, xMax2),
234
+ max(yMax1, yMax2),
235
+ )
236
+ return (xMin, yMin, xMax, yMax)
237
+
238
+
239
+ def rectCenter(rect):
240
+ """Determine rectangle center.
241
+
242
+ Args:
243
+ rect: Bounding rectangle, expressed as tuples
244
+ ``(xMin, yMin, xMax, yMax)``.
245
+
246
+ Returns:
247
+ A 2D tuple representing the point at the center of the rectangle.
248
+ """
249
+ (xMin, yMin, xMax, yMax) = rect
250
+ return (xMin + xMax) / 2, (yMin + yMax) / 2
251
+
252
+
253
+ def rectArea(rect):
254
+ """Determine rectangle area.
255
+
256
+ Args:
257
+ rect: Bounding rectangle, expressed as tuples
258
+ ``(xMin, yMin, xMax, yMax)``.
259
+
260
+ Returns:
261
+ The area of the rectangle.
262
+ """
263
+ (xMin, yMin, xMax, yMax) = rect
264
+ return (yMax - yMin) * (xMax - xMin)
265
+
266
+
267
+ def intRect(rect):
268
+ """Round a rectangle to integer values.
269
+
270
+ Guarantees that the resulting rectangle is NOT smaller than the original.
271
+
272
+ Args:
273
+ rect: Bounding rectangle, expressed as tuples
274
+ ``(xMin, yMin, xMax, yMax)``.
275
+
276
+ Returns:
277
+ A rounded bounding rectangle.
278
+ """
279
+ (xMin, yMin, xMax, yMax) = rect
280
+ xMin = int(math.floor(xMin))
281
+ yMin = int(math.floor(yMin))
282
+ xMax = int(math.ceil(xMax))
283
+ yMax = int(math.ceil(yMax))
284
+ return (xMin, yMin, xMax, yMax)
285
+
286
+
287
+ def quantizeRect(rect, factor=1):
288
+ """
289
+ >>> bounds = (72.3, -218.4, 1201.3, 919.1)
290
+ >>> quantizeRect(bounds)
291
+ (72, -219, 1202, 920)
292
+ >>> quantizeRect(bounds, factor=10)
293
+ (70, -220, 1210, 920)
294
+ >>> quantizeRect(bounds, factor=100)
295
+ (0, -300, 1300, 1000)
296
+ """
297
+ if factor < 1:
298
+ raise ValueError(f"Expected quantization factor >= 1, found: {factor!r}")
299
+ xMin, yMin, xMax, yMax = normRect(rect)
300
+ return (
301
+ int(math.floor(xMin / factor) * factor),
302
+ int(math.floor(yMin / factor) * factor),
303
+ int(math.ceil(xMax / factor) * factor),
304
+ int(math.ceil(yMax / factor) * factor),
305
+ )
306
+
307
+
308
+ class Vector(_Vector):
309
+ def __init__(self, *args, **kwargs):
310
+ warnings.warn(
311
+ "fontTools.misc.arrayTools.Vector has been deprecated, please use "
312
+ "fontTools.misc.vector.Vector instead.",
313
+ DeprecationWarning,
314
+ )
315
+
316
+
317
+ def pairwise(iterable, reverse=False):
318
+ """Iterate over current and next items in iterable.
319
+
320
+ Args:
321
+ iterable: An iterable
322
+ reverse: If true, iterate in reverse order.
323
+
324
+ Returns:
325
+ A iterable yielding two elements per iteration.
326
+
327
+ Example:
328
+
329
+ >>> tuple(pairwise([]))
330
+ ()
331
+ >>> tuple(pairwise([], reverse=True))
332
+ ()
333
+ >>> tuple(pairwise([0]))
334
+ ((0, 0),)
335
+ >>> tuple(pairwise([0], reverse=True))
336
+ ((0, 0),)
337
+ >>> tuple(pairwise([0, 1]))
338
+ ((0, 1), (1, 0))
339
+ >>> tuple(pairwise([0, 1], reverse=True))
340
+ ((1, 0), (0, 1))
341
+ >>> tuple(pairwise([0, 1, 2]))
342
+ ((0, 1), (1, 2), (2, 0))
343
+ >>> tuple(pairwise([0, 1, 2], reverse=True))
344
+ ((2, 1), (1, 0), (0, 2))
345
+ >>> tuple(pairwise(['a', 'b', 'c', 'd']))
346
+ (('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'a'))
347
+ >>> tuple(pairwise(['a', 'b', 'c', 'd'], reverse=True))
348
+ (('d', 'c'), ('c', 'b'), ('b', 'a'), ('a', 'd'))
349
+ """
350
+ if not iterable:
351
+ return
352
+ if reverse:
353
+ it = reversed(iterable)
354
+ else:
355
+ it = iter(iterable)
356
+ first = next(it, None)
357
+ a = first
358
+ for b in it:
359
+ yield (a, b)
360
+ a = b
361
+ yield (a, first)
362
+
363
+
364
+ def _test():
365
+ """
366
+ >>> import math
367
+ >>> calcBounds([])
368
+ (0, 0, 0, 0)
369
+ >>> calcBounds([(0, 40), (0, 100), (50, 50), (80, 10)])
370
+ (0, 10, 80, 100)
371
+ >>> updateBounds((0, 0, 0, 0), (100, 100))
372
+ (0, 0, 100, 100)
373
+ >>> pointInRect((50, 50), (0, 0, 100, 100))
374
+ True
375
+ >>> pointInRect((0, 0), (0, 0, 100, 100))
376
+ True
377
+ >>> pointInRect((100, 100), (0, 0, 100, 100))
378
+ True
379
+ >>> not pointInRect((101, 100), (0, 0, 100, 100))
380
+ True
381
+ >>> list(pointsInRect([(50, 50), (0, 0), (100, 100), (101, 100)], (0, 0, 100, 100)))
382
+ [True, True, True, False]
383
+ >>> vectorLength((3, 4))
384
+ 5.0
385
+ >>> vectorLength((1, 1)) == math.sqrt(2)
386
+ True
387
+ >>> list(asInt16([0, 0.1, 0.5, 0.9]))
388
+ [0, 0, 1, 1]
389
+ >>> normRect((0, 10, 100, 200))
390
+ (0, 10, 100, 200)
391
+ >>> normRect((100, 200, 0, 10))
392
+ (0, 10, 100, 200)
393
+ >>> scaleRect((10, 20, 50, 150), 1.5, 2)
394
+ (15.0, 40, 75.0, 300)
395
+ >>> offsetRect((10, 20, 30, 40), 5, 6)
396
+ (15, 26, 35, 46)
397
+ >>> insetRect((10, 20, 50, 60), 5, 10)
398
+ (15, 30, 45, 50)
399
+ >>> insetRect((10, 20, 50, 60), -5, -10)
400
+ (5, 10, 55, 70)
401
+ >>> intersects, rect = sectRect((0, 10, 20, 30), (0, 40, 20, 50))
402
+ >>> not intersects
403
+ True
404
+ >>> intersects, rect = sectRect((0, 10, 20, 30), (5, 20, 35, 50))
405
+ >>> intersects
406
+ 1
407
+ >>> rect
408
+ (5, 20, 20, 30)
409
+ >>> unionRect((0, 10, 20, 30), (0, 40, 20, 50))
410
+ (0, 10, 20, 50)
411
+ >>> rectCenter((0, 0, 100, 200))
412
+ (50.0, 100.0)
413
+ >>> rectCenter((0, 0, 100, 199.0))
414
+ (50.0, 99.5)
415
+ >>> intRect((0.9, 2.9, 3.1, 4.1))
416
+ (0, 2, 4, 5)
417
+ """
418
+
419
+
420
+ if __name__ == "__main__":
421
+ import sys
422
+ import doctest
423
+
424
+ sys.exit(doctest.testmod().failed)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/bezierTools.c ADDED
The diff for this file is too large to render. See raw diff
 
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/bezierTools.py ADDED
@@ -0,0 +1,1490 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """fontTools.misc.bezierTools.py -- tools for working with Bezier path segments.
3
+ """
4
+
5
+ from fontTools.misc.arrayTools import calcBounds, sectRect, rectArea
6
+ from fontTools.misc.transform import Identity
7
+ import math
8
+ from collections import namedtuple
9
+
10
+ try:
11
+ import cython
12
+ except (AttributeError, ImportError):
13
+ # if cython not installed, use mock module with no-op decorators and types
14
+ from fontTools.misc import cython
15
+ COMPILED = cython.compiled
16
+
17
+
18
+ EPSILON = 1e-9
19
+
20
+
21
+ Intersection = namedtuple("Intersection", ["pt", "t1", "t2"])
22
+
23
+
24
+ __all__ = [
25
+ "approximateCubicArcLength",
26
+ "approximateCubicArcLengthC",
27
+ "approximateQuadraticArcLength",
28
+ "approximateQuadraticArcLengthC",
29
+ "calcCubicArcLength",
30
+ "calcCubicArcLengthC",
31
+ "calcQuadraticArcLength",
32
+ "calcQuadraticArcLengthC",
33
+ "calcCubicBounds",
34
+ "calcQuadraticBounds",
35
+ "splitLine",
36
+ "splitQuadratic",
37
+ "splitCubic",
38
+ "splitQuadraticAtT",
39
+ "splitCubicAtT",
40
+ "splitCubicAtTC",
41
+ "splitCubicIntoTwoAtTC",
42
+ "solveQuadratic",
43
+ "solveCubic",
44
+ "quadraticPointAtT",
45
+ "cubicPointAtT",
46
+ "cubicPointAtTC",
47
+ "linePointAtT",
48
+ "segmentPointAtT",
49
+ "lineLineIntersections",
50
+ "curveLineIntersections",
51
+ "curveCurveIntersections",
52
+ "segmentSegmentIntersections",
53
+ ]
54
+
55
+
56
+ def calcCubicArcLength(pt1, pt2, pt3, pt4, tolerance=0.005):
57
+ """Calculates the arc length for a cubic Bezier segment.
58
+
59
+ Whereas :func:`approximateCubicArcLength` approximates the length, this
60
+ function calculates it by "measuring", recursively dividing the curve
61
+ until the divided segments are shorter than ``tolerance``.
62
+
63
+ Args:
64
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
65
+ tolerance: Controls the precision of the calcuation.
66
+
67
+ Returns:
68
+ Arc length value.
69
+ """
70
+ return calcCubicArcLengthC(
71
+ complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4), tolerance
72
+ )
73
+
74
+
75
+ def _split_cubic_into_two(p0, p1, p2, p3):
76
+ mid = (p0 + 3 * (p1 + p2) + p3) * 0.125
77
+ deriv3 = (p3 + p2 - p1 - p0) * 0.125
78
+ return (
79
+ (p0, (p0 + p1) * 0.5, mid - deriv3, mid),
80
+ (mid, mid + deriv3, (p2 + p3) * 0.5, p3),
81
+ )
82
+
83
+
84
+ @cython.returns(cython.double)
85
+ @cython.locals(
86
+ p0=cython.complex,
87
+ p1=cython.complex,
88
+ p2=cython.complex,
89
+ p3=cython.complex,
90
+ )
91
+ @cython.locals(mult=cython.double, arch=cython.double, box=cython.double)
92
+ def _calcCubicArcLengthCRecurse(mult, p0, p1, p2, p3):
93
+ arch = abs(p0 - p3)
94
+ box = abs(p0 - p1) + abs(p1 - p2) + abs(p2 - p3)
95
+ if arch * mult + EPSILON >= box:
96
+ return (arch + box) * 0.5
97
+ else:
98
+ one, two = _split_cubic_into_two(p0, p1, p2, p3)
99
+ return _calcCubicArcLengthCRecurse(mult, *one) + _calcCubicArcLengthCRecurse(
100
+ mult, *two
101
+ )
102
+
103
+
104
+ @cython.returns(cython.double)
105
+ @cython.locals(
106
+ pt1=cython.complex,
107
+ pt2=cython.complex,
108
+ pt3=cython.complex,
109
+ pt4=cython.complex,
110
+ )
111
+ @cython.locals(
112
+ tolerance=cython.double,
113
+ mult=cython.double,
114
+ )
115
+ def calcCubicArcLengthC(pt1, pt2, pt3, pt4, tolerance=0.005):
116
+ """Calculates the arc length for a cubic Bezier segment.
117
+
118
+ Args:
119
+ pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers.
120
+ tolerance: Controls the precision of the calcuation.
121
+
122
+ Returns:
123
+ Arc length value.
124
+ """
125
+ mult = 1.0 + 1.5 * tolerance # The 1.5 is a empirical hack; no math
126
+ return _calcCubicArcLengthCRecurse(mult, pt1, pt2, pt3, pt4)
127
+
128
+
129
+ epsilonDigits = 6
130
+ epsilon = 1e-10
131
+
132
+
133
+ @cython.cfunc
134
+ @cython.inline
135
+ @cython.returns(cython.double)
136
+ @cython.locals(v1=cython.complex, v2=cython.complex)
137
+ def _dot(v1, v2):
138
+ return (v1 * v2.conjugate()).real
139
+
140
+
141
+ @cython.cfunc
142
+ @cython.inline
143
+ @cython.returns(cython.double)
144
+ @cython.locals(x=cython.double)
145
+ def _intSecAtan(x):
146
+ # In : sympy.integrate(sp.sec(sp.atan(x)))
147
+ # Out: x*sqrt(x**2 + 1)/2 + asinh(x)/2
148
+ return x * math.sqrt(x**2 + 1) / 2 + math.asinh(x) / 2
149
+
150
+
151
+ def calcQuadraticArcLength(pt1, pt2, pt3):
152
+ """Calculates the arc length for a quadratic Bezier segment.
153
+
154
+ Args:
155
+ pt1: Start point of the Bezier as 2D tuple.
156
+ pt2: Handle point of the Bezier as 2D tuple.
157
+ pt3: End point of the Bezier as 2D tuple.
158
+
159
+ Returns:
160
+ Arc length value.
161
+
162
+ Example::
163
+
164
+ >>> calcQuadraticArcLength((0, 0), (0, 0), (0, 0)) # empty segment
165
+ 0.0
166
+ >>> calcQuadraticArcLength((0, 0), (50, 0), (80, 0)) # collinear points
167
+ 80.0
168
+ >>> calcQuadraticArcLength((0, 0), (0, 50), (0, 80)) # collinear points vertical
169
+ 80.0
170
+ >>> calcQuadraticArcLength((0, 0), (50, 20), (100, 40)) # collinear points
171
+ 107.70329614269008
172
+ >>> calcQuadraticArcLength((0, 0), (0, 100), (100, 0))
173
+ 154.02976155645263
174
+ >>> calcQuadraticArcLength((0, 0), (0, 50), (100, 0))
175
+ 120.21581243984076
176
+ >>> calcQuadraticArcLength((0, 0), (50, -10), (80, 50))
177
+ 102.53273816445825
178
+ >>> calcQuadraticArcLength((0, 0), (40, 0), (-40, 0)) # collinear points, control point outside
179
+ 66.66666666666667
180
+ >>> calcQuadraticArcLength((0, 0), (40, 0), (0, 0)) # collinear points, looping back
181
+ 40.0
182
+ """
183
+ return calcQuadraticArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3))
184
+
185
+
186
+ @cython.returns(cython.double)
187
+ @cython.locals(
188
+ pt1=cython.complex,
189
+ pt2=cython.complex,
190
+ pt3=cython.complex,
191
+ d0=cython.complex,
192
+ d1=cython.complex,
193
+ d=cython.complex,
194
+ n=cython.complex,
195
+ )
196
+ @cython.locals(
197
+ scale=cython.double,
198
+ origDist=cython.double,
199
+ a=cython.double,
200
+ b=cython.double,
201
+ x0=cython.double,
202
+ x1=cython.double,
203
+ Len=cython.double,
204
+ )
205
+ def calcQuadraticArcLengthC(pt1, pt2, pt3):
206
+ """Calculates the arc length for a quadratic Bezier segment.
207
+
208
+ Args:
209
+ pt1: Start point of the Bezier as a complex number.
210
+ pt2: Handle point of the Bezier as a complex number.
211
+ pt3: End point of the Bezier as a complex number.
212
+
213
+ Returns:
214
+ Arc length value.
215
+ """
216
+ # Analytical solution to the length of a quadratic bezier.
217
+ # Documentation: https://github.com/fonttools/fonttools/issues/3055
218
+ d0 = pt2 - pt1
219
+ d1 = pt3 - pt2
220
+ d = d1 - d0
221
+ n = d * 1j
222
+ scale = abs(n)
223
+ if scale == 0.0:
224
+ return abs(pt3 - pt1)
225
+ origDist = _dot(n, d0)
226
+ if abs(origDist) < epsilon:
227
+ if _dot(d0, d1) >= 0:
228
+ return abs(pt3 - pt1)
229
+ a, b = abs(d0), abs(d1)
230
+ return (a * a + b * b) / (a + b)
231
+ x0 = _dot(d, d0) / origDist
232
+ x1 = _dot(d, d1) / origDist
233
+ Len = abs(2 * (_intSecAtan(x1) - _intSecAtan(x0)) * origDist / (scale * (x1 - x0)))
234
+ return Len
235
+
236
+
237
+ def approximateQuadraticArcLength(pt1, pt2, pt3):
238
+ """Calculates the arc length for a quadratic Bezier segment.
239
+
240
+ Uses Gauss-Legendre quadrature for a branch-free approximation.
241
+ See :func:`calcQuadraticArcLength` for a slower but more accurate result.
242
+
243
+ Args:
244
+ pt1: Start point of the Bezier as 2D tuple.
245
+ pt2: Handle point of the Bezier as 2D tuple.
246
+ pt3: End point of the Bezier as 2D tuple.
247
+
248
+ Returns:
249
+ Approximate arc length value.
250
+ """
251
+ return approximateQuadraticArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3))
252
+
253
+
254
+ @cython.returns(cython.double)
255
+ @cython.locals(
256
+ pt1=cython.complex,
257
+ pt2=cython.complex,
258
+ pt3=cython.complex,
259
+ )
260
+ @cython.locals(
261
+ v0=cython.double,
262
+ v1=cython.double,
263
+ v2=cython.double,
264
+ )
265
+ def approximateQuadraticArcLengthC(pt1, pt2, pt3):
266
+ """Calculates the arc length for a quadratic Bezier segment.
267
+
268
+ Uses Gauss-Legendre quadrature for a branch-free approximation.
269
+ See :func:`calcQuadraticArcLength` for a slower but more accurate result.
270
+
271
+ Args:
272
+ pt1: Start point of the Bezier as a complex number.
273
+ pt2: Handle point of the Bezier as a complex number.
274
+ pt3: End point of the Bezier as a complex number.
275
+
276
+ Returns:
277
+ Approximate arc length value.
278
+ """
279
+ # This, essentially, approximates the length-of-derivative function
280
+ # to be integrated with the best-matching fifth-degree polynomial
281
+ # approximation of it.
282
+ #
283
+ # https://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss.E2.80.93Legendre_quadrature
284
+
285
+ # abs(BezierCurveC[2].diff(t).subs({t:T})) for T in sorted(.5, .5±sqrt(3/5)/2),
286
+ # weighted 5/18, 8/18, 5/18 respectively.
287
+ v0 = abs(
288
+ -0.492943519233745 * pt1 + 0.430331482911935 * pt2 + 0.0626120363218102 * pt3
289
+ )
290
+ v1 = abs(pt3 - pt1) * 0.4444444444444444
291
+ v2 = abs(
292
+ -0.0626120363218102 * pt1 - 0.430331482911935 * pt2 + 0.492943519233745 * pt3
293
+ )
294
+
295
+ return v0 + v1 + v2
296
+
297
+
298
+ def calcQuadraticBounds(pt1, pt2, pt3):
299
+ """Calculates the bounding rectangle for a quadratic Bezier segment.
300
+
301
+ Args:
302
+ pt1: Start point of the Bezier as a 2D tuple.
303
+ pt2: Handle point of the Bezier as a 2D tuple.
304
+ pt3: End point of the Bezier as a 2D tuple.
305
+
306
+ Returns:
307
+ A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.
308
+
309
+ Example::
310
+
311
+ >>> calcQuadraticBounds((0, 0), (50, 100), (100, 0))
312
+ (0, 0, 100, 50.0)
313
+ >>> calcQuadraticBounds((0, 0), (100, 0), (100, 100))
314
+ (0.0, 0.0, 100, 100)
315
+ """
316
+ (ax, ay), (bx, by), (cx, cy) = calcQuadraticParameters(pt1, pt2, pt3)
317
+ ax2 = ax * 2.0
318
+ ay2 = ay * 2.0
319
+ roots = []
320
+ if ax2 != 0:
321
+ roots.append(-bx / ax2)
322
+ if ay2 != 0:
323
+ roots.append(-by / ay2)
324
+ points = [
325
+ (ax * t * t + bx * t + cx, ay * t * t + by * t + cy)
326
+ for t in roots
327
+ if 0 <= t < 1
328
+ ] + [pt1, pt3]
329
+ return calcBounds(points)
330
+
331
+
332
+ def approximateCubicArcLength(pt1, pt2, pt3, pt4):
333
+ """Approximates the arc length for a cubic Bezier segment.
334
+
335
+ Uses Gauss-Lobatto quadrature with n=5 points to approximate arc length.
336
+ See :func:`calcCubicArcLength` for a slower but more accurate result.
337
+
338
+ Args:
339
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
340
+
341
+ Returns:
342
+ Arc length value.
343
+
344
+ Example::
345
+
346
+ >>> approximateCubicArcLength((0, 0), (25, 100), (75, 100), (100, 0))
347
+ 190.04332968932817
348
+ >>> approximateCubicArcLength((0, 0), (50, 0), (100, 50), (100, 100))
349
+ 154.8852074945903
350
+ >>> approximateCubicArcLength((0, 0), (50, 0), (100, 0), (150, 0)) # line; exact result should be 150.
351
+ 149.99999999999991
352
+ >>> approximateCubicArcLength((0, 0), (50, 0), (100, 0), (-50, 0)) # cusp; exact result should be 150.
353
+ 136.9267662156362
354
+ >>> approximateCubicArcLength((0, 0), (50, 0), (100, -50), (-50, 0)) # cusp
355
+ 154.80848416537057
356
+ """
357
+ return approximateCubicArcLengthC(
358
+ complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4)
359
+ )
360
+
361
+
362
+ @cython.returns(cython.double)
363
+ @cython.locals(
364
+ pt1=cython.complex,
365
+ pt2=cython.complex,
366
+ pt3=cython.complex,
367
+ pt4=cython.complex,
368
+ )
369
+ @cython.locals(
370
+ v0=cython.double,
371
+ v1=cython.double,
372
+ v2=cython.double,
373
+ v3=cython.double,
374
+ v4=cython.double,
375
+ )
376
+ def approximateCubicArcLengthC(pt1, pt2, pt3, pt4):
377
+ """Approximates the arc length for a cubic Bezier segment.
378
+
379
+ Args:
380
+ pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers.
381
+
382
+ Returns:
383
+ Arc length value.
384
+ """
385
+ # This, essentially, approximates the length-of-derivative function
386
+ # to be integrated with the best-matching seventh-degree polynomial
387
+ # approximation of it.
388
+ #
389
+ # https://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss.E2.80.93Lobatto_rules
390
+
391
+ # abs(BezierCurveC[3].diff(t).subs({t:T})) for T in sorted(0, .5±(3/7)**.5/2, .5, 1),
392
+ # weighted 1/20, 49/180, 32/90, 49/180, 1/20 respectively.
393
+ v0 = abs(pt2 - pt1) * 0.15
394
+ v1 = abs(
395
+ -0.558983582205757 * pt1
396
+ + 0.325650248872424 * pt2
397
+ + 0.208983582205757 * pt3
398
+ + 0.024349751127576 * pt4
399
+ )
400
+ v2 = abs(pt4 - pt1 + pt3 - pt2) * 0.26666666666666666
401
+ v3 = abs(
402
+ -0.024349751127576 * pt1
403
+ - 0.208983582205757 * pt2
404
+ - 0.325650248872424 * pt3
405
+ + 0.558983582205757 * pt4
406
+ )
407
+ v4 = abs(pt4 - pt3) * 0.15
408
+
409
+ return v0 + v1 + v2 + v3 + v4
410
+
411
+
412
+ def calcCubicBounds(pt1, pt2, pt3, pt4):
413
+ """Calculates the bounding rectangle for a quadratic Bezier segment.
414
+
415
+ Args:
416
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
417
+
418
+ Returns:
419
+ A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``.
420
+
421
+ Example::
422
+
423
+ >>> calcCubicBounds((0, 0), (25, 100), (75, 100), (100, 0))
424
+ (0, 0, 100, 75.0)
425
+ >>> calcCubicBounds((0, 0), (50, 0), (100, 50), (100, 100))
426
+ (0.0, 0.0, 100, 100)
427
+ >>> print("%f %f %f %f" % calcCubicBounds((50, 0), (0, 100), (100, 100), (50, 0)))
428
+ 35.566243 0.000000 64.433757 75.000000
429
+ """
430
+ (ax, ay), (bx, by), (cx, cy), (dx, dy) = calcCubicParameters(pt1, pt2, pt3, pt4)
431
+ # calc first derivative
432
+ ax3 = ax * 3.0
433
+ ay3 = ay * 3.0
434
+ bx2 = bx * 2.0
435
+ by2 = by * 2.0
436
+ xRoots = [t for t in solveQuadratic(ax3, bx2, cx) if 0 <= t < 1]
437
+ yRoots = [t for t in solveQuadratic(ay3, by2, cy) if 0 <= t < 1]
438
+ roots = xRoots + yRoots
439
+
440
+ points = [
441
+ (
442
+ ax * t * t * t + bx * t * t + cx * t + dx,
443
+ ay * t * t * t + by * t * t + cy * t + dy,
444
+ )
445
+ for t in roots
446
+ ] + [pt1, pt4]
447
+ return calcBounds(points)
448
+
449
+
450
+ def splitLine(pt1, pt2, where, isHorizontal):
451
+ """Split a line at a given coordinate.
452
+
453
+ Args:
454
+ pt1: Start point of line as 2D tuple.
455
+ pt2: End point of line as 2D tuple.
456
+ where: Position at which to split the line.
457
+ isHorizontal: Direction of the ray splitting the line. If true,
458
+ ``where`` is interpreted as a Y coordinate; if false, then
459
+ ``where`` is interpreted as an X coordinate.
460
+
461
+ Returns:
462
+ A list of two line segments (each line segment being two 2D tuples)
463
+ if the line was successfully split, or a list containing the original
464
+ line.
465
+
466
+ Example::
467
+
468
+ >>> printSegments(splitLine((0, 0), (100, 100), 50, True))
469
+ ((0, 0), (50, 50))
470
+ ((50, 50), (100, 100))
471
+ >>> printSegments(splitLine((0, 0), (100, 100), 100, True))
472
+ ((0, 0), (100, 100))
473
+ >>> printSegments(splitLine((0, 0), (100, 100), 0, True))
474
+ ((0, 0), (0, 0))
475
+ ((0, 0), (100, 100))
476
+ >>> printSegments(splitLine((0, 0), (100, 100), 0, False))
477
+ ((0, 0), (0, 0))
478
+ ((0, 0), (100, 100))
479
+ >>> printSegments(splitLine((100, 0), (0, 0), 50, False))
480
+ ((100, 0), (50, 0))
481
+ ((50, 0), (0, 0))
482
+ >>> printSegments(splitLine((0, 100), (0, 0), 50, True))
483
+ ((0, 100), (0, 50))
484
+ ((0, 50), (0, 0))
485
+ """
486
+ pt1x, pt1y = pt1
487
+ pt2x, pt2y = pt2
488
+
489
+ ax = pt2x - pt1x
490
+ ay = pt2y - pt1y
491
+
492
+ bx = pt1x
493
+ by = pt1y
494
+
495
+ a = (ax, ay)[isHorizontal]
496
+
497
+ if a == 0:
498
+ return [(pt1, pt2)]
499
+ t = (where - (bx, by)[isHorizontal]) / a
500
+ if 0 <= t < 1:
501
+ midPt = ax * t + bx, ay * t + by
502
+ return [(pt1, midPt), (midPt, pt2)]
503
+ else:
504
+ return [(pt1, pt2)]
505
+
506
+
507
+ def splitQuadratic(pt1, pt2, pt3, where, isHorizontal):
508
+ """Split a quadratic Bezier curve at a given coordinate.
509
+
510
+ Args:
511
+ pt1,pt2,pt3: Control points of the Bezier as 2D tuples.
512
+ where: Position at which to split the curve.
513
+ isHorizontal: Direction of the ray splitting the curve. If true,
514
+ ``where`` is interpreted as a Y coordinate; if false, then
515
+ ``where`` is interpreted as an X coordinate.
516
+
517
+ Returns:
518
+ A list of two curve segments (each curve segment being three 2D tuples)
519
+ if the curve was successfully split, or a list containing the original
520
+ curve.
521
+
522
+ Example::
523
+
524
+ >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 150, False))
525
+ ((0, 0), (50, 100), (100, 0))
526
+ >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, False))
527
+ ((0, 0), (25, 50), (50, 50))
528
+ ((50, 50), (75, 50), (100, 0))
529
+ >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, False))
530
+ ((0, 0), (12.5, 25), (25, 37.5))
531
+ ((25, 37.5), (62.5, 75), (100, 0))
532
+ >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, True))
533
+ ((0, 0), (7.32233, 14.6447), (14.6447, 25))
534
+ ((14.6447, 25), (50, 75), (85.3553, 25))
535
+ ((85.3553, 25), (92.6777, 14.6447), (100, -7.10543e-15))
536
+ >>> # XXX I'm not at all sure if the following behavior is desirable:
537
+ >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, True))
538
+ ((0, 0), (25, 50), (50, 50))
539
+ ((50, 50), (50, 50), (50, 50))
540
+ ((50, 50), (75, 50), (100, 0))
541
+ """
542
+ a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
543
+ solutions = solveQuadratic(
544
+ a[isHorizontal], b[isHorizontal], c[isHorizontal] - where
545
+ )
546
+ solutions = sorted(t for t in solutions if 0 <= t < 1)
547
+ if not solutions:
548
+ return [(pt1, pt2, pt3)]
549
+ return _splitQuadraticAtT(a, b, c, *solutions)
550
+
551
+
552
+ def splitCubic(pt1, pt2, pt3, pt4, where, isHorizontal):
553
+ """Split a cubic Bezier curve at a given coordinate.
554
+
555
+ Args:
556
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
557
+ where: Position at which to split the curve.
558
+ isHorizontal: Direction of the ray splitting the curve. If true,
559
+ ``where`` is interpreted as a Y coordinate; if false, then
560
+ ``where`` is interpreted as an X coordinate.
561
+
562
+ Returns:
563
+ A list of two curve segments (each curve segment being four 2D tuples)
564
+ if the curve was successfully split, or a list containing the original
565
+ curve.
566
+
567
+ Example::
568
+
569
+ >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 150, False))
570
+ ((0, 0), (25, 100), (75, 100), (100, 0))
571
+ >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 50, False))
572
+ ((0, 0), (12.5, 50), (31.25, 75), (50, 75))
573
+ ((50, 75), (68.75, 75), (87.5, 50), (100, 0))
574
+ >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 25, True))
575
+ ((0, 0), (2.29379, 9.17517), (4.79804, 17.5085), (7.47414, 25))
576
+ ((7.47414, 25), (31.2886, 91.6667), (68.7114, 91.6667), (92.5259, 25))
577
+ ((92.5259, 25), (95.202, 17.5085), (97.7062, 9.17517), (100, 1.77636e-15))
578
+ """
579
+ a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
580
+ solutions = solveCubic(
581
+ a[isHorizontal], b[isHorizontal], c[isHorizontal], d[isHorizontal] - where
582
+ )
583
+ solutions = sorted(t for t in solutions if 0 <= t < 1)
584
+ if not solutions:
585
+ return [(pt1, pt2, pt3, pt4)]
586
+ return _splitCubicAtT(a, b, c, d, *solutions)
587
+
588
+
589
+ def splitQuadraticAtT(pt1, pt2, pt3, *ts):
590
+ """Split a quadratic Bezier curve at one or more values of t.
591
+
592
+ Args:
593
+ pt1,pt2,pt3: Control points of the Bezier as 2D tuples.
594
+ *ts: Positions at which to split the curve.
595
+
596
+ Returns:
597
+ A list of curve segments (each curve segment being three 2D tuples).
598
+
599
+ Examples::
600
+
601
+ >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5))
602
+ ((0, 0), (25, 50), (50, 50))
603
+ ((50, 50), (75, 50), (100, 0))
604
+ >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5, 0.75))
605
+ ((0, 0), (25, 50), (50, 50))
606
+ ((50, 50), (62.5, 50), (75, 37.5))
607
+ ((75, 37.5), (87.5, 25), (100, 0))
608
+ """
609
+ a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
610
+ return _splitQuadraticAtT(a, b, c, *ts)
611
+
612
+
613
+ def splitCubicAtT(pt1, pt2, pt3, pt4, *ts):
614
+ """Split a cubic Bezier curve at one or more values of t.
615
+
616
+ Args:
617
+ pt1,pt2,pt3,pt4: Control points of the Bezier as 2D tuples.
618
+ *ts: Positions at which to split the curve.
619
+
620
+ Returns:
621
+ A list of curve segments (each curve segment being four 2D tuples).
622
+
623
+ Examples::
624
+
625
+ >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5))
626
+ ((0, 0), (12.5, 50), (31.25, 75), (50, 75))
627
+ ((50, 75), (68.75, 75), (87.5, 50), (100, 0))
628
+ >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75))
629
+ ((0, 0), (12.5, 50), (31.25, 75), (50, 75))
630
+ ((50, 75), (59.375, 75), (68.75, 68.75), (77.3438, 56.25))
631
+ ((77.3438, 56.25), (85.9375, 43.75), (93.75, 25), (100, 0))
632
+ """
633
+ a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
634
+ return _splitCubicAtT(a, b, c, d, *ts)
635
+
636
+
637
+ @cython.locals(
638
+ pt1=cython.complex,
639
+ pt2=cython.complex,
640
+ pt3=cython.complex,
641
+ pt4=cython.complex,
642
+ a=cython.complex,
643
+ b=cython.complex,
644
+ c=cython.complex,
645
+ d=cython.complex,
646
+ )
647
+ def splitCubicAtTC(pt1, pt2, pt3, pt4, *ts):
648
+ """Split a cubic Bezier curve at one or more values of t.
649
+
650
+ Args:
651
+ pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers..
652
+ *ts: Positions at which to split the curve.
653
+
654
+ Yields:
655
+ Curve segments (each curve segment being four complex numbers).
656
+ """
657
+ a, b, c, d = calcCubicParametersC(pt1, pt2, pt3, pt4)
658
+ yield from _splitCubicAtTC(a, b, c, d, *ts)
659
+
660
+
661
+ @cython.returns(cython.complex)
662
+ @cython.locals(
663
+ t=cython.double,
664
+ pt1=cython.complex,
665
+ pt2=cython.complex,
666
+ pt3=cython.complex,
667
+ pt4=cython.complex,
668
+ pointAtT=cython.complex,
669
+ off1=cython.complex,
670
+ off2=cython.complex,
671
+ )
672
+ @cython.locals(
673
+ t2=cython.double, _1_t=cython.double, _1_t_2=cython.double, _2_t_1_t=cython.double
674
+ )
675
+ def splitCubicIntoTwoAtTC(pt1, pt2, pt3, pt4, t):
676
+ """Split a cubic Bezier curve at t.
677
+
678
+ Args:
679
+ pt1,pt2,pt3,pt4: Control points of the Bezier as complex numbers.
680
+ t: Position at which to split the curve.
681
+
682
+ Returns:
683
+ A tuple of two curve segments (each curve segment being four complex numbers).
684
+ """
685
+ t2 = t * t
686
+ _1_t = 1 - t
687
+ _1_t_2 = _1_t * _1_t
688
+ _2_t_1_t = 2 * t * _1_t
689
+ pointAtT = (
690
+ _1_t_2 * _1_t * pt1 + 3 * (_1_t_2 * t * pt2 + _1_t * t2 * pt3) + t2 * t * pt4
691
+ )
692
+ off1 = _1_t_2 * pt1 + _2_t_1_t * pt2 + t2 * pt3
693
+ off2 = _1_t_2 * pt2 + _2_t_1_t * pt3 + t2 * pt4
694
+
695
+ pt2 = pt1 + (pt2 - pt1) * t
696
+ pt3 = pt4 + (pt3 - pt4) * _1_t
697
+
698
+ return ((pt1, pt2, off1, pointAtT), (pointAtT, off2, pt3, pt4))
699
+
700
+
701
+ def _splitQuadraticAtT(a, b, c, *ts):
702
+ ts = list(ts)
703
+ segments = []
704
+ ts.insert(0, 0.0)
705
+ ts.append(1.0)
706
+ ax, ay = a
707
+ bx, by = b
708
+ cx, cy = c
709
+ for i in range(len(ts) - 1):
710
+ t1 = ts[i]
711
+ t2 = ts[i + 1]
712
+ delta = t2 - t1
713
+ # calc new a, b and c
714
+ delta_2 = delta * delta
715
+ a1x = ax * delta_2
716
+ a1y = ay * delta_2
717
+ b1x = (2 * ax * t1 + bx) * delta
718
+ b1y = (2 * ay * t1 + by) * delta
719
+ t1_2 = t1 * t1
720
+ c1x = ax * t1_2 + bx * t1 + cx
721
+ c1y = ay * t1_2 + by * t1 + cy
722
+
723
+ pt1, pt2, pt3 = calcQuadraticPoints((a1x, a1y), (b1x, b1y), (c1x, c1y))
724
+ segments.append((pt1, pt2, pt3))
725
+ return segments
726
+
727
+
728
+ def _splitCubicAtT(a, b, c, d, *ts):
729
+ ts = list(ts)
730
+ ts.insert(0, 0.0)
731
+ ts.append(1.0)
732
+ segments = []
733
+ ax, ay = a
734
+ bx, by = b
735
+ cx, cy = c
736
+ dx, dy = d
737
+ for i in range(len(ts) - 1):
738
+ t1 = ts[i]
739
+ t2 = ts[i + 1]
740
+ delta = t2 - t1
741
+
742
+ delta_2 = delta * delta
743
+ delta_3 = delta * delta_2
744
+ t1_2 = t1 * t1
745
+ t1_3 = t1 * t1_2
746
+
747
+ # calc new a, b, c and d
748
+ a1x = ax * delta_3
749
+ a1y = ay * delta_3
750
+ b1x = (3 * ax * t1 + bx) * delta_2
751
+ b1y = (3 * ay * t1 + by) * delta_2
752
+ c1x = (2 * bx * t1 + cx + 3 * ax * t1_2) * delta
753
+ c1y = (2 * by * t1 + cy + 3 * ay * t1_2) * delta
754
+ d1x = ax * t1_3 + bx * t1_2 + cx * t1 + dx
755
+ d1y = ay * t1_3 + by * t1_2 + cy * t1 + dy
756
+ pt1, pt2, pt3, pt4 = calcCubicPoints(
757
+ (a1x, a1y), (b1x, b1y), (c1x, c1y), (d1x, d1y)
758
+ )
759
+ segments.append((pt1, pt2, pt3, pt4))
760
+ return segments
761
+
762
+
763
+ @cython.locals(
764
+ a=cython.complex,
765
+ b=cython.complex,
766
+ c=cython.complex,
767
+ d=cython.complex,
768
+ t1=cython.double,
769
+ t2=cython.double,
770
+ delta=cython.double,
771
+ delta_2=cython.double,
772
+ delta_3=cython.double,
773
+ a1=cython.complex,
774
+ b1=cython.complex,
775
+ c1=cython.complex,
776
+ d1=cython.complex,
777
+ )
778
+ def _splitCubicAtTC(a, b, c, d, *ts):
779
+ ts = list(ts)
780
+ ts.insert(0, 0.0)
781
+ ts.append(1.0)
782
+ for i in range(len(ts) - 1):
783
+ t1 = ts[i]
784
+ t2 = ts[i + 1]
785
+ delta = t2 - t1
786
+
787
+ delta_2 = delta * delta
788
+ delta_3 = delta * delta_2
789
+ t1_2 = t1 * t1
790
+ t1_3 = t1 * t1_2
791
+
792
+ # calc new a, b, c and d
793
+ a1 = a * delta_3
794
+ b1 = (3 * a * t1 + b) * delta_2
795
+ c1 = (2 * b * t1 + c + 3 * a * t1_2) * delta
796
+ d1 = a * t1_3 + b * t1_2 + c * t1 + d
797
+ pt1, pt2, pt3, pt4 = calcCubicPointsC(a1, b1, c1, d1)
798
+ yield (pt1, pt2, pt3, pt4)
799
+
800
+
801
+ #
802
+ # Equation solvers.
803
+ #
804
+
805
+ from math import sqrt, acos, cos, pi
806
+
807
+
808
+ def solveQuadratic(a, b, c, sqrt=sqrt):
809
+ """Solve a quadratic equation.
810
+
811
+ Solves *a*x*x + b*x + c = 0* where a, b and c are real.
812
+
813
+ Args:
814
+ a: coefficient of *x²*
815
+ b: coefficient of *x*
816
+ c: constant term
817
+
818
+ Returns:
819
+ A list of roots. Note that the returned list is neither guaranteed to
820
+ be sorted nor to contain unique values!
821
+ """
822
+ if abs(a) < epsilon:
823
+ if abs(b) < epsilon:
824
+ # We have a non-equation; therefore, we have no valid solution
825
+ roots = []
826
+ else:
827
+ # We have a linear equation with 1 root.
828
+ roots = [-c / b]
829
+ else:
830
+ # We have a true quadratic equation. Apply the quadratic formula to find two roots.
831
+ DD = b * b - 4.0 * a * c
832
+ if DD >= 0.0:
833
+ rDD = sqrt(DD)
834
+ roots = [(-b + rDD) / 2.0 / a, (-b - rDD) / 2.0 / a]
835
+ else:
836
+ # complex roots, ignore
837
+ roots = []
838
+ return roots
839
+
840
+
841
+ def solveCubic(a, b, c, d):
842
+ """Solve a cubic equation.
843
+
844
+ Solves *a*x*x*x + b*x*x + c*x + d = 0* where a, b, c and d are real.
845
+
846
+ Args:
847
+ a: coefficient of *x³*
848
+ b: coefficient of *x²*
849
+ c: coefficient of *x*
850
+ d: constant term
851
+
852
+ Returns:
853
+ A list of roots. Note that the returned list is neither guaranteed to
854
+ be sorted nor to contain unique values!
855
+
856
+ Examples::
857
+
858
+ >>> solveCubic(1, 1, -6, 0)
859
+ [-3.0, -0.0, 2.0]
860
+ >>> solveCubic(-10.0, -9.0, 48.0, -29.0)
861
+ [-2.9, 1.0, 1.0]
862
+ >>> solveCubic(-9.875, -9.0, 47.625, -28.75)
863
+ [-2.911392, 1.0, 1.0]
864
+ >>> solveCubic(1.0, -4.5, 6.75, -3.375)
865
+ [1.5, 1.5, 1.5]
866
+ >>> solveCubic(-12.0, 18.0, -9.0, 1.50023651123)
867
+ [0.5, 0.5, 0.5]
868
+ >>> solveCubic(
869
+ ... 9.0, 0.0, 0.0, -7.62939453125e-05
870
+ ... ) == [-0.0, -0.0, -0.0]
871
+ True
872
+ """
873
+ #
874
+ # adapted from:
875
+ # CUBIC.C - Solve a cubic polynomial
876
+ # public domain by Ross Cottrell
877
+ # found at: http://www.strangecreations.com/library/snippets/Cubic.C
878
+ #
879
+ if abs(a) < epsilon:
880
+ # don't just test for zero; for very small values of 'a' solveCubic()
881
+ # returns unreliable results, so we fall back to quad.
882
+ return solveQuadratic(b, c, d)
883
+ a = float(a)
884
+ a1 = b / a
885
+ a2 = c / a
886
+ a3 = d / a
887
+
888
+ Q = (a1 * a1 - 3.0 * a2) / 9.0
889
+ R = (2.0 * a1 * a1 * a1 - 9.0 * a1 * a2 + 27.0 * a3) / 54.0
890
+
891
+ R2 = R * R
892
+ Q3 = Q * Q * Q
893
+ R2 = 0 if R2 < epsilon else R2
894
+ Q3 = 0 if abs(Q3) < epsilon else Q3
895
+
896
+ R2_Q3 = R2 - Q3
897
+
898
+ if R2 == 0.0 and Q3 == 0.0:
899
+ x = round(-a1 / 3.0, epsilonDigits)
900
+ return [x, x, x]
901
+ elif R2_Q3 <= epsilon * 0.5:
902
+ # The epsilon * .5 above ensures that Q3 is not zero.
903
+ theta = acos(max(min(R / sqrt(Q3), 1.0), -1.0))
904
+ rQ2 = -2.0 * sqrt(Q)
905
+ a1_3 = a1 / 3.0
906
+ x0 = rQ2 * cos(theta / 3.0) - a1_3
907
+ x1 = rQ2 * cos((theta + 2.0 * pi) / 3.0) - a1_3
908
+ x2 = rQ2 * cos((theta + 4.0 * pi) / 3.0) - a1_3
909
+ x0, x1, x2 = sorted([x0, x1, x2])
910
+ # Merge roots that are close-enough
911
+ if x1 - x0 < epsilon and x2 - x1 < epsilon:
912
+ x0 = x1 = x2 = round((x0 + x1 + x2) / 3.0, epsilonDigits)
913
+ elif x1 - x0 < epsilon:
914
+ x0 = x1 = round((x0 + x1) / 2.0, epsilonDigits)
915
+ x2 = round(x2, epsilonDigits)
916
+ elif x2 - x1 < epsilon:
917
+ x0 = round(x0, epsilonDigits)
918
+ x1 = x2 = round((x1 + x2) / 2.0, epsilonDigits)
919
+ else:
920
+ x0 = round(x0, epsilonDigits)
921
+ x1 = round(x1, epsilonDigits)
922
+ x2 = round(x2, epsilonDigits)
923
+ return [x0, x1, x2]
924
+ else:
925
+ x = pow(sqrt(R2_Q3) + abs(R), 1 / 3.0)
926
+ x = x + Q / x
927
+ if R >= 0.0:
928
+ x = -x
929
+ x = round(x - a1 / 3.0, epsilonDigits)
930
+ return [x]
931
+
932
+
933
+ #
934
+ # Conversion routines for points to parameters and vice versa
935
+ #
936
+
937
+
938
+ def calcQuadraticParameters(pt1, pt2, pt3):
939
+ x2, y2 = pt2
940
+ x3, y3 = pt3
941
+ cx, cy = pt1
942
+ bx = (x2 - cx) * 2.0
943
+ by = (y2 - cy) * 2.0
944
+ ax = x3 - cx - bx
945
+ ay = y3 - cy - by
946
+ return (ax, ay), (bx, by), (cx, cy)
947
+
948
+
949
+ def calcCubicParameters(pt1, pt2, pt3, pt4):
950
+ x2, y2 = pt2
951
+ x3, y3 = pt3
952
+ x4, y4 = pt4
953
+ dx, dy = pt1
954
+ cx = (x2 - dx) * 3.0
955
+ cy = (y2 - dy) * 3.0
956
+ bx = (x3 - x2) * 3.0 - cx
957
+ by = (y3 - y2) * 3.0 - cy
958
+ ax = x4 - dx - cx - bx
959
+ ay = y4 - dy - cy - by
960
+ return (ax, ay), (bx, by), (cx, cy), (dx, dy)
961
+
962
+
963
+ @cython.cfunc
964
+ @cython.inline
965
+ @cython.locals(
966
+ pt1=cython.complex,
967
+ pt2=cython.complex,
968
+ pt3=cython.complex,
969
+ pt4=cython.complex,
970
+ a=cython.complex,
971
+ b=cython.complex,
972
+ c=cython.complex,
973
+ )
974
+ def calcCubicParametersC(pt1, pt2, pt3, pt4):
975
+ c = (pt2 - pt1) * 3.0
976
+ b = (pt3 - pt2) * 3.0 - c
977
+ a = pt4 - pt1 - c - b
978
+ return (a, b, c, pt1)
979
+
980
+
981
+ def calcQuadraticPoints(a, b, c):
982
+ ax, ay = a
983
+ bx, by = b
984
+ cx, cy = c
985
+ x1 = cx
986
+ y1 = cy
987
+ x2 = (bx * 0.5) + cx
988
+ y2 = (by * 0.5) + cy
989
+ x3 = ax + bx + cx
990
+ y3 = ay + by + cy
991
+ return (x1, y1), (x2, y2), (x3, y3)
992
+
993
+
994
+ def calcCubicPoints(a, b, c, d):
995
+ ax, ay = a
996
+ bx, by = b
997
+ cx, cy = c
998
+ dx, dy = d
999
+ x1 = dx
1000
+ y1 = dy
1001
+ x2 = (cx / 3.0) + dx
1002
+ y2 = (cy / 3.0) + dy
1003
+ x3 = (bx + cx) / 3.0 + x2
1004
+ y3 = (by + cy) / 3.0 + y2
1005
+ x4 = ax + dx + cx + bx
1006
+ y4 = ay + dy + cy + by
1007
+ return (x1, y1), (x2, y2), (x3, y3), (x4, y4)
1008
+
1009
+
1010
+ @cython.cfunc
1011
+ @cython.inline
1012
+ @cython.locals(
1013
+ a=cython.complex,
1014
+ b=cython.complex,
1015
+ c=cython.complex,
1016
+ d=cython.complex,
1017
+ p2=cython.complex,
1018
+ p3=cython.complex,
1019
+ p4=cython.complex,
1020
+ )
1021
+ def calcCubicPointsC(a, b, c, d):
1022
+ p2 = c * (1 / 3) + d
1023
+ p3 = (b + c) * (1 / 3) + p2
1024
+ p4 = a + b + c + d
1025
+ return (d, p2, p3, p4)
1026
+
1027
+
1028
+ #
1029
+ # Point at time
1030
+ #
1031
+
1032
+
1033
+ def linePointAtT(pt1, pt2, t):
1034
+ """Finds the point at time `t` on a line.
1035
+
1036
+ Args:
1037
+ pt1, pt2: Coordinates of the line as 2D tuples.
1038
+ t: The time along the line.
1039
+
1040
+ Returns:
1041
+ A 2D tuple with the coordinates of the point.
1042
+ """
1043
+ return ((pt1[0] * (1 - t) + pt2[0] * t), (pt1[1] * (1 - t) + pt2[1] * t))
1044
+
1045
+
1046
+ def quadraticPointAtT(pt1, pt2, pt3, t):
1047
+ """Finds the point at time `t` on a quadratic curve.
1048
+
1049
+ Args:
1050
+ pt1, pt2, pt3: Coordinates of the curve as 2D tuples.
1051
+ t: The time along the curve.
1052
+
1053
+ Returns:
1054
+ A 2D tuple with the coordinates of the point.
1055
+ """
1056
+ x = (1 - t) * (1 - t) * pt1[0] + 2 * (1 - t) * t * pt2[0] + t * t * pt3[0]
1057
+ y = (1 - t) * (1 - t) * pt1[1] + 2 * (1 - t) * t * pt2[1] + t * t * pt3[1]
1058
+ return (x, y)
1059
+
1060
+
1061
+ def cubicPointAtT(pt1, pt2, pt3, pt4, t):
1062
+ """Finds the point at time `t` on a cubic curve.
1063
+
1064
+ Args:
1065
+ pt1, pt2, pt3, pt4: Coordinates of the curve as 2D tuples.
1066
+ t: The time along the curve.
1067
+
1068
+ Returns:
1069
+ A 2D tuple with the coordinates of the point.
1070
+ """
1071
+ t2 = t * t
1072
+ _1_t = 1 - t
1073
+ _1_t_2 = _1_t * _1_t
1074
+ x = (
1075
+ _1_t_2 * _1_t * pt1[0]
1076
+ + 3 * (_1_t_2 * t * pt2[0] + _1_t * t2 * pt3[0])
1077
+ + t2 * t * pt4[0]
1078
+ )
1079
+ y = (
1080
+ _1_t_2 * _1_t * pt1[1]
1081
+ + 3 * (_1_t_2 * t * pt2[1] + _1_t * t2 * pt3[1])
1082
+ + t2 * t * pt4[1]
1083
+ )
1084
+ return (x, y)
1085
+
1086
+
1087
+ @cython.returns(cython.complex)
1088
+ @cython.locals(
1089
+ t=cython.double,
1090
+ pt1=cython.complex,
1091
+ pt2=cython.complex,
1092
+ pt3=cython.complex,
1093
+ pt4=cython.complex,
1094
+ )
1095
+ @cython.locals(t2=cython.double, _1_t=cython.double, _1_t_2=cython.double)
1096
+ def cubicPointAtTC(pt1, pt2, pt3, pt4, t):
1097
+ """Finds the point at time `t` on a cubic curve.
1098
+
1099
+ Args:
1100
+ pt1, pt2, pt3, pt4: Coordinates of the curve as complex numbers.
1101
+ t: The time along the curve.
1102
+
1103
+ Returns:
1104
+ A complex number with the coordinates of the point.
1105
+ """
1106
+ t2 = t * t
1107
+ _1_t = 1 - t
1108
+ _1_t_2 = _1_t * _1_t
1109
+ return _1_t_2 * _1_t * pt1 + 3 * (_1_t_2 * t * pt2 + _1_t * t2 * pt3) + t2 * t * pt4
1110
+
1111
+
1112
+ def segmentPointAtT(seg, t):
1113
+ if len(seg) == 2:
1114
+ return linePointAtT(*seg, t)
1115
+ elif len(seg) == 3:
1116
+ return quadraticPointAtT(*seg, t)
1117
+ elif len(seg) == 4:
1118
+ return cubicPointAtT(*seg, t)
1119
+ raise ValueError("Unknown curve degree")
1120
+
1121
+
1122
+ #
1123
+ # Intersection finders
1124
+ #
1125
+
1126
+
1127
+ def _line_t_of_pt(s, e, pt):
1128
+ sx, sy = s
1129
+ ex, ey = e
1130
+ px, py = pt
1131
+ if abs(sx - ex) < epsilon and abs(sy - ey) < epsilon:
1132
+ # Line is a point!
1133
+ return -1
1134
+ # Use the largest
1135
+ if abs(sx - ex) > abs(sy - ey):
1136
+ return (px - sx) / (ex - sx)
1137
+ else:
1138
+ return (py - sy) / (ey - sy)
1139
+
1140
+
1141
+ def _both_points_are_on_same_side_of_origin(a, b, origin):
1142
+ xDiff = (a[0] - origin[0]) * (b[0] - origin[0])
1143
+ yDiff = (a[1] - origin[1]) * (b[1] - origin[1])
1144
+ return not (xDiff <= 0.0 and yDiff <= 0.0)
1145
+
1146
+
1147
+ def lineLineIntersections(s1, e1, s2, e2):
1148
+ """Finds intersections between two line segments.
1149
+
1150
+ Args:
1151
+ s1, e1: Coordinates of the first line as 2D tuples.
1152
+ s2, e2: Coordinates of the second line as 2D tuples.
1153
+
1154
+ Returns:
1155
+ A list of ``Intersection`` objects, each object having ``pt``, ``t1``
1156
+ and ``t2`` attributes containing the intersection point, time on first
1157
+ segment and time on second segment respectively.
1158
+
1159
+ Examples::
1160
+
1161
+ >>> a = lineLineIntersections( (310,389), (453, 222), (289, 251), (447, 367))
1162
+ >>> len(a)
1163
+ 1
1164
+ >>> intersection = a[0]
1165
+ >>> intersection.pt
1166
+ (374.44882952482897, 313.73458370177315)
1167
+ >>> (intersection.t1, intersection.t2)
1168
+ (0.45069111555824465, 0.5408153767394238)
1169
+ """
1170
+ s1x, s1y = s1
1171
+ e1x, e1y = e1
1172
+ s2x, s2y = s2
1173
+ e2x, e2y = e2
1174
+ if (
1175
+ math.isclose(s2x, e2x) and math.isclose(s1x, e1x) and not math.isclose(s1x, s2x)
1176
+ ): # Parallel vertical
1177
+ return []
1178
+ if (
1179
+ math.isclose(s2y, e2y) and math.isclose(s1y, e1y) and not math.isclose(s1y, s2y)
1180
+ ): # Parallel horizontal
1181
+ return []
1182
+ if math.isclose(s2x, e2x) and math.isclose(s2y, e2y): # Line segment is tiny
1183
+ return []
1184
+ if math.isclose(s1x, e1x) and math.isclose(s1y, e1y): # Line segment is tiny
1185
+ return []
1186
+ if math.isclose(e1x, s1x):
1187
+ x = s1x
1188
+ slope34 = (e2y - s2y) / (e2x - s2x)
1189
+ y = slope34 * (x - s2x) + s2y
1190
+ pt = (x, y)
1191
+ return [
1192
+ Intersection(
1193
+ pt=pt, t1=_line_t_of_pt(s1, e1, pt), t2=_line_t_of_pt(s2, e2, pt)
1194
+ )
1195
+ ]
1196
+ if math.isclose(s2x, e2x):
1197
+ x = s2x
1198
+ slope12 = (e1y - s1y) / (e1x - s1x)
1199
+ y = slope12 * (x - s1x) + s1y
1200
+ pt = (x, y)
1201
+ return [
1202
+ Intersection(
1203
+ pt=pt, t1=_line_t_of_pt(s1, e1, pt), t2=_line_t_of_pt(s2, e2, pt)
1204
+ )
1205
+ ]
1206
+
1207
+ slope12 = (e1y - s1y) / (e1x - s1x)
1208
+ slope34 = (e2y - s2y) / (e2x - s2x)
1209
+ if math.isclose(slope12, slope34):
1210
+ return []
1211
+ x = (slope12 * s1x - s1y - slope34 * s2x + s2y) / (slope12 - slope34)
1212
+ y = slope12 * (x - s1x) + s1y
1213
+ pt = (x, y)
1214
+ if _both_points_are_on_same_side_of_origin(
1215
+ pt, e1, s1
1216
+ ) and _both_points_are_on_same_side_of_origin(pt, s2, e2):
1217
+ return [
1218
+ Intersection(
1219
+ pt=pt, t1=_line_t_of_pt(s1, e1, pt), t2=_line_t_of_pt(s2, e2, pt)
1220
+ )
1221
+ ]
1222
+ return []
1223
+
1224
+
1225
+ def _alignment_transformation(segment):
1226
+ # Returns a transformation which aligns a segment horizontally at the
1227
+ # origin. Apply this transformation to curves and root-find to find
1228
+ # intersections with the segment.
1229
+ start = segment[0]
1230
+ end = segment[-1]
1231
+ angle = math.atan2(end[1] - start[1], end[0] - start[0])
1232
+ return Identity.rotate(-angle).translate(-start[0], -start[1])
1233
+
1234
+
1235
+ def _curve_line_intersections_t(curve, line):
1236
+ aligned_curve = _alignment_transformation(line).transformPoints(curve)
1237
+ if len(curve) == 3:
1238
+ a, b, c = calcQuadraticParameters(*aligned_curve)
1239
+ intersections = solveQuadratic(a[1], b[1], c[1])
1240
+ elif len(curve) == 4:
1241
+ a, b, c, d = calcCubicParameters(*aligned_curve)
1242
+ intersections = solveCubic(a[1], b[1], c[1], d[1])
1243
+ else:
1244
+ raise ValueError("Unknown curve degree")
1245
+ return sorted(i for i in intersections if 0.0 <= i <= 1)
1246
+
1247
+
1248
+ def curveLineIntersections(curve, line):
1249
+ """Finds intersections between a curve and a line.
1250
+
1251
+ Args:
1252
+ curve: List of coordinates of the curve segment as 2D tuples.
1253
+ line: List of coordinates of the line segment as 2D tuples.
1254
+
1255
+ Returns:
1256
+ A list of ``Intersection`` objects, each object having ``pt``, ``t1``
1257
+ and ``t2`` attributes containing the intersection point, time on first
1258
+ segment and time on second segment respectively.
1259
+
1260
+ Examples::
1261
+ >>> curve = [ (100, 240), (30, 60), (210, 230), (160, 30) ]
1262
+ >>> line = [ (25, 260), (230, 20) ]
1263
+ >>> intersections = curveLineIntersections(curve, line)
1264
+ >>> len(intersections)
1265
+ 3
1266
+ >>> intersections[0].pt
1267
+ (84.9000930760723, 189.87306176459828)
1268
+ """
1269
+ if len(curve) == 3:
1270
+ pointFinder = quadraticPointAtT
1271
+ elif len(curve) == 4:
1272
+ pointFinder = cubicPointAtT
1273
+ else:
1274
+ raise ValueError("Unknown curve degree")
1275
+ intersections = []
1276
+ for t in _curve_line_intersections_t(curve, line):
1277
+ pt = pointFinder(*curve, t)
1278
+ # Back-project the point onto the line, to avoid problems with
1279
+ # numerical accuracy in the case of vertical and horizontal lines
1280
+ line_t = _line_t_of_pt(*line, pt)
1281
+ pt = linePointAtT(*line, line_t)
1282
+ intersections.append(Intersection(pt=pt, t1=t, t2=line_t))
1283
+ return intersections
1284
+
1285
+
1286
+ def _curve_bounds(c):
1287
+ if len(c) == 3:
1288
+ return calcQuadraticBounds(*c)
1289
+ elif len(c) == 4:
1290
+ return calcCubicBounds(*c)
1291
+ raise ValueError("Unknown curve degree")
1292
+
1293
+
1294
+ def _split_segment_at_t(c, t):
1295
+ if len(c) == 2:
1296
+ s, e = c
1297
+ midpoint = linePointAtT(s, e, t)
1298
+ return [(s, midpoint), (midpoint, e)]
1299
+ if len(c) == 3:
1300
+ return splitQuadraticAtT(*c, t)
1301
+ elif len(c) == 4:
1302
+ return splitCubicAtT(*c, t)
1303
+ raise ValueError("Unknown curve degree")
1304
+
1305
+
1306
+ def _curve_curve_intersections_t(
1307
+ curve1, curve2, precision=1e-3, range1=None, range2=None
1308
+ ):
1309
+ bounds1 = _curve_bounds(curve1)
1310
+ bounds2 = _curve_bounds(curve2)
1311
+
1312
+ if not range1:
1313
+ range1 = (0.0, 1.0)
1314
+ if not range2:
1315
+ range2 = (0.0, 1.0)
1316
+
1317
+ # If bounds don't intersect, go home
1318
+ intersects, _ = sectRect(bounds1, bounds2)
1319
+ if not intersects:
1320
+ return []
1321
+
1322
+ def midpoint(r):
1323
+ return 0.5 * (r[0] + r[1])
1324
+
1325
+ # If they do overlap but they're tiny, approximate
1326
+ if rectArea(bounds1) < precision and rectArea(bounds2) < precision:
1327
+ return [(midpoint(range1), midpoint(range2))]
1328
+
1329
+ c11, c12 = _split_segment_at_t(curve1, 0.5)
1330
+ c11_range = (range1[0], midpoint(range1))
1331
+ c12_range = (midpoint(range1), range1[1])
1332
+
1333
+ c21, c22 = _split_segment_at_t(curve2, 0.5)
1334
+ c21_range = (range2[0], midpoint(range2))
1335
+ c22_range = (midpoint(range2), range2[1])
1336
+
1337
+ found = []
1338
+ found.extend(
1339
+ _curve_curve_intersections_t(
1340
+ c11, c21, precision, range1=c11_range, range2=c21_range
1341
+ )
1342
+ )
1343
+ found.extend(
1344
+ _curve_curve_intersections_t(
1345
+ c12, c21, precision, range1=c12_range, range2=c21_range
1346
+ )
1347
+ )
1348
+ found.extend(
1349
+ _curve_curve_intersections_t(
1350
+ c11, c22, precision, range1=c11_range, range2=c22_range
1351
+ )
1352
+ )
1353
+ found.extend(
1354
+ _curve_curve_intersections_t(
1355
+ c12, c22, precision, range1=c12_range, range2=c22_range
1356
+ )
1357
+ )
1358
+
1359
+ unique_key = lambda ts: (int(ts[0] / precision), int(ts[1] / precision))
1360
+ seen = set()
1361
+ unique_values = []
1362
+
1363
+ for ts in found:
1364
+ key = unique_key(ts)
1365
+ if key in seen:
1366
+ continue
1367
+ seen.add(key)
1368
+ unique_values.append(ts)
1369
+
1370
+ return unique_values
1371
+
1372
+
1373
+ def _is_linelike(segment):
1374
+ maybeline = _alignment_transformation(segment).transformPoints(segment)
1375
+ return all(math.isclose(p[1], 0.0) for p in maybeline)
1376
+
1377
+
1378
+ def curveCurveIntersections(curve1, curve2):
1379
+ """Finds intersections between a curve and a curve.
1380
+
1381
+ Args:
1382
+ curve1: List of coordinates of the first curve segment as 2D tuples.
1383
+ curve2: List of coordinates of the second curve segment as 2D tuples.
1384
+
1385
+ Returns:
1386
+ A list of ``Intersection`` objects, each object having ``pt``, ``t1``
1387
+ and ``t2`` attributes containing the intersection point, time on first
1388
+ segment and time on second segment respectively.
1389
+
1390
+ Examples::
1391
+ >>> curve1 = [ (10,100), (90,30), (40,140), (220,220) ]
1392
+ >>> curve2 = [ (5,150), (180,20), (80,250), (210,190) ]
1393
+ >>> intersections = curveCurveIntersections(curve1, curve2)
1394
+ >>> len(intersections)
1395
+ 3
1396
+ >>> intersections[0].pt
1397
+ (81.7831487395506, 109.88904552375288)
1398
+ """
1399
+ if _is_linelike(curve1):
1400
+ line1 = curve1[0], curve1[-1]
1401
+ if _is_linelike(curve2):
1402
+ line2 = curve2[0], curve2[-1]
1403
+ return lineLineIntersections(*line1, *line2)
1404
+ else:
1405
+ return curveLineIntersections(curve2, line1)
1406
+ elif _is_linelike(curve2):
1407
+ line2 = curve2[0], curve2[-1]
1408
+ return curveLineIntersections(curve1, line2)
1409
+
1410
+ intersection_ts = _curve_curve_intersections_t(curve1, curve2)
1411
+ return [
1412
+ Intersection(pt=segmentPointAtT(curve1, ts[0]), t1=ts[0], t2=ts[1])
1413
+ for ts in intersection_ts
1414
+ ]
1415
+
1416
+
1417
+ def segmentSegmentIntersections(seg1, seg2):
1418
+ """Finds intersections between two segments.
1419
+
1420
+ Args:
1421
+ seg1: List of coordinates of the first segment as 2D tuples.
1422
+ seg2: List of coordinates of the second segment as 2D tuples.
1423
+
1424
+ Returns:
1425
+ A list of ``Intersection`` objects, each object having ``pt``, ``t1``
1426
+ and ``t2`` attributes containing the intersection point, time on first
1427
+ segment and time on second segment respectively.
1428
+
1429
+ Examples::
1430
+ >>> curve1 = [ (10,100), (90,30), (40,140), (220,220) ]
1431
+ >>> curve2 = [ (5,150), (180,20), (80,250), (210,190) ]
1432
+ >>> intersections = segmentSegmentIntersections(curve1, curve2)
1433
+ >>> len(intersections)
1434
+ 3
1435
+ >>> intersections[0].pt
1436
+ (81.7831487395506, 109.88904552375288)
1437
+ >>> curve3 = [ (100, 240), (30, 60), (210, 230), (160, 30) ]
1438
+ >>> line = [ (25, 260), (230, 20) ]
1439
+ >>> intersections = segmentSegmentIntersections(curve3, line)
1440
+ >>> len(intersections)
1441
+ 3
1442
+ >>> intersections[0].pt
1443
+ (84.9000930760723, 189.87306176459828)
1444
+
1445
+ """
1446
+ # Arrange by degree
1447
+ swapped = False
1448
+ if len(seg2) > len(seg1):
1449
+ seg2, seg1 = seg1, seg2
1450
+ swapped = True
1451
+ if len(seg1) > 2:
1452
+ if len(seg2) > 2:
1453
+ intersections = curveCurveIntersections(seg1, seg2)
1454
+ else:
1455
+ intersections = curveLineIntersections(seg1, seg2)
1456
+ elif len(seg1) == 2 and len(seg2) == 2:
1457
+ intersections = lineLineIntersections(*seg1, *seg2)
1458
+ else:
1459
+ raise ValueError("Couldn't work out which intersection function to use")
1460
+ if not swapped:
1461
+ return intersections
1462
+ return [Intersection(pt=i.pt, t1=i.t2, t2=i.t1) for i in intersections]
1463
+
1464
+
1465
+ def _segmentrepr(obj):
1466
+ """
1467
+ >>> _segmentrepr([1, [2, 3], [], [[2, [3, 4], [0.1, 2.2]]]])
1468
+ '(1, (2, 3), (), ((2, (3, 4), (0.1, 2.2))))'
1469
+ """
1470
+ try:
1471
+ it = iter(obj)
1472
+ except TypeError:
1473
+ return "%g" % obj
1474
+ else:
1475
+ return "(%s)" % ", ".join(_segmentrepr(x) for x in it)
1476
+
1477
+
1478
+ def printSegments(segments):
1479
+ """Helper for the doctests, displaying each segment in a list of
1480
+ segments on a single line as a tuple.
1481
+ """
1482
+ for segment in segments:
1483
+ print(_segmentrepr(segment))
1484
+
1485
+
1486
+ if __name__ == "__main__":
1487
+ import sys
1488
+ import doctest
1489
+
1490
+ sys.exit(doctest.testmod().failed)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/cliTools.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Collection of utilities for command-line interfaces and console scripts."""
2
+
3
+ import os
4
+ import re
5
+
6
+
7
+ numberAddedRE = re.compile(r"#\d+$")
8
+
9
+
10
+ def makeOutputFileName(
11
+ input, outputDir=None, extension=None, overWrite=False, suffix=""
12
+ ):
13
+ """Generates a suitable file name for writing output.
14
+
15
+ Often tools will want to take a file, do some kind of transformation to it,
16
+ and write it out again. This function determines an appropriate name for the
17
+ output file, through one or more of the following steps:
18
+
19
+ - changing the output directory
20
+ - appending suffix before file extension
21
+ - replacing the file extension
22
+ - suffixing the filename with a number (``#1``, ``#2``, etc.) to avoid
23
+ overwriting an existing file.
24
+
25
+ Args:
26
+ input: Name of input file.
27
+ outputDir: Optionally, a new directory to write the file into.
28
+ suffix: Optionally, a string suffix is appended to file name before
29
+ the extension.
30
+ extension: Optionally, a replacement for the current file extension.
31
+ overWrite: Overwriting an existing file is permitted if true; if false
32
+ and the proposed filename exists, a new name will be generated by
33
+ adding an appropriate number suffix.
34
+
35
+ Returns:
36
+ str: Suitable output filename
37
+ """
38
+ dirName, fileName = os.path.split(input)
39
+ fileName, ext = os.path.splitext(fileName)
40
+ if outputDir:
41
+ dirName = outputDir
42
+ fileName = numberAddedRE.split(fileName)[0]
43
+ if extension is None:
44
+ extension = os.path.splitext(input)[1]
45
+ output = os.path.join(dirName, fileName + suffix + extension)
46
+ n = 1
47
+ if not overWrite:
48
+ while os.path.exists(output):
49
+ output = os.path.join(
50
+ dirName, fileName + suffix + "#" + repr(n) + extension
51
+ )
52
+ n += 1
53
+ return output
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/configTools.py ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Code of the config system; not related to fontTools or fonts in particular.
3
+
4
+ The options that are specific to fontTools are in :mod:`fontTools.config`.
5
+
6
+ To create your own config system, you need to create an instance of
7
+ :class:`Options`, and a subclass of :class:`AbstractConfig` with its
8
+ ``options`` class variable set to your instance of Options.
9
+
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ from dataclasses import dataclass
16
+ from typing import (
17
+ Any,
18
+ Callable,
19
+ ClassVar,
20
+ Dict,
21
+ Iterable,
22
+ Mapping,
23
+ MutableMapping,
24
+ Optional,
25
+ Set,
26
+ Union,
27
+ )
28
+
29
+
30
+ log = logging.getLogger(__name__)
31
+
32
+ __all__ = [
33
+ "AbstractConfig",
34
+ "ConfigAlreadyRegisteredError",
35
+ "ConfigError",
36
+ "ConfigUnknownOptionError",
37
+ "ConfigValueParsingError",
38
+ "ConfigValueValidationError",
39
+ "Option",
40
+ "Options",
41
+ ]
42
+
43
+
44
+ class ConfigError(Exception):
45
+ """Base exception for the config module."""
46
+
47
+
48
+ class ConfigAlreadyRegisteredError(ConfigError):
49
+ """Raised when a module tries to register a configuration option that
50
+ already exists.
51
+
52
+ Should not be raised too much really, only when developing new fontTools
53
+ modules.
54
+ """
55
+
56
+ def __init__(self, name):
57
+ super().__init__(f"Config option {name} is already registered.")
58
+
59
+
60
+ class ConfigValueParsingError(ConfigError):
61
+ """Raised when a configuration value cannot be parsed."""
62
+
63
+ def __init__(self, name, value):
64
+ super().__init__(
65
+ f"Config option {name}: value cannot be parsed (given {repr(value)})"
66
+ )
67
+
68
+
69
+ class ConfigValueValidationError(ConfigError):
70
+ """Raised when a configuration value cannot be validated."""
71
+
72
+ def __init__(self, name, value):
73
+ super().__init__(
74
+ f"Config option {name}: value is invalid (given {repr(value)})"
75
+ )
76
+
77
+
78
+ class ConfigUnknownOptionError(ConfigError):
79
+ """Raised when a configuration option is unknown."""
80
+
81
+ def __init__(self, option_or_name):
82
+ name = (
83
+ f"'{option_or_name.name}' (id={id(option_or_name)})>"
84
+ if isinstance(option_or_name, Option)
85
+ else f"'{option_or_name}'"
86
+ )
87
+ super().__init__(f"Config option {name} is unknown")
88
+
89
+
90
+ # eq=False because Options are unique, not fungible objects
91
+ @dataclass(frozen=True, eq=False)
92
+ class Option:
93
+ name: str
94
+ """Unique name identifying the option (e.g. package.module:MY_OPTION)."""
95
+ help: str
96
+ """Help text for this option."""
97
+ default: Any
98
+ """Default value for this option."""
99
+ parse: Callable[[str], Any]
100
+ """Turn input (e.g. string) into proper type. Only when reading from file."""
101
+ validate: Optional[Callable[[Any], bool]] = None
102
+ """Return true if the given value is an acceptable value."""
103
+
104
+ @staticmethod
105
+ def parse_optional_bool(v: str) -> Optional[bool]:
106
+ s = str(v).lower()
107
+ if s in {"0", "no", "false"}:
108
+ return False
109
+ if s in {"1", "yes", "true"}:
110
+ return True
111
+ if s in {"auto", "none"}:
112
+ return None
113
+ raise ValueError("invalid optional bool: {v!r}")
114
+
115
+ @staticmethod
116
+ def validate_optional_bool(v: Any) -> bool:
117
+ return v is None or isinstance(v, bool)
118
+
119
+
120
+ class Options(Mapping):
121
+ """Registry of available options for a given config system.
122
+
123
+ Define new options using the :meth:`register()` method.
124
+
125
+ Access existing options using the Mapping interface.
126
+ """
127
+
128
+ __options: Dict[str, Option]
129
+
130
+ def __init__(self, other: "Options" = None) -> None:
131
+ self.__options = {}
132
+ if other is not None:
133
+ for option in other.values():
134
+ self.register_option(option)
135
+
136
+ def register(
137
+ self,
138
+ name: str,
139
+ help: str,
140
+ default: Any,
141
+ parse: Callable[[str], Any],
142
+ validate: Optional[Callable[[Any], bool]] = None,
143
+ ) -> Option:
144
+ """Create and register a new option."""
145
+ return self.register_option(Option(name, help, default, parse, validate))
146
+
147
+ def register_option(self, option: Option) -> Option:
148
+ """Register a new option."""
149
+ name = option.name
150
+ if name in self.__options:
151
+ raise ConfigAlreadyRegisteredError(name)
152
+ self.__options[name] = option
153
+ return option
154
+
155
+ def is_registered(self, option: Option) -> bool:
156
+ """Return True if the same option object is already registered."""
157
+ return self.__options.get(option.name) is option
158
+
159
+ def __getitem__(self, key: str) -> Option:
160
+ return self.__options.__getitem__(key)
161
+
162
+ def __iter__(self) -> Iterator[str]:
163
+ return self.__options.__iter__()
164
+
165
+ def __len__(self) -> int:
166
+ return self.__options.__len__()
167
+
168
+ def __repr__(self) -> str:
169
+ return (
170
+ f"{self.__class__.__name__}({{\n"
171
+ + "".join(
172
+ f" {k!r}: Option(default={v.default!r}, ...),\n"
173
+ for k, v in self.__options.items()
174
+ )
175
+ + "})"
176
+ )
177
+
178
+
179
+ _USE_GLOBAL_DEFAULT = object()
180
+
181
+
182
+ class AbstractConfig(MutableMapping):
183
+ """
184
+ Create a set of config values, optionally pre-filled with values from
185
+ the given dictionary or pre-existing config object.
186
+
187
+ The class implements the MutableMapping protocol keyed by option name (`str`).
188
+ For convenience its methods accept either Option or str as the key parameter.
189
+
190
+ .. seealso:: :meth:`set()`
191
+
192
+ This config class is abstract because it needs its ``options`` class
193
+ var to be set to an instance of :class:`Options` before it can be
194
+ instanciated and used.
195
+
196
+ .. code:: python
197
+
198
+ class MyConfig(AbstractConfig):
199
+ options = Options()
200
+
201
+ MyConfig.register_option( "test:option_name", "This is an option", 0, int, lambda v: isinstance(v, int))
202
+
203
+ cfg = MyConfig({"test:option_name": 10})
204
+
205
+ """
206
+
207
+ options: ClassVar[Options]
208
+
209
+ @classmethod
210
+ def register_option(
211
+ cls,
212
+ name: str,
213
+ help: str,
214
+ default: Any,
215
+ parse: Callable[[str], Any],
216
+ validate: Optional[Callable[[Any], bool]] = None,
217
+ ) -> Option:
218
+ """Register an available option in this config system."""
219
+ return cls.options.register(
220
+ name, help=help, default=default, parse=parse, validate=validate
221
+ )
222
+
223
+ _values: Dict[str, Any]
224
+
225
+ def __init__(
226
+ self,
227
+ values: Union[AbstractConfig, Dict[Union[Option, str], Any]] = {},
228
+ parse_values: bool = False,
229
+ skip_unknown: bool = False,
230
+ ):
231
+ self._values = {}
232
+ values_dict = values._values if isinstance(values, AbstractConfig) else values
233
+ for name, value in values_dict.items():
234
+ self.set(name, value, parse_values, skip_unknown)
235
+
236
+ def _resolve_option(self, option_or_name: Union[Option, str]) -> Option:
237
+ if isinstance(option_or_name, Option):
238
+ option = option_or_name
239
+ if not self.options.is_registered(option):
240
+ raise ConfigUnknownOptionError(option)
241
+ return option
242
+ elif isinstance(option_or_name, str):
243
+ name = option_or_name
244
+ try:
245
+ return self.options[name]
246
+ except KeyError:
247
+ raise ConfigUnknownOptionError(name)
248
+ else:
249
+ raise TypeError(
250
+ "expected Option or str, found "
251
+ f"{type(option_or_name).__name__}: {option_or_name!r}"
252
+ )
253
+
254
+ def set(
255
+ self,
256
+ option_or_name: Union[Option, str],
257
+ value: Any,
258
+ parse_values: bool = False,
259
+ skip_unknown: bool = False,
260
+ ):
261
+ """Set the value of an option.
262
+
263
+ Args:
264
+ * `option_or_name`: an `Option` object or its name (`str`).
265
+ * `value`: the value to be assigned to given option.
266
+ * `parse_values`: parse the configuration value from a string into
267
+ its proper type, as per its `Option` object. The default
268
+ behavior is to raise `ConfigValueValidationError` when the value
269
+ is not of the right type. Useful when reading options from a
270
+ file type that doesn't support as many types as Python.
271
+ * `skip_unknown`: skip unknown configuration options. The default
272
+ behaviour is to raise `ConfigUnknownOptionError`. Useful when
273
+ reading options from a configuration file that has extra entries
274
+ (e.g. for a later version of fontTools)
275
+ """
276
+ try:
277
+ option = self._resolve_option(option_or_name)
278
+ except ConfigUnknownOptionError as e:
279
+ if skip_unknown:
280
+ log.debug(str(e))
281
+ return
282
+ raise
283
+
284
+ # Can be useful if the values come from a source that doesn't have
285
+ # strict typing (.ini file? Terminal input?)
286
+ if parse_values:
287
+ try:
288
+ value = option.parse(value)
289
+ except Exception as e:
290
+ raise ConfigValueParsingError(option.name, value) from e
291
+
292
+ if option.validate is not None and not option.validate(value):
293
+ raise ConfigValueValidationError(option.name, value)
294
+
295
+ self._values[option.name] = value
296
+
297
+ def get(
298
+ self, option_or_name: Union[Option, str], default: Any = _USE_GLOBAL_DEFAULT
299
+ ) -> Any:
300
+ """
301
+ Get the value of an option. The value which is returned is the first
302
+ provided among:
303
+
304
+ 1. a user-provided value in the options's ``self._values`` dict
305
+ 2. a caller-provided default value to this method call
306
+ 3. the global default for the option provided in ``fontTools.config``
307
+
308
+ This is to provide the ability to migrate progressively from config
309
+ options passed as arguments to fontTools APIs to config options read
310
+ from the current TTFont, e.g.
311
+
312
+ .. code:: python
313
+
314
+ def fontToolsAPI(font, some_option):
315
+ value = font.cfg.get("someLib.module:SOME_OPTION", some_option)
316
+ # use value
317
+
318
+ That way, the function will work the same for users of the API that
319
+ still pass the option to the function call, but will favour the new
320
+ config mechanism if the given font specifies a value for that option.
321
+ """
322
+ option = self._resolve_option(option_or_name)
323
+ if option.name in self._values:
324
+ return self._values[option.name]
325
+ if default is not _USE_GLOBAL_DEFAULT:
326
+ return default
327
+ return option.default
328
+
329
+ def copy(self):
330
+ return self.__class__(self._values)
331
+
332
+ def __getitem__(self, option_or_name: Union[Option, str]) -> Any:
333
+ return self.get(option_or_name)
334
+
335
+ def __setitem__(self, option_or_name: Union[Option, str], value: Any) -> None:
336
+ return self.set(option_or_name, value)
337
+
338
+ def __delitem__(self, option_or_name: Union[Option, str]) -> None:
339
+ option = self._resolve_option(option_or_name)
340
+ del self._values[option.name]
341
+
342
+ def __iter__(self) -> Iterable[str]:
343
+ return self._values.__iter__()
344
+
345
+ def __len__(self) -> int:
346
+ return len(self._values)
347
+
348
+ def __repr__(self) -> str:
349
+ return f"{self.__class__.__name__}({repr(self._values)})"
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/cython.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ Exports a no-op 'cython' namespace similar to
2
+ https://github.com/cython/cython/blob/master/Cython/Shadow.py
3
+
4
+ This allows to optionally compile @cython decorated functions
5
+ (when cython is available at built time), or run the same code
6
+ as pure-python, without runtime dependency on cython module.
7
+
8
+ We only define the symbols that we use. E.g. see fontTools.cu2qu
9
+ """
10
+
11
+ from types import SimpleNamespace
12
+
13
+
14
+ def _empty_decorator(x):
15
+ return x
16
+
17
+
18
+ compiled = False
19
+
20
+ for name in ("double", "complex", "int"):
21
+ globals()[name] = None
22
+
23
+ for name in ("cfunc", "inline"):
24
+ globals()[name] = _empty_decorator
25
+
26
+ locals = lambda **_: _empty_decorator
27
+ returns = lambda _: _empty_decorator
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/dictTools.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Misc dict tools."""
2
+
3
+ __all__ = ["hashdict"]
4
+
5
+
6
+ # https://stackoverflow.com/questions/1151658/python-hashable-dicts
7
+ class hashdict(dict):
8
+ """
9
+ hashable dict implementation, suitable for use as a key into
10
+ other dicts.
11
+
12
+ >>> h1 = hashdict({"apples": 1, "bananas":2})
13
+ >>> h2 = hashdict({"bananas": 3, "mangoes": 5})
14
+ >>> h1+h2
15
+ hashdict(apples=1, bananas=3, mangoes=5)
16
+ >>> d1 = {}
17
+ >>> d1[h1] = "salad"
18
+ >>> d1[h1]
19
+ 'salad'
20
+ >>> d1[h2]
21
+ Traceback (most recent call last):
22
+ ...
23
+ KeyError: hashdict(bananas=3, mangoes=5)
24
+
25
+ based on answers from
26
+ http://stackoverflow.com/questions/1151658/python-hashable-dicts
27
+
28
+ """
29
+
30
+ def __key(self):
31
+ return tuple(sorted(self.items()))
32
+
33
+ def __repr__(self):
34
+ return "{0}({1})".format(
35
+ self.__class__.__name__,
36
+ ", ".join("{0}={1}".format(str(i[0]), repr(i[1])) for i in self.__key()),
37
+ )
38
+
39
+ def __hash__(self):
40
+ return hash(self.__key())
41
+
42
+ def __setitem__(self, key, value):
43
+ raise TypeError(
44
+ "{0} does not support item assignment".format(self.__class__.__name__)
45
+ )
46
+
47
+ def __delitem__(self, key):
48
+ raise TypeError(
49
+ "{0} does not support item assignment".format(self.__class__.__name__)
50
+ )
51
+
52
+ def clear(self):
53
+ raise TypeError(
54
+ "{0} does not support item assignment".format(self.__class__.__name__)
55
+ )
56
+
57
+ def pop(self, *args, **kwargs):
58
+ raise TypeError(
59
+ "{0} does not support item assignment".format(self.__class__.__name__)
60
+ )
61
+
62
+ def popitem(self, *args, **kwargs):
63
+ raise TypeError(
64
+ "{0} does not support item assignment".format(self.__class__.__name__)
65
+ )
66
+
67
+ def setdefault(self, *args, **kwargs):
68
+ raise TypeError(
69
+ "{0} does not support item assignment".format(self.__class__.__name__)
70
+ )
71
+
72
+ def update(self, *args, **kwargs):
73
+ raise TypeError(
74
+ "{0} does not support item assignment".format(self.__class__.__name__)
75
+ )
76
+
77
+ # update is not ok because it mutates the object
78
+ # __add__ is ok because it creates a new object
79
+ # while the new object is under construction, it's ok to mutate it
80
+ def __add__(self, right):
81
+ result = hashdict(self)
82
+ dict.update(result, right)
83
+ return result
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/eexec.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PostScript Type 1 fonts make use of two types of encryption: charstring
3
+ encryption and ``eexec`` encryption. Charstring encryption is used for
4
+ the charstrings themselves, while ``eexec`` is used to encrypt larger
5
+ sections of the font program, such as the ``Private`` and ``CharStrings``
6
+ dictionaries. Despite the different names, the algorithm is the same,
7
+ although ``eexec`` encryption uses a fixed initial key R=55665.
8
+
9
+ The algorithm uses cipher feedback, meaning that the ciphertext is used
10
+ to modify the key. Because of this, the routines in this module return
11
+ the new key at the end of the operation.
12
+
13
+ """
14
+
15
+ from fontTools.misc.textTools import bytechr, bytesjoin, byteord
16
+
17
+
18
+ def _decryptChar(cipher, R):
19
+ cipher = byteord(cipher)
20
+ plain = ((cipher ^ (R >> 8))) & 0xFF
21
+ R = ((cipher + R) * 52845 + 22719) & 0xFFFF
22
+ return bytechr(plain), R
23
+
24
+
25
+ def _encryptChar(plain, R):
26
+ plain = byteord(plain)
27
+ cipher = ((plain ^ (R >> 8))) & 0xFF
28
+ R = ((cipher + R) * 52845 + 22719) & 0xFFFF
29
+ return bytechr(cipher), R
30
+
31
+
32
+ def decrypt(cipherstring, R):
33
+ r"""
34
+ Decrypts a string using the Type 1 encryption algorithm.
35
+
36
+ Args:
37
+ cipherstring: String of ciphertext.
38
+ R: Initial key.
39
+
40
+ Returns:
41
+ decryptedStr: Plaintext string.
42
+ R: Output key for subsequent decryptions.
43
+
44
+ Examples::
45
+
46
+ >>> testStr = b"\0\0asdadads asds\265"
47
+ >>> decryptedStr, R = decrypt(testStr, 12321)
48
+ >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
49
+ True
50
+ >>> R == 36142
51
+ True
52
+ """
53
+ plainList = []
54
+ for cipher in cipherstring:
55
+ plain, R = _decryptChar(cipher, R)
56
+ plainList.append(plain)
57
+ plainstring = bytesjoin(plainList)
58
+ return plainstring, int(R)
59
+
60
+
61
+ def encrypt(plainstring, R):
62
+ r"""
63
+ Encrypts a string using the Type 1 encryption algorithm.
64
+
65
+ Note that the algorithm as described in the Type 1 specification requires the
66
+ plaintext to be prefixed with a number of random bytes. (For ``eexec`` the
67
+ number of random bytes is set to 4.) This routine does *not* add the random
68
+ prefix to its input.
69
+
70
+ Args:
71
+ plainstring: String of plaintext.
72
+ R: Initial key.
73
+
74
+ Returns:
75
+ cipherstring: Ciphertext string.
76
+ R: Output key for subsequent encryptions.
77
+
78
+ Examples::
79
+
80
+ >>> testStr = b"\0\0asdadads asds\265"
81
+ >>> decryptedStr, R = decrypt(testStr, 12321)
82
+ >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
83
+ True
84
+ >>> R == 36142
85
+ True
86
+
87
+ >>> testStr = b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
88
+ >>> encryptedStr, R = encrypt(testStr, 12321)
89
+ >>> encryptedStr == b"\0\0asdadads asds\265"
90
+ True
91
+ >>> R == 36142
92
+ True
93
+ """
94
+ cipherList = []
95
+ for plain in plainstring:
96
+ cipher, R = _encryptChar(plain, R)
97
+ cipherList.append(cipher)
98
+ cipherstring = bytesjoin(cipherList)
99
+ return cipherstring, int(R)
100
+
101
+
102
+ def hexString(s):
103
+ import binascii
104
+
105
+ return binascii.hexlify(s)
106
+
107
+
108
+ def deHexString(h):
109
+ import binascii
110
+
111
+ h = bytesjoin(h.split())
112
+ return binascii.unhexlify(h)
113
+
114
+
115
+ if __name__ == "__main__":
116
+ import sys
117
+ import doctest
118
+
119
+ sys.exit(doctest.testmod().failed)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/encodingTools.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """fontTools.misc.encodingTools.py -- tools for working with OpenType encodings.
2
+ """
3
+
4
+ import fontTools.encodings.codecs
5
+
6
+ # Map keyed by platformID, then platEncID, then possibly langID
7
+ _encodingMap = {
8
+ 0: { # Unicode
9
+ 0: "utf_16_be",
10
+ 1: "utf_16_be",
11
+ 2: "utf_16_be",
12
+ 3: "utf_16_be",
13
+ 4: "utf_16_be",
14
+ 5: "utf_16_be",
15
+ 6: "utf_16_be",
16
+ },
17
+ 1: { # Macintosh
18
+ # See
19
+ # https://github.com/fonttools/fonttools/issues/236
20
+ 0: { # Macintosh, platEncID==0, keyed by langID
21
+ 15: "mac_iceland",
22
+ 17: "mac_turkish",
23
+ 18: "mac_croatian",
24
+ 24: "mac_latin2",
25
+ 25: "mac_latin2",
26
+ 26: "mac_latin2",
27
+ 27: "mac_latin2",
28
+ 28: "mac_latin2",
29
+ 36: "mac_latin2",
30
+ 37: "mac_romanian",
31
+ 38: "mac_latin2",
32
+ 39: "mac_latin2",
33
+ 40: "mac_latin2",
34
+ Ellipsis: "mac_roman", # Other
35
+ },
36
+ 1: "x_mac_japanese_ttx",
37
+ 2: "x_mac_trad_chinese_ttx",
38
+ 3: "x_mac_korean_ttx",
39
+ 6: "mac_greek",
40
+ 7: "mac_cyrillic",
41
+ 25: "x_mac_simp_chinese_ttx",
42
+ 29: "mac_latin2",
43
+ 35: "mac_turkish",
44
+ 37: "mac_iceland",
45
+ },
46
+ 2: { # ISO
47
+ 0: "ascii",
48
+ 1: "utf_16_be",
49
+ 2: "latin1",
50
+ },
51
+ 3: { # Microsoft
52
+ 0: "utf_16_be",
53
+ 1: "utf_16_be",
54
+ 2: "shift_jis",
55
+ 3: "gb2312",
56
+ 4: "big5",
57
+ 5: "euc_kr",
58
+ 6: "johab",
59
+ 10: "utf_16_be",
60
+ },
61
+ }
62
+
63
+
64
+ def getEncoding(platformID, platEncID, langID, default=None):
65
+ """Returns the Python encoding name for OpenType platformID/encodingID/langID
66
+ triplet. If encoding for these values is not known, by default None is
67
+ returned. That can be overriden by passing a value to the default argument.
68
+ """
69
+ encoding = _encodingMap.get(platformID, {}).get(platEncID, default)
70
+ if isinstance(encoding, dict):
71
+ encoding = encoding.get(langID, encoding[Ellipsis])
72
+ return encoding
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/etree.py ADDED
@@ -0,0 +1,479 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shim module exporting the same ElementTree API for lxml and
2
+ xml.etree backends.
3
+
4
+ When lxml is installed, it is automatically preferred over the built-in
5
+ xml.etree module.
6
+ On Python 2.7, the cElementTree module is preferred over the pure-python
7
+ ElementTree module.
8
+
9
+ Besides exporting a unified interface, this also defines extra functions
10
+ or subclasses built-in ElementTree classes to add features that are
11
+ only availble in lxml, like OrderedDict for attributes, pretty_print and
12
+ iterwalk.
13
+ """
14
+
15
+ from fontTools.misc.textTools import tostr
16
+
17
+
18
+ XML_DECLARATION = """<?xml version='1.0' encoding='%s'?>"""
19
+
20
+ __all__ = [
21
+ # public symbols
22
+ "Comment",
23
+ "dump",
24
+ "Element",
25
+ "ElementTree",
26
+ "fromstring",
27
+ "fromstringlist",
28
+ "iselement",
29
+ "iterparse",
30
+ "parse",
31
+ "ParseError",
32
+ "PI",
33
+ "ProcessingInstruction",
34
+ "QName",
35
+ "SubElement",
36
+ "tostring",
37
+ "tostringlist",
38
+ "TreeBuilder",
39
+ "XML",
40
+ "XMLParser",
41
+ "register_namespace",
42
+ ]
43
+
44
+ try:
45
+ from lxml.etree import *
46
+
47
+ _have_lxml = True
48
+ except ImportError:
49
+ try:
50
+ from xml.etree.cElementTree import *
51
+
52
+ # the cElementTree version of XML function doesn't support
53
+ # the optional 'parser' keyword argument
54
+ from xml.etree.ElementTree import XML
55
+ except ImportError: # pragma: no cover
56
+ from xml.etree.ElementTree import *
57
+ _have_lxml = False
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
74
+
75
+ if isinstance(Element, type):
76
+ _Element = Element
77
+ else:
78
+ # in py27, cElementTree.Element cannot be subclassed, so
79
+ # we need to import the pure-python class
80
+ from xml.etree.ElementTree import Element as _Element
81
+
82
+ class Element(_Element):
83
+ """Element subclass that keeps the order of attributes."""
84
+
85
+ def __init__(self, tag, attrib=_Attrib(), **extra):
86
+ super(Element, self).__init__(tag)
87
+ self.attrib = _Attrib()
88
+ if attrib:
89
+ self.attrib.update(attrib)
90
+ if extra:
91
+ self.attrib.update(extra)
92
+
93
+ def SubElement(parent, tag, attrib=_Attrib(), **extra):
94
+ """Must override SubElement as well otherwise _elementtree.SubElement
95
+ fails if 'parent' is a subclass of Element object.
96
+ """
97
+ element = parent.__class__(tag, attrib, **extra)
98
+ parent.append(element)
99
+ return element
100
+
101
+ def _iterwalk(element, events, tag):
102
+ include = tag is None or element.tag == tag
103
+ if include and "start" in events:
104
+ yield ("start", element)
105
+ for e in element:
106
+ for item in _iterwalk(e, events, tag):
107
+ yield item
108
+ if include:
109
+ yield ("end", element)
110
+
111
+ def iterwalk(element_or_tree, events=("end",), tag=None):
112
+ """A tree walker that generates events from an existing tree as
113
+ if it was parsing XML data with iterparse().
114
+ Drop-in replacement for lxml.etree.iterwalk.
115
+ """
116
+ if iselement(element_or_tree):
117
+ element = element_or_tree
118
+ else:
119
+ element = element_or_tree.getroot()
120
+ if tag == "*":
121
+ tag = None
122
+ for item in _iterwalk(element, events, tag):
123
+ yield item
124
+
125
+ _ElementTree = ElementTree
126
+
127
+ class ElementTree(_ElementTree):
128
+ """ElementTree subclass that adds 'pretty_print' and 'doctype'
129
+ arguments to the 'write' method.
130
+ Currently these are only supported for the default XML serialization
131
+ 'method', and not also for "html" or "text", for these are delegated
132
+ to the base class.
133
+ """
134
+
135
+ def write(
136
+ self,
137
+ file_or_filename,
138
+ encoding=None,
139
+ xml_declaration=False,
140
+ method=None,
141
+ doctype=None,
142
+ pretty_print=False,
143
+ ):
144
+ if method and method != "xml":
145
+ # delegate to super-class
146
+ super(ElementTree, self).write(
147
+ file_or_filename,
148
+ encoding=encoding,
149
+ xml_declaration=xml_declaration,
150
+ method=method,
151
+ )
152
+ return
153
+
154
+ if encoding is not None and encoding.lower() == "unicode":
155
+ if xml_declaration:
156
+ raise ValueError(
157
+ "Serialisation to unicode must not request an XML declaration"
158
+ )
159
+ write_declaration = False
160
+ encoding = "unicode"
161
+ elif xml_declaration is None:
162
+ # by default, write an XML declaration only for non-standard encodings
163
+ write_declaration = encoding is not None and encoding.upper() not in (
164
+ "ASCII",
165
+ "UTF-8",
166
+ "UTF8",
167
+ "US-ASCII",
168
+ )
169
+ else:
170
+ write_declaration = xml_declaration
171
+
172
+ if encoding is None:
173
+ encoding = "ASCII"
174
+
175
+ if pretty_print:
176
+ # NOTE this will modify the tree in-place
177
+ _indent(self._root)
178
+
179
+ with _get_writer(file_or_filename, encoding) as write:
180
+ if write_declaration:
181
+ write(XML_DECLARATION % encoding.upper())
182
+ if pretty_print:
183
+ write("\n")
184
+ if doctype:
185
+ write(_tounicode(doctype))
186
+ if pretty_print:
187
+ write("\n")
188
+
189
+ qnames, namespaces = _namespaces(self._root)
190
+ _serialize_xml(write, self._root, qnames, namespaces)
191
+
192
+ import io
193
+
194
+ def tostring(
195
+ element,
196
+ encoding=None,
197
+ xml_declaration=None,
198
+ method=None,
199
+ doctype=None,
200
+ pretty_print=False,
201
+ ):
202
+ """Custom 'tostring' function that uses our ElementTree subclass, with
203
+ pretty_print support.
204
+ """
205
+ stream = io.StringIO() if encoding == "unicode" else io.BytesIO()
206
+ ElementTree(element).write(
207
+ stream,
208
+ encoding=encoding,
209
+ xml_declaration=xml_declaration,
210
+ method=method,
211
+ doctype=doctype,
212
+ pretty_print=pretty_print,
213
+ )
214
+ return stream.getvalue()
215
+
216
+ # serialization support
217
+
218
+ import re
219
+
220
+ # Valid XML strings can include any Unicode character, excluding control
221
+ # characters, the surrogate blocks, FFFE, and FFFF:
222
+ # Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
223
+ # 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
+ )
236
+
237
+ def _tounicode(s):
238
+ """Test if a string is valid user input and decode it to unicode string
239
+ using ASCII encoding if it's a bytes string.
240
+ Reject all bytes/unicode input that contains non-XML characters.
241
+ Reject all bytes input that contains non-ASCII characters.
242
+ """
243
+ try:
244
+ s = tostr(s, encoding="ascii", errors="strict")
245
+ except UnicodeDecodeError:
246
+ raise ValueError(
247
+ "Bytes strings can only contain ASCII characters. "
248
+ "Use unicode strings for non-ASCII characters."
249
+ )
250
+ except AttributeError:
251
+ _raise_serialization_error(s)
252
+ if s and _invalid_xml_string.search(s):
253
+ raise ValueError(
254
+ "All strings must be XML compatible: Unicode or ASCII, "
255
+ "no NULL bytes or control characters"
256
+ )
257
+ return s
258
+
259
+ import contextlib
260
+
261
+ @contextlib.contextmanager
262
+ def _get_writer(file_or_filename, encoding):
263
+ # returns text write method and release all resources after using
264
+ try:
265
+ write = file_or_filename.write
266
+ except AttributeError:
267
+ # file_or_filename is a file name
268
+ f = open(
269
+ file_or_filename,
270
+ "w",
271
+ encoding="utf-8" if encoding == "unicode" else encoding,
272
+ errors="xmlcharrefreplace",
273
+ )
274
+ with f:
275
+ yield f.write
276
+ else:
277
+ # file_or_filename is a file-like object
278
+ # encoding determines if it is a text or binary writer
279
+ if encoding == "unicode":
280
+ # use a text writer as is
281
+ yield write
282
+ else:
283
+ # wrap a binary writer with TextIOWrapper
284
+ detach_buffer = False
285
+ if isinstance(file_or_filename, io.BufferedIOBase):
286
+ buf = file_or_filename
287
+ elif isinstance(file_or_filename, io.RawIOBase):
288
+ buf = io.BufferedWriter(file_or_filename)
289
+ detach_buffer = True
290
+ else:
291
+ # This is to handle passed objects that aren't in the
292
+ # IOBase hierarchy, but just have a write method
293
+ buf = io.BufferedIOBase()
294
+ buf.writable = lambda: True
295
+ buf.write = write
296
+ try:
297
+ # TextIOWrapper uses this methods to determine
298
+ # if BOM (for UTF-16, etc) should be added
299
+ buf.seekable = file_or_filename.seekable
300
+ buf.tell = file_or_filename.tell
301
+ except AttributeError:
302
+ pass
303
+ wrapper = io.TextIOWrapper(
304
+ buf,
305
+ encoding=encoding,
306
+ errors="xmlcharrefreplace",
307
+ newline="\n",
308
+ )
309
+ try:
310
+ yield wrapper.write
311
+ finally:
312
+ # Keep the original file open when the TextIOWrapper and
313
+ # the BufferedWriter are destroyed
314
+ wrapper.detach()
315
+ if detach_buffer:
316
+ buf.detach()
317
+
318
+ from xml.etree.ElementTree import _namespace_map
319
+
320
+ def _namespaces(elem):
321
+ # identify namespaces used in this tree
322
+
323
+ # maps qnames to *encoded* prefix:local names
324
+ qnames = {None: None}
325
+
326
+ # maps uri:s to prefixes
327
+ namespaces = {}
328
+
329
+ def add_qname(qname):
330
+ # calculate serialized qname representation
331
+ try:
332
+ qname = _tounicode(qname)
333
+ if qname[:1] == "{":
334
+ uri, tag = qname[1:].rsplit("}", 1)
335
+ prefix = namespaces.get(uri)
336
+ if prefix is None:
337
+ prefix = _namespace_map.get(uri)
338
+ if prefix is None:
339
+ prefix = "ns%d" % len(namespaces)
340
+ else:
341
+ prefix = _tounicode(prefix)
342
+ if prefix != "xml":
343
+ namespaces[uri] = prefix
344
+ if prefix:
345
+ qnames[qname] = "%s:%s" % (prefix, tag)
346
+ else:
347
+ qnames[qname] = tag # default element
348
+ else:
349
+ qnames[qname] = qname
350
+ except TypeError:
351
+ _raise_serialization_error(qname)
352
+
353
+ # populate qname and namespaces table
354
+ for elem in elem.iter():
355
+ tag = elem.tag
356
+ if isinstance(tag, QName):
357
+ if tag.text not in qnames:
358
+ add_qname(tag.text)
359
+ elif isinstance(tag, str):
360
+ if tag not in qnames:
361
+ add_qname(tag)
362
+ elif tag is not None and tag is not Comment and tag is not PI:
363
+ _raise_serialization_error(tag)
364
+ for key, value in elem.items():
365
+ if isinstance(key, QName):
366
+ key = key.text
367
+ if key not in qnames:
368
+ add_qname(key)
369
+ if isinstance(value, QName) and value.text not in qnames:
370
+ add_qname(value.text)
371
+ text = elem.text
372
+ if isinstance(text, QName) and text.text not in qnames:
373
+ add_qname(text.text)
374
+ return qnames, namespaces
375
+
376
+ def _serialize_xml(write, elem, qnames, namespaces, **kwargs):
377
+ tag = elem.tag
378
+ text = elem.text
379
+ if tag is Comment:
380
+ write("<!--%s-->" % _tounicode(text))
381
+ elif tag is ProcessingInstruction:
382
+ write("<?%s?>" % _tounicode(text))
383
+ else:
384
+ tag = qnames[_tounicode(tag) if tag is not None else None]
385
+ if tag is None:
386
+ if text:
387
+ write(_escape_cdata(text))
388
+ for e in elem:
389
+ _serialize_xml(write, e, qnames, None)
390
+ else:
391
+ write("<" + tag)
392
+ if namespaces:
393
+ for uri, prefix in sorted(
394
+ namespaces.items(), key=lambda x: x[1]
395
+ ): # sort on prefix
396
+ if prefix:
397
+ prefix = ":" + prefix
398
+ write(' xmlns%s="%s"' % (prefix, _escape_attrib(uri)))
399
+ attrs = elem.attrib
400
+ if attrs:
401
+ # try to keep existing attrib order
402
+ if len(attrs) <= 1 or type(attrs) is _Attrib:
403
+ items = attrs.items()
404
+ else:
405
+ # if plain dict, use lexical order
406
+ items = sorted(attrs.items())
407
+ for k, v in items:
408
+ if isinstance(k, QName):
409
+ k = _tounicode(k.text)
410
+ else:
411
+ k = _tounicode(k)
412
+ if isinstance(v, QName):
413
+ v = qnames[_tounicode(v.text)]
414
+ else:
415
+ v = _escape_attrib(v)
416
+ write(' %s="%s"' % (qnames[k], v))
417
+ if text is not None or len(elem):
418
+ write(">")
419
+ if text:
420
+ write(_escape_cdata(text))
421
+ for e in elem:
422
+ _serialize_xml(write, e, qnames, None)
423
+ write("</" + tag + ">")
424
+ else:
425
+ write("/>")
426
+ if elem.tail:
427
+ write(_escape_cdata(elem.tail))
428
+
429
+ def _raise_serialization_error(text):
430
+ raise TypeError("cannot serialize %r (type %s)" % (text, type(text).__name__))
431
+
432
+ def _escape_cdata(text):
433
+ # escape character data
434
+ try:
435
+ text = _tounicode(text)
436
+ # it's worth avoiding do-nothing calls for short strings
437
+ if "&" in text:
438
+ text = text.replace("&", "&amp;")
439
+ if "<" in text:
440
+ text = text.replace("<", "&lt;")
441
+ if ">" in text:
442
+ text = text.replace(">", "&gt;")
443
+ return text
444
+ except (TypeError, AttributeError):
445
+ _raise_serialization_error(text)
446
+
447
+ def _escape_attrib(text):
448
+ # escape attribute value
449
+ try:
450
+ text = _tounicode(text)
451
+ if "&" in text:
452
+ text = text.replace("&", "&amp;")
453
+ if "<" in text:
454
+ text = text.replace("<", "&lt;")
455
+ if ">" in text:
456
+ text = text.replace(">", "&gt;")
457
+ if '"' in text:
458
+ text = text.replace('"', "&quot;")
459
+ if "\n" in text:
460
+ text = text.replace("\n", "&#10;")
461
+ return text
462
+ except (TypeError, AttributeError):
463
+ _raise_serialization_error(text)
464
+
465
+ def _indent(elem, level=0):
466
+ # From http://effbot.org/zone/element-lib.htm#prettyprint
467
+ i = "\n" + level * " "
468
+ if len(elem):
469
+ if not elem.text or not elem.text.strip():
470
+ elem.text = i + " "
471
+ if not elem.tail or not elem.tail.strip():
472
+ elem.tail = i
473
+ for elem in elem:
474
+ _indent(elem, level + 1)
475
+ if not elem.tail or not elem.tail.strip():
476
+ elem.tail = i
477
+ else:
478
+ if level and (not elem.tail or not elem.tail.strip()):
479
+ elem.tail = i
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/filenames.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This module implements the algorithm for converting between a "user name" -
3
+ something that a user can choose arbitrarily inside a font editor - and a file
4
+ name suitable for use in a wide range of operating systems and filesystems.
5
+
6
+ The `UFO 3 specification <http://unifiedfontobject.org/versions/ufo3/conventions/>`_
7
+ provides an example of an algorithm for such conversion, which avoids illegal
8
+ characters, reserved file names, ambiguity between upper- and lower-case
9
+ characters, and clashes with existing files.
10
+
11
+ This code was originally copied from
12
+ `ufoLib <https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py>`_
13
+ by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:
14
+
15
+ - Erik van Blokland
16
+ - Tal Leming
17
+ - Just van Rossum
18
+ """
19
+
20
+ illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ")
21
+ illegalCharacters += [chr(i) for i in range(1, 32)]
22
+ illegalCharacters += [chr(0x7F)]
23
+ reservedFileNames = "CON PRN AUX CLOCK$ NUL A:-Z: COM1".lower().split(" ")
24
+ reservedFileNames += "LPT1 LPT2 LPT3 COM2 COM3 COM4".lower().split(" ")
25
+ maxFileNameLength = 255
26
+
27
+
28
+ class NameTranslationError(Exception):
29
+ pass
30
+
31
+
32
+ def userNameToFileName(userName, existing=[], prefix="", suffix=""):
33
+ """Converts from a user name to a file name.
34
+
35
+ Takes care to avoid illegal characters, reserved file names, ambiguity between
36
+ upper- and lower-case characters, and clashes with existing files.
37
+
38
+ Args:
39
+ userName (str): The input file name.
40
+ existing: A case-insensitive list of all existing file names.
41
+ prefix: Prefix to be prepended to the file name.
42
+ suffix: Suffix to be appended to the file name.
43
+
44
+ Returns:
45
+ A suitable filename.
46
+
47
+ Raises:
48
+ NameTranslationError: If no suitable name could be generated.
49
+
50
+ Examples::
51
+
52
+ >>> userNameToFileName("a") == "a"
53
+ True
54
+ >>> userNameToFileName("A") == "A_"
55
+ True
56
+ >>> userNameToFileName("AE") == "A_E_"
57
+ True
58
+ >>> userNameToFileName("Ae") == "A_e"
59
+ True
60
+ >>> userNameToFileName("ae") == "ae"
61
+ True
62
+ >>> userNameToFileName("aE") == "aE_"
63
+ True
64
+ >>> userNameToFileName("a.alt") == "a.alt"
65
+ True
66
+ >>> userNameToFileName("A.alt") == "A_.alt"
67
+ True
68
+ >>> userNameToFileName("A.Alt") == "A_.A_lt"
69
+ True
70
+ >>> userNameToFileName("A.aLt") == "A_.aL_t"
71
+ True
72
+ >>> userNameToFileName(u"A.alT") == "A_.alT_"
73
+ True
74
+ >>> userNameToFileName("T_H") == "T__H_"
75
+ True
76
+ >>> userNameToFileName("T_h") == "T__h"
77
+ True
78
+ >>> userNameToFileName("t_h") == "t_h"
79
+ True
80
+ >>> userNameToFileName("F_F_I") == "F__F__I_"
81
+ True
82
+ >>> userNameToFileName("f_f_i") == "f_f_i"
83
+ True
84
+ >>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
85
+ True
86
+ >>> userNameToFileName(".notdef") == "_notdef"
87
+ True
88
+ >>> userNameToFileName("con") == "_con"
89
+ True
90
+ >>> userNameToFileName("CON") == "C_O_N_"
91
+ True
92
+ >>> userNameToFileName("con.alt") == "_con.alt"
93
+ True
94
+ >>> userNameToFileName("alt.con") == "alt._con"
95
+ True
96
+ """
97
+ # the incoming name must be a str
98
+ if not isinstance(userName, str):
99
+ raise ValueError("The value for userName must be a string.")
100
+ # establish the prefix and suffix lengths
101
+ prefixLength = len(prefix)
102
+ suffixLength = len(suffix)
103
+ # replace an initial period with an _
104
+ # if no prefix is to be added
105
+ if not prefix and userName[0] == ".":
106
+ userName = "_" + userName[1:]
107
+ # filter the user name
108
+ filteredUserName = []
109
+ for character in userName:
110
+ # replace illegal characters with _
111
+ if character in illegalCharacters:
112
+ character = "_"
113
+ # add _ to all non-lower characters
114
+ elif character != character.lower():
115
+ character += "_"
116
+ filteredUserName.append(character)
117
+ userName = "".join(filteredUserName)
118
+ # clip to 255
119
+ sliceLength = maxFileNameLength - prefixLength - suffixLength
120
+ userName = userName[:sliceLength]
121
+ # test for illegal files names
122
+ parts = []
123
+ for part in userName.split("."):
124
+ if part.lower() in reservedFileNames:
125
+ part = "_" + part
126
+ parts.append(part)
127
+ userName = ".".join(parts)
128
+ # test for clash
129
+ fullName = prefix + userName + suffix
130
+ if fullName.lower() in existing:
131
+ fullName = handleClash1(userName, existing, prefix, suffix)
132
+ # finished
133
+ return fullName
134
+
135
+
136
+ def handleClash1(userName, existing=[], prefix="", suffix=""):
137
+ """
138
+ existing should be a case-insensitive list
139
+ of all existing file names.
140
+
141
+ >>> prefix = ("0" * 5) + "."
142
+ >>> suffix = "." + ("0" * 10)
143
+ >>> existing = ["a" * 5]
144
+
145
+ >>> e = list(existing)
146
+ >>> handleClash1(userName="A" * 5, existing=e,
147
+ ... prefix=prefix, suffix=suffix) == (
148
+ ... '00000.AAAAA000000000000001.0000000000')
149
+ True
150
+
151
+ >>> e = list(existing)
152
+ >>> e.append(prefix + "aaaaa" + "1".zfill(15) + suffix)
153
+ >>> handleClash1(userName="A" * 5, existing=e,
154
+ ... prefix=prefix, suffix=suffix) == (
155
+ ... '00000.AAAAA000000000000002.0000000000')
156
+ True
157
+
158
+ >>> e = list(existing)
159
+ >>> e.append(prefix + "AAAAA" + "2".zfill(15) + suffix)
160
+ >>> handleClash1(userName="A" * 5, existing=e,
161
+ ... prefix=prefix, suffix=suffix) == (
162
+ ... '00000.AAAAA000000000000001.0000000000')
163
+ True
164
+ """
165
+ # if the prefix length + user name length + suffix length + 15 is at
166
+ # or past the maximum length, silce 15 characters off of the user name
167
+ prefixLength = len(prefix)
168
+ suffixLength = len(suffix)
169
+ if prefixLength + len(userName) + suffixLength + 15 > maxFileNameLength:
170
+ l = prefixLength + len(userName) + suffixLength + 15
171
+ sliceLength = maxFileNameLength - l
172
+ userName = userName[:sliceLength]
173
+ finalName = None
174
+ # try to add numbers to create a unique name
175
+ counter = 1
176
+ while finalName is None:
177
+ name = userName + str(counter).zfill(15)
178
+ fullName = prefix + name + suffix
179
+ if fullName.lower() not in existing:
180
+ finalName = fullName
181
+ break
182
+ else:
183
+ counter += 1
184
+ if counter >= 999999999999999:
185
+ break
186
+ # if there is a clash, go to the next fallback
187
+ if finalName is None:
188
+ finalName = handleClash2(existing, prefix, suffix)
189
+ # finished
190
+ return finalName
191
+
192
+
193
+ def handleClash2(existing=[], prefix="", suffix=""):
194
+ """
195
+ existing should be a case-insensitive list
196
+ of all existing file names.
197
+
198
+ >>> prefix = ("0" * 5) + "."
199
+ >>> suffix = "." + ("0" * 10)
200
+ >>> existing = [prefix + str(i) + suffix for i in range(100)]
201
+
202
+ >>> e = list(existing)
203
+ >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
204
+ ... '00000.100.0000000000')
205
+ True
206
+
207
+ >>> e = list(existing)
208
+ >>> e.remove(prefix + "1" + suffix)
209
+ >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
210
+ ... '00000.1.0000000000')
211
+ True
212
+
213
+ >>> e = list(existing)
214
+ >>> e.remove(prefix + "2" + suffix)
215
+ >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
216
+ ... '00000.2.0000000000')
217
+ True
218
+ """
219
+ # calculate the longest possible string
220
+ maxLength = maxFileNameLength - len(prefix) - len(suffix)
221
+ maxValue = int("9" * maxLength)
222
+ # try to find a number
223
+ finalName = None
224
+ counter = 1
225
+ while finalName is None:
226
+ fullName = prefix + str(counter) + suffix
227
+ if fullName.lower() not in existing:
228
+ finalName = fullName
229
+ break
230
+ else:
231
+ counter += 1
232
+ if counter >= maxValue:
233
+ break
234
+ # raise an error if nothing has been found
235
+ if finalName is None:
236
+ raise NameTranslationError("No unique name could be found.")
237
+ # finished
238
+ return finalName
239
+
240
+
241
+ if __name__ == "__main__":
242
+ import doctest
243
+ import sys
244
+
245
+ sys.exit(doctest.testmod().failed)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/fixedTools.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ The `OpenType specification <https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types>`_
3
+ defines two fixed-point data types:
4
+
5
+ ``Fixed``
6
+ A 32-bit signed fixed-point number with a 16 bit twos-complement
7
+ magnitude component and 16 fractional bits.
8
+ ``F2DOT14``
9
+ A 16-bit signed fixed-point number with a 2 bit twos-complement
10
+ magnitude component and 14 fractional bits.
11
+
12
+ To support reading and writing data with these data types, this module provides
13
+ functions for converting between fixed-point, float and string representations.
14
+
15
+ .. data:: MAX_F2DOT14
16
+
17
+ The maximum value that can still fit in an F2Dot14. (1.99993896484375)
18
+ """
19
+
20
+ from .roundTools import otRound, nearestMultipleShortestRepr
21
+ import logging
22
+
23
+ log = logging.getLogger(__name__)
24
+
25
+ __all__ = [
26
+ "MAX_F2DOT14",
27
+ "fixedToFloat",
28
+ "floatToFixed",
29
+ "floatToFixedToFloat",
30
+ "floatToFixedToStr",
31
+ "fixedToStr",
32
+ "strToFixed",
33
+ "strToFixedToFloat",
34
+ "ensureVersionIsLong",
35
+ "versionToFixed",
36
+ ]
37
+
38
+
39
+ MAX_F2DOT14 = 0x7FFF / (1 << 14)
40
+
41
+
42
+ def fixedToFloat(value, precisionBits):
43
+ """Converts a fixed-point number to a float given the number of
44
+ precision bits.
45
+
46
+ Args:
47
+ value (int): Number in fixed-point format.
48
+ precisionBits (int): Number of precision bits.
49
+
50
+ Returns:
51
+ Floating point value.
52
+
53
+ Examples::
54
+
55
+ >>> import math
56
+ >>> f = fixedToFloat(-10139, precisionBits=14)
57
+ >>> math.isclose(f, -0.61883544921875)
58
+ True
59
+ """
60
+ return value / (1 << precisionBits)
61
+
62
+
63
+ def floatToFixed(value, precisionBits):
64
+ """Converts a float to a fixed-point number given the number of
65
+ precision bits.
66
+
67
+ Args:
68
+ value (float): Floating point value.
69
+ precisionBits (int): Number of precision bits.
70
+
71
+ Returns:
72
+ int: Fixed-point representation.
73
+
74
+ Examples::
75
+
76
+ >>> floatToFixed(-0.61883544921875, precisionBits=14)
77
+ -10139
78
+ >>> floatToFixed(-0.61884, precisionBits=14)
79
+ -10139
80
+ """
81
+ return otRound(value * (1 << precisionBits))
82
+
83
+
84
+ def floatToFixedToFloat(value, precisionBits):
85
+ """Converts a float to a fixed-point number and back again.
86
+
87
+ By converting the float to fixed, rounding it, and converting it back
88
+ to float again, this returns a floating point values which is exactly
89
+ representable in fixed-point format.
90
+
91
+ Note: this **is** equivalent to ``fixedToFloat(floatToFixed(value))``.
92
+
93
+ Args:
94
+ value (float): The input floating point value.
95
+ precisionBits (int): Number of precision bits.
96
+
97
+ Returns:
98
+ float: The transformed and rounded value.
99
+
100
+ Examples::
101
+ >>> import math
102
+ >>> f1 = -0.61884
103
+ >>> f2 = floatToFixedToFloat(-0.61884, precisionBits=14)
104
+ >>> f1 != f2
105
+ True
106
+ >>> math.isclose(f2, -0.61883544921875)
107
+ True
108
+ """
109
+ scale = 1 << precisionBits
110
+ return otRound(value * scale) / scale
111
+
112
+
113
+ def fixedToStr(value, precisionBits):
114
+ """Converts a fixed-point number to a string representing a decimal float.
115
+
116
+ This chooses the float that has the shortest decimal representation (the least
117
+ number of fractional decimal digits).
118
+
119
+ For example, to convert a fixed-point number in a 2.14 format, use
120
+ ``precisionBits=14``::
121
+
122
+ >>> fixedToStr(-10139, precisionBits=14)
123
+ '-0.61884'
124
+
125
+ This is pretty slow compared to the simple division used in ``fixedToFloat``.
126
+ Use sporadically when you need to serialize or print the fixed-point number in
127
+ a human-readable form.
128
+ It uses nearestMultipleShortestRepr under the hood.
129
+
130
+ Args:
131
+ value (int): The fixed-point value to convert.
132
+ precisionBits (int): Number of precision bits, *up to a maximum of 16*.
133
+
134
+ Returns:
135
+ str: A string representation of the value.
136
+ """
137
+ scale = 1 << precisionBits
138
+ return nearestMultipleShortestRepr(value / scale, factor=1.0 / scale)
139
+
140
+
141
+ def strToFixed(string, precisionBits):
142
+ """Converts a string representing a decimal float to a fixed-point number.
143
+
144
+ Args:
145
+ string (str): A string representing a decimal float.
146
+ precisionBits (int): Number of precision bits, *up to a maximum of 16*.
147
+
148
+ Returns:
149
+ int: Fixed-point representation.
150
+
151
+ Examples::
152
+
153
+ >>> ## to convert a float string to a 2.14 fixed-point number:
154
+ >>> strToFixed('-0.61884', precisionBits=14)
155
+ -10139
156
+ """
157
+ value = float(string)
158
+ return otRound(value * (1 << precisionBits))
159
+
160
+
161
+ def strToFixedToFloat(string, precisionBits):
162
+ """Convert a string to a decimal float with fixed-point rounding.
163
+
164
+ This first converts string to a float, then turns it into a fixed-point
165
+ number with ``precisionBits`` fractional binary digits, then back to a
166
+ float again.
167
+
168
+ This is simply a shorthand for fixedToFloat(floatToFixed(float(s))).
169
+
170
+ Args:
171
+ string (str): A string representing a decimal float.
172
+ precisionBits (int): Number of precision bits.
173
+
174
+ Returns:
175
+ float: The transformed and rounded value.
176
+
177
+ Examples::
178
+
179
+ >>> import math
180
+ >>> s = '-0.61884'
181
+ >>> bits = 14
182
+ >>> f = strToFixedToFloat(s, precisionBits=bits)
183
+ >>> math.isclose(f, -0.61883544921875)
184
+ True
185
+ >>> f == fixedToFloat(floatToFixed(float(s), precisionBits=bits), precisionBits=bits)
186
+ True
187
+ """
188
+ value = float(string)
189
+ scale = 1 << precisionBits
190
+ return otRound(value * scale) / scale
191
+
192
+
193
+ def floatToFixedToStr(value, precisionBits):
194
+ """Convert float to string with fixed-point rounding.
195
+
196
+ This uses the shortest decimal representation (ie. the least
197
+ number of fractional decimal digits) to represent the equivalent
198
+ fixed-point number with ``precisionBits`` fractional binary digits.
199
+ It uses nearestMultipleShortestRepr under the hood.
200
+
201
+ >>> floatToFixedToStr(-0.61883544921875, precisionBits=14)
202
+ '-0.61884'
203
+
204
+ Args:
205
+ value (float): The float value to convert.
206
+ precisionBits (int): Number of precision bits, *up to a maximum of 16*.
207
+
208
+ Returns:
209
+ str: A string representation of the value.
210
+
211
+ """
212
+ scale = 1 << precisionBits
213
+ return nearestMultipleShortestRepr(value, factor=1.0 / scale)
214
+
215
+
216
+ def ensureVersionIsLong(value):
217
+ """Ensure a table version is an unsigned long.
218
+
219
+ OpenType table version numbers are expressed as a single unsigned long
220
+ comprising of an unsigned short major version and unsigned short minor
221
+ version. This function detects if the value to be used as a version number
222
+ looks too small (i.e. is less than ``0x10000``), and converts it to
223
+ fixed-point using :func:`floatToFixed` if so.
224
+
225
+ Args:
226
+ value (Number): a candidate table version number.
227
+
228
+ Returns:
229
+ int: A table version number, possibly corrected to fixed-point.
230
+ """
231
+ if value < 0x10000:
232
+ newValue = floatToFixed(value, 16)
233
+ log.warning(
234
+ "Table version value is a float: %.4f; " "fix to use hex instead: 0x%08x",
235
+ value,
236
+ newValue,
237
+ )
238
+ value = newValue
239
+ return value
240
+
241
+
242
+ def versionToFixed(value):
243
+ """Ensure a table version number is fixed-point.
244
+
245
+ Args:
246
+ value (str): a candidate table version number.
247
+
248
+ Returns:
249
+ int: A table version number, possibly corrected to fixed-point.
250
+ """
251
+ value = int(value, 0) if value.startswith("0") else float(value)
252
+ value = ensureVersionIsLong(value)
253
+ return value
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/intTools.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __all__ = ["popCount", "bit_count", "bit_indices"]
2
+
3
+
4
+ try:
5
+ bit_count = int.bit_count
6
+ except AttributeError:
7
+
8
+ def bit_count(v):
9
+ return bin(v).count("1")
10
+
11
+
12
+ """Return number of 1 bits (population count) of the absolute value of an integer.
13
+
14
+ See https://docs.python.org/3.10/library/stdtypes.html#int.bit_count
15
+ """
16
+ popCount = bit_count # alias
17
+
18
+
19
+ def bit_indices(v):
20
+ """Return list of indices where bits are set, 0 being the index of the least significant bit.
21
+
22
+ >>> bit_indices(0b101)
23
+ [0, 2]
24
+ """
25
+ return [i for i, b in enumerate(bin(v)[::-1]) if b == "1"]
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/iterTools.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import *
2
+
3
+ # Python 3.12:
4
+ if "batched" not in globals():
5
+ # https://docs.python.org/3/library/itertools.html#itertools.batched
6
+ def batched(iterable, n):
7
+ # batched('ABCDEFG', 3) --> ABC DEF G
8
+ if n < 1:
9
+ raise ValueError("n must be at least one")
10
+ it = iter(iterable)
11
+ while batch := tuple(islice(it, n)):
12
+ yield batch
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/loggingTools.py ADDED
@@ -0,0 +1,543 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import logging
3
+ import timeit
4
+ from functools import wraps
5
+ from collections.abc import Mapping, Callable
6
+ import warnings
7
+ from logging import PercentStyle
8
+
9
+
10
+ # default logging level used by Timer class
11
+ TIME_LEVEL = logging.DEBUG
12
+
13
+ # per-level format strings used by the default formatter
14
+ # (the level name is not printed for INFO and DEBUG messages)
15
+ DEFAULT_FORMATS = {
16
+ "*": "%(levelname)s: %(message)s",
17
+ "INFO": "%(message)s",
18
+ "DEBUG": "%(message)s",
19
+ }
20
+
21
+
22
+ class LevelFormatter(logging.Formatter):
23
+ """Log formatter with level-specific formatting.
24
+
25
+ Formatter class which optionally takes a dict of logging levels to
26
+ format strings, allowing to customise the log records appearance for
27
+ specific levels.
28
+
29
+
30
+ Attributes:
31
+ fmt: A dictionary mapping logging levels to format strings.
32
+ The ``*`` key identifies the default format string.
33
+ datefmt: As per py:class:`logging.Formatter`
34
+ style: As per py:class:`logging.Formatter`
35
+
36
+ >>> import sys
37
+ >>> handler = logging.StreamHandler(sys.stdout)
38
+ >>> formatter = LevelFormatter(
39
+ ... fmt={
40
+ ... '*': '[%(levelname)s] %(message)s',
41
+ ... 'DEBUG': '%(name)s [%(levelname)s] %(message)s',
42
+ ... 'INFO': '%(message)s',
43
+ ... })
44
+ >>> handler.setFormatter(formatter)
45
+ >>> log = logging.getLogger('test')
46
+ >>> log.setLevel(logging.DEBUG)
47
+ >>> log.addHandler(handler)
48
+ >>> log.debug('this uses a custom format string')
49
+ test [DEBUG] this uses a custom format string
50
+ >>> log.info('this also uses a custom format string')
51
+ this also uses a custom format string
52
+ >>> log.warning("this one uses the default format string")
53
+ [WARNING] this one uses the default format string
54
+ """
55
+
56
+ def __init__(self, fmt=None, datefmt=None, style="%"):
57
+ if style != "%":
58
+ raise ValueError(
59
+ "only '%' percent style is supported in both python 2 and 3"
60
+ )
61
+ if fmt is None:
62
+ fmt = DEFAULT_FORMATS
63
+ if isinstance(fmt, str):
64
+ default_format = fmt
65
+ custom_formats = {}
66
+ elif isinstance(fmt, Mapping):
67
+ custom_formats = dict(fmt)
68
+ default_format = custom_formats.pop("*", None)
69
+ else:
70
+ raise TypeError("fmt must be a str or a dict of str: %r" % fmt)
71
+ super(LevelFormatter, self).__init__(default_format, datefmt)
72
+ self.default_format = self._fmt
73
+ self.custom_formats = {}
74
+ for level, fmt in custom_formats.items():
75
+ level = logging._checkLevel(level)
76
+ self.custom_formats[level] = fmt
77
+
78
+ def format(self, record):
79
+ if self.custom_formats:
80
+ fmt = self.custom_formats.get(record.levelno, self.default_format)
81
+ if self._fmt != fmt:
82
+ self._fmt = fmt
83
+ # for python >= 3.2, _style needs to be set if _fmt changes
84
+ if PercentStyle:
85
+ self._style = PercentStyle(fmt)
86
+ return super(LevelFormatter, self).format(record)
87
+
88
+
89
+ def configLogger(**kwargs):
90
+ """A more sophisticated logging system configuation manager.
91
+
92
+ This is more or less the same as :py:func:`logging.basicConfig`,
93
+ with some additional options and defaults.
94
+
95
+ The default behaviour is to create a ``StreamHandler`` which writes to
96
+ sys.stderr, set a formatter using the ``DEFAULT_FORMATS`` strings, and add
97
+ the handler to the top-level library logger ("fontTools").
98
+
99
+ A number of optional keyword arguments may be specified, which can alter
100
+ the default behaviour.
101
+
102
+ Args:
103
+
104
+ logger: Specifies the logger name or a Logger instance to be
105
+ configured. (Defaults to "fontTools" logger). Unlike ``basicConfig``,
106
+ this function can be called multiple times to reconfigure a logger.
107
+ If the logger or any of its children already exists before the call is
108
+ made, they will be reset before the new configuration is applied.
109
+ filename: Specifies that a ``FileHandler`` be created, using the
110
+ specified filename, rather than a ``StreamHandler``.
111
+ filemode: Specifies the mode to open the file, if filename is
112
+ specified. (If filemode is unspecified, it defaults to ``a``).
113
+ format: Use the specified format string for the handler. This
114
+ argument also accepts a dictionary of format strings keyed by
115
+ level name, to allow customising the records appearance for
116
+ specific levels. The special ``'*'`` key is for 'any other' level.
117
+ datefmt: Use the specified date/time format.
118
+ level: Set the logger level to the specified level.
119
+ stream: Use the specified stream to initialize the StreamHandler. Note
120
+ that this argument is incompatible with ``filename`` - if both
121
+ are present, ``stream`` is ignored.
122
+ handlers: If specified, this should be an iterable of already created
123
+ handlers, which will be added to the logger. Any handler in the
124
+ list which does not have a formatter assigned will be assigned the
125
+ formatter created in this function.
126
+ filters: If specified, this should be an iterable of already created
127
+ filters. If the ``handlers`` do not already have filters assigned,
128
+ these filters will be added to them.
129
+ propagate: All loggers have a ``propagate`` attribute which determines
130
+ whether to continue searching for handlers up the logging hierarchy.
131
+ If not provided, the "propagate" attribute will be set to ``False``.
132
+ """
133
+ # using kwargs to enforce keyword-only arguments in py2.
134
+ handlers = kwargs.pop("handlers", None)
135
+ if handlers is None:
136
+ if "stream" in kwargs and "filename" in kwargs:
137
+ raise ValueError(
138
+ "'stream' and 'filename' should not be " "specified together"
139
+ )
140
+ else:
141
+ if "stream" in kwargs or "filename" in kwargs:
142
+ raise ValueError(
143
+ "'stream' or 'filename' should not be "
144
+ "specified together with 'handlers'"
145
+ )
146
+ if handlers is None:
147
+ filename = kwargs.pop("filename", None)
148
+ mode = kwargs.pop("filemode", "a")
149
+ if filename:
150
+ h = logging.FileHandler(filename, mode)
151
+ else:
152
+ stream = kwargs.pop("stream", None)
153
+ h = logging.StreamHandler(stream)
154
+ handlers = [h]
155
+ # By default, the top-level library logger is configured.
156
+ logger = kwargs.pop("logger", "fontTools")
157
+ if not logger or isinstance(logger, str):
158
+ # empty "" or None means the 'root' logger
159
+ logger = logging.getLogger(logger)
160
+ # before (re)configuring, reset named logger and its children (if exist)
161
+ _resetExistingLoggers(parent=logger.name)
162
+ # use DEFAULT_FORMATS if 'format' is None
163
+ fs = kwargs.pop("format", None)
164
+ dfs = kwargs.pop("datefmt", None)
165
+ # XXX: '%' is the only format style supported on both py2 and 3
166
+ style = kwargs.pop("style", "%")
167
+ fmt = LevelFormatter(fs, dfs, style)
168
+ filters = kwargs.pop("filters", [])
169
+ for h in handlers:
170
+ if h.formatter is None:
171
+ h.setFormatter(fmt)
172
+ if not h.filters:
173
+ for f in filters:
174
+ h.addFilter(f)
175
+ logger.addHandler(h)
176
+ if logger.name != "root":
177
+ # stop searching up the hierarchy for handlers
178
+ logger.propagate = kwargs.pop("propagate", False)
179
+ # set a custom severity level
180
+ level = kwargs.pop("level", None)
181
+ if level is not None:
182
+ logger.setLevel(level)
183
+ if kwargs:
184
+ keys = ", ".join(kwargs.keys())
185
+ raise ValueError("Unrecognised argument(s): %s" % keys)
186
+
187
+
188
+ def _resetExistingLoggers(parent="root"):
189
+ """Reset the logger named 'parent' and all its children to their initial
190
+ state, if they already exist in the current configuration.
191
+ """
192
+ root = logging.root
193
+ # get sorted list of all existing loggers
194
+ existing = sorted(root.manager.loggerDict.keys())
195
+ if parent == "root":
196
+ # all the existing loggers are children of 'root'
197
+ loggers_to_reset = [parent] + existing
198
+ elif parent not in existing:
199
+ # nothing to do
200
+ return
201
+ elif parent in existing:
202
+ loggers_to_reset = [parent]
203
+ # collect children, starting with the entry after parent name
204
+ i = existing.index(parent) + 1
205
+ prefixed = parent + "."
206
+ pflen = len(prefixed)
207
+ num_existing = len(existing)
208
+ while i < num_existing:
209
+ if existing[i][:pflen] == prefixed:
210
+ loggers_to_reset.append(existing[i])
211
+ i += 1
212
+ for name in loggers_to_reset:
213
+ if name == "root":
214
+ root.setLevel(logging.WARNING)
215
+ for h in root.handlers[:]:
216
+ root.removeHandler(h)
217
+ for f in root.filters[:]:
218
+ root.removeFilters(f)
219
+ root.disabled = False
220
+ else:
221
+ logger = root.manager.loggerDict[name]
222
+ logger.level = logging.NOTSET
223
+ logger.handlers = []
224
+ logger.filters = []
225
+ logger.propagate = True
226
+ logger.disabled = False
227
+
228
+
229
+ class Timer(object):
230
+ """Keeps track of overall time and split/lap times.
231
+
232
+ >>> import time
233
+ >>> timer = Timer()
234
+ >>> time.sleep(0.01)
235
+ >>> print("First lap:", timer.split())
236
+ First lap: ...
237
+ >>> time.sleep(0.02)
238
+ >>> print("Second lap:", timer.split())
239
+ Second lap: ...
240
+ >>> print("Overall time:", timer.time())
241
+ Overall time: ...
242
+
243
+ Can be used as a context manager inside with-statements.
244
+
245
+ >>> with Timer() as t:
246
+ ... time.sleep(0.01)
247
+ >>> print("%0.3f seconds" % t.elapsed)
248
+ 0... seconds
249
+
250
+ If initialised with a logger, it can log the elapsed time automatically
251
+ upon exiting the with-statement.
252
+
253
+ >>> import logging
254
+ >>> log = logging.getLogger("my-fancy-timer-logger")
255
+ >>> configLogger(logger=log, level="DEBUG", format="%(message)s", stream=sys.stdout)
256
+ >>> with Timer(log, 'do something'):
257
+ ... time.sleep(0.01)
258
+ Took ... to do something
259
+
260
+ The same Timer instance, holding a reference to a logger, can be reused
261
+ in multiple with-statements, optionally with different messages or levels.
262
+
263
+ >>> timer = Timer(log)
264
+ >>> with timer():
265
+ ... time.sleep(0.01)
266
+ elapsed time: ...s
267
+ >>> with timer('redo it', level=logging.INFO):
268
+ ... time.sleep(0.02)
269
+ Took ... to redo it
270
+
271
+ It can also be used as a function decorator to log the time elapsed to run
272
+ the decorated function.
273
+
274
+ >>> @timer()
275
+ ... def test1():
276
+ ... time.sleep(0.01)
277
+ >>> @timer('run test 2', level=logging.INFO)
278
+ ... def test2():
279
+ ... time.sleep(0.02)
280
+ >>> test1()
281
+ Took ... to run 'test1'
282
+ >>> test2()
283
+ Took ... to run test 2
284
+ """
285
+
286
+ # timeit.default_timer choses the most accurate clock for each platform
287
+ _time = timeit.default_timer
288
+ default_msg = "elapsed time: %(time).3fs"
289
+ default_format = "Took %(time).3fs to %(msg)s"
290
+
291
+ def __init__(self, logger=None, msg=None, level=None, start=None):
292
+ self.reset(start)
293
+ if logger is None:
294
+ for arg in ("msg", "level"):
295
+ if locals().get(arg) is not None:
296
+ raise ValueError("'%s' can't be specified without a 'logger'" % arg)
297
+ self.logger = logger
298
+ self.level = level if level is not None else TIME_LEVEL
299
+ self.msg = msg
300
+
301
+ def reset(self, start=None):
302
+ """Reset timer to 'start_time' or the current time."""
303
+ if start is None:
304
+ self.start = self._time()
305
+ else:
306
+ self.start = start
307
+ self.last = self.start
308
+ self.elapsed = 0.0
309
+
310
+ def time(self):
311
+ """Return the overall time (in seconds) since the timer started."""
312
+ return self._time() - self.start
313
+
314
+ def split(self):
315
+ """Split and return the lap time (in seconds) in between splits."""
316
+ current = self._time()
317
+ self.elapsed = current - self.last
318
+ self.last = current
319
+ return self.elapsed
320
+
321
+ def formatTime(self, msg, time):
322
+ """Format 'time' value in 'msg' and return formatted string.
323
+ If 'msg' contains a '%(time)' format string, try to use that.
324
+ Otherwise, use the predefined 'default_format'.
325
+ If 'msg' is empty or None, fall back to 'default_msg'.
326
+ """
327
+ if not msg:
328
+ msg = self.default_msg
329
+ if msg.find("%(time)") < 0:
330
+ msg = self.default_format % {"msg": msg, "time": time}
331
+ else:
332
+ try:
333
+ msg = msg % {"time": time}
334
+ except (KeyError, ValueError):
335
+ pass # skip if the format string is malformed
336
+ return msg
337
+
338
+ def __enter__(self):
339
+ """Start a new lap"""
340
+ self.last = self._time()
341
+ self.elapsed = 0.0
342
+ return self
343
+
344
+ def __exit__(self, exc_type, exc_value, traceback):
345
+ """End the current lap. If timer has a logger, log the time elapsed,
346
+ using the format string in self.msg (or the default one).
347
+ """
348
+ time = self.split()
349
+ if self.logger is None or exc_type:
350
+ # if there's no logger attached, or if any exception occurred in
351
+ # the with-statement, exit without logging the time
352
+ return
353
+ message = self.formatTime(self.msg, time)
354
+ # Allow log handlers to see the individual parts to facilitate things
355
+ # like a server accumulating aggregate stats.
356
+ msg_parts = {"msg": self.msg, "time": time}
357
+ self.logger.log(self.level, message, msg_parts)
358
+
359
+ def __call__(self, func_or_msg=None, **kwargs):
360
+ """If the first argument is a function, return a decorator which runs
361
+ the wrapped function inside Timer's context manager.
362
+ Otherwise, treat the first argument as a 'msg' string and return an updated
363
+ Timer instance, referencing the same logger.
364
+ A 'level' keyword can also be passed to override self.level.
365
+ """
366
+ if isinstance(func_or_msg, Callable):
367
+ func = func_or_msg
368
+ # use the function name when no explicit 'msg' is provided
369
+ if not self.msg:
370
+ self.msg = "run '%s'" % func.__name__
371
+
372
+ @wraps(func)
373
+ def wrapper(*args, **kwds):
374
+ with self:
375
+ return func(*args, **kwds)
376
+
377
+ return wrapper
378
+ else:
379
+ msg = func_or_msg or kwargs.get("msg")
380
+ level = kwargs.get("level", self.level)
381
+ return self.__class__(self.logger, msg, level)
382
+
383
+ def __float__(self):
384
+ return self.elapsed
385
+
386
+ def __int__(self):
387
+ return int(self.elapsed)
388
+
389
+ def __str__(self):
390
+ return "%.3f" % self.elapsed
391
+
392
+
393
+ class ChannelsFilter(logging.Filter):
394
+ """Provides a hierarchical filter for log entries based on channel names.
395
+
396
+ Filters out records emitted from a list of enabled channel names,
397
+ including their children. It works the same as the ``logging.Filter``
398
+ class, but allows the user to specify multiple channel names.
399
+
400
+ >>> import sys
401
+ >>> handler = logging.StreamHandler(sys.stdout)
402
+ >>> handler.setFormatter(logging.Formatter("%(message)s"))
403
+ >>> filter = ChannelsFilter("A.B", "C.D")
404
+ >>> handler.addFilter(filter)
405
+ >>> root = logging.getLogger()
406
+ >>> root.addHandler(handler)
407
+ >>> root.setLevel(level=logging.DEBUG)
408
+ >>> logging.getLogger('A.B').debug('this record passes through')
409
+ this record passes through
410
+ >>> logging.getLogger('A.B.C').debug('records from children also pass')
411
+ records from children also pass
412
+ >>> logging.getLogger('C.D').debug('this one as well')
413
+ this one as well
414
+ >>> logging.getLogger('A.B.').debug('also this one')
415
+ also this one
416
+ >>> logging.getLogger('A.F').debug('but this one does not!')
417
+ >>> logging.getLogger('C.DE').debug('neither this one!')
418
+ """
419
+
420
+ def __init__(self, *names):
421
+ self.names = names
422
+ self.num = len(names)
423
+ self.lengths = {n: len(n) for n in names}
424
+
425
+ def filter(self, record):
426
+ if self.num == 0:
427
+ return True
428
+ for name in self.names:
429
+ nlen = self.lengths[name]
430
+ if name == record.name:
431
+ return True
432
+ elif record.name.find(name, 0, nlen) == 0 and record.name[nlen] == ".":
433
+ return True
434
+ return False
435
+
436
+
437
+ class CapturingLogHandler(logging.Handler):
438
+ def __init__(self, logger, level):
439
+ super(CapturingLogHandler, self).__init__(level=level)
440
+ self.records = []
441
+ if isinstance(logger, str):
442
+ self.logger = logging.getLogger(logger)
443
+ else:
444
+ self.logger = logger
445
+
446
+ def __enter__(self):
447
+ self.original_disabled = self.logger.disabled
448
+ self.original_level = self.logger.level
449
+ self.original_propagate = self.logger.propagate
450
+
451
+ self.logger.addHandler(self)
452
+ self.logger.setLevel(self.level)
453
+ self.logger.disabled = False
454
+ self.logger.propagate = False
455
+
456
+ return self
457
+
458
+ def __exit__(self, type, value, traceback):
459
+ self.logger.removeHandler(self)
460
+ self.logger.setLevel(self.original_level)
461
+ self.logger.disabled = self.original_disabled
462
+ self.logger.propagate = self.original_propagate
463
+
464
+ return self
465
+
466
+ def emit(self, record):
467
+ self.records.append(record)
468
+
469
+ def assertRegex(self, regexp, msg=None):
470
+ import re
471
+
472
+ pattern = re.compile(regexp)
473
+ for r in self.records:
474
+ if pattern.search(r.getMessage()):
475
+ return True
476
+ if msg is None:
477
+ msg = "Pattern '%s' not found in logger records" % regexp
478
+ assert 0, msg
479
+
480
+
481
+ class LogMixin(object):
482
+ """Mixin class that adds logging functionality to another class.
483
+
484
+ You can define a new class that subclasses from ``LogMixin`` as well as
485
+ other base classes through multiple inheritance.
486
+ All instances of that class will have a ``log`` property that returns
487
+ a ``logging.Logger`` named after their respective ``<module>.<class>``.
488
+
489
+ For example:
490
+
491
+ >>> class BaseClass(object):
492
+ ... pass
493
+ >>> class MyClass(LogMixin, BaseClass):
494
+ ... pass
495
+ >>> a = MyClass()
496
+ >>> isinstance(a.log, logging.Logger)
497
+ True
498
+ >>> print(a.log.name)
499
+ fontTools.misc.loggingTools.MyClass
500
+ >>> class AnotherClass(MyClass):
501
+ ... pass
502
+ >>> b = AnotherClass()
503
+ >>> isinstance(b.log, logging.Logger)
504
+ True
505
+ >>> print(b.log.name)
506
+ fontTools.misc.loggingTools.AnotherClass
507
+ """
508
+
509
+ @property
510
+ def log(self):
511
+ if not hasattr(self, "_log"):
512
+ name = ".".join((self.__class__.__module__, self.__class__.__name__))
513
+ self._log = logging.getLogger(name)
514
+ return self._log
515
+
516
+
517
+ def deprecateArgument(name, msg, category=UserWarning):
518
+ """Raise a warning about deprecated function argument 'name'."""
519
+ warnings.warn("%r is deprecated; %s" % (name, msg), category=category, stacklevel=3)
520
+
521
+
522
+ def deprecateFunction(msg, category=UserWarning):
523
+ """Decorator to raise a warning when a deprecated function is called."""
524
+
525
+ def decorator(func):
526
+ @wraps(func)
527
+ def wrapper(*args, **kwargs):
528
+ warnings.warn(
529
+ "%r is deprecated; %s" % (func.__name__, msg),
530
+ category=category,
531
+ stacklevel=2,
532
+ )
533
+ return func(*args, **kwargs)
534
+
535
+ return wrapper
536
+
537
+ return decorator
538
+
539
+
540
+ if __name__ == "__main__":
541
+ import doctest
542
+
543
+ sys.exit(doctest.testmod(optionflags=doctest.ELLIPSIS).failed)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/macCreatorType.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc.textTools import Tag, bytesjoin, strjoin
2
+
3
+ try:
4
+ import xattr
5
+ except ImportError:
6
+ xattr = None
7
+
8
+
9
+ def _reverseString(s):
10
+ s = list(s)
11
+ s.reverse()
12
+ return strjoin(s)
13
+
14
+
15
+ def getMacCreatorAndType(path):
16
+ """Returns file creator and file type codes for a path.
17
+
18
+ Args:
19
+ path (str): A file path.
20
+
21
+ Returns:
22
+ A tuple of two :py:class:`fontTools.textTools.Tag` objects, the first
23
+ representing the file creator and the second representing the
24
+ file type.
25
+ """
26
+ if xattr is not None:
27
+ try:
28
+ finderInfo = xattr.getxattr(path, "com.apple.FinderInfo")
29
+ except (KeyError, IOError):
30
+ pass
31
+ else:
32
+ fileType = Tag(finderInfo[:4])
33
+ fileCreator = Tag(finderInfo[4:8])
34
+ return fileCreator, fileType
35
+ return None, None
36
+
37
+
38
+ def setMacCreatorAndType(path, fileCreator, fileType):
39
+ """Set file creator and file type codes for a path.
40
+
41
+ Note that if the ``xattr`` module is not installed, no action is
42
+ taken but no error is raised.
43
+
44
+ Args:
45
+ path (str): A file path.
46
+ fileCreator: A four-character file creator tag.
47
+ fileType: A four-character file type tag.
48
+
49
+ """
50
+ if xattr is not None:
51
+ from fontTools.misc.textTools import pad
52
+
53
+ if not all(len(s) == 4 for s in (fileCreator, fileType)):
54
+ raise TypeError("arg must be string of 4 chars")
55
+ finderInfo = pad(bytesjoin([fileType, fileCreator]), 32)
56
+ xattr.setxattr(path, "com.apple.FinderInfo", finderInfo)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/macRes.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from io import BytesIO
2
+ import struct
3
+ from fontTools.misc import sstruct
4
+ from fontTools.misc.textTools import bytesjoin, tostr
5
+ from collections import OrderedDict
6
+ from collections.abc import MutableMapping
7
+
8
+
9
+ class ResourceError(Exception):
10
+ pass
11
+
12
+
13
+ class ResourceReader(MutableMapping):
14
+ """Reader for Mac OS resource forks.
15
+
16
+ Parses a resource fork and returns resources according to their type.
17
+ If run on OS X, this will open the resource fork in the filesystem.
18
+ Otherwise, it will open the file itself and attempt to read it as
19
+ though it were a resource fork.
20
+
21
+ The returned object can be indexed by type and iterated over,
22
+ returning in each case a list of py:class:`Resource` objects
23
+ representing all the resources of a certain type.
24
+
25
+ """
26
+
27
+ def __init__(self, fileOrPath):
28
+ """Open a file
29
+
30
+ Args:
31
+ fileOrPath: Either an object supporting a ``read`` method, an
32
+ ``os.PathLike`` object, or a string.
33
+ """
34
+ self._resources = OrderedDict()
35
+ if hasattr(fileOrPath, "read"):
36
+ self.file = fileOrPath
37
+ else:
38
+ try:
39
+ # try reading from the resource fork (only works on OS X)
40
+ self.file = self.openResourceFork(fileOrPath)
41
+ self._readFile()
42
+ return
43
+ except (ResourceError, IOError):
44
+ # if it fails, use the data fork
45
+ self.file = self.openDataFork(fileOrPath)
46
+ self._readFile()
47
+
48
+ @staticmethod
49
+ def openResourceFork(path):
50
+ if hasattr(path, "__fspath__"): # support os.PathLike objects
51
+ path = path.__fspath__()
52
+ with open(path + "/..namedfork/rsrc", "rb") as resfork:
53
+ data = resfork.read()
54
+ infile = BytesIO(data)
55
+ infile.name = path
56
+ return infile
57
+
58
+ @staticmethod
59
+ def openDataFork(path):
60
+ with open(path, "rb") as datafork:
61
+ data = datafork.read()
62
+ infile = BytesIO(data)
63
+ infile.name = path
64
+ return infile
65
+
66
+ def _readFile(self):
67
+ self._readHeaderAndMap()
68
+ self._readTypeList()
69
+
70
+ def _read(self, numBytes, offset=None):
71
+ if offset is not None:
72
+ try:
73
+ self.file.seek(offset)
74
+ except OverflowError:
75
+ raise ResourceError("Failed to seek offset ('offset' is too large)")
76
+ if self.file.tell() != offset:
77
+ raise ResourceError("Failed to seek offset (reached EOF)")
78
+ try:
79
+ data = self.file.read(numBytes)
80
+ except OverflowError:
81
+ raise ResourceError("Cannot read resource ('numBytes' is too large)")
82
+ if len(data) != numBytes:
83
+ raise ResourceError("Cannot read resource (not enough data)")
84
+ return data
85
+
86
+ def _readHeaderAndMap(self):
87
+ self.file.seek(0)
88
+ headerData = self._read(ResourceForkHeaderSize)
89
+ sstruct.unpack(ResourceForkHeader, headerData, self)
90
+ # seek to resource map, skip reserved
91
+ mapOffset = self.mapOffset + 22
92
+ resourceMapData = self._read(ResourceMapHeaderSize, mapOffset)
93
+ sstruct.unpack(ResourceMapHeader, resourceMapData, self)
94
+ self.absTypeListOffset = self.mapOffset + self.typeListOffset
95
+ self.absNameListOffset = self.mapOffset + self.nameListOffset
96
+
97
+ def _readTypeList(self):
98
+ absTypeListOffset = self.absTypeListOffset
99
+ numTypesData = self._read(2, absTypeListOffset)
100
+ (self.numTypes,) = struct.unpack(">H", numTypesData)
101
+ absTypeListOffset2 = absTypeListOffset + 2
102
+ for i in range(self.numTypes + 1):
103
+ resTypeItemOffset = absTypeListOffset2 + ResourceTypeItemSize * i
104
+ resTypeItemData = self._read(ResourceTypeItemSize, resTypeItemOffset)
105
+ item = sstruct.unpack(ResourceTypeItem, resTypeItemData)
106
+ resType = tostr(item["type"], encoding="mac-roman")
107
+ refListOffset = absTypeListOffset + item["refListOffset"]
108
+ numRes = item["numRes"] + 1
109
+ resources = self._readReferenceList(resType, refListOffset, numRes)
110
+ self._resources[resType] = resources
111
+
112
+ def _readReferenceList(self, resType, refListOffset, numRes):
113
+ resources = []
114
+ for i in range(numRes):
115
+ refOffset = refListOffset + ResourceRefItemSize * i
116
+ refData = self._read(ResourceRefItemSize, refOffset)
117
+ res = Resource(resType)
118
+ res.decompile(refData, self)
119
+ resources.append(res)
120
+ return resources
121
+
122
+ def __getitem__(self, resType):
123
+ return self._resources[resType]
124
+
125
+ def __delitem__(self, resType):
126
+ del self._resources[resType]
127
+
128
+ def __setitem__(self, resType, resources):
129
+ self._resources[resType] = resources
130
+
131
+ def __len__(self):
132
+ return len(self._resources)
133
+
134
+ def __iter__(self):
135
+ return iter(self._resources)
136
+
137
+ def keys(self):
138
+ return self._resources.keys()
139
+
140
+ @property
141
+ def types(self):
142
+ """A list of the types of resources in the resource fork."""
143
+ return list(self._resources.keys())
144
+
145
+ def countResources(self, resType):
146
+ """Return the number of resources of a given type."""
147
+ try:
148
+ return len(self[resType])
149
+ except KeyError:
150
+ return 0
151
+
152
+ def getIndices(self, resType):
153
+ """Returns a list of indices of resources of a given type."""
154
+ numRes = self.countResources(resType)
155
+ if numRes:
156
+ return list(range(1, numRes + 1))
157
+ else:
158
+ return []
159
+
160
+ def getNames(self, resType):
161
+ """Return list of names of all resources of a given type."""
162
+ return [res.name for res in self.get(resType, []) if res.name is not None]
163
+
164
+ def getIndResource(self, resType, index):
165
+ """Return resource of given type located at an index ranging from 1
166
+ to the number of resources for that type, or None if not found.
167
+ """
168
+ if index < 1:
169
+ return None
170
+ try:
171
+ res = self[resType][index - 1]
172
+ except (KeyError, IndexError):
173
+ return None
174
+ return res
175
+
176
+ def getNamedResource(self, resType, name):
177
+ """Return the named resource of given type, else return None."""
178
+ name = tostr(name, encoding="mac-roman")
179
+ for res in self.get(resType, []):
180
+ if res.name == name:
181
+ return res
182
+ return None
183
+
184
+ def close(self):
185
+ if not self.file.closed:
186
+ self.file.close()
187
+
188
+
189
+ class Resource(object):
190
+ """Represents a resource stored within a resource fork.
191
+
192
+ Attributes:
193
+ type: resource type.
194
+ data: resource data.
195
+ id: ID.
196
+ name: resource name.
197
+ attr: attributes.
198
+ """
199
+
200
+ def __init__(
201
+ self, resType=None, resData=None, resID=None, resName=None, resAttr=None
202
+ ):
203
+ self.type = resType
204
+ self.data = resData
205
+ self.id = resID
206
+ self.name = resName
207
+ self.attr = resAttr
208
+
209
+ def decompile(self, refData, reader):
210
+ sstruct.unpack(ResourceRefItem, refData, self)
211
+ # interpret 3-byte dataOffset as (padded) ULONG to unpack it with struct
212
+ (self.dataOffset,) = struct.unpack(">L", bytesjoin([b"\0", self.dataOffset]))
213
+ absDataOffset = reader.dataOffset + self.dataOffset
214
+ (dataLength,) = struct.unpack(">L", reader._read(4, absDataOffset))
215
+ self.data = reader._read(dataLength)
216
+ if self.nameOffset == -1:
217
+ return
218
+ absNameOffset = reader.absNameListOffset + self.nameOffset
219
+ (nameLength,) = struct.unpack("B", reader._read(1, absNameOffset))
220
+ (name,) = struct.unpack(">%ss" % nameLength, reader._read(nameLength))
221
+ self.name = tostr(name, encoding="mac-roman")
222
+
223
+
224
+ ResourceForkHeader = """
225
+ > # big endian
226
+ dataOffset: L
227
+ mapOffset: L
228
+ dataLen: L
229
+ mapLen: L
230
+ """
231
+
232
+ ResourceForkHeaderSize = sstruct.calcsize(ResourceForkHeader)
233
+
234
+ ResourceMapHeader = """
235
+ > # big endian
236
+ attr: H
237
+ typeListOffset: H
238
+ nameListOffset: H
239
+ """
240
+
241
+ ResourceMapHeaderSize = sstruct.calcsize(ResourceMapHeader)
242
+
243
+ ResourceTypeItem = """
244
+ > # big endian
245
+ type: 4s
246
+ numRes: H
247
+ refListOffset: H
248
+ """
249
+
250
+ ResourceTypeItemSize = sstruct.calcsize(ResourceTypeItem)
251
+
252
+ ResourceRefItem = """
253
+ > # big endian
254
+ id: h
255
+ nameOffset: h
256
+ attr: B
257
+ dataOffset: 3s
258
+ reserved: L
259
+ """
260
+
261
+ ResourceRefItemSize = sstruct.calcsize(ResourceRefItem)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/psCharStrings.py ADDED
@@ -0,0 +1,1496 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """psCharStrings.py -- module implementing various kinds of CharStrings:
2
+ CFF dictionary data and Type1/Type2 CharStrings.
3
+ """
4
+
5
+ from fontTools.misc.fixedTools import (
6
+ fixedToFloat,
7
+ floatToFixed,
8
+ floatToFixedToStr,
9
+ strToFixedToFloat,
10
+ )
11
+ from fontTools.misc.textTools import bytechr, byteord, bytesjoin, strjoin
12
+ from fontTools.pens.boundsPen import BoundsPen
13
+ import struct
14
+ import logging
15
+
16
+
17
+ log = logging.getLogger(__name__)
18
+
19
+
20
+ def read_operator(self, b0, data, index):
21
+ if b0 == 12:
22
+ op = (b0, byteord(data[index]))
23
+ index = index + 1
24
+ else:
25
+ op = b0
26
+ try:
27
+ operator = self.operators[op]
28
+ except KeyError:
29
+ return None, index
30
+ value = self.handle_operator(operator)
31
+ return value, index
32
+
33
+
34
+ def read_byte(self, b0, data, index):
35
+ return b0 - 139, index
36
+
37
+
38
+ def read_smallInt1(self, b0, data, index):
39
+ b1 = byteord(data[index])
40
+ return (b0 - 247) * 256 + b1 + 108, index + 1
41
+
42
+
43
+ def read_smallInt2(self, b0, data, index):
44
+ b1 = byteord(data[index])
45
+ return -(b0 - 251) * 256 - b1 - 108, index + 1
46
+
47
+
48
+ def read_shortInt(self, b0, data, index):
49
+ (value,) = struct.unpack(">h", data[index : index + 2])
50
+ return value, index + 2
51
+
52
+
53
+ def read_longInt(self, b0, data, index):
54
+ (value,) = struct.unpack(">l", data[index : index + 4])
55
+ return value, index + 4
56
+
57
+
58
+ def read_fixed1616(self, b0, data, index):
59
+ (value,) = struct.unpack(">l", data[index : index + 4])
60
+ return fixedToFloat(value, precisionBits=16), index + 4
61
+
62
+
63
+ def read_reserved(self, b0, data, index):
64
+ assert NotImplementedError
65
+ return NotImplemented, index
66
+
67
+
68
+ def read_realNumber(self, b0, data, index):
69
+ number = ""
70
+ while True:
71
+ b = byteord(data[index])
72
+ index = index + 1
73
+ nibble0 = (b & 0xF0) >> 4
74
+ nibble1 = b & 0x0F
75
+ if nibble0 == 0xF:
76
+ break
77
+ number = number + realNibbles[nibble0]
78
+ if nibble1 == 0xF:
79
+ break
80
+ number = number + realNibbles[nibble1]
81
+ return float(number), index
82
+
83
+
84
+ t1OperandEncoding = [None] * 256
85
+ t1OperandEncoding[0:32] = (32) * [read_operator]
86
+ t1OperandEncoding[32:247] = (247 - 32) * [read_byte]
87
+ t1OperandEncoding[247:251] = (251 - 247) * [read_smallInt1]
88
+ t1OperandEncoding[251:255] = (255 - 251) * [read_smallInt2]
89
+ t1OperandEncoding[255] = read_longInt
90
+ assert len(t1OperandEncoding) == 256
91
+
92
+ t2OperandEncoding = t1OperandEncoding[:]
93
+ t2OperandEncoding[28] = read_shortInt
94
+ t2OperandEncoding[255] = read_fixed1616
95
+
96
+ cffDictOperandEncoding = t2OperandEncoding[:]
97
+ cffDictOperandEncoding[29] = read_longInt
98
+ cffDictOperandEncoding[30] = read_realNumber
99
+ cffDictOperandEncoding[255] = read_reserved
100
+
101
+
102
+ realNibbles = [
103
+ "0",
104
+ "1",
105
+ "2",
106
+ "3",
107
+ "4",
108
+ "5",
109
+ "6",
110
+ "7",
111
+ "8",
112
+ "9",
113
+ ".",
114
+ "E",
115
+ "E-",
116
+ None,
117
+ "-",
118
+ ]
119
+ realNibblesDict = {v: i for i, v in enumerate(realNibbles)}
120
+
121
+ maxOpStack = 193
122
+
123
+
124
+ def buildOperatorDict(operatorList):
125
+ oper = {}
126
+ opc = {}
127
+ for item in operatorList:
128
+ if len(item) == 2:
129
+ oper[item[0]] = item[1]
130
+ else:
131
+ oper[item[0]] = item[1:]
132
+ if isinstance(item[0], tuple):
133
+ opc[item[1]] = item[0]
134
+ else:
135
+ opc[item[1]] = (item[0],)
136
+ return oper, opc
137
+
138
+
139
+ t2Operators = [
140
+ # opcode name
141
+ (1, "hstem"),
142
+ (3, "vstem"),
143
+ (4, "vmoveto"),
144
+ (5, "rlineto"),
145
+ (6, "hlineto"),
146
+ (7, "vlineto"),
147
+ (8, "rrcurveto"),
148
+ (10, "callsubr"),
149
+ (11, "return"),
150
+ (14, "endchar"),
151
+ (15, "vsindex"),
152
+ (16, "blend"),
153
+ (18, "hstemhm"),
154
+ (19, "hintmask"),
155
+ (20, "cntrmask"),
156
+ (21, "rmoveto"),
157
+ (22, "hmoveto"),
158
+ (23, "vstemhm"),
159
+ (24, "rcurveline"),
160
+ (25, "rlinecurve"),
161
+ (26, "vvcurveto"),
162
+ (27, "hhcurveto"),
163
+ # (28, 'shortint'), # not really an operator
164
+ (29, "callgsubr"),
165
+ (30, "vhcurveto"),
166
+ (31, "hvcurveto"),
167
+ ((12, 0), "ignore"), # dotsection. Yes, there a few very early OTF/CFF
168
+ # fonts with this deprecated operator. Just ignore it.
169
+ ((12, 3), "and"),
170
+ ((12, 4), "or"),
171
+ ((12, 5), "not"),
172
+ ((12, 8), "store"),
173
+ ((12, 9), "abs"),
174
+ ((12, 10), "add"),
175
+ ((12, 11), "sub"),
176
+ ((12, 12), "div"),
177
+ ((12, 13), "load"),
178
+ ((12, 14), "neg"),
179
+ ((12, 15), "eq"),
180
+ ((12, 18), "drop"),
181
+ ((12, 20), "put"),
182
+ ((12, 21), "get"),
183
+ ((12, 22), "ifelse"),
184
+ ((12, 23), "random"),
185
+ ((12, 24), "mul"),
186
+ ((12, 26), "sqrt"),
187
+ ((12, 27), "dup"),
188
+ ((12, 28), "exch"),
189
+ ((12, 29), "index"),
190
+ ((12, 30), "roll"),
191
+ ((12, 34), "hflex"),
192
+ ((12, 35), "flex"),
193
+ ((12, 36), "hflex1"),
194
+ ((12, 37), "flex1"),
195
+ ]
196
+
197
+
198
+ def getIntEncoder(format):
199
+ if format == "cff":
200
+ twoByteOp = bytechr(28)
201
+ fourByteOp = bytechr(29)
202
+ elif format == "t1":
203
+ twoByteOp = None
204
+ fourByteOp = bytechr(255)
205
+ else:
206
+ assert format == "t2"
207
+ twoByteOp = bytechr(28)
208
+ fourByteOp = None
209
+
210
+ def encodeInt(
211
+ value,
212
+ fourByteOp=fourByteOp,
213
+ bytechr=bytechr,
214
+ pack=struct.pack,
215
+ unpack=struct.unpack,
216
+ twoByteOp=twoByteOp,
217
+ ):
218
+ if -107 <= value <= 107:
219
+ code = bytechr(value + 139)
220
+ elif 108 <= value <= 1131:
221
+ value = value - 108
222
+ code = bytechr((value >> 8) + 247) + bytechr(value & 0xFF)
223
+ elif -1131 <= value <= -108:
224
+ value = -value - 108
225
+ code = bytechr((value >> 8) + 251) + bytechr(value & 0xFF)
226
+ elif twoByteOp is not None and -32768 <= value <= 32767:
227
+ code = twoByteOp + pack(">h", value)
228
+ elif fourByteOp is None:
229
+ # Backwards compatible hack: due to a previous bug in FontTools,
230
+ # 16.16 fixed numbers were written out as 4-byte ints. When
231
+ # these numbers were small, they were wrongly written back as
232
+ # small ints instead of 4-byte ints, breaking round-tripping.
233
+ # This here workaround doesn't do it any better, since we can't
234
+ # distinguish anymore between small ints that were supposed to
235
+ # be small fixed numbers and small ints that were just small
236
+ # ints. Hence the warning.
237
+ log.warning(
238
+ "4-byte T2 number got passed to the "
239
+ "IntType handler. This should happen only when reading in "
240
+ "old XML files.\n"
241
+ )
242
+ code = bytechr(255) + pack(">l", value)
243
+ else:
244
+ code = fourByteOp + pack(">l", value)
245
+ return code
246
+
247
+ return encodeInt
248
+
249
+
250
+ encodeIntCFF = getIntEncoder("cff")
251
+ encodeIntT1 = getIntEncoder("t1")
252
+ encodeIntT2 = getIntEncoder("t2")
253
+
254
+
255
+ def encodeFixed(f, pack=struct.pack):
256
+ """For T2 only"""
257
+ value = floatToFixed(f, precisionBits=16)
258
+ if value & 0xFFFF == 0: # check if the fractional part is zero
259
+ return encodeIntT2(value >> 16) # encode only the integer part
260
+ else:
261
+ return b"\xff" + pack(">l", value) # encode the entire fixed point value
262
+
263
+
264
+ realZeroBytes = bytechr(30) + bytechr(0xF)
265
+
266
+
267
+ def encodeFloat(f):
268
+ # For CFF only, used in cffLib
269
+ if f == 0.0: # 0.0 == +0.0 == -0.0
270
+ return realZeroBytes
271
+ # Note: 14 decimal digits seems to be the limitation for CFF real numbers
272
+ # in macOS. However, we use 8 here to match the implementation of AFDKO.
273
+ s = "%.8G" % f
274
+ if s[:2] == "0.":
275
+ s = s[1:]
276
+ elif s[:3] == "-0.":
277
+ s = "-" + s[2:]
278
+ elif s.endswith("000"):
279
+ significantDigits = s.rstrip("0")
280
+ s = "%sE%d" % (significantDigits, len(s) - len(significantDigits))
281
+ else:
282
+ dotIndex = s.find(".")
283
+ eIndex = s.find("E")
284
+ if dotIndex != -1 and eIndex != -1:
285
+ integerPart = s[:dotIndex]
286
+ fractionalPart = s[dotIndex + 1 : eIndex]
287
+ exponent = int(s[eIndex + 1 :])
288
+ newExponent = exponent - len(fractionalPart)
289
+ if newExponent == 1:
290
+ s = "%s%s0" % (integerPart, fractionalPart)
291
+ else:
292
+ s = "%s%sE%d" % (integerPart, fractionalPart, newExponent)
293
+ if s.startswith((".0", "-.0")):
294
+ sign, s = s.split(".", 1)
295
+ s = "%s%sE-%d" % (sign, s.lstrip("0"), len(s))
296
+ nibbles = []
297
+ while s:
298
+ c = s[0]
299
+ s = s[1:]
300
+ if c == "E":
301
+ c2 = s[:1]
302
+ if c2 == "-":
303
+ s = s[1:]
304
+ c = "E-"
305
+ elif c2 == "+":
306
+ s = s[1:]
307
+ if s.startswith("0"):
308
+ s = s[1:]
309
+ nibbles.append(realNibblesDict[c])
310
+ nibbles.append(0xF)
311
+ if len(nibbles) % 2:
312
+ nibbles.append(0xF)
313
+ d = bytechr(30)
314
+ for i in range(0, len(nibbles), 2):
315
+ d = d + bytechr(nibbles[i] << 4 | nibbles[i + 1])
316
+ return d
317
+
318
+
319
+ class CharStringCompileError(Exception):
320
+ pass
321
+
322
+
323
+ class SimpleT2Decompiler(object):
324
+ def __init__(self, localSubrs, globalSubrs, private=None, blender=None):
325
+ self.localSubrs = localSubrs
326
+ self.localBias = calcSubrBias(localSubrs)
327
+ self.globalSubrs = globalSubrs
328
+ self.globalBias = calcSubrBias(globalSubrs)
329
+ self.private = private
330
+ self.blender = blender
331
+ self.reset()
332
+
333
+ def reset(self):
334
+ self.callingStack = []
335
+ self.operandStack = []
336
+ self.hintCount = 0
337
+ self.hintMaskBytes = 0
338
+ self.numRegions = 0
339
+ self.vsIndex = 0
340
+
341
+ def execute(self, charString):
342
+ self.callingStack.append(charString)
343
+ needsDecompilation = charString.needsDecompilation()
344
+ if needsDecompilation:
345
+ program = []
346
+ pushToProgram = program.append
347
+ else:
348
+ pushToProgram = lambda x: None
349
+ pushToStack = self.operandStack.append
350
+ index = 0
351
+ while True:
352
+ token, isOperator, index = charString.getToken(index)
353
+ if token is None:
354
+ break # we're done!
355
+ pushToProgram(token)
356
+ if isOperator:
357
+ handlerName = "op_" + token
358
+ handler = getattr(self, handlerName, None)
359
+ if handler is not None:
360
+ rv = handler(index)
361
+ if rv:
362
+ hintMaskBytes, index = rv
363
+ pushToProgram(hintMaskBytes)
364
+ else:
365
+ self.popall()
366
+ else:
367
+ pushToStack(token)
368
+ if needsDecompilation:
369
+ charString.setProgram(program)
370
+ del self.callingStack[-1]
371
+
372
+ def pop(self):
373
+ value = self.operandStack[-1]
374
+ del self.operandStack[-1]
375
+ return value
376
+
377
+ def popall(self):
378
+ stack = self.operandStack[:]
379
+ self.operandStack[:] = []
380
+ return stack
381
+
382
+ def push(self, value):
383
+ self.operandStack.append(value)
384
+
385
+ def op_return(self, index):
386
+ if self.operandStack:
387
+ pass
388
+
389
+ def op_endchar(self, index):
390
+ pass
391
+
392
+ def op_ignore(self, index):
393
+ pass
394
+
395
+ def op_callsubr(self, index):
396
+ subrIndex = self.pop()
397
+ subr = self.localSubrs[subrIndex + self.localBias]
398
+ self.execute(subr)
399
+
400
+ def op_callgsubr(self, index):
401
+ subrIndex = self.pop()
402
+ subr = self.globalSubrs[subrIndex + self.globalBias]
403
+ self.execute(subr)
404
+
405
+ def op_hstem(self, index):
406
+ self.countHints()
407
+
408
+ def op_vstem(self, index):
409
+ self.countHints()
410
+
411
+ def op_hstemhm(self, index):
412
+ self.countHints()
413
+
414
+ def op_vstemhm(self, index):
415
+ self.countHints()
416
+
417
+ def op_hintmask(self, index):
418
+ if not self.hintMaskBytes:
419
+ self.countHints()
420
+ self.hintMaskBytes = (self.hintCount + 7) // 8
421
+ hintMaskBytes, index = self.callingStack[-1].getBytes(index, self.hintMaskBytes)
422
+ return hintMaskBytes, index
423
+
424
+ op_cntrmask = op_hintmask
425
+
426
+ def countHints(self):
427
+ args = self.popall()
428
+ self.hintCount = self.hintCount + len(args) // 2
429
+
430
+ # misc
431
+ def op_and(self, index):
432
+ raise NotImplementedError
433
+
434
+ def op_or(self, index):
435
+ raise NotImplementedError
436
+
437
+ def op_not(self, index):
438
+ raise NotImplementedError
439
+
440
+ def op_store(self, index):
441
+ raise NotImplementedError
442
+
443
+ def op_abs(self, index):
444
+ raise NotImplementedError
445
+
446
+ def op_add(self, index):
447
+ raise NotImplementedError
448
+
449
+ def op_sub(self, index):
450
+ raise NotImplementedError
451
+
452
+ def op_div(self, index):
453
+ raise NotImplementedError
454
+
455
+ def op_load(self, index):
456
+ raise NotImplementedError
457
+
458
+ def op_neg(self, index):
459
+ raise NotImplementedError
460
+
461
+ def op_eq(self, index):
462
+ raise NotImplementedError
463
+
464
+ def op_drop(self, index):
465
+ raise NotImplementedError
466
+
467
+ def op_put(self, index):
468
+ raise NotImplementedError
469
+
470
+ def op_get(self, index):
471
+ raise NotImplementedError
472
+
473
+ def op_ifelse(self, index):
474
+ raise NotImplementedError
475
+
476
+ def op_random(self, index):
477
+ raise NotImplementedError
478
+
479
+ def op_mul(self, index):
480
+ raise NotImplementedError
481
+
482
+ def op_sqrt(self, index):
483
+ raise NotImplementedError
484
+
485
+ def op_dup(self, index):
486
+ raise NotImplementedError
487
+
488
+ def op_exch(self, index):
489
+ raise NotImplementedError
490
+
491
+ def op_index(self, index):
492
+ raise NotImplementedError
493
+
494
+ def op_roll(self, index):
495
+ raise NotImplementedError
496
+
497
+ def op_blend(self, index):
498
+ if self.numRegions == 0:
499
+ self.numRegions = self.private.getNumRegions()
500
+ numBlends = self.pop()
501
+ numOps = numBlends * (self.numRegions + 1)
502
+ if self.blender is None:
503
+ del self.operandStack[
504
+ -(numOps - numBlends) :
505
+ ] # Leave the default operands on the stack.
506
+ else:
507
+ argi = len(self.operandStack) - numOps
508
+ end_args = tuplei = argi + numBlends
509
+ while argi < end_args:
510
+ next_ti = tuplei + self.numRegions
511
+ deltas = self.operandStack[tuplei:next_ti]
512
+ delta = self.blender(self.vsIndex, deltas)
513
+ self.operandStack[argi] += delta
514
+ tuplei = next_ti
515
+ argi += 1
516
+ self.operandStack[end_args:] = []
517
+
518
+ def op_vsindex(self, index):
519
+ vi = self.pop()
520
+ self.vsIndex = vi
521
+ self.numRegions = self.private.getNumRegions(vi)
522
+
523
+
524
+ t1Operators = [
525
+ # opcode name
526
+ (1, "hstem"),
527
+ (3, "vstem"),
528
+ (4, "vmoveto"),
529
+ (5, "rlineto"),
530
+ (6, "hlineto"),
531
+ (7, "vlineto"),
532
+ (8, "rrcurveto"),
533
+ (9, "closepath"),
534
+ (10, "callsubr"),
535
+ (11, "return"),
536
+ (13, "hsbw"),
537
+ (14, "endchar"),
538
+ (21, "rmoveto"),
539
+ (22, "hmoveto"),
540
+ (30, "vhcurveto"),
541
+ (31, "hvcurveto"),
542
+ ((12, 0), "dotsection"),
543
+ ((12, 1), "vstem3"),
544
+ ((12, 2), "hstem3"),
545
+ ((12, 6), "seac"),
546
+ ((12, 7), "sbw"),
547
+ ((12, 12), "div"),
548
+ ((12, 16), "callothersubr"),
549
+ ((12, 17), "pop"),
550
+ ((12, 33), "setcurrentpoint"),
551
+ ]
552
+
553
+
554
+ class T2WidthExtractor(SimpleT2Decompiler):
555
+ def __init__(
556
+ self,
557
+ localSubrs,
558
+ globalSubrs,
559
+ nominalWidthX,
560
+ defaultWidthX,
561
+ private=None,
562
+ blender=None,
563
+ ):
564
+ SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, private, blender)
565
+ self.nominalWidthX = nominalWidthX
566
+ self.defaultWidthX = defaultWidthX
567
+
568
+ def reset(self):
569
+ SimpleT2Decompiler.reset(self)
570
+ self.gotWidth = 0
571
+ self.width = 0
572
+
573
+ def popallWidth(self, evenOdd=0):
574
+ args = self.popall()
575
+ if not self.gotWidth:
576
+ if evenOdd ^ (len(args) % 2):
577
+ # For CFF2 charstrings, this should never happen
578
+ assert (
579
+ self.defaultWidthX is not None
580
+ ), "CFF2 CharStrings must not have an initial width value"
581
+ self.width = self.nominalWidthX + args[0]
582
+ args = args[1:]
583
+ else:
584
+ self.width = self.defaultWidthX
585
+ self.gotWidth = 1
586
+ return args
587
+
588
+ def countHints(self):
589
+ args = self.popallWidth()
590
+ self.hintCount = self.hintCount + len(args) // 2
591
+
592
+ def op_rmoveto(self, index):
593
+ self.popallWidth()
594
+
595
+ def op_hmoveto(self, index):
596
+ self.popallWidth(1)
597
+
598
+ def op_vmoveto(self, index):
599
+ self.popallWidth(1)
600
+
601
+ def op_endchar(self, index):
602
+ self.popallWidth()
603
+
604
+
605
+ class T2OutlineExtractor(T2WidthExtractor):
606
+ def __init__(
607
+ self,
608
+ pen,
609
+ localSubrs,
610
+ globalSubrs,
611
+ nominalWidthX,
612
+ defaultWidthX,
613
+ private=None,
614
+ blender=None,
615
+ ):
616
+ T2WidthExtractor.__init__(
617
+ self,
618
+ localSubrs,
619
+ globalSubrs,
620
+ nominalWidthX,
621
+ defaultWidthX,
622
+ private,
623
+ blender,
624
+ )
625
+ self.pen = pen
626
+ self.subrLevel = 0
627
+
628
+ def reset(self):
629
+ T2WidthExtractor.reset(self)
630
+ self.currentPoint = (0, 0)
631
+ self.sawMoveTo = 0
632
+ self.subrLevel = 0
633
+
634
+ def execute(self, charString):
635
+ self.subrLevel += 1
636
+ super().execute(charString)
637
+ self.subrLevel -= 1
638
+ if self.subrLevel == 0:
639
+ self.endPath()
640
+
641
+ def _nextPoint(self, point):
642
+ x, y = self.currentPoint
643
+ point = x + point[0], y + point[1]
644
+ self.currentPoint = point
645
+ return point
646
+
647
+ def rMoveTo(self, point):
648
+ self.pen.moveTo(self._nextPoint(point))
649
+ self.sawMoveTo = 1
650
+
651
+ def rLineTo(self, point):
652
+ if not self.sawMoveTo:
653
+ self.rMoveTo((0, 0))
654
+ self.pen.lineTo(self._nextPoint(point))
655
+
656
+ def rCurveTo(self, pt1, pt2, pt3):
657
+ if not self.sawMoveTo:
658
+ self.rMoveTo((0, 0))
659
+ nextPoint = self._nextPoint
660
+ self.pen.curveTo(nextPoint(pt1), nextPoint(pt2), nextPoint(pt3))
661
+
662
+ def closePath(self):
663
+ if self.sawMoveTo:
664
+ self.pen.closePath()
665
+ self.sawMoveTo = 0
666
+
667
+ def endPath(self):
668
+ # In T2 there are no open paths, so always do a closePath when
669
+ # finishing a sub path. We avoid spurious calls to closePath()
670
+ # because its a real T1 op we're emulating in T2 whereas
671
+ # endPath() is just a means to that emulation
672
+ if self.sawMoveTo:
673
+ self.closePath()
674
+
675
+ #
676
+ # hint operators
677
+ #
678
+ # def op_hstem(self, index):
679
+ # self.countHints()
680
+ # def op_vstem(self, index):
681
+ # self.countHints()
682
+ # def op_hstemhm(self, index):
683
+ # self.countHints()
684
+ # def op_vstemhm(self, index):
685
+ # self.countHints()
686
+ # def op_hintmask(self, index):
687
+ # self.countHints()
688
+ # def op_cntrmask(self, index):
689
+ # self.countHints()
690
+
691
+ #
692
+ # path constructors, moveto
693
+ #
694
+ def op_rmoveto(self, index):
695
+ self.endPath()
696
+ self.rMoveTo(self.popallWidth())
697
+
698
+ def op_hmoveto(self, index):
699
+ self.endPath()
700
+ self.rMoveTo((self.popallWidth(1)[0], 0))
701
+
702
+ def op_vmoveto(self, index):
703
+ self.endPath()
704
+ self.rMoveTo((0, self.popallWidth(1)[0]))
705
+
706
+ def op_endchar(self, index):
707
+ self.endPath()
708
+ args = self.popallWidth()
709
+ if args:
710
+ from fontTools.encodings.StandardEncoding import StandardEncoding
711
+
712
+ # endchar can do seac accent bulding; The T2 spec says it's deprecated,
713
+ # but recent software that shall remain nameless does output it.
714
+ adx, ady, bchar, achar = args
715
+ baseGlyph = StandardEncoding[bchar]
716
+ self.pen.addComponent(baseGlyph, (1, 0, 0, 1, 0, 0))
717
+ accentGlyph = StandardEncoding[achar]
718
+ self.pen.addComponent(accentGlyph, (1, 0, 0, 1, adx, ady))
719
+
720
+ #
721
+ # path constructors, lines
722
+ #
723
+ def op_rlineto(self, index):
724
+ args = self.popall()
725
+ for i in range(0, len(args), 2):
726
+ point = args[i : i + 2]
727
+ self.rLineTo(point)
728
+
729
+ def op_hlineto(self, index):
730
+ self.alternatingLineto(1)
731
+
732
+ def op_vlineto(self, index):
733
+ self.alternatingLineto(0)
734
+
735
+ #
736
+ # path constructors, curves
737
+ #
738
+ def op_rrcurveto(self, index):
739
+ """{dxa dya dxb dyb dxc dyc}+ rrcurveto"""
740
+ args = self.popall()
741
+ for i in range(0, len(args), 6):
742
+ (
743
+ dxa,
744
+ dya,
745
+ dxb,
746
+ dyb,
747
+ dxc,
748
+ dyc,
749
+ ) = args[i : i + 6]
750
+ self.rCurveTo((dxa, dya), (dxb, dyb), (dxc, dyc))
751
+
752
+ def op_rcurveline(self, index):
753
+ """{dxa dya dxb dyb dxc dyc}+ dxd dyd rcurveline"""
754
+ args = self.popall()
755
+ for i in range(0, len(args) - 2, 6):
756
+ dxb, dyb, dxc, dyc, dxd, dyd = args[i : i + 6]
757
+ self.rCurveTo((dxb, dyb), (dxc, dyc), (dxd, dyd))
758
+ self.rLineTo(args[-2:])
759
+
760
+ def op_rlinecurve(self, index):
761
+ """{dxa dya}+ dxb dyb dxc dyc dxd dyd rlinecurve"""
762
+ args = self.popall()
763
+ lineArgs = args[:-6]
764
+ for i in range(0, len(lineArgs), 2):
765
+ self.rLineTo(lineArgs[i : i + 2])
766
+ dxb, dyb, dxc, dyc, dxd, dyd = args[-6:]
767
+ self.rCurveTo((dxb, dyb), (dxc, dyc), (dxd, dyd))
768
+
769
+ def op_vvcurveto(self, index):
770
+ "dx1? {dya dxb dyb dyc}+ vvcurveto"
771
+ args = self.popall()
772
+ if len(args) % 2:
773
+ dx1 = args[0]
774
+ args = args[1:]
775
+ else:
776
+ dx1 = 0
777
+ for i in range(0, len(args), 4):
778
+ dya, dxb, dyb, dyc = args[i : i + 4]
779
+ self.rCurveTo((dx1, dya), (dxb, dyb), (0, dyc))
780
+ dx1 = 0
781
+
782
+ def op_hhcurveto(self, index):
783
+ """dy1? {dxa dxb dyb dxc}+ hhcurveto"""
784
+ args = self.popall()
785
+ if len(args) % 2:
786
+ dy1 = args[0]
787
+ args = args[1:]
788
+ else:
789
+ dy1 = 0
790
+ for i in range(0, len(args), 4):
791
+ dxa, dxb, dyb, dxc = args[i : i + 4]
792
+ self.rCurveTo((dxa, dy1), (dxb, dyb), (dxc, 0))
793
+ dy1 = 0
794
+
795
+ def op_vhcurveto(self, index):
796
+ """dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto (30)
797
+ {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto
798
+ """
799
+ args = self.popall()
800
+ while args:
801
+ args = self.vcurveto(args)
802
+ if args:
803
+ args = self.hcurveto(args)
804
+
805
+ def op_hvcurveto(self, index):
806
+ """dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf?
807
+ {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf?
808
+ """
809
+ args = self.popall()
810
+ while args:
811
+ args = self.hcurveto(args)
812
+ if args:
813
+ args = self.vcurveto(args)
814
+
815
+ #
816
+ # path constructors, flex
817
+ #
818
+ def op_hflex(self, index):
819
+ dx1, dx2, dy2, dx3, dx4, dx5, dx6 = self.popall()
820
+ dy1 = dy3 = dy4 = dy6 = 0
821
+ dy5 = -dy2
822
+ self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
823
+ self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
824
+
825
+ def op_flex(self, index):
826
+ dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, dx6, dy6, fd = self.popall()
827
+ self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
828
+ self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
829
+
830
+ def op_hflex1(self, index):
831
+ dx1, dy1, dx2, dy2, dx3, dx4, dx5, dy5, dx6 = self.popall()
832
+ dy3 = dy4 = 0
833
+ dy6 = -(dy1 + dy2 + dy3 + dy4 + dy5)
834
+
835
+ self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
836
+ self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
837
+
838
+ def op_flex1(self, index):
839
+ dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4, dx5, dy5, d6 = self.popall()
840
+ dx = dx1 + dx2 + dx3 + dx4 + dx5
841
+ dy = dy1 + dy2 + dy3 + dy4 + dy5
842
+ if abs(dx) > abs(dy):
843
+ dx6 = d6
844
+ dy6 = -dy
845
+ else:
846
+ dx6 = -dx
847
+ dy6 = d6
848
+ self.rCurveTo((dx1, dy1), (dx2, dy2), (dx3, dy3))
849
+ self.rCurveTo((dx4, dy4), (dx5, dy5), (dx6, dy6))
850
+
851
+ # misc
852
+ def op_and(self, index):
853
+ raise NotImplementedError
854
+
855
+ def op_or(self, index):
856
+ raise NotImplementedError
857
+
858
+ def op_not(self, index):
859
+ raise NotImplementedError
860
+
861
+ def op_store(self, index):
862
+ raise NotImplementedError
863
+
864
+ def op_abs(self, index):
865
+ raise NotImplementedError
866
+
867
+ def op_add(self, index):
868
+ raise NotImplementedError
869
+
870
+ def op_sub(self, index):
871
+ raise NotImplementedError
872
+
873
+ def op_div(self, index):
874
+ num2 = self.pop()
875
+ num1 = self.pop()
876
+ d1 = num1 // num2
877
+ d2 = num1 / num2
878
+ if d1 == d2:
879
+ self.push(d1)
880
+ else:
881
+ self.push(d2)
882
+
883
+ def op_load(self, index):
884
+ raise NotImplementedError
885
+
886
+ def op_neg(self, index):
887
+ raise NotImplementedError
888
+
889
+ def op_eq(self, index):
890
+ raise NotImplementedError
891
+
892
+ def op_drop(self, index):
893
+ raise NotImplementedError
894
+
895
+ def op_put(self, index):
896
+ raise NotImplementedError
897
+
898
+ def op_get(self, index):
899
+ raise NotImplementedError
900
+
901
+ def op_ifelse(self, index):
902
+ raise NotImplementedError
903
+
904
+ def op_random(self, index):
905
+ raise NotImplementedError
906
+
907
+ def op_mul(self, index):
908
+ raise NotImplementedError
909
+
910
+ def op_sqrt(self, index):
911
+ raise NotImplementedError
912
+
913
+ def op_dup(self, index):
914
+ raise NotImplementedError
915
+
916
+ def op_exch(self, index):
917
+ raise NotImplementedError
918
+
919
+ def op_index(self, index):
920
+ raise NotImplementedError
921
+
922
+ def op_roll(self, index):
923
+ raise NotImplementedError
924
+
925
+ #
926
+ # miscellaneous helpers
927
+ #
928
+ def alternatingLineto(self, isHorizontal):
929
+ args = self.popall()
930
+ for arg in args:
931
+ if isHorizontal:
932
+ point = (arg, 0)
933
+ else:
934
+ point = (0, arg)
935
+ self.rLineTo(point)
936
+ isHorizontal = not isHorizontal
937
+
938
+ def vcurveto(self, args):
939
+ dya, dxb, dyb, dxc = args[:4]
940
+ args = args[4:]
941
+ if len(args) == 1:
942
+ dyc = args[0]
943
+ args = []
944
+ else:
945
+ dyc = 0
946
+ self.rCurveTo((0, dya), (dxb, dyb), (dxc, dyc))
947
+ return args
948
+
949
+ def hcurveto(self, args):
950
+ dxa, dxb, dyb, dyc = args[:4]
951
+ args = args[4:]
952
+ if len(args) == 1:
953
+ dxc = args[0]
954
+ args = []
955
+ else:
956
+ dxc = 0
957
+ self.rCurveTo((dxa, 0), (dxb, dyb), (dxc, dyc))
958
+ return args
959
+
960
+
961
+ class T1OutlineExtractor(T2OutlineExtractor):
962
+ def __init__(self, pen, subrs):
963
+ self.pen = pen
964
+ self.subrs = subrs
965
+ self.reset()
966
+
967
+ def reset(self):
968
+ self.flexing = 0
969
+ self.width = 0
970
+ self.sbx = 0
971
+ T2OutlineExtractor.reset(self)
972
+
973
+ def endPath(self):
974
+ if self.sawMoveTo:
975
+ self.pen.endPath()
976
+ self.sawMoveTo = 0
977
+
978
+ def popallWidth(self, evenOdd=0):
979
+ return self.popall()
980
+
981
+ def exch(self):
982
+ stack = self.operandStack
983
+ stack[-1], stack[-2] = stack[-2], stack[-1]
984
+
985
+ #
986
+ # path constructors
987
+ #
988
+ def op_rmoveto(self, index):
989
+ if self.flexing:
990
+ return
991
+ self.endPath()
992
+ self.rMoveTo(self.popall())
993
+
994
+ def op_hmoveto(self, index):
995
+ if self.flexing:
996
+ # We must add a parameter to the stack if we are flexing
997
+ self.push(0)
998
+ return
999
+ self.endPath()
1000
+ self.rMoveTo((self.popall()[0], 0))
1001
+
1002
+ def op_vmoveto(self, index):
1003
+ if self.flexing:
1004
+ # We must add a parameter to the stack if we are flexing
1005
+ self.push(0)
1006
+ self.exch()
1007
+ return
1008
+ self.endPath()
1009
+ self.rMoveTo((0, self.popall()[0]))
1010
+
1011
+ def op_closepath(self, index):
1012
+ self.closePath()
1013
+
1014
+ def op_setcurrentpoint(self, index):
1015
+ args = self.popall()
1016
+ x, y = args
1017
+ self.currentPoint = x, y
1018
+
1019
+ def op_endchar(self, index):
1020
+ self.endPath()
1021
+
1022
+ def op_hsbw(self, index):
1023
+ sbx, wx = self.popall()
1024
+ self.width = wx
1025
+ self.sbx = sbx
1026
+ self.currentPoint = sbx, self.currentPoint[1]
1027
+
1028
+ def op_sbw(self, index):
1029
+ self.popall() # XXX
1030
+
1031
+ #
1032
+ def op_callsubr(self, index):
1033
+ subrIndex = self.pop()
1034
+ subr = self.subrs[subrIndex]
1035
+ self.execute(subr)
1036
+
1037
+ def op_callothersubr(self, index):
1038
+ subrIndex = self.pop()
1039
+ nArgs = self.pop()
1040
+ # print nArgs, subrIndex, "callothersubr"
1041
+ if subrIndex == 0 and nArgs == 3:
1042
+ self.doFlex()
1043
+ self.flexing = 0
1044
+ elif subrIndex == 1 and nArgs == 0:
1045
+ self.flexing = 1
1046
+ # ignore...
1047
+
1048
+ def op_pop(self, index):
1049
+ pass # ignore...
1050
+
1051
+ def doFlex(self):
1052
+ finaly = self.pop()
1053
+ finalx = self.pop()
1054
+ self.pop() # flex height is unused
1055
+
1056
+ p3y = self.pop()
1057
+ p3x = self.pop()
1058
+ bcp4y = self.pop()
1059
+ bcp4x = self.pop()
1060
+ bcp3y = self.pop()
1061
+ bcp3x = self.pop()
1062
+ p2y = self.pop()
1063
+ p2x = self.pop()
1064
+ bcp2y = self.pop()
1065
+ bcp2x = self.pop()
1066
+ bcp1y = self.pop()
1067
+ bcp1x = self.pop()
1068
+ rpy = self.pop()
1069
+ rpx = self.pop()
1070
+
1071
+ # call rrcurveto
1072
+ self.push(bcp1x + rpx)
1073
+ self.push(bcp1y + rpy)
1074
+ self.push(bcp2x)
1075
+ self.push(bcp2y)
1076
+ self.push(p2x)
1077
+ self.push(p2y)
1078
+ self.op_rrcurveto(None)
1079
+
1080
+ # call rrcurveto
1081
+ self.push(bcp3x)
1082
+ self.push(bcp3y)
1083
+ self.push(bcp4x)
1084
+ self.push(bcp4y)
1085
+ self.push(p3x)
1086
+ self.push(p3y)
1087
+ self.op_rrcurveto(None)
1088
+
1089
+ # Push back final coords so subr 0 can find them
1090
+ self.push(finalx)
1091
+ self.push(finaly)
1092
+
1093
+ def op_dotsection(self, index):
1094
+ self.popall() # XXX
1095
+
1096
+ def op_hstem3(self, index):
1097
+ self.popall() # XXX
1098
+
1099
+ def op_seac(self, index):
1100
+ "asb adx ady bchar achar seac"
1101
+ from fontTools.encodings.StandardEncoding import StandardEncoding
1102
+
1103
+ asb, adx, ady, bchar, achar = self.popall()
1104
+ baseGlyph = StandardEncoding[bchar]
1105
+ self.pen.addComponent(baseGlyph, (1, 0, 0, 1, 0, 0))
1106
+ accentGlyph = StandardEncoding[achar]
1107
+ adx = adx + self.sbx - asb # seac weirdness
1108
+ self.pen.addComponent(accentGlyph, (1, 0, 0, 1, adx, ady))
1109
+
1110
+ def op_vstem3(self, index):
1111
+ self.popall() # XXX
1112
+
1113
+
1114
+ class T2CharString(object):
1115
+ operandEncoding = t2OperandEncoding
1116
+ operators, opcodes = buildOperatorDict(t2Operators)
1117
+ decompilerClass = SimpleT2Decompiler
1118
+ outlineExtractor = T2OutlineExtractor
1119
+
1120
+ def __init__(self, bytecode=None, program=None, private=None, globalSubrs=None):
1121
+ if program is None:
1122
+ program = []
1123
+ self.bytecode = bytecode
1124
+ self.program = program
1125
+ self.private = private
1126
+ self.globalSubrs = globalSubrs if globalSubrs is not None else []
1127
+ self._cur_vsindex = None
1128
+
1129
+ def getNumRegions(self, vsindex=None):
1130
+ pd = self.private
1131
+ assert pd is not None
1132
+ if vsindex is not None:
1133
+ self._cur_vsindex = vsindex
1134
+ elif self._cur_vsindex is None:
1135
+ self._cur_vsindex = pd.vsindex if hasattr(pd, "vsindex") else 0
1136
+ return pd.getNumRegions(self._cur_vsindex)
1137
+
1138
+ def __repr__(self):
1139
+ if self.bytecode is None:
1140
+ return "<%s (source) at %x>" % (self.__class__.__name__, id(self))
1141
+ else:
1142
+ return "<%s (bytecode) at %x>" % (self.__class__.__name__, id(self))
1143
+
1144
+ def getIntEncoder(self):
1145
+ return encodeIntT2
1146
+
1147
+ def getFixedEncoder(self):
1148
+ return encodeFixed
1149
+
1150
+ def decompile(self):
1151
+ if not self.needsDecompilation():
1152
+ return
1153
+ subrs = getattr(self.private, "Subrs", [])
1154
+ decompiler = self.decompilerClass(subrs, self.globalSubrs, self.private)
1155
+ decompiler.execute(self)
1156
+
1157
+ def draw(self, pen, blender=None):
1158
+ subrs = getattr(self.private, "Subrs", [])
1159
+ extractor = self.outlineExtractor(
1160
+ pen,
1161
+ subrs,
1162
+ self.globalSubrs,
1163
+ self.private.nominalWidthX,
1164
+ self.private.defaultWidthX,
1165
+ self.private,
1166
+ blender,
1167
+ )
1168
+ extractor.execute(self)
1169
+ self.width = extractor.width
1170
+
1171
+ def calcBounds(self, glyphSet):
1172
+ boundsPen = BoundsPen(glyphSet)
1173
+ self.draw(boundsPen)
1174
+ return boundsPen.bounds
1175
+
1176
+ def compile(self, isCFF2=False):
1177
+ if self.bytecode is not None:
1178
+ return
1179
+ opcodes = self.opcodes
1180
+ program = self.program
1181
+
1182
+ if isCFF2:
1183
+ # If present, remove return and endchar operators.
1184
+ if program and program[-1] in ("return", "endchar"):
1185
+ program = program[:-1]
1186
+ elif program and not isinstance(program[-1], str):
1187
+ raise CharStringCompileError(
1188
+ "T2CharString or Subr has items on the stack after last operator."
1189
+ )
1190
+
1191
+ bytecode = []
1192
+ encodeInt = self.getIntEncoder()
1193
+ encodeFixed = self.getFixedEncoder()
1194
+ i = 0
1195
+ end = len(program)
1196
+ while i < end:
1197
+ token = program[i]
1198
+ i = i + 1
1199
+ if isinstance(token, str):
1200
+ try:
1201
+ bytecode.extend(bytechr(b) for b in opcodes[token])
1202
+ except KeyError:
1203
+ raise CharStringCompileError("illegal operator: %s" % token)
1204
+ if token in ("hintmask", "cntrmask"):
1205
+ bytecode.append(program[i]) # hint mask
1206
+ i = i + 1
1207
+ elif isinstance(token, int):
1208
+ bytecode.append(encodeInt(token))
1209
+ elif isinstance(token, float):
1210
+ bytecode.append(encodeFixed(token))
1211
+ else:
1212
+ assert 0, "unsupported type: %s" % type(token)
1213
+ try:
1214
+ bytecode = bytesjoin(bytecode)
1215
+ except TypeError:
1216
+ log.error(bytecode)
1217
+ raise
1218
+ self.setBytecode(bytecode)
1219
+
1220
+ def needsDecompilation(self):
1221
+ return self.bytecode is not None
1222
+
1223
+ def setProgram(self, program):
1224
+ self.program = program
1225
+ self.bytecode = None
1226
+
1227
+ def setBytecode(self, bytecode):
1228
+ self.bytecode = bytecode
1229
+ self.program = None
1230
+
1231
+ def getToken(self, index, len=len, byteord=byteord, isinstance=isinstance):
1232
+ if self.bytecode is not None:
1233
+ if index >= len(self.bytecode):
1234
+ return None, 0, 0
1235
+ b0 = byteord(self.bytecode[index])
1236
+ index = index + 1
1237
+ handler = self.operandEncoding[b0]
1238
+ token, index = handler(self, b0, self.bytecode, index)
1239
+ else:
1240
+ if index >= len(self.program):
1241
+ return None, 0, 0
1242
+ token = self.program[index]
1243
+ index = index + 1
1244
+ isOperator = isinstance(token, str)
1245
+ return token, isOperator, index
1246
+
1247
+ def getBytes(self, index, nBytes):
1248
+ if self.bytecode is not None:
1249
+ newIndex = index + nBytes
1250
+ bytes = self.bytecode[index:newIndex]
1251
+ index = newIndex
1252
+ else:
1253
+ bytes = self.program[index]
1254
+ index = index + 1
1255
+ assert len(bytes) == nBytes
1256
+ return bytes, index
1257
+
1258
+ def handle_operator(self, operator):
1259
+ return operator
1260
+
1261
+ def toXML(self, xmlWriter, ttFont=None):
1262
+ from fontTools.misc.textTools import num2binary
1263
+
1264
+ if self.bytecode is not None:
1265
+ xmlWriter.dumphex(self.bytecode)
1266
+ else:
1267
+ index = 0
1268
+ args = []
1269
+ while True:
1270
+ token, isOperator, index = self.getToken(index)
1271
+ if token is None:
1272
+ break
1273
+ if isOperator:
1274
+ if token in ("hintmask", "cntrmask"):
1275
+ hintMask, isOperator, index = self.getToken(index)
1276
+ bits = []
1277
+ for byte in hintMask:
1278
+ bits.append(num2binary(byteord(byte), 8))
1279
+ hintMask = strjoin(bits)
1280
+ line = " ".join(args + [token, hintMask])
1281
+ else:
1282
+ line = " ".join(args + [token])
1283
+ xmlWriter.write(line)
1284
+ xmlWriter.newline()
1285
+ args = []
1286
+ else:
1287
+ if isinstance(token, float):
1288
+ token = floatToFixedToStr(token, precisionBits=16)
1289
+ else:
1290
+ token = str(token)
1291
+ args.append(token)
1292
+ if args:
1293
+ # NOTE: only CFF2 charstrings/subrs can have numeric arguments on
1294
+ # the stack after the last operator. Compiling this would fail if
1295
+ # this is part of CFF 1.0 table.
1296
+ line = " ".join(args)
1297
+ xmlWriter.write(line)
1298
+
1299
+ def fromXML(self, name, attrs, content):
1300
+ from fontTools.misc.textTools import binary2num, readHex
1301
+
1302
+ if attrs.get("raw"):
1303
+ self.setBytecode(readHex(content))
1304
+ return
1305
+ content = strjoin(content)
1306
+ content = content.split()
1307
+ program = []
1308
+ end = len(content)
1309
+ i = 0
1310
+ while i < end:
1311
+ token = content[i]
1312
+ i = i + 1
1313
+ try:
1314
+ token = int(token)
1315
+ except ValueError:
1316
+ try:
1317
+ token = strToFixedToFloat(token, precisionBits=16)
1318
+ except ValueError:
1319
+ program.append(token)
1320
+ if token in ("hintmask", "cntrmask"):
1321
+ mask = content[i]
1322
+ maskBytes = b""
1323
+ for j in range(0, len(mask), 8):
1324
+ maskBytes = maskBytes + bytechr(binary2num(mask[j : j + 8]))
1325
+ program.append(maskBytes)
1326
+ i = i + 1
1327
+ else:
1328
+ program.append(token)
1329
+ else:
1330
+ program.append(token)
1331
+ self.setProgram(program)
1332
+
1333
+
1334
+ class T1CharString(T2CharString):
1335
+ operandEncoding = t1OperandEncoding
1336
+ operators, opcodes = buildOperatorDict(t1Operators)
1337
+
1338
+ def __init__(self, bytecode=None, program=None, subrs=None):
1339
+ super().__init__(bytecode, program)
1340
+ self.subrs = subrs
1341
+
1342
+ def getIntEncoder(self):
1343
+ return encodeIntT1
1344
+
1345
+ def getFixedEncoder(self):
1346
+ def encodeFixed(value):
1347
+ raise TypeError("Type 1 charstrings don't support floating point operands")
1348
+
1349
+ def decompile(self):
1350
+ if self.bytecode is None:
1351
+ return
1352
+ program = []
1353
+ index = 0
1354
+ while True:
1355
+ token, isOperator, index = self.getToken(index)
1356
+ if token is None:
1357
+ break
1358
+ program.append(token)
1359
+ self.setProgram(program)
1360
+
1361
+ def draw(self, pen):
1362
+ extractor = T1OutlineExtractor(pen, self.subrs)
1363
+ extractor.execute(self)
1364
+ self.width = extractor.width
1365
+
1366
+
1367
+ class DictDecompiler(object):
1368
+ operandEncoding = cffDictOperandEncoding
1369
+
1370
+ def __init__(self, strings, parent=None):
1371
+ self.stack = []
1372
+ self.strings = strings
1373
+ self.dict = {}
1374
+ self.parent = parent
1375
+
1376
+ def getDict(self):
1377
+ assert len(self.stack) == 0, "non-empty stack"
1378
+ return self.dict
1379
+
1380
+ def decompile(self, data):
1381
+ index = 0
1382
+ lenData = len(data)
1383
+ push = self.stack.append
1384
+ while index < lenData:
1385
+ b0 = byteord(data[index])
1386
+ index = index + 1
1387
+ handler = self.operandEncoding[b0]
1388
+ value, index = handler(self, b0, data, index)
1389
+ if value is not None:
1390
+ push(value)
1391
+
1392
+ def pop(self):
1393
+ value = self.stack[-1]
1394
+ del self.stack[-1]
1395
+ return value
1396
+
1397
+ def popall(self):
1398
+ args = self.stack[:]
1399
+ del self.stack[:]
1400
+ return args
1401
+
1402
+ def handle_operator(self, operator):
1403
+ operator, argType = operator
1404
+ if isinstance(argType, tuple):
1405
+ value = ()
1406
+ for i in range(len(argType) - 1, -1, -1):
1407
+ arg = argType[i]
1408
+ arghandler = getattr(self, "arg_" + arg)
1409
+ value = (arghandler(operator),) + value
1410
+ else:
1411
+ arghandler = getattr(self, "arg_" + argType)
1412
+ value = arghandler(operator)
1413
+ if operator == "blend":
1414
+ self.stack.extend(value)
1415
+ else:
1416
+ self.dict[operator] = value
1417
+
1418
+ def arg_number(self, name):
1419
+ if isinstance(self.stack[0], list):
1420
+ out = self.arg_blend_number(self.stack)
1421
+ else:
1422
+ out = self.pop()
1423
+ return out
1424
+
1425
+ def arg_blend_number(self, name):
1426
+ out = []
1427
+ blendArgs = self.pop()
1428
+ numMasters = len(blendArgs)
1429
+ out.append(blendArgs)
1430
+ out.append("blend")
1431
+ dummy = self.popall()
1432
+ return blendArgs
1433
+
1434
+ def arg_SID(self, name):
1435
+ return self.strings[self.pop()]
1436
+
1437
+ def arg_array(self, name):
1438
+ return self.popall()
1439
+
1440
+ def arg_blendList(self, name):
1441
+ """
1442
+ There may be non-blend args at the top of the stack. We first calculate
1443
+ where the blend args start in the stack. These are the last
1444
+ numMasters*numBlends) +1 args.
1445
+ The blend args starts with numMasters relative coordinate values, the BlueValues in the list from the default master font. This is followed by
1446
+ numBlends list of values. Each of value in one of these lists is the
1447
+ Variable Font delta for the matching region.
1448
+
1449
+ We re-arrange this to be a list of numMaster entries. Each entry starts with the corresponding default font relative value, and is followed by
1450
+ the delta values. We then convert the default values, the first item in each entry, to an absolute value.
1451
+ """
1452
+ vsindex = self.dict.get("vsindex", 0)
1453
+ numMasters = (
1454
+ self.parent.getNumRegions(vsindex) + 1
1455
+ ) # only a PrivateDict has blended ops.
1456
+ numBlends = self.pop()
1457
+ args = self.popall()
1458
+ numArgs = len(args)
1459
+ # The spec says that there should be no non-blended Blue Values,.
1460
+ assert numArgs == numMasters * numBlends
1461
+ value = [None] * numBlends
1462
+ numDeltas = numMasters - 1
1463
+ i = 0
1464
+ prevVal = 0
1465
+ while i < numBlends:
1466
+ newVal = args[i] + prevVal
1467
+ prevVal = newVal
1468
+ masterOffset = numBlends + (i * numDeltas)
1469
+ blendList = [newVal] + args[masterOffset : masterOffset + numDeltas]
1470
+ value[i] = blendList
1471
+ i += 1
1472
+ return value
1473
+
1474
+ def arg_delta(self, name):
1475
+ valueList = self.popall()
1476
+ out = []
1477
+ if valueList and isinstance(valueList[0], list):
1478
+ # arg_blendList() has already converted these to absolute values.
1479
+ out = valueList
1480
+ else:
1481
+ current = 0
1482
+ for v in valueList:
1483
+ current = current + v
1484
+ out.append(current)
1485
+ return out
1486
+
1487
+
1488
+ def calcSubrBias(subrs):
1489
+ nSubrs = len(subrs)
1490
+ if nSubrs < 1240:
1491
+ bias = 107
1492
+ elif nSubrs < 33900:
1493
+ bias = 1131
1494
+ else:
1495
+ bias = 32768
1496
+ return bias
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/psLib.py ADDED
@@ -0,0 +1,398 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc.textTools import bytechr, byteord, bytesjoin, tobytes, tostr
2
+ from fontTools.misc import eexec
3
+ from .psOperators import (
4
+ PSOperators,
5
+ ps_StandardEncoding,
6
+ ps_array,
7
+ ps_boolean,
8
+ ps_dict,
9
+ ps_integer,
10
+ ps_literal,
11
+ ps_mark,
12
+ ps_name,
13
+ ps_operator,
14
+ ps_procedure,
15
+ ps_procmark,
16
+ ps_real,
17
+ ps_string,
18
+ )
19
+ import re
20
+ from collections.abc import Callable
21
+ from string import whitespace
22
+ import logging
23
+
24
+
25
+ log = logging.getLogger(__name__)
26
+
27
+ ps_special = b"()<>[]{}%" # / is one too, but we take care of that one differently
28
+
29
+ skipwhiteRE = re.compile(bytesjoin([b"[", whitespace, b"]*"]))
30
+ endofthingPat = bytesjoin([b"[^][(){}<>/%", whitespace, b"]*"])
31
+ endofthingRE = re.compile(endofthingPat)
32
+ commentRE = re.compile(b"%[^\n\r]*")
33
+
34
+ # XXX This not entirely correct as it doesn't allow *nested* embedded parens:
35
+ stringPat = rb"""
36
+ \(
37
+ (
38
+ (
39
+ [^()]* \ [()]
40
+ )
41
+ |
42
+ (
43
+ [^()]* \( [^()]* \)
44
+ )
45
+ )*
46
+ [^()]*
47
+ \)
48
+ """
49
+ stringPat = b"".join(stringPat.split())
50
+ stringRE = re.compile(stringPat)
51
+
52
+ hexstringRE = re.compile(bytesjoin([b"<[", whitespace, b"0-9A-Fa-f]*>"]))
53
+
54
+
55
+ class PSTokenError(Exception):
56
+ pass
57
+
58
+
59
+ class PSError(Exception):
60
+ pass
61
+
62
+
63
+ class PSTokenizer(object):
64
+ def __init__(self, buf=b"", encoding="ascii"):
65
+ # Force self.buf to be a byte string
66
+ buf = tobytes(buf)
67
+ self.buf = buf
68
+ self.len = len(buf)
69
+ self.pos = 0
70
+ self.closed = False
71
+ self.encoding = encoding
72
+
73
+ def read(self, n=-1):
74
+ """Read at most 'n' bytes from the buffer, or less if the read
75
+ hits EOF before obtaining 'n' bytes.
76
+ If 'n' is negative or omitted, read all data until EOF is reached.
77
+ """
78
+ if self.closed:
79
+ raise ValueError("I/O operation on closed file")
80
+ if n is None or n < 0:
81
+ newpos = self.len
82
+ else:
83
+ newpos = min(self.pos + n, self.len)
84
+ r = self.buf[self.pos : newpos]
85
+ self.pos = newpos
86
+ return r
87
+
88
+ def close(self):
89
+ if not self.closed:
90
+ self.closed = True
91
+ del self.buf, self.pos
92
+
93
+ def getnexttoken(
94
+ self,
95
+ # localize some stuff, for performance
96
+ len=len,
97
+ ps_special=ps_special,
98
+ stringmatch=stringRE.match,
99
+ hexstringmatch=hexstringRE.match,
100
+ commentmatch=commentRE.match,
101
+ endmatch=endofthingRE.match,
102
+ ):
103
+ self.skipwhite()
104
+ if self.pos >= self.len:
105
+ return None, None
106
+ pos = self.pos
107
+ buf = self.buf
108
+ char = bytechr(byteord(buf[pos]))
109
+ if char in ps_special:
110
+ if char in b"{}[]":
111
+ tokentype = "do_special"
112
+ token = char
113
+ elif char == b"%":
114
+ tokentype = "do_comment"
115
+ _, nextpos = commentmatch(buf, pos).span()
116
+ token = buf[pos:nextpos]
117
+ elif char == b"(":
118
+ tokentype = "do_string"
119
+ m = stringmatch(buf, pos)
120
+ if m is None:
121
+ raise PSTokenError("bad string at character %d" % pos)
122
+ _, nextpos = m.span()
123
+ token = buf[pos:nextpos]
124
+ elif char == b"<":
125
+ tokentype = "do_hexstring"
126
+ m = hexstringmatch(buf, pos)
127
+ if m is None:
128
+ raise PSTokenError("bad hexstring at character %d" % pos)
129
+ _, nextpos = m.span()
130
+ token = buf[pos:nextpos]
131
+ else:
132
+ raise PSTokenError("bad token at character %d" % pos)
133
+ else:
134
+ if char == b"/":
135
+ tokentype = "do_literal"
136
+ m = endmatch(buf, pos + 1)
137
+ else:
138
+ tokentype = ""
139
+ m = endmatch(buf, pos)
140
+ if m is None:
141
+ raise PSTokenError("bad token at character %d" % pos)
142
+ _, nextpos = m.span()
143
+ token = buf[pos:nextpos]
144
+ self.pos = pos + len(token)
145
+ token = tostr(token, encoding=self.encoding)
146
+ return tokentype, token
147
+
148
+ def skipwhite(self, whitematch=skipwhiteRE.match):
149
+ _, nextpos = whitematch(self.buf, self.pos).span()
150
+ self.pos = nextpos
151
+
152
+ def starteexec(self):
153
+ self.pos = self.pos + 1
154
+ self.dirtybuf = self.buf[self.pos :]
155
+ self.buf, R = eexec.decrypt(self.dirtybuf, 55665)
156
+ self.len = len(self.buf)
157
+ self.pos = 4
158
+
159
+ def stopeexec(self):
160
+ if not hasattr(self, "dirtybuf"):
161
+ return
162
+ self.buf = self.dirtybuf
163
+ del self.dirtybuf
164
+
165
+
166
+ class PSInterpreter(PSOperators):
167
+ def __init__(self, encoding="ascii"):
168
+ systemdict = {}
169
+ userdict = {}
170
+ self.encoding = encoding
171
+ self.dictstack = [systemdict, userdict]
172
+ self.stack = []
173
+ self.proclevel = 0
174
+ self.procmark = ps_procmark()
175
+ self.fillsystemdict()
176
+
177
+ def fillsystemdict(self):
178
+ systemdict = self.dictstack[0]
179
+ systemdict["["] = systemdict["mark"] = self.mark = ps_mark()
180
+ systemdict["]"] = ps_operator("]", self.do_makearray)
181
+ systemdict["true"] = ps_boolean(1)
182
+ systemdict["false"] = ps_boolean(0)
183
+ systemdict["StandardEncoding"] = ps_array(ps_StandardEncoding)
184
+ systemdict["FontDirectory"] = ps_dict({})
185
+ self.suckoperators(systemdict, self.__class__)
186
+
187
+ def suckoperators(self, systemdict, klass):
188
+ for name in dir(klass):
189
+ attr = getattr(self, name)
190
+ if isinstance(attr, Callable) and name[:3] == "ps_":
191
+ name = name[3:]
192
+ systemdict[name] = ps_operator(name, attr)
193
+ for baseclass in klass.__bases__:
194
+ self.suckoperators(systemdict, baseclass)
195
+
196
+ def interpret(self, data, getattr=getattr):
197
+ tokenizer = self.tokenizer = PSTokenizer(data, self.encoding)
198
+ getnexttoken = tokenizer.getnexttoken
199
+ do_token = self.do_token
200
+ handle_object = self.handle_object
201
+ try:
202
+ while 1:
203
+ tokentype, token = getnexttoken()
204
+ if not token:
205
+ break
206
+ if tokentype:
207
+ handler = getattr(self, tokentype)
208
+ object = handler(token)
209
+ else:
210
+ object = do_token(token)
211
+ if object is not None:
212
+ handle_object(object)
213
+ tokenizer.close()
214
+ self.tokenizer = None
215
+ except:
216
+ if self.tokenizer is not None:
217
+ log.debug(
218
+ "ps error:\n"
219
+ "- - - - - - -\n"
220
+ "%s\n"
221
+ ">>>\n"
222
+ "%s\n"
223
+ "- - - - - - -",
224
+ self.tokenizer.buf[self.tokenizer.pos - 50 : self.tokenizer.pos],
225
+ self.tokenizer.buf[self.tokenizer.pos : self.tokenizer.pos + 50],
226
+ )
227
+ raise
228
+
229
+ def handle_object(self, object):
230
+ if not (self.proclevel or object.literal or object.type == "proceduretype"):
231
+ if object.type != "operatortype":
232
+ object = self.resolve_name(object.value)
233
+ if object.literal:
234
+ self.push(object)
235
+ else:
236
+ if object.type == "proceduretype":
237
+ self.call_procedure(object)
238
+ else:
239
+ object.function()
240
+ else:
241
+ self.push(object)
242
+
243
+ def call_procedure(self, proc):
244
+ handle_object = self.handle_object
245
+ for item in proc.value:
246
+ handle_object(item)
247
+
248
+ def resolve_name(self, name):
249
+ dictstack = self.dictstack
250
+ for i in range(len(dictstack) - 1, -1, -1):
251
+ if name in dictstack[i]:
252
+ return dictstack[i][name]
253
+ raise PSError("name error: " + str(name))
254
+
255
+ def do_token(
256
+ self,
257
+ token,
258
+ int=int,
259
+ float=float,
260
+ ps_name=ps_name,
261
+ ps_integer=ps_integer,
262
+ ps_real=ps_real,
263
+ ):
264
+ try:
265
+ num = int(token)
266
+ except (ValueError, OverflowError):
267
+ try:
268
+ num = float(token)
269
+ except (ValueError, OverflowError):
270
+ if "#" in token:
271
+ hashpos = token.find("#")
272
+ try:
273
+ base = int(token[:hashpos])
274
+ num = int(token[hashpos + 1 :], base)
275
+ except (ValueError, OverflowError):
276
+ return ps_name(token)
277
+ else:
278
+ return ps_integer(num)
279
+ else:
280
+ return ps_name(token)
281
+ else:
282
+ return ps_real(num)
283
+ else:
284
+ return ps_integer(num)
285
+
286
+ def do_comment(self, token):
287
+ pass
288
+
289
+ def do_literal(self, token):
290
+ return ps_literal(token[1:])
291
+
292
+ def do_string(self, token):
293
+ return ps_string(token[1:-1])
294
+
295
+ def do_hexstring(self, token):
296
+ hexStr = "".join(token[1:-1].split())
297
+ if len(hexStr) % 2:
298
+ hexStr = hexStr + "0"
299
+ cleanstr = []
300
+ for i in range(0, len(hexStr), 2):
301
+ cleanstr.append(chr(int(hexStr[i : i + 2], 16)))
302
+ cleanstr = "".join(cleanstr)
303
+ return ps_string(cleanstr)
304
+
305
+ def do_special(self, token):
306
+ if token == "{":
307
+ self.proclevel = self.proclevel + 1
308
+ return self.procmark
309
+ elif token == "}":
310
+ proc = []
311
+ while 1:
312
+ topobject = self.pop()
313
+ if topobject == self.procmark:
314
+ break
315
+ proc.append(topobject)
316
+ self.proclevel = self.proclevel - 1
317
+ proc.reverse()
318
+ return ps_procedure(proc)
319
+ elif token == "[":
320
+ return self.mark
321
+ elif token == "]":
322
+ return ps_name("]")
323
+ else:
324
+ raise PSTokenError("huh?")
325
+
326
+ def push(self, object):
327
+ self.stack.append(object)
328
+
329
+ def pop(self, *types):
330
+ stack = self.stack
331
+ if not stack:
332
+ raise PSError("stack underflow")
333
+ object = stack[-1]
334
+ if types:
335
+ if object.type not in types:
336
+ raise PSError(
337
+ "typecheck, expected %s, found %s" % (repr(types), object.type)
338
+ )
339
+ del stack[-1]
340
+ return object
341
+
342
+ def do_makearray(self):
343
+ array = []
344
+ while 1:
345
+ topobject = self.pop()
346
+ if topobject == self.mark:
347
+ break
348
+ array.append(topobject)
349
+ array.reverse()
350
+ self.push(ps_array(array))
351
+
352
+ def close(self):
353
+ """Remove circular references."""
354
+ del self.stack
355
+ del self.dictstack
356
+
357
+
358
+ def unpack_item(item):
359
+ tp = type(item.value)
360
+ if tp == dict:
361
+ newitem = {}
362
+ for key, value in item.value.items():
363
+ newitem[key] = unpack_item(value)
364
+ elif tp == list:
365
+ newitem = [None] * len(item.value)
366
+ for i in range(len(item.value)):
367
+ newitem[i] = unpack_item(item.value[i])
368
+ if item.type == "proceduretype":
369
+ newitem = tuple(newitem)
370
+ else:
371
+ newitem = item.value
372
+ return newitem
373
+
374
+
375
+ def suckfont(data, encoding="ascii"):
376
+ m = re.search(rb"/FontName\s+/([^ \t\n\r]+)\s+def", data)
377
+ if m:
378
+ fontName = m.group(1)
379
+ fontName = fontName.decode()
380
+ else:
381
+ fontName = None
382
+ interpreter = PSInterpreter(encoding=encoding)
383
+ interpreter.interpret(
384
+ b"/Helvetica 4 dict dup /Encoding StandardEncoding put definefont pop"
385
+ )
386
+ interpreter.interpret(data)
387
+ fontdir = interpreter.dictstack[0]["FontDirectory"].value
388
+ if fontName in fontdir:
389
+ rawfont = fontdir[fontName]
390
+ else:
391
+ # fall back, in case fontName wasn't found
392
+ fontNames = list(fontdir.keys())
393
+ if len(fontNames) > 1:
394
+ fontNames.remove("Helvetica")
395
+ fontNames.sort()
396
+ rawfont = fontdir[fontNames[0]]
397
+ interpreter.close()
398
+ return unpack_item(rawfont)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/psOperators.py ADDED
@@ -0,0 +1,572 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _accessstrings = {0: "", 1: "readonly", 2: "executeonly", 3: "noaccess"}
2
+
3
+
4
+ class ps_object(object):
5
+ literal = 1
6
+ access = 0
7
+ value = None
8
+
9
+ def __init__(self, value):
10
+ self.value = value
11
+ self.type = self.__class__.__name__[3:] + "type"
12
+
13
+ def __repr__(self):
14
+ return "<%s %s>" % (self.__class__.__name__[3:], repr(self.value))
15
+
16
+
17
+ class ps_operator(ps_object):
18
+ literal = 0
19
+
20
+ def __init__(self, name, function):
21
+ self.name = name
22
+ self.function = function
23
+ self.type = self.__class__.__name__[3:] + "type"
24
+
25
+ def __repr__(self):
26
+ return "<operator %s>" % self.name
27
+
28
+
29
+ class ps_procedure(ps_object):
30
+ literal = 0
31
+
32
+ def __repr__(self):
33
+ return "<procedure>"
34
+
35
+ def __str__(self):
36
+ psstring = "{"
37
+ for i in range(len(self.value)):
38
+ if i:
39
+ psstring = psstring + " " + str(self.value[i])
40
+ else:
41
+ psstring = psstring + str(self.value[i])
42
+ return psstring + "}"
43
+
44
+
45
+ class ps_name(ps_object):
46
+ literal = 0
47
+
48
+ def __str__(self):
49
+ if self.literal:
50
+ return "/" + self.value
51
+ else:
52
+ return self.value
53
+
54
+
55
+ class ps_literal(ps_object):
56
+ def __str__(self):
57
+ return "/" + self.value
58
+
59
+
60
+ class ps_array(ps_object):
61
+ def __str__(self):
62
+ psstring = "["
63
+ for i in range(len(self.value)):
64
+ item = self.value[i]
65
+ access = _accessstrings[item.access]
66
+ if access:
67
+ access = " " + access
68
+ if i:
69
+ psstring = psstring + " " + str(item) + access
70
+ else:
71
+ psstring = psstring + str(item) + access
72
+ return psstring + "]"
73
+
74
+ def __repr__(self):
75
+ return "<array>"
76
+
77
+
78
+ _type1_pre_eexec_order = [
79
+ "FontInfo",
80
+ "FontName",
81
+ "Encoding",
82
+ "PaintType",
83
+ "FontType",
84
+ "FontMatrix",
85
+ "FontBBox",
86
+ "UniqueID",
87
+ "Metrics",
88
+ "StrokeWidth",
89
+ ]
90
+
91
+ _type1_fontinfo_order = [
92
+ "version",
93
+ "Notice",
94
+ "FullName",
95
+ "FamilyName",
96
+ "Weight",
97
+ "ItalicAngle",
98
+ "isFixedPitch",
99
+ "UnderlinePosition",
100
+ "UnderlineThickness",
101
+ ]
102
+
103
+ _type1_post_eexec_order = ["Private", "CharStrings", "FID"]
104
+
105
+
106
+ def _type1_item_repr(key, value):
107
+ psstring = ""
108
+ access = _accessstrings[value.access]
109
+ if access:
110
+ access = access + " "
111
+ if key == "CharStrings":
112
+ psstring = psstring + "/%s %s def\n" % (
113
+ key,
114
+ _type1_CharString_repr(value.value),
115
+ )
116
+ elif key == "Encoding":
117
+ psstring = psstring + _type1_Encoding_repr(value, access)
118
+ else:
119
+ psstring = psstring + "/%s %s %sdef\n" % (str(key), str(value), access)
120
+ return psstring
121
+
122
+
123
+ def _type1_Encoding_repr(encoding, access):
124
+ encoding = encoding.value
125
+ psstring = "/Encoding 256 array\n0 1 255 {1 index exch /.notdef put} for\n"
126
+ for i in range(256):
127
+ name = encoding[i].value
128
+ if name != ".notdef":
129
+ psstring = psstring + "dup %d /%s put\n" % (i, name)
130
+ return psstring + access + "def\n"
131
+
132
+
133
+ def _type1_CharString_repr(charstrings):
134
+ items = sorted(charstrings.items())
135
+ return "xxx"
136
+
137
+
138
+ class ps_font(ps_object):
139
+ def __str__(self):
140
+ psstring = "%d dict dup begin\n" % len(self.value)
141
+ for key in _type1_pre_eexec_order:
142
+ try:
143
+ value = self.value[key]
144
+ except KeyError:
145
+ pass
146
+ else:
147
+ psstring = psstring + _type1_item_repr(key, value)
148
+ items = sorted(self.value.items())
149
+ for key, value in items:
150
+ if key not in _type1_pre_eexec_order + _type1_post_eexec_order:
151
+ psstring = psstring + _type1_item_repr(key, value)
152
+ psstring = psstring + "currentdict end\ncurrentfile eexec\ndup "
153
+ for key in _type1_post_eexec_order:
154
+ try:
155
+ value = self.value[key]
156
+ except KeyError:
157
+ pass
158
+ else:
159
+ psstring = psstring + _type1_item_repr(key, value)
160
+ return (
161
+ psstring
162
+ + "dup/FontName get exch definefont pop\nmark currentfile closefile\n"
163
+ + 8 * (64 * "0" + "\n")
164
+ + "cleartomark"
165
+ + "\n"
166
+ )
167
+
168
+ def __repr__(self):
169
+ return "<font>"
170
+
171
+
172
+ class ps_file(ps_object):
173
+ pass
174
+
175
+
176
+ class ps_dict(ps_object):
177
+ def __str__(self):
178
+ psstring = "%d dict dup begin\n" % len(self.value)
179
+ items = sorted(self.value.items())
180
+ for key, value in items:
181
+ access = _accessstrings[value.access]
182
+ if access:
183
+ access = access + " "
184
+ psstring = psstring + "/%s %s %sdef\n" % (str(key), str(value), access)
185
+ return psstring + "end "
186
+
187
+ def __repr__(self):
188
+ return "<dict>"
189
+
190
+
191
+ class ps_mark(ps_object):
192
+ def __init__(self):
193
+ self.value = "mark"
194
+ self.type = self.__class__.__name__[3:] + "type"
195
+
196
+
197
+ class ps_procmark(ps_object):
198
+ def __init__(self):
199
+ self.value = "procmark"
200
+ self.type = self.__class__.__name__[3:] + "type"
201
+
202
+
203
+ class ps_null(ps_object):
204
+ def __init__(self):
205
+ self.type = self.__class__.__name__[3:] + "type"
206
+
207
+
208
+ class ps_boolean(ps_object):
209
+ def __str__(self):
210
+ if self.value:
211
+ return "true"
212
+ else:
213
+ return "false"
214
+
215
+
216
+ class ps_string(ps_object):
217
+ def __str__(self):
218
+ return "(%s)" % repr(self.value)[1:-1]
219
+
220
+
221
+ class ps_integer(ps_object):
222
+ def __str__(self):
223
+ return repr(self.value)
224
+
225
+
226
+ class ps_real(ps_object):
227
+ def __str__(self):
228
+ return repr(self.value)
229
+
230
+
231
+ class PSOperators(object):
232
+ def ps_def(self):
233
+ obj = self.pop()
234
+ name = self.pop()
235
+ self.dictstack[-1][name.value] = obj
236
+
237
+ def ps_bind(self):
238
+ proc = self.pop("proceduretype")
239
+ self.proc_bind(proc)
240
+ self.push(proc)
241
+
242
+ def proc_bind(self, proc):
243
+ for i in range(len(proc.value)):
244
+ item = proc.value[i]
245
+ if item.type == "proceduretype":
246
+ self.proc_bind(item)
247
+ else:
248
+ if not item.literal:
249
+ try:
250
+ obj = self.resolve_name(item.value)
251
+ except:
252
+ pass
253
+ else:
254
+ if obj.type == "operatortype":
255
+ proc.value[i] = obj
256
+
257
+ def ps_exch(self):
258
+ if len(self.stack) < 2:
259
+ raise RuntimeError("stack underflow")
260
+ obj1 = self.pop()
261
+ obj2 = self.pop()
262
+ self.push(obj1)
263
+ self.push(obj2)
264
+
265
+ def ps_dup(self):
266
+ if not self.stack:
267
+ raise RuntimeError("stack underflow")
268
+ self.push(self.stack[-1])
269
+
270
+ def ps_exec(self):
271
+ obj = self.pop()
272
+ if obj.type == "proceduretype":
273
+ self.call_procedure(obj)
274
+ else:
275
+ self.handle_object(obj)
276
+
277
+ def ps_count(self):
278
+ self.push(ps_integer(len(self.stack)))
279
+
280
+ def ps_eq(self):
281
+ any1 = self.pop()
282
+ any2 = self.pop()
283
+ self.push(ps_boolean(any1.value == any2.value))
284
+
285
+ def ps_ne(self):
286
+ any1 = self.pop()
287
+ any2 = self.pop()
288
+ self.push(ps_boolean(any1.value != any2.value))
289
+
290
+ def ps_cvx(self):
291
+ obj = self.pop()
292
+ obj.literal = 0
293
+ self.push(obj)
294
+
295
+ def ps_matrix(self):
296
+ matrix = [
297
+ ps_real(1.0),
298
+ ps_integer(0),
299
+ ps_integer(0),
300
+ ps_real(1.0),
301
+ ps_integer(0),
302
+ ps_integer(0),
303
+ ]
304
+ self.push(ps_array(matrix))
305
+
306
+ def ps_string(self):
307
+ num = self.pop("integertype").value
308
+ self.push(ps_string("\0" * num))
309
+
310
+ def ps_type(self):
311
+ obj = self.pop()
312
+ self.push(ps_string(obj.type))
313
+
314
+ def ps_store(self):
315
+ value = self.pop()
316
+ key = self.pop()
317
+ name = key.value
318
+ for i in range(len(self.dictstack) - 1, -1, -1):
319
+ if name in self.dictstack[i]:
320
+ self.dictstack[i][name] = value
321
+ break
322
+ self.dictstack[-1][name] = value
323
+
324
+ def ps_where(self):
325
+ name = self.pop()
326
+ # XXX
327
+ self.push(ps_boolean(0))
328
+
329
+ def ps_systemdict(self):
330
+ self.push(ps_dict(self.dictstack[0]))
331
+
332
+ def ps_userdict(self):
333
+ self.push(ps_dict(self.dictstack[1]))
334
+
335
+ def ps_currentdict(self):
336
+ self.push(ps_dict(self.dictstack[-1]))
337
+
338
+ def ps_currentfile(self):
339
+ self.push(ps_file(self.tokenizer))
340
+
341
+ def ps_eexec(self):
342
+ f = self.pop("filetype").value
343
+ f.starteexec()
344
+
345
+ def ps_closefile(self):
346
+ f = self.pop("filetype").value
347
+ f.skipwhite()
348
+ f.stopeexec()
349
+
350
+ def ps_cleartomark(self):
351
+ obj = self.pop()
352
+ while obj != self.mark:
353
+ obj = self.pop()
354
+
355
+ def ps_readstring(self, ps_boolean=ps_boolean, len=len):
356
+ s = self.pop("stringtype")
357
+ oldstr = s.value
358
+ f = self.pop("filetype")
359
+ # pad = file.value.read(1)
360
+ # for StringIO, this is faster
361
+ f.value.pos = f.value.pos + 1
362
+ newstr = f.value.read(len(oldstr))
363
+ s.value = newstr
364
+ self.push(s)
365
+ self.push(ps_boolean(len(oldstr) == len(newstr)))
366
+
367
+ def ps_known(self):
368
+ key = self.pop()
369
+ d = self.pop("dicttype", "fonttype")
370
+ self.push(ps_boolean(key.value in d.value))
371
+
372
+ def ps_if(self):
373
+ proc = self.pop("proceduretype")
374
+ if self.pop("booleantype").value:
375
+ self.call_procedure(proc)
376
+
377
+ def ps_ifelse(self):
378
+ proc2 = self.pop("proceduretype")
379
+ proc1 = self.pop("proceduretype")
380
+ if self.pop("booleantype").value:
381
+ self.call_procedure(proc1)
382
+ else:
383
+ self.call_procedure(proc2)
384
+
385
+ def ps_readonly(self):
386
+ obj = self.pop()
387
+ if obj.access < 1:
388
+ obj.access = 1
389
+ self.push(obj)
390
+
391
+ def ps_executeonly(self):
392
+ obj = self.pop()
393
+ if obj.access < 2:
394
+ obj.access = 2
395
+ self.push(obj)
396
+
397
+ def ps_noaccess(self):
398
+ obj = self.pop()
399
+ if obj.access < 3:
400
+ obj.access = 3
401
+ self.push(obj)
402
+
403
+ def ps_not(self):
404
+ obj = self.pop("booleantype", "integertype")
405
+ if obj.type == "booleantype":
406
+ self.push(ps_boolean(not obj.value))
407
+ else:
408
+ self.push(ps_integer(~obj.value))
409
+
410
+ def ps_print(self):
411
+ str = self.pop("stringtype")
412
+ print("PS output --->", str.value)
413
+
414
+ def ps_anchorsearch(self):
415
+ seek = self.pop("stringtype")
416
+ s = self.pop("stringtype")
417
+ seeklen = len(seek.value)
418
+ if s.value[:seeklen] == seek.value:
419
+ self.push(ps_string(s.value[seeklen:]))
420
+ self.push(seek)
421
+ self.push(ps_boolean(1))
422
+ else:
423
+ self.push(s)
424
+ self.push(ps_boolean(0))
425
+
426
+ def ps_array(self):
427
+ num = self.pop("integertype")
428
+ array = ps_array([None] * num.value)
429
+ self.push(array)
430
+
431
+ def ps_astore(self):
432
+ array = self.pop("arraytype")
433
+ for i in range(len(array.value) - 1, -1, -1):
434
+ array.value[i] = self.pop()
435
+ self.push(array)
436
+
437
+ def ps_load(self):
438
+ name = self.pop()
439
+ self.push(self.resolve_name(name.value))
440
+
441
+ def ps_put(self):
442
+ obj1 = self.pop()
443
+ obj2 = self.pop()
444
+ obj3 = self.pop("arraytype", "dicttype", "stringtype", "proceduretype")
445
+ tp = obj3.type
446
+ if tp == "arraytype" or tp == "proceduretype":
447
+ obj3.value[obj2.value] = obj1
448
+ elif tp == "dicttype":
449
+ obj3.value[obj2.value] = obj1
450
+ elif tp == "stringtype":
451
+ index = obj2.value
452
+ obj3.value = obj3.value[:index] + chr(obj1.value) + obj3.value[index + 1 :]
453
+
454
+ def ps_get(self):
455
+ obj1 = self.pop()
456
+ if obj1.value == "Encoding":
457
+ pass
458
+ obj2 = self.pop(
459
+ "arraytype", "dicttype", "stringtype", "proceduretype", "fonttype"
460
+ )
461
+ tp = obj2.type
462
+ if tp in ("arraytype", "proceduretype"):
463
+ self.push(obj2.value[obj1.value])
464
+ elif tp in ("dicttype", "fonttype"):
465
+ self.push(obj2.value[obj1.value])
466
+ elif tp == "stringtype":
467
+ self.push(ps_integer(ord(obj2.value[obj1.value])))
468
+ else:
469
+ assert False, "shouldn't get here"
470
+
471
+ def ps_getinterval(self):
472
+ obj1 = self.pop("integertype")
473
+ obj2 = self.pop("integertype")
474
+ obj3 = self.pop("arraytype", "stringtype")
475
+ tp = obj3.type
476
+ if tp == "arraytype":
477
+ self.push(ps_array(obj3.value[obj2.value : obj2.value + obj1.value]))
478
+ elif tp == "stringtype":
479
+ self.push(ps_string(obj3.value[obj2.value : obj2.value + obj1.value]))
480
+
481
+ def ps_putinterval(self):
482
+ obj1 = self.pop("arraytype", "stringtype")
483
+ obj2 = self.pop("integertype")
484
+ obj3 = self.pop("arraytype", "stringtype")
485
+ tp = obj3.type
486
+ if tp == "arraytype":
487
+ obj3.value[obj2.value : obj2.value + len(obj1.value)] = obj1.value
488
+ elif tp == "stringtype":
489
+ newstr = obj3.value[: obj2.value]
490
+ newstr = newstr + obj1.value
491
+ newstr = newstr + obj3.value[obj2.value + len(obj1.value) :]
492
+ obj3.value = newstr
493
+
494
+ def ps_cvn(self):
495
+ self.push(ps_name(self.pop("stringtype").value))
496
+
497
+ def ps_index(self):
498
+ n = self.pop("integertype").value
499
+ if n < 0:
500
+ raise RuntimeError("index may not be negative")
501
+ self.push(self.stack[-1 - n])
502
+
503
+ def ps_for(self):
504
+ proc = self.pop("proceduretype")
505
+ limit = self.pop("integertype", "realtype").value
506
+ increment = self.pop("integertype", "realtype").value
507
+ i = self.pop("integertype", "realtype").value
508
+ while 1:
509
+ if increment > 0:
510
+ if i > limit:
511
+ break
512
+ else:
513
+ if i < limit:
514
+ break
515
+ if type(i) == type(0.0):
516
+ self.push(ps_real(i))
517
+ else:
518
+ self.push(ps_integer(i))
519
+ self.call_procedure(proc)
520
+ i = i + increment
521
+
522
+ def ps_forall(self):
523
+ proc = self.pop("proceduretype")
524
+ obj = self.pop("arraytype", "stringtype", "dicttype")
525
+ tp = obj.type
526
+ if tp == "arraytype":
527
+ for item in obj.value:
528
+ self.push(item)
529
+ self.call_procedure(proc)
530
+ elif tp == "stringtype":
531
+ for item in obj.value:
532
+ self.push(ps_integer(ord(item)))
533
+ self.call_procedure(proc)
534
+ elif tp == "dicttype":
535
+ for key, value in obj.value.items():
536
+ self.push(ps_name(key))
537
+ self.push(value)
538
+ self.call_procedure(proc)
539
+
540
+ def ps_definefont(self):
541
+ font = self.pop("dicttype")
542
+ name = self.pop()
543
+ font = ps_font(font.value)
544
+ self.dictstack[0]["FontDirectory"].value[name.value] = font
545
+ self.push(font)
546
+
547
+ def ps_findfont(self):
548
+ name = self.pop()
549
+ font = self.dictstack[0]["FontDirectory"].value[name.value]
550
+ self.push(font)
551
+
552
+ def ps_pop(self):
553
+ self.pop()
554
+
555
+ def ps_dict(self):
556
+ self.pop("integertype")
557
+ self.push(ps_dict({}))
558
+
559
+ def ps_begin(self):
560
+ self.dictstack.append(self.pop("dicttype").value)
561
+
562
+ def ps_end(self):
563
+ if len(self.dictstack) > 2:
564
+ del self.dictstack[-1]
565
+ else:
566
+ raise RuntimeError("dictstack underflow")
567
+
568
+
569
+ notdef = ".notdef"
570
+ from fontTools.encodings.StandardEncoding import StandardEncoding
571
+
572
+ ps_StandardEncoding = list(map(ps_name, StandardEncoding))
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/py23.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Python 2/3 compat layer leftovers."""
2
+
3
+ import decimal as _decimal
4
+ import math as _math
5
+ import warnings
6
+ from contextlib import redirect_stderr, redirect_stdout
7
+ from io import BytesIO
8
+ from io import StringIO as UnicodeIO
9
+ from types import SimpleNamespace
10
+
11
+ from .textTools import Tag, bytechr, byteord, bytesjoin, strjoin, tobytes, tostr
12
+
13
+ warnings.warn(
14
+ "The py23 module has been deprecated and will be removed in a future release. "
15
+ "Please update your code.",
16
+ DeprecationWarning,
17
+ )
18
+
19
+ __all__ = [
20
+ "basestring",
21
+ "bytechr",
22
+ "byteord",
23
+ "BytesIO",
24
+ "bytesjoin",
25
+ "open",
26
+ "Py23Error",
27
+ "range",
28
+ "RecursionError",
29
+ "round",
30
+ "SimpleNamespace",
31
+ "StringIO",
32
+ "strjoin",
33
+ "Tag",
34
+ "tobytes",
35
+ "tostr",
36
+ "tounicode",
37
+ "unichr",
38
+ "unicode",
39
+ "UnicodeIO",
40
+ "xrange",
41
+ "zip",
42
+ ]
43
+
44
+
45
+ class Py23Error(NotImplementedError):
46
+ pass
47
+
48
+
49
+ RecursionError = RecursionError
50
+ StringIO = UnicodeIO
51
+
52
+ basestring = str
53
+ isclose = _math.isclose
54
+ isfinite = _math.isfinite
55
+ open = open
56
+ range = range
57
+ round = round3 = round
58
+ unichr = chr
59
+ unicode = str
60
+ zip = zip
61
+
62
+ tounicode = tostr
63
+
64
+
65
+ def xrange(*args, **kwargs):
66
+ raise Py23Error("'xrange' is not defined. Use 'range' instead.")
67
+
68
+
69
+ def round2(number, ndigits=None):
70
+ """
71
+ Implementation of Python 2 built-in round() function.
72
+ Rounds a number to a given precision in decimal digits (default
73
+ 0 digits). The result is a floating point number. Values are rounded
74
+ to the closest multiple of 10 to the power minus ndigits; if two
75
+ multiples are equally close, rounding is done away from 0.
76
+ ndigits may be negative.
77
+ See Python 2 documentation:
78
+ https://docs.python.org/2/library/functions.html?highlight=round#round
79
+ """
80
+ if ndigits is None:
81
+ ndigits = 0
82
+
83
+ if ndigits < 0:
84
+ exponent = 10 ** (-ndigits)
85
+ quotient, remainder = divmod(number, exponent)
86
+ if remainder >= exponent // 2 and number >= 0:
87
+ quotient += 1
88
+ return float(quotient * exponent)
89
+ else:
90
+ exponent = _decimal.Decimal("10") ** (-ndigits)
91
+
92
+ d = _decimal.Decimal.from_float(number).quantize(
93
+ exponent, rounding=_decimal.ROUND_HALF_UP
94
+ )
95
+
96
+ return float(d)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/roundTools.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Various round-to-integer helpers.
3
+ """
4
+
5
+ import math
6
+ import functools
7
+ import logging
8
+
9
+ log = logging.getLogger(__name__)
10
+
11
+ __all__ = [
12
+ "noRound",
13
+ "otRound",
14
+ "maybeRound",
15
+ "roundFunc",
16
+ "nearestMultipleShortestRepr",
17
+ ]
18
+
19
+
20
+ def noRound(value):
21
+ return value
22
+
23
+
24
+ def otRound(value):
25
+ """Round float value to nearest integer towards ``+Infinity``.
26
+
27
+ The OpenType spec (in the section on `"normalization" of OpenType Font Variations <https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview#coordinate-scales-and-normalization>`_)
28
+ defines the required method for converting floating point values to
29
+ fixed-point. In particular it specifies the following rounding strategy:
30
+
31
+ for fractional values of 0.5 and higher, take the next higher integer;
32
+ for other fractional values, truncate.
33
+
34
+ This function rounds the floating-point value according to this strategy
35
+ in preparation for conversion to fixed-point.
36
+
37
+ Args:
38
+ value (float): The input floating-point value.
39
+
40
+ Returns
41
+ float: The rounded value.
42
+ """
43
+ # See this thread for how we ended up with this implementation:
44
+ # https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166
45
+ return int(math.floor(value + 0.5))
46
+
47
+
48
+ def maybeRound(v, tolerance, round=otRound):
49
+ rounded = round(v)
50
+ return rounded if abs(rounded - v) <= tolerance else v
51
+
52
+
53
+ def roundFunc(tolerance, round=otRound):
54
+ if tolerance < 0:
55
+ raise ValueError("Rounding tolerance must be positive")
56
+
57
+ if tolerance == 0:
58
+ return noRound
59
+
60
+ if tolerance >= 0.5:
61
+ return round
62
+
63
+ return functools.partial(maybeRound, tolerance=tolerance, round=round)
64
+
65
+
66
+ def nearestMultipleShortestRepr(value: float, factor: float) -> str:
67
+ """Round to nearest multiple of factor and return shortest decimal representation.
68
+
69
+ This chooses the float that is closer to a multiple of the given factor while
70
+ having the shortest decimal representation (the least number of fractional decimal
71
+ digits).
72
+
73
+ For example, given the following:
74
+
75
+ >>> nearestMultipleShortestRepr(-0.61883544921875, 1.0/(1<<14))
76
+ '-0.61884'
77
+
78
+ Useful when you need to serialize or print a fixed-point number (or multiples
79
+ thereof, such as F2Dot14 fractions of 180 degrees in COLRv1 PaintRotate) in
80
+ a human-readable form.
81
+
82
+ Args:
83
+ value (value): The value to be rounded and serialized.
84
+ factor (float): The value which the result is a close multiple of.
85
+
86
+ Returns:
87
+ str: A compact string representation of the value.
88
+ """
89
+ if not value:
90
+ return "0.0"
91
+
92
+ value = otRound(value / factor) * factor
93
+ eps = 0.5 * factor
94
+ lo = value - eps
95
+ hi = value + eps
96
+ # If the range of valid choices spans an integer, return the integer.
97
+ if int(lo) != int(hi):
98
+ return str(float(round(value)))
99
+
100
+ fmt = "%.8f"
101
+ lo = fmt % lo
102
+ hi = fmt % hi
103
+ assert len(lo) == len(hi) and lo != hi
104
+ for i in range(len(lo)):
105
+ if lo[i] != hi[i]:
106
+ break
107
+ period = lo.find(".")
108
+ assert period < i
109
+ fmt = "%%.%df" % (i - period)
110
+ return fmt % value
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/sstruct.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """sstruct.py -- SuperStruct
2
+
3
+ Higher level layer on top of the struct module, enabling to
4
+ bind names to struct elements. The interface is similar to
5
+ struct, except the objects passed and returned are not tuples
6
+ (or argument lists), but dictionaries or instances.
7
+
8
+ Just like struct, we use fmt strings to describe a data
9
+ structure, except we use one line per element. Lines are
10
+ separated by newlines or semi-colons. Each line contains
11
+ either one of the special struct characters ('@', '=', '<',
12
+ '>' or '!') or a 'name:formatchar' combo (eg. 'myFloat:f').
13
+ Repetitions, like the struct module offers them are not useful
14
+ in this context, except for fixed length strings (eg. 'myInt:5h'
15
+ is not allowed but 'myString:5s' is). The 'x' fmt character
16
+ (pad byte) is treated as 'special', since it is by definition
17
+ anonymous. Extra whitespace is allowed everywhere.
18
+
19
+ The sstruct module offers one feature that the "normal" struct
20
+ module doesn't: support for fixed point numbers. These are spelled
21
+ as "n.mF", where n is the number of bits before the point, and m
22
+ the number of bits after the point. Fixed point numbers get
23
+ converted to floats.
24
+
25
+ pack(fmt, object):
26
+ 'object' is either a dictionary or an instance (or actually
27
+ anything that has a __dict__ attribute). If it is a dictionary,
28
+ its keys are used for names. If it is an instance, it's
29
+ attributes are used to grab struct elements from. Returns
30
+ a string containing the data.
31
+
32
+ unpack(fmt, data, object=None)
33
+ If 'object' is omitted (or None), a new dictionary will be
34
+ returned. If 'object' is a dictionary, it will be used to add
35
+ struct elements to. If it is an instance (or in fact anything
36
+ that has a __dict__ attribute), an attribute will be added for
37
+ each struct element. In the latter two cases, 'object' itself
38
+ is returned.
39
+
40
+ unpack2(fmt, data, object=None)
41
+ Convenience function. Same as unpack, except data may be longer
42
+ than needed. The returned value is a tuple: (object, leftoverdata).
43
+
44
+ calcsize(fmt)
45
+ like struct.calcsize(), but uses our own fmt strings:
46
+ it returns the size of the data in bytes.
47
+ """
48
+
49
+ from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
50
+ from fontTools.misc.textTools import tobytes, tostr
51
+ import struct
52
+ import re
53
+
54
+ __version__ = "1.2"
55
+ __copyright__ = "Copyright 1998, Just van Rossum <just@letterror.com>"
56
+
57
+
58
+ class Error(Exception):
59
+ pass
60
+
61
+
62
+ def pack(fmt, obj):
63
+ formatstring, names, fixes = getformat(fmt, keep_pad_byte=True)
64
+ elements = []
65
+ if not isinstance(obj, dict):
66
+ obj = obj.__dict__
67
+ string_index = formatstring
68
+ if formatstring.startswith(">"):
69
+ string_index = formatstring[1:]
70
+ for ix, name in enumerate(names.keys()):
71
+ value = obj[name]
72
+ if name in fixes:
73
+ # fixed point conversion
74
+ value = fl2fi(value, fixes[name])
75
+ elif isinstance(value, str):
76
+ value = tobytes(value)
77
+ elements.append(value)
78
+ # Check it fits
79
+ try:
80
+ struct.pack(names[name], value)
81
+ except Exception as e:
82
+ raise ValueError(
83
+ "Value %s does not fit in format %s for %s" % (value, names[name], name)
84
+ ) from e
85
+ data = struct.pack(*(formatstring,) + tuple(elements))
86
+ return data
87
+
88
+
89
+ def unpack(fmt, data, obj=None):
90
+ if obj is None:
91
+ obj = {}
92
+ data = tobytes(data)
93
+ formatstring, names, fixes = getformat(fmt)
94
+ if isinstance(obj, dict):
95
+ d = obj
96
+ else:
97
+ d = obj.__dict__
98
+ elements = struct.unpack(formatstring, data)
99
+ for i in range(len(names)):
100
+ name = list(names.keys())[i]
101
+ value = elements[i]
102
+ if name in fixes:
103
+ # fixed point conversion
104
+ value = fi2fl(value, fixes[name])
105
+ elif isinstance(value, bytes):
106
+ try:
107
+ value = tostr(value)
108
+ except UnicodeDecodeError:
109
+ pass
110
+ d[name] = value
111
+ return obj
112
+
113
+
114
+ def unpack2(fmt, data, obj=None):
115
+ length = calcsize(fmt)
116
+ return unpack(fmt, data[:length], obj), data[length:]
117
+
118
+
119
+ def calcsize(fmt):
120
+ formatstring, names, fixes = getformat(fmt)
121
+ return struct.calcsize(formatstring)
122
+
123
+
124
+ # matches "name:formatchar" (whitespace is allowed)
125
+ _elementRE = re.compile(
126
+ r"\s*" # whitespace
127
+ r"([A-Za-z_][A-Za-z_0-9]*)" # name (python identifier)
128
+ r"\s*:\s*" # whitespace : whitespace
129
+ r"([xcbB?hHiIlLqQfd]|" # formatchar...
130
+ r"[0-9]+[ps]|" # ...formatchar...
131
+ r"([0-9]+)\.([0-9]+)(F))" # ...formatchar
132
+ r"\s*" # whitespace
133
+ r"(#.*)?$" # [comment] + end of string
134
+ )
135
+
136
+ # matches the special struct fmt chars and 'x' (pad byte)
137
+ _extraRE = re.compile(r"\s*([x@=<>!])\s*(#.*)?$")
138
+
139
+ # matches an "empty" string, possibly containing whitespace and/or a comment
140
+ _emptyRE = re.compile(r"\s*(#.*)?$")
141
+
142
+ _fixedpointmappings = {8: "b", 16: "h", 32: "l"}
143
+
144
+ _formatcache = {}
145
+
146
+
147
+ def getformat(fmt, keep_pad_byte=False):
148
+ fmt = tostr(fmt, encoding="ascii")
149
+ try:
150
+ formatstring, names, fixes = _formatcache[fmt]
151
+ except KeyError:
152
+ lines = re.split("[\n;]", fmt)
153
+ formatstring = ""
154
+ names = {}
155
+ fixes = {}
156
+ for line in lines:
157
+ if _emptyRE.match(line):
158
+ continue
159
+ m = _extraRE.match(line)
160
+ if m:
161
+ formatchar = m.group(1)
162
+ if formatchar != "x" and formatstring:
163
+ raise Error("a special fmt char must be first")
164
+ else:
165
+ m = _elementRE.match(line)
166
+ if not m:
167
+ raise Error("syntax error in fmt: '%s'" % line)
168
+ name = m.group(1)
169
+ formatchar = m.group(2)
170
+ if keep_pad_byte or formatchar != "x":
171
+ names[name] = formatchar
172
+ if m.group(3):
173
+ # fixed point
174
+ before = int(m.group(3))
175
+ after = int(m.group(4))
176
+ bits = before + after
177
+ if bits not in [8, 16, 32]:
178
+ raise Error("fixed point must be 8, 16 or 32 bits long")
179
+ formatchar = _fixedpointmappings[bits]
180
+ names[name] = formatchar
181
+ assert m.group(5) == "F"
182
+ fixes[name] = after
183
+ formatstring += formatchar
184
+ _formatcache[fmt] = formatstring, names, fixes
185
+ return formatstring, names, fixes
186
+
187
+
188
+ def _test():
189
+ fmt = """
190
+ # comments are allowed
191
+ > # big endian (see documentation for struct)
192
+ # empty lines are allowed:
193
+
194
+ ashort: h
195
+ along: l
196
+ abyte: b # a byte
197
+ achar: c
198
+ astr: 5s
199
+ afloat: f; adouble: d # multiple "statements" are allowed
200
+ afixed: 16.16F
201
+ abool: ?
202
+ apad: x
203
+ """
204
+
205
+ print("size:", calcsize(fmt))
206
+
207
+ class foo(object):
208
+ pass
209
+
210
+ i = foo()
211
+
212
+ i.ashort = 0x7FFF
213
+ i.along = 0x7FFFFFFF
214
+ i.abyte = 0x7F
215
+ i.achar = "a"
216
+ i.astr = "12345"
217
+ i.afloat = 0.5
218
+ i.adouble = 0.5
219
+ i.afixed = 1.5
220
+ i.abool = True
221
+
222
+ data = pack(fmt, i)
223
+ print("data:", repr(data))
224
+ print(unpack(fmt, data))
225
+ i2 = foo()
226
+ unpack(fmt, data, i2)
227
+ print(vars(i2))
228
+
229
+
230
+ if __name__ == "__main__":
231
+ _test()
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/symfont.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.pens.basePen import BasePen
2
+ from functools import partial
3
+ from itertools import count
4
+ import sympy as sp
5
+ import sys
6
+
7
+ n = 3 # Max Bezier degree; 3 for cubic, 2 for quadratic
8
+
9
+ t, x, y = sp.symbols("t x y", real=True)
10
+ c = sp.symbols("c", real=False) # Complex representation instead of x/y
11
+
12
+ X = tuple(sp.symbols("x:%d" % (n + 1), real=True))
13
+ Y = tuple(sp.symbols("y:%d" % (n + 1), real=True))
14
+ P = tuple(zip(*(sp.symbols("p:%d[%s]" % (n + 1, w), real=True) for w in "01")))
15
+ C = tuple(sp.symbols("c:%d" % (n + 1), real=False))
16
+
17
+ # Cubic Bernstein basis functions
18
+ BinomialCoefficient = [(1, 0)]
19
+ for i in range(1, n + 1):
20
+ last = BinomialCoefficient[-1]
21
+ this = tuple(last[j - 1] + last[j] for j in range(len(last))) + (0,)
22
+ BinomialCoefficient.append(this)
23
+ BinomialCoefficient = tuple(tuple(item[:-1]) for item in BinomialCoefficient)
24
+ del last, this
25
+
26
+ BernsteinPolynomial = tuple(
27
+ tuple(c * t**i * (1 - t) ** (n - i) for i, c in enumerate(coeffs))
28
+ for n, coeffs in enumerate(BinomialCoefficient)
29
+ )
30
+
31
+ BezierCurve = tuple(
32
+ tuple(
33
+ sum(P[i][j] * bernstein for i, bernstein in enumerate(bernsteins))
34
+ for j in range(2)
35
+ )
36
+ for n, bernsteins in enumerate(BernsteinPolynomial)
37
+ )
38
+ BezierCurveC = tuple(
39
+ sum(C[i] * bernstein for i, bernstein in enumerate(bernsteins))
40
+ for n, bernsteins in enumerate(BernsteinPolynomial)
41
+ )
42
+
43
+
44
+ def green(f, curveXY):
45
+ f = -sp.integrate(sp.sympify(f), y)
46
+ f = f.subs({x: curveXY[0], y: curveXY[1]})
47
+ f = sp.integrate(f * sp.diff(curveXY[0], t), (t, 0, 1))
48
+ return f
49
+
50
+
51
+ class _BezierFuncsLazy(dict):
52
+ def __init__(self, symfunc):
53
+ self._symfunc = symfunc
54
+ self._bezfuncs = {}
55
+
56
+ def __missing__(self, i):
57
+ args = ["p%d" % d for d in range(i + 1)]
58
+ f = green(self._symfunc, BezierCurve[i])
59
+ f = sp.gcd_terms(f.collect(sum(P, ()))) # Optimize
60
+ return sp.lambdify(args, f)
61
+
62
+
63
+ class GreenPen(BasePen):
64
+ _BezierFuncs = {}
65
+
66
+ @classmethod
67
+ def _getGreenBezierFuncs(celf, func):
68
+ funcstr = str(func)
69
+ if not funcstr in celf._BezierFuncs:
70
+ celf._BezierFuncs[funcstr] = _BezierFuncsLazy(func)
71
+ return celf._BezierFuncs[funcstr]
72
+
73
+ def __init__(self, func, glyphset=None):
74
+ BasePen.__init__(self, glyphset)
75
+ self._funcs = self._getGreenBezierFuncs(func)
76
+ self.value = 0
77
+
78
+ def _moveTo(self, p0):
79
+ self._startPoint = p0
80
+
81
+ def _closePath(self):
82
+ p0 = self._getCurrentPoint()
83
+ if p0 != self._startPoint:
84
+ self._lineTo(self._startPoint)
85
+
86
+ def _endPath(self):
87
+ p0 = self._getCurrentPoint()
88
+ if p0 != self._startPoint:
89
+ # Green theorem is not defined on open contours.
90
+ raise NotImplementedError
91
+
92
+ def _lineTo(self, p1):
93
+ p0 = self._getCurrentPoint()
94
+ self.value += self._funcs[1](p0, p1)
95
+
96
+ def _qCurveToOne(self, p1, p2):
97
+ p0 = self._getCurrentPoint()
98
+ self.value += self._funcs[2](p0, p1, p2)
99
+
100
+ def _curveToOne(self, p1, p2, p3):
101
+ p0 = self._getCurrentPoint()
102
+ self.value += self._funcs[3](p0, p1, p2, p3)
103
+
104
+
105
+ # Sample pens.
106
+ # Do not use this in real code.
107
+ # Use fontTools.pens.momentsPen.MomentsPen instead.
108
+ AreaPen = partial(GreenPen, func=1)
109
+ MomentXPen = partial(GreenPen, func=x)
110
+ MomentYPen = partial(GreenPen, func=y)
111
+ MomentXXPen = partial(GreenPen, func=x * x)
112
+ MomentYYPen = partial(GreenPen, func=y * y)
113
+ MomentXYPen = partial(GreenPen, func=x * y)
114
+
115
+
116
+ def printGreenPen(penName, funcs, file=sys.stdout, docstring=None):
117
+ if docstring is not None:
118
+ print('"""%s"""' % docstring)
119
+
120
+ print(
121
+ """from fontTools.pens.basePen import BasePen, OpenContourError
122
+ try:
123
+ import cython
124
+ except (AttributeError, ImportError):
125
+ # if cython not installed, use mock module with no-op decorators and types
126
+ from fontTools.misc import cython
127
+ COMPILED = cython.compiled
128
+
129
+
130
+ __all__ = ["%s"]
131
+
132
+ class %s(BasePen):
133
+
134
+ def __init__(self, glyphset=None):
135
+ BasePen.__init__(self, glyphset)
136
+ """
137
+ % (penName, penName),
138
+ file=file,
139
+ )
140
+ for name, f in funcs:
141
+ print(" self.%s = 0" % name, file=file)
142
+ print(
143
+ """
144
+ def _moveTo(self, p0):
145
+ self._startPoint = p0
146
+
147
+ def _closePath(self):
148
+ p0 = self._getCurrentPoint()
149
+ if p0 != self._startPoint:
150
+ self._lineTo(self._startPoint)
151
+
152
+ def _endPath(self):
153
+ p0 = self._getCurrentPoint()
154
+ if p0 != self._startPoint:
155
+ raise OpenContourError(
156
+ "Glyph statistics is not defined on open contours."
157
+ )
158
+ """,
159
+ end="",
160
+ file=file,
161
+ )
162
+
163
+ for n in (1, 2, 3):
164
+ subs = {P[i][j]: [X, Y][j][i] for i in range(n + 1) for j in range(2)}
165
+ greens = [green(f, BezierCurve[n]) for name, f in funcs]
166
+ greens = [sp.gcd_terms(f.collect(sum(P, ()))) for f in greens] # Optimize
167
+ greens = [f.subs(subs) for f in greens] # Convert to p to x/y
168
+ defs, exprs = sp.cse(
169
+ greens,
170
+ optimizations="basic",
171
+ symbols=(sp.Symbol("r%d" % i) for i in count()),
172
+ )
173
+
174
+ print()
175
+ for name, value in defs:
176
+ print(" @cython.locals(%s=cython.double)" % name, file=file)
177
+ if n == 1:
178
+ print(
179
+ """\
180
+ @cython.locals(x0=cython.double, y0=cython.double)
181
+ @cython.locals(x1=cython.double, y1=cython.double)
182
+ def _lineTo(self, p1):
183
+ x0,y0 = self._getCurrentPoint()
184
+ x1,y1 = p1
185
+ """,
186
+ file=file,
187
+ )
188
+ elif n == 2:
189
+ print(
190
+ """\
191
+ @cython.locals(x0=cython.double, y0=cython.double)
192
+ @cython.locals(x1=cython.double, y1=cython.double)
193
+ @cython.locals(x2=cython.double, y2=cython.double)
194
+ def _qCurveToOne(self, p1, p2):
195
+ x0,y0 = self._getCurrentPoint()
196
+ x1,y1 = p1
197
+ x2,y2 = p2
198
+ """,
199
+ file=file,
200
+ )
201
+ elif n == 3:
202
+ print(
203
+ """\
204
+ @cython.locals(x0=cython.double, y0=cython.double)
205
+ @cython.locals(x1=cython.double, y1=cython.double)
206
+ @cython.locals(x2=cython.double, y2=cython.double)
207
+ @cython.locals(x3=cython.double, y3=cython.double)
208
+ def _curveToOne(self, p1, p2, p3):
209
+ x0,y0 = self._getCurrentPoint()
210
+ x1,y1 = p1
211
+ x2,y2 = p2
212
+ x3,y3 = p3
213
+ """,
214
+ file=file,
215
+ )
216
+ for name, value in defs:
217
+ print(" %s = %s" % (name, value), file=file)
218
+
219
+ print(file=file)
220
+ for name, value in zip([f[0] for f in funcs], exprs):
221
+ print(" self.%s += %s" % (name, value), file=file)
222
+
223
+ print(
224
+ """
225
+ if __name__ == '__main__':
226
+ from fontTools.misc.symfont import x, y, printGreenPen
227
+ printGreenPen('%s', ["""
228
+ % penName,
229
+ file=file,
230
+ )
231
+ for name, f in funcs:
232
+ print(" ('%s', %s)," % (name, str(f)), file=file)
233
+ print(" ])", file=file)
234
+
235
+
236
+ if __name__ == "__main__":
237
+ pen = AreaPen()
238
+ pen.moveTo((100, 100))
239
+ pen.lineTo((100, 200))
240
+ pen.lineTo((200, 200))
241
+ pen.curveTo((200, 250), (300, 300), (250, 350))
242
+ pen.lineTo((200, 100))
243
+ pen.closePath()
244
+ print(pen.value)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/testTools.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Helpers for writing unit tests."""
2
+
3
+ from collections.abc import Iterable
4
+ from io import BytesIO
5
+ import os
6
+ import re
7
+ import shutil
8
+ import sys
9
+ import tempfile
10
+ from unittest import TestCase as _TestCase
11
+ from fontTools.config import Config
12
+ from fontTools.misc.textTools import tobytes
13
+ from fontTools.misc.xmlWriter import XMLWriter
14
+
15
+
16
+ def parseXML(xmlSnippet):
17
+ """Parses a snippet of XML.
18
+
19
+ Input can be either a single string (unicode or UTF-8 bytes), or a
20
+ a sequence of strings.
21
+
22
+ The result is in the same format that would be returned by
23
+ XMLReader, but the parser imposes no constraints on the root
24
+ element so it can be called on small snippets of TTX files.
25
+ """
26
+ # To support snippets with multiple elements, we add a fake root.
27
+ reader = TestXMLReader_()
28
+ xml = b"<root>"
29
+ if isinstance(xmlSnippet, bytes):
30
+ xml += xmlSnippet
31
+ elif isinstance(xmlSnippet, str):
32
+ xml += tobytes(xmlSnippet, "utf-8")
33
+ elif isinstance(xmlSnippet, Iterable):
34
+ xml += b"".join(tobytes(s, "utf-8") for s in xmlSnippet)
35
+ else:
36
+ raise TypeError(
37
+ "expected string or sequence of strings; found %r"
38
+ % type(xmlSnippet).__name__
39
+ )
40
+ xml += b"</root>"
41
+ reader.parser.Parse(xml, 1)
42
+ return reader.root[2]
43
+
44
+
45
+ def parseXmlInto(font, parseInto, xmlSnippet):
46
+ parsed_xml = [e for e in parseXML(xmlSnippet.strip()) if not isinstance(e, str)]
47
+ for name, attrs, content in parsed_xml:
48
+ parseInto.fromXML(name, attrs, content, font)
49
+ parseInto.populateDefaults()
50
+ return parseInto
51
+
52
+
53
+ class FakeFont:
54
+ def __init__(self, glyphs):
55
+ self.glyphOrder_ = glyphs
56
+ self.reverseGlyphOrderDict_ = {g: i for i, g in enumerate(glyphs)}
57
+ self.lazy = False
58
+ self.tables = {}
59
+ self.cfg = Config()
60
+
61
+ def __getitem__(self, tag):
62
+ return self.tables[tag]
63
+
64
+ def __setitem__(self, tag, table):
65
+ self.tables[tag] = table
66
+
67
+ def get(self, tag, default=None):
68
+ return self.tables.get(tag, default)
69
+
70
+ def getGlyphID(self, name):
71
+ return self.reverseGlyphOrderDict_[name]
72
+
73
+ def getGlyphIDMany(self, lst):
74
+ return [self.getGlyphID(gid) for gid in lst]
75
+
76
+ def getGlyphName(self, glyphID):
77
+ if glyphID < len(self.glyphOrder_):
78
+ return self.glyphOrder_[glyphID]
79
+ else:
80
+ return "glyph%.5d" % glyphID
81
+
82
+ def getGlyphNameMany(self, lst):
83
+ return [self.getGlyphName(gid) for gid in lst]
84
+
85
+ def getGlyphOrder(self):
86
+ return self.glyphOrder_
87
+
88
+ def getReverseGlyphMap(self):
89
+ return self.reverseGlyphOrderDict_
90
+
91
+ def getGlyphNames(self):
92
+ return sorted(self.getGlyphOrder())
93
+
94
+
95
+ class TestXMLReader_(object):
96
+ def __init__(self):
97
+ from xml.parsers.expat import ParserCreate
98
+
99
+ self.parser = ParserCreate()
100
+ self.parser.StartElementHandler = self.startElement_
101
+ self.parser.EndElementHandler = self.endElement_
102
+ self.parser.CharacterDataHandler = self.addCharacterData_
103
+ self.root = None
104
+ self.stack = []
105
+
106
+ def startElement_(self, name, attrs):
107
+ element = (name, attrs, [])
108
+ if self.stack:
109
+ self.stack[-1][2].append(element)
110
+ else:
111
+ self.root = element
112
+ self.stack.append(element)
113
+
114
+ def endElement_(self, name):
115
+ self.stack.pop()
116
+
117
+ def addCharacterData_(self, data):
118
+ self.stack[-1][2].append(data)
119
+
120
+
121
+ def makeXMLWriter(newlinestr="\n"):
122
+ # don't write OS-specific new lines
123
+ writer = XMLWriter(BytesIO(), newlinestr=newlinestr)
124
+ # erase XML declaration
125
+ writer.file.seek(0)
126
+ writer.file.truncate()
127
+ return writer
128
+
129
+
130
+ def getXML(func, ttFont=None):
131
+ """Call the passed toXML function and return the written content as a
132
+ list of lines (unicode strings).
133
+ Result is stripped of XML declaration and OS-specific newline characters.
134
+ """
135
+ writer = makeXMLWriter()
136
+ func(writer, ttFont)
137
+ xml = writer.file.getvalue().decode("utf-8")
138
+ # toXML methods must always end with a writer.newline()
139
+ assert xml.endswith("\n")
140
+ return xml.splitlines()
141
+
142
+
143
+ def stripVariableItemsFromTTX(
144
+ string: str,
145
+ ttLibVersion: bool = True,
146
+ checkSumAdjustment: bool = True,
147
+ modified: bool = True,
148
+ created: bool = True,
149
+ sfntVersion: bool = False, # opt-in only
150
+ ) -> str:
151
+ """Strip stuff like ttLibVersion, checksums, timestamps, etc. from TTX dumps."""
152
+ # ttlib changes with the fontTools version
153
+ if ttLibVersion:
154
+ string = re.sub(' ttLibVersion="[^"]+"', "", string)
155
+ # sometimes (e.g. some subsetter tests) we don't care whether it's OTF or TTF
156
+ if sfntVersion:
157
+ string = re.sub(' sfntVersion="[^"]+"', "", string)
158
+ # head table checksum and creation and mod date changes with each save.
159
+ if checkSumAdjustment:
160
+ string = re.sub('<checkSumAdjustment value="[^"]+"/>', "", string)
161
+ if modified:
162
+ string = re.sub('<modified value="[^"]+"/>', "", string)
163
+ if created:
164
+ string = re.sub('<created value="[^"]+"/>', "", string)
165
+ return string
166
+
167
+
168
+ class MockFont(object):
169
+ """A font-like object that automatically adds any looked up glyphname
170
+ to its glyphOrder."""
171
+
172
+ def __init__(self):
173
+ self._glyphOrder = [".notdef"]
174
+
175
+ class AllocatingDict(dict):
176
+ def __missing__(reverseDict, key):
177
+ self._glyphOrder.append(key)
178
+ gid = len(reverseDict)
179
+ reverseDict[key] = gid
180
+ return gid
181
+
182
+ self._reverseGlyphOrder = AllocatingDict({".notdef": 0})
183
+ self.lazy = False
184
+
185
+ def getGlyphID(self, glyph):
186
+ gid = self._reverseGlyphOrder[glyph]
187
+ return gid
188
+
189
+ def getReverseGlyphMap(self):
190
+ return self._reverseGlyphOrder
191
+
192
+ def getGlyphName(self, gid):
193
+ return self._glyphOrder[gid]
194
+
195
+ def getGlyphOrder(self):
196
+ return self._glyphOrder
197
+
198
+
199
+ class TestCase(_TestCase):
200
+ def __init__(self, methodName):
201
+ _TestCase.__init__(self, methodName)
202
+ # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
203
+ # and fires deprecation warnings if a program uses the old name.
204
+ if not hasattr(self, "assertRaisesRegex"):
205
+ self.assertRaisesRegex = self.assertRaisesRegexp
206
+
207
+
208
+ class DataFilesHandler(TestCase):
209
+ def setUp(self):
210
+ self.tempdir = None
211
+ self.num_tempfiles = 0
212
+
213
+ def tearDown(self):
214
+ if self.tempdir:
215
+ shutil.rmtree(self.tempdir)
216
+
217
+ def getpath(self, testfile):
218
+ folder = os.path.dirname(sys.modules[self.__module__].__file__)
219
+ return os.path.join(folder, "data", testfile)
220
+
221
+ def temp_dir(self):
222
+ if not self.tempdir:
223
+ self.tempdir = tempfile.mkdtemp()
224
+
225
+ def temp_font(self, font_path, file_name):
226
+ self.temp_dir()
227
+ temppath = os.path.join(self.tempdir, file_name)
228
+ shutil.copy2(font_path, temppath)
229
+ return temppath
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/textTools.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """fontTools.misc.textTools.py -- miscellaneous routines."""
2
+
3
+ import ast
4
+ import string
5
+
6
+
7
+ # alias kept for backward compatibility
8
+ safeEval = ast.literal_eval
9
+
10
+
11
+ class Tag(str):
12
+ @staticmethod
13
+ def transcode(blob):
14
+ if isinstance(blob, bytes):
15
+ blob = blob.decode("latin-1")
16
+ return blob
17
+
18
+ def __new__(self, content):
19
+ return str.__new__(self, self.transcode(content))
20
+
21
+ def __ne__(self, other):
22
+ return not self.__eq__(other)
23
+
24
+ def __eq__(self, other):
25
+ return str.__eq__(self, self.transcode(other))
26
+
27
+ def __hash__(self):
28
+ return str.__hash__(self)
29
+
30
+ def tobytes(self):
31
+ return self.encode("latin-1")
32
+
33
+
34
+ def readHex(content):
35
+ """Convert a list of hex strings to binary data."""
36
+ return deHexStr(strjoin(chunk for chunk in content if isinstance(chunk, str)))
37
+
38
+
39
+ def deHexStr(hexdata):
40
+ """Convert a hex string to binary data."""
41
+ hexdata = strjoin(hexdata.split())
42
+ if len(hexdata) % 2:
43
+ hexdata = hexdata + "0"
44
+ data = []
45
+ for i in range(0, len(hexdata), 2):
46
+ data.append(bytechr(int(hexdata[i : i + 2], 16)))
47
+ return bytesjoin(data)
48
+
49
+
50
+ def hexStr(data):
51
+ """Convert binary data to a hex string."""
52
+ h = string.hexdigits
53
+ r = ""
54
+ for c in data:
55
+ i = byteord(c)
56
+ r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
57
+ return r
58
+
59
+
60
+ def num2binary(l, bits=32):
61
+ items = []
62
+ binary = ""
63
+ for i in range(bits):
64
+ if l & 0x1:
65
+ binary = "1" + binary
66
+ else:
67
+ binary = "0" + binary
68
+ l = l >> 1
69
+ if not ((i + 1) % 8):
70
+ items.append(binary)
71
+ binary = ""
72
+ if binary:
73
+ items.append(binary)
74
+ items.reverse()
75
+ assert l in (0, -1), "number doesn't fit in number of bits"
76
+ return " ".join(items)
77
+
78
+
79
+ def binary2num(bin):
80
+ bin = strjoin(bin.split())
81
+ l = 0
82
+ for digit in bin:
83
+ l = l << 1
84
+ if digit != "0":
85
+ l = l | 0x1
86
+ return l
87
+
88
+
89
+ def caselessSort(alist):
90
+ """Return a sorted copy of a list. If there are only strings
91
+ in the list, it will not consider case.
92
+ """
93
+
94
+ try:
95
+ return sorted(alist, key=lambda a: (a.lower(), a))
96
+ except TypeError:
97
+ return sorted(alist)
98
+
99
+
100
+ def pad(data, size):
101
+ r"""Pad byte string 'data' with null bytes until its length is a
102
+ multiple of 'size'.
103
+
104
+ >>> len(pad(b'abcd', 4))
105
+ 4
106
+ >>> len(pad(b'abcde', 2))
107
+ 6
108
+ >>> len(pad(b'abcde', 4))
109
+ 8
110
+ >>> pad(b'abcdef', 4) == b'abcdef\x00\x00'
111
+ True
112
+ """
113
+ data = tobytes(data)
114
+ if size > 1:
115
+ remainder = len(data) % size
116
+ if remainder:
117
+ data += b"\0" * (size - remainder)
118
+ return data
119
+
120
+
121
+ def tostr(s, encoding="ascii", errors="strict"):
122
+ if not isinstance(s, str):
123
+ return s.decode(encoding, errors)
124
+ else:
125
+ return s
126
+
127
+
128
+ def tobytes(s, encoding="ascii", errors="strict"):
129
+ if isinstance(s, str):
130
+ return s.encode(encoding, errors)
131
+ else:
132
+ return bytes(s)
133
+
134
+
135
+ def bytechr(n):
136
+ return bytes([n])
137
+
138
+
139
+ def byteord(c):
140
+ return c if isinstance(c, int) else ord(c)
141
+
142
+
143
+ def strjoin(iterable, joiner=""):
144
+ return tostr(joiner).join(iterable)
145
+
146
+
147
+ def bytesjoin(iterable, joiner=b""):
148
+ return tobytes(joiner).join(tobytes(item) for item in iterable)
149
+
150
+
151
+ if __name__ == "__main__":
152
+ import doctest, sys
153
+
154
+ sys.exit(doctest.testmod().failed)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/timeTools.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """fontTools.misc.timeTools.py -- tools for working with OpenType timestamps.
2
+ """
3
+
4
+ import os
5
+ import time
6
+ from datetime import datetime, timezone
7
+ import calendar
8
+
9
+
10
+ epoch_diff = calendar.timegm((1904, 1, 1, 0, 0, 0, 0, 0, 0))
11
+
12
+ DAYNAMES = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
13
+ MONTHNAMES = [
14
+ None,
15
+ "Jan",
16
+ "Feb",
17
+ "Mar",
18
+ "Apr",
19
+ "May",
20
+ "Jun",
21
+ "Jul",
22
+ "Aug",
23
+ "Sep",
24
+ "Oct",
25
+ "Nov",
26
+ "Dec",
27
+ ]
28
+
29
+
30
+ def asctime(t=None):
31
+ """
32
+ Convert a tuple or struct_time representing a time as returned by gmtime()
33
+ or localtime() to a 24-character string of the following form:
34
+
35
+ >>> asctime(time.gmtime(0))
36
+ 'Thu Jan 1 00:00:00 1970'
37
+
38
+ If t is not provided, the current time as returned by localtime() is used.
39
+ Locale information is not used by asctime().
40
+
41
+ This is meant to normalise the output of the built-in time.asctime() across
42
+ different platforms and Python versions.
43
+ In Python 3.x, the day of the month is right-justified, whereas on Windows
44
+ Python 2.7 it is padded with zeros.
45
+
46
+ See https://github.com/fonttools/fonttools/issues/455
47
+ """
48
+ if t is None:
49
+ t = time.localtime()
50
+ s = "%s %s %2s %s" % (
51
+ DAYNAMES[t.tm_wday],
52
+ MONTHNAMES[t.tm_mon],
53
+ t.tm_mday,
54
+ time.strftime("%H:%M:%S %Y", t),
55
+ )
56
+ return s
57
+
58
+
59
+ def timestampToString(value):
60
+ return asctime(time.gmtime(max(0, value + epoch_diff)))
61
+
62
+
63
+ def timestampFromString(value):
64
+ wkday, mnth = value[:7].split()
65
+ t = datetime.strptime(value[7:], " %d %H:%M:%S %Y")
66
+ t = t.replace(month=MONTHNAMES.index(mnth), tzinfo=timezone.utc)
67
+ wkday_idx = DAYNAMES.index(wkday)
68
+ assert t.weekday() == wkday_idx, '"' + value + '" has inconsistent weekday'
69
+ return int(t.timestamp()) - epoch_diff
70
+
71
+
72
+ def timestampNow():
73
+ # https://reproducible-builds.org/specs/source-date-epoch/
74
+ source_date_epoch = os.environ.get("SOURCE_DATE_EPOCH")
75
+ if source_date_epoch is not None:
76
+ return int(source_date_epoch) - epoch_diff
77
+ return int(time.time() - epoch_diff)
78
+
79
+
80
+ def timestampSinceEpoch(value):
81
+ return int(value - epoch_diff)
82
+
83
+
84
+ if __name__ == "__main__":
85
+ import sys
86
+ import doctest
87
+
88
+ sys.exit(doctest.testmod().failed)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/transform.py ADDED
@@ -0,0 +1,518 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Affine 2D transformation matrix class.
2
+
3
+ The Transform class implements various transformation matrix operations,
4
+ both on the matrix itself, as well as on 2D coordinates.
5
+
6
+ Transform instances are effectively immutable: all methods that operate on the
7
+ transformation itself always return a new instance. This has as the
8
+ interesting side effect that Transform instances are hashable, ie. they can be
9
+ used as dictionary keys.
10
+
11
+ This module exports the following symbols:
12
+
13
+ Transform
14
+ this is the main class
15
+ Identity
16
+ Transform instance set to the identity transformation
17
+ Offset
18
+ Convenience function that returns a translating transformation
19
+ Scale
20
+ Convenience function that returns a scaling transformation
21
+
22
+ The DecomposedTransform class implements a transformation with separate
23
+ translate, rotation, scale, skew, and transformation-center components.
24
+
25
+ :Example:
26
+
27
+ >>> t = Transform(2, 0, 0, 3, 0, 0)
28
+ >>> t.transformPoint((100, 100))
29
+ (200, 300)
30
+ >>> t = Scale(2, 3)
31
+ >>> t.transformPoint((100, 100))
32
+ (200, 300)
33
+ >>> t.transformPoint((0, 0))
34
+ (0, 0)
35
+ >>> t = Offset(2, 3)
36
+ >>> t.transformPoint((100, 100))
37
+ (102, 103)
38
+ >>> t.transformPoint((0, 0))
39
+ (2, 3)
40
+ >>> t2 = t.scale(0.5)
41
+ >>> t2.transformPoint((100, 100))
42
+ (52.0, 53.0)
43
+ >>> import math
44
+ >>> t3 = t2.rotate(math.pi / 2)
45
+ >>> t3.transformPoint((0, 0))
46
+ (2.0, 3.0)
47
+ >>> t3.transformPoint((100, 100))
48
+ (-48.0, 53.0)
49
+ >>> t = Identity.scale(0.5).translate(100, 200).skew(0.1, 0.2)
50
+ >>> t.transformPoints([(0, 0), (1, 1), (100, 100)])
51
+ [(50.0, 100.0), (50.550167336042726, 100.60135501775433), (105.01673360427253, 160.13550177543362)]
52
+ >>>
53
+ """
54
+
55
+ import math
56
+ from typing import NamedTuple
57
+ from dataclasses import dataclass
58
+
59
+
60
+ __all__ = ["Transform", "Identity", "Offset", "Scale", "DecomposedTransform"]
61
+
62
+
63
+ _EPSILON = 1e-15
64
+ _ONE_EPSILON = 1 - _EPSILON
65
+ _MINUS_ONE_EPSILON = -1 + _EPSILON
66
+
67
+
68
+ def _normSinCos(v):
69
+ if abs(v) < _EPSILON:
70
+ v = 0
71
+ elif v > _ONE_EPSILON:
72
+ v = 1
73
+ elif v < _MINUS_ONE_EPSILON:
74
+ v = -1
75
+ return v
76
+
77
+
78
+ class Transform(NamedTuple):
79
+ """2x2 transformation matrix plus offset, a.k.a. Affine transform.
80
+ Transform instances are immutable: all transforming methods, eg.
81
+ rotate(), return a new Transform instance.
82
+
83
+ :Example:
84
+
85
+ >>> t = Transform()
86
+ >>> t
87
+ <Transform [1 0 0 1 0 0]>
88
+ >>> t.scale(2)
89
+ <Transform [2 0 0 2 0 0]>
90
+ >>> t.scale(2.5, 5.5)
91
+ <Transform [2.5 0 0 5.5 0 0]>
92
+ >>>
93
+ >>> t.scale(2, 3).transformPoint((100, 100))
94
+ (200, 300)
95
+
96
+ Transform's constructor takes six arguments, all of which are
97
+ optional, and can be used as keyword arguments::
98
+
99
+ >>> Transform(12)
100
+ <Transform [12 0 0 1 0 0]>
101
+ >>> Transform(dx=12)
102
+ <Transform [1 0 0 1 12 0]>
103
+ >>> Transform(yx=12)
104
+ <Transform [1 0 12 1 0 0]>
105
+
106
+ Transform instances also behave like sequences of length 6::
107
+
108
+ >>> len(Identity)
109
+ 6
110
+ >>> list(Identity)
111
+ [1, 0, 0, 1, 0, 0]
112
+ >>> tuple(Identity)
113
+ (1, 0, 0, 1, 0, 0)
114
+
115
+ Transform instances are comparable::
116
+
117
+ >>> t1 = Identity.scale(2, 3).translate(4, 6)
118
+ >>> t2 = Identity.translate(8, 18).scale(2, 3)
119
+ >>> t1 == t2
120
+ 1
121
+
122
+ But beware of floating point rounding errors::
123
+
124
+ >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
125
+ >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
126
+ >>> t1
127
+ <Transform [0.2 0 0 0.3 0.08 0.18]>
128
+ >>> t2
129
+ <Transform [0.2 0 0 0.3 0.08 0.18]>
130
+ >>> t1 == t2
131
+ 0
132
+
133
+ Transform instances are hashable, meaning you can use them as
134
+ keys in dictionaries::
135
+
136
+ >>> d = {Scale(12, 13): None}
137
+ >>> d
138
+ {<Transform [12 0 0 13 0 0]>: None}
139
+
140
+ But again, beware of floating point rounding errors::
141
+
142
+ >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
143
+ >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
144
+ >>> t1
145
+ <Transform [0.2 0 0 0.3 0.08 0.18]>
146
+ >>> t2
147
+ <Transform [0.2 0 0 0.3 0.08 0.18]>
148
+ >>> d = {t1: None}
149
+ >>> d
150
+ {<Transform [0.2 0 0 0.3 0.08 0.18]>: None}
151
+ >>> d[t2]
152
+ Traceback (most recent call last):
153
+ File "<stdin>", line 1, in ?
154
+ KeyError: <Transform [0.2 0 0 0.3 0.08 0.18]>
155
+ """
156
+
157
+ xx: float = 1
158
+ xy: float = 0
159
+ yx: float = 0
160
+ yy: float = 1
161
+ dx: float = 0
162
+ dy: float = 0
163
+
164
+ def transformPoint(self, p):
165
+ """Transform a point.
166
+
167
+ :Example:
168
+
169
+ >>> t = Transform()
170
+ >>> t = t.scale(2.5, 5.5)
171
+ >>> t.transformPoint((100, 100))
172
+ (250.0, 550.0)
173
+ """
174
+ (x, y) = p
175
+ xx, xy, yx, yy, dx, dy = self
176
+ return (xx * x + yx * y + dx, xy * x + yy * y + dy)
177
+
178
+ def transformPoints(self, points):
179
+ """Transform a list of points.
180
+
181
+ :Example:
182
+
183
+ >>> t = Scale(2, 3)
184
+ >>> t.transformPoints([(0, 0), (0, 100), (100, 100), (100, 0)])
185
+ [(0, 0), (0, 300), (200, 300), (200, 0)]
186
+ >>>
187
+ """
188
+ xx, xy, yx, yy, dx, dy = self
189
+ return [(xx * x + yx * y + dx, xy * x + yy * y + dy) for x, y in points]
190
+
191
+ def transformVector(self, v):
192
+ """Transform an (dx, dy) vector, treating translation as zero.
193
+
194
+ :Example:
195
+
196
+ >>> t = Transform(2, 0, 0, 2, 10, 20)
197
+ >>> t.transformVector((3, -4))
198
+ (6, -8)
199
+ >>>
200
+ """
201
+ (dx, dy) = v
202
+ xx, xy, yx, yy = self[:4]
203
+ return (xx * dx + yx * dy, xy * dx + yy * dy)
204
+
205
+ def transformVectors(self, vectors):
206
+ """Transform a list of (dx, dy) vector, treating translation as zero.
207
+
208
+ :Example:
209
+ >>> t = Transform(2, 0, 0, 2, 10, 20)
210
+ >>> t.transformVectors([(3, -4), (5, -6)])
211
+ [(6, -8), (10, -12)]
212
+ >>>
213
+ """
214
+ xx, xy, yx, yy = self[:4]
215
+ return [(xx * dx + yx * dy, xy * dx + yy * dy) for dx, dy in vectors]
216
+
217
+ def translate(self, x=0, y=0):
218
+ """Return a new transformation, translated (offset) by x, y.
219
+
220
+ :Example:
221
+ >>> t = Transform()
222
+ >>> t.translate(20, 30)
223
+ <Transform [1 0 0 1 20 30]>
224
+ >>>
225
+ """
226
+ return self.transform((1, 0, 0, 1, x, y))
227
+
228
+ def scale(self, x=1, y=None):
229
+ """Return a new transformation, scaled by x, y. The 'y' argument
230
+ may be None, which implies to use the x value for y as well.
231
+
232
+ :Example:
233
+ >>> t = Transform()
234
+ >>> t.scale(5)
235
+ <Transform [5 0 0 5 0 0]>
236
+ >>> t.scale(5, 6)
237
+ <Transform [5 0 0 6 0 0]>
238
+ >>>
239
+ """
240
+ if y is None:
241
+ y = x
242
+ return self.transform((x, 0, 0, y, 0, 0))
243
+
244
+ def rotate(self, angle):
245
+ """Return a new transformation, rotated by 'angle' (radians).
246
+
247
+ :Example:
248
+ >>> import math
249
+ >>> t = Transform()
250
+ >>> t.rotate(math.pi / 2)
251
+ <Transform [0 1 -1 0 0 0]>
252
+ >>>
253
+ """
254
+ import math
255
+
256
+ c = _normSinCos(math.cos(angle))
257
+ s = _normSinCos(math.sin(angle))
258
+ return self.transform((c, s, -s, c, 0, 0))
259
+
260
+ def skew(self, x=0, y=0):
261
+ """Return a new transformation, skewed by x and y.
262
+
263
+ :Example:
264
+ >>> import math
265
+ >>> t = Transform()
266
+ >>> t.skew(math.pi / 4)
267
+ <Transform [1 0 1 1 0 0]>
268
+ >>>
269
+ """
270
+ import math
271
+
272
+ return self.transform((1, math.tan(y), math.tan(x), 1, 0, 0))
273
+
274
+ def transform(self, other):
275
+ """Return a new transformation, transformed by another
276
+ transformation.
277
+
278
+ :Example:
279
+ >>> t = Transform(2, 0, 0, 3, 1, 6)
280
+ >>> t.transform((4, 3, 2, 1, 5, 6))
281
+ <Transform [8 9 4 3 11 24]>
282
+ >>>
283
+ """
284
+ xx1, xy1, yx1, yy1, dx1, dy1 = other
285
+ xx2, xy2, yx2, yy2, dx2, dy2 = self
286
+ return self.__class__(
287
+ xx1 * xx2 + xy1 * yx2,
288
+ xx1 * xy2 + xy1 * yy2,
289
+ yx1 * xx2 + yy1 * yx2,
290
+ yx1 * xy2 + yy1 * yy2,
291
+ xx2 * dx1 + yx2 * dy1 + dx2,
292
+ xy2 * dx1 + yy2 * dy1 + dy2,
293
+ )
294
+
295
+ def reverseTransform(self, other):
296
+ """Return a new transformation, which is the other transformation
297
+ transformed by self. self.reverseTransform(other) is equivalent to
298
+ other.transform(self).
299
+
300
+ :Example:
301
+ >>> t = Transform(2, 0, 0, 3, 1, 6)
302
+ >>> t.reverseTransform((4, 3, 2, 1, 5, 6))
303
+ <Transform [8 6 6 3 21 15]>
304
+ >>> Transform(4, 3, 2, 1, 5, 6).transform((2, 0, 0, 3, 1, 6))
305
+ <Transform [8 6 6 3 21 15]>
306
+ >>>
307
+ """
308
+ xx1, xy1, yx1, yy1, dx1, dy1 = self
309
+ xx2, xy2, yx2, yy2, dx2, dy2 = other
310
+ return self.__class__(
311
+ xx1 * xx2 + xy1 * yx2,
312
+ xx1 * xy2 + xy1 * yy2,
313
+ yx1 * xx2 + yy1 * yx2,
314
+ yx1 * xy2 + yy1 * yy2,
315
+ xx2 * dx1 + yx2 * dy1 + dx2,
316
+ xy2 * dx1 + yy2 * dy1 + dy2,
317
+ )
318
+
319
+ def inverse(self):
320
+ """Return the inverse transformation.
321
+
322
+ :Example:
323
+ >>> t = Identity.translate(2, 3).scale(4, 5)
324
+ >>> t.transformPoint((10, 20))
325
+ (42, 103)
326
+ >>> it = t.inverse()
327
+ >>> it.transformPoint((42, 103))
328
+ (10.0, 20.0)
329
+ >>>
330
+ """
331
+ if self == Identity:
332
+ return self
333
+ xx, xy, yx, yy, dx, dy = self
334
+ det = xx * yy - yx * xy
335
+ xx, xy, yx, yy = yy / det, -xy / det, -yx / det, xx / det
336
+ dx, dy = -xx * dx - yx * dy, -xy * dx - yy * dy
337
+ return self.__class__(xx, xy, yx, yy, dx, dy)
338
+
339
+ def toPS(self):
340
+ """Return a PostScript representation
341
+
342
+ :Example:
343
+
344
+ >>> t = Identity.scale(2, 3).translate(4, 5)
345
+ >>> t.toPS()
346
+ '[2 0 0 3 8 15]'
347
+ >>>
348
+ """
349
+ return "[%s %s %s %s %s %s]" % self
350
+
351
+ def toDecomposed(self) -> "DecomposedTransform":
352
+ """Decompose into a DecomposedTransform."""
353
+ return DecomposedTransform.fromTransform(self)
354
+
355
+ def __bool__(self):
356
+ """Returns True if transform is not identity, False otherwise.
357
+
358
+ :Example:
359
+
360
+ >>> bool(Identity)
361
+ False
362
+ >>> bool(Transform())
363
+ False
364
+ >>> bool(Scale(1.))
365
+ False
366
+ >>> bool(Scale(2))
367
+ True
368
+ >>> bool(Offset())
369
+ False
370
+ >>> bool(Offset(0))
371
+ False
372
+ >>> bool(Offset(2))
373
+ True
374
+ """
375
+ return self != Identity
376
+
377
+ def __repr__(self):
378
+ return "<%s [%g %g %g %g %g %g]>" % ((self.__class__.__name__,) + self)
379
+
380
+
381
+ Identity = Transform()
382
+
383
+
384
+ def Offset(x=0, y=0):
385
+ """Return the identity transformation offset by x, y.
386
+
387
+ :Example:
388
+ >>> Offset(2, 3)
389
+ <Transform [1 0 0 1 2 3]>
390
+ >>>
391
+ """
392
+ return Transform(1, 0, 0, 1, x, y)
393
+
394
+
395
+ def Scale(x, y=None):
396
+ """Return the identity transformation scaled by x, y. The 'y' argument
397
+ may be None, which implies to use the x value for y as well.
398
+
399
+ :Example:
400
+ >>> Scale(2, 3)
401
+ <Transform [2 0 0 3 0 0]>
402
+ >>>
403
+ """
404
+ if y is None:
405
+ y = x
406
+ return Transform(x, 0, 0, y, 0, 0)
407
+
408
+
409
+ @dataclass
410
+ class DecomposedTransform:
411
+ """The DecomposedTransform class implements a transformation with separate
412
+ translate, rotation, scale, skew, and transformation-center components.
413
+ """
414
+
415
+ translateX: float = 0
416
+ translateY: float = 0
417
+ rotation: float = 0 # in degrees, counter-clockwise
418
+ scaleX: float = 1
419
+ scaleY: float = 1
420
+ skewX: float = 0 # in degrees, clockwise
421
+ skewY: float = 0 # in degrees, counter-clockwise
422
+ tCenterX: float = 0
423
+ tCenterY: float = 0
424
+
425
+ def __bool__(self):
426
+ return (
427
+ self.translateX != 0
428
+ or self.translateY != 0
429
+ or self.rotation != 0
430
+ or self.scaleX != 1
431
+ or self.scaleY != 1
432
+ or self.skewX != 0
433
+ or self.skewY != 0
434
+ or self.tCenterX != 0
435
+ or self.tCenterY != 0
436
+ )
437
+
438
+ @classmethod
439
+ def fromTransform(self, transform):
440
+ """Return a DecomposedTransform() equivalent of this transformation.
441
+ The returned solution always has skewY = 0, and angle in the (-180, 180].
442
+
443
+ :Example:
444
+ >>> DecomposedTransform.fromTransform(Transform(3, 0, 0, 2, 0, 0))
445
+ DecomposedTransform(translateX=0, translateY=0, rotation=0.0, scaleX=3.0, scaleY=2.0, skewX=0.0, skewY=0.0, tCenterX=0, tCenterY=0)
446
+ >>> DecomposedTransform.fromTransform(Transform(0, 0, 0, 1, 0, 0))
447
+ DecomposedTransform(translateX=0, translateY=0, rotation=0.0, scaleX=0.0, scaleY=1.0, skewX=0.0, skewY=0.0, tCenterX=0, tCenterY=0)
448
+ >>> DecomposedTransform.fromTransform(Transform(0, 0, 1, 1, 0, 0))
449
+ DecomposedTransform(translateX=0, translateY=0, rotation=-45.0, scaleX=0.0, scaleY=1.4142135623730951, skewX=0.0, skewY=0.0, tCenterX=0, tCenterY=0)
450
+ """
451
+ # Adapted from an answer on
452
+ # https://math.stackexchange.com/questions/13150/extracting-rotation-scale-values-from-2d-transformation-matrix
453
+
454
+ a, b, c, d, x, y = transform
455
+
456
+ sx = math.copysign(1, a)
457
+ if sx < 0:
458
+ a *= sx
459
+ b *= sx
460
+
461
+ delta = a * d - b * c
462
+
463
+ rotation = 0
464
+ scaleX = scaleY = 0
465
+ skewX = 0
466
+
467
+ # Apply the QR-like decomposition.
468
+ if a != 0 or b != 0:
469
+ r = math.sqrt(a * a + b * b)
470
+ rotation = math.acos(a / r) if b >= 0 else -math.acos(a / r)
471
+ scaleX, scaleY = (r, delta / r)
472
+ skewX = math.atan((a * c + b * d) / (r * r))
473
+ elif c != 0 or d != 0:
474
+ s = math.sqrt(c * c + d * d)
475
+ rotation = math.pi / 2 - (
476
+ math.acos(-c / s) if d >= 0 else -math.acos(c / s)
477
+ )
478
+ scaleX, scaleY = (delta / s, s)
479
+ else:
480
+ # a = b = c = d = 0
481
+ pass
482
+
483
+ return DecomposedTransform(
484
+ x,
485
+ y,
486
+ math.degrees(rotation),
487
+ scaleX * sx,
488
+ scaleY,
489
+ math.degrees(skewX) * sx,
490
+ 0.0,
491
+ 0,
492
+ 0,
493
+ )
494
+
495
+ def toTransform(self):
496
+ """Return the Transform() equivalent of this transformation.
497
+
498
+ :Example:
499
+ >>> DecomposedTransform(scaleX=2, scaleY=2).toTransform()
500
+ <Transform [2 0 0 2 0 0]>
501
+ >>>
502
+ """
503
+ t = Transform()
504
+ t = t.translate(
505
+ self.translateX + self.tCenterX, self.translateY + self.tCenterY
506
+ )
507
+ t = t.rotate(math.radians(self.rotation))
508
+ t = t.scale(self.scaleX, self.scaleY)
509
+ t = t.skew(math.radians(self.skewX), math.radians(self.skewY))
510
+ t = t.translate(-self.tCenterX, -self.tCenterY)
511
+ return t
512
+
513
+
514
+ if __name__ == "__main__":
515
+ import sys
516
+ import doctest
517
+
518
+ sys.exit(doctest.testmod().failed)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/treeTools.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generic tools for working with trees."""
2
+
3
+ from math import ceil, log
4
+
5
+
6
+ def build_n_ary_tree(leaves, n):
7
+ """Build N-ary tree from sequence of leaf nodes.
8
+
9
+ Return a list of lists where each non-leaf node is a list containing
10
+ max n nodes.
11
+ """
12
+ if not leaves:
13
+ return []
14
+
15
+ assert n > 1
16
+
17
+ depth = ceil(log(len(leaves), n))
18
+
19
+ if depth <= 1:
20
+ return list(leaves)
21
+
22
+ # Fully populate complete subtrees of root until we have enough leaves left
23
+ root = []
24
+ unassigned = None
25
+ full_step = n ** (depth - 1)
26
+ for i in range(0, len(leaves), full_step):
27
+ subtree = leaves[i : i + full_step]
28
+ if len(subtree) < full_step:
29
+ unassigned = subtree
30
+ break
31
+ while len(subtree) > n:
32
+ subtree = [subtree[k : k + n] for k in range(0, len(subtree), n)]
33
+ root.append(subtree)
34
+
35
+ if unassigned:
36
+ # Recurse to fill the last subtree, which is the only partially populated one
37
+ subtree = build_n_ary_tree(unassigned, n)
38
+ if len(subtree) <= n - len(root):
39
+ # replace last subtree with its children if they can still fit
40
+ root.extend(subtree)
41
+ else:
42
+ root.append(subtree)
43
+ assert len(root) <= n
44
+
45
+ return root
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/vector.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from numbers import Number
2
+ import math
3
+ import operator
4
+ import warnings
5
+
6
+
7
+ __all__ = ["Vector"]
8
+
9
+
10
+ class Vector(tuple):
11
+ """A math-like vector.
12
+
13
+ Represents an n-dimensional numeric vector. ``Vector`` objects support
14
+ vector addition and subtraction, scalar multiplication and division,
15
+ negation, rounding, and comparison tests.
16
+ """
17
+
18
+ __slots__ = ()
19
+
20
+ def __new__(cls, values, keep=False):
21
+ if keep is not False:
22
+ warnings.warn(
23
+ "the 'keep' argument has been deprecated",
24
+ DeprecationWarning,
25
+ )
26
+ if type(values) == Vector:
27
+ # No need to create a new object
28
+ return values
29
+ return super().__new__(cls, values)
30
+
31
+ def __repr__(self):
32
+ return f"{self.__class__.__name__}({super().__repr__()})"
33
+
34
+ def _vectorOp(self, other, op):
35
+ if isinstance(other, Vector):
36
+ assert len(self) == len(other)
37
+ return self.__class__(op(a, b) for a, b in zip(self, other))
38
+ if isinstance(other, Number):
39
+ return self.__class__(op(v, other) for v in self)
40
+ raise NotImplementedError()
41
+
42
+ def _scalarOp(self, other, op):
43
+ if isinstance(other, Number):
44
+ return self.__class__(op(v, other) for v in self)
45
+ raise NotImplementedError()
46
+
47
+ def _unaryOp(self, op):
48
+ return self.__class__(op(v) for v in self)
49
+
50
+ def __add__(self, other):
51
+ return self._vectorOp(other, operator.add)
52
+
53
+ __radd__ = __add__
54
+
55
+ def __sub__(self, other):
56
+ return self._vectorOp(other, operator.sub)
57
+
58
+ def __rsub__(self, other):
59
+ return self._vectorOp(other, _operator_rsub)
60
+
61
+ def __mul__(self, other):
62
+ return self._scalarOp(other, operator.mul)
63
+
64
+ __rmul__ = __mul__
65
+
66
+ def __truediv__(self, other):
67
+ return self._scalarOp(other, operator.truediv)
68
+
69
+ def __rtruediv__(self, other):
70
+ return self._scalarOp(other, _operator_rtruediv)
71
+
72
+ def __pos__(self):
73
+ return self._unaryOp(operator.pos)
74
+
75
+ def __neg__(self):
76
+ return self._unaryOp(operator.neg)
77
+
78
+ def __round__(self, *, round=round):
79
+ return self._unaryOp(round)
80
+
81
+ def __eq__(self, other):
82
+ if isinstance(other, list):
83
+ # bw compat Vector([1, 2, 3]) == [1, 2, 3]
84
+ other = tuple(other)
85
+ return super().__eq__(other)
86
+
87
+ def __ne__(self, other):
88
+ return not self.__eq__(other)
89
+
90
+ def __bool__(self):
91
+ return any(self)
92
+
93
+ __nonzero__ = __bool__
94
+
95
+ def __abs__(self):
96
+ return math.sqrt(sum(x * x for x in self))
97
+
98
+ def length(self):
99
+ """Return the length of the vector. Equivalent to abs(vector)."""
100
+ return abs(self)
101
+
102
+ def normalized(self):
103
+ """Return the normalized vector of the vector."""
104
+ return self / abs(self)
105
+
106
+ def dot(self, other):
107
+ """Performs vector dot product, returning the sum of
108
+ ``a[0] * b[0], a[1] * b[1], ...``"""
109
+ assert len(self) == len(other)
110
+ return sum(a * b for a, b in zip(self, other))
111
+
112
+ # Deprecated methods/properties
113
+
114
+ def toInt(self):
115
+ warnings.warn(
116
+ "the 'toInt' method has been deprecated, use round(vector) instead",
117
+ DeprecationWarning,
118
+ )
119
+ return self.__round__()
120
+
121
+ @property
122
+ def values(self):
123
+ warnings.warn(
124
+ "the 'values' attribute has been deprecated, use "
125
+ "the vector object itself instead",
126
+ DeprecationWarning,
127
+ )
128
+ return list(self)
129
+
130
+ @values.setter
131
+ def values(self, values):
132
+ raise AttributeError(
133
+ "can't set attribute, the 'values' attribute has been deprecated",
134
+ )
135
+
136
+ def isclose(self, other: "Vector", **kwargs) -> bool:
137
+ """Return True if the vector is close to another Vector."""
138
+ assert len(self) == len(other)
139
+ return all(math.isclose(a, b, **kwargs) for a, b in zip(self, other))
140
+
141
+
142
+ def _operator_rsub(a, b):
143
+ return operator.sub(b, a)
144
+
145
+
146
+ def _operator_rtruediv(a, b):
147
+ return operator.truediv(b, a)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/visitor.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generic visitor pattern implementation for Python objects."""
2
+
3
+ import enum
4
+
5
+
6
+ class Visitor(object):
7
+ defaultStop = False
8
+
9
+ @classmethod
10
+ def _register(celf, clazzes_attrs):
11
+ assert celf != Visitor, "Subclass Visitor instead."
12
+ if "_visitors" not in celf.__dict__:
13
+ celf._visitors = {}
14
+
15
+ def wrapper(method):
16
+ assert method.__name__ == "visit"
17
+ for clazzes, attrs in clazzes_attrs:
18
+ if type(clazzes) != tuple:
19
+ clazzes = (clazzes,)
20
+ if type(attrs) == str:
21
+ attrs = (attrs,)
22
+ for clazz in clazzes:
23
+ _visitors = celf._visitors.setdefault(clazz, {})
24
+ for attr in attrs:
25
+ assert attr not in _visitors, (
26
+ "Oops, class '%s' has visitor function for '%s' defined already."
27
+ % (clazz.__name__, attr)
28
+ )
29
+ _visitors[attr] = method
30
+ return None
31
+
32
+ return wrapper
33
+
34
+ @classmethod
35
+ def register(celf, clazzes):
36
+ if type(clazzes) != tuple:
37
+ clazzes = (clazzes,)
38
+ return celf._register([(clazzes, (None,))])
39
+
40
+ @classmethod
41
+ def register_attr(celf, clazzes, attrs):
42
+ clazzes_attrs = []
43
+ if type(clazzes) != tuple:
44
+ clazzes = (clazzes,)
45
+ if type(attrs) == str:
46
+ attrs = (attrs,)
47
+ for clazz in clazzes:
48
+ clazzes_attrs.append((clazz, attrs))
49
+ return celf._register(clazzes_attrs)
50
+
51
+ @classmethod
52
+ def register_attrs(celf, clazzes_attrs):
53
+ return celf._register(clazzes_attrs)
54
+
55
+ @classmethod
56
+ def _visitorsFor(celf, thing, _default={}):
57
+ typ = type(thing)
58
+
59
+ for celf in celf.mro():
60
+ _visitors = getattr(celf, "_visitors", None)
61
+ if _visitors is None:
62
+ break
63
+
64
+ for base in typ.mro():
65
+ m = celf._visitors.get(base, None)
66
+ if m is not None:
67
+ return m
68
+
69
+ return _default
70
+
71
+ def visitObject(self, obj, *args, **kwargs):
72
+ """Called to visit an object. This function loops over all non-private
73
+ attributes of the objects and calls any user-registered (via
74
+ @register_attr() or @register_attrs()) visit() functions.
75
+
76
+ If there is no user-registered visit function, of if there is and it
77
+ returns True, or it returns None (or doesn't return anything) and
78
+ visitor.defaultStop is False (default), then the visitor will proceed
79
+ to call self.visitAttr()"""
80
+
81
+ keys = sorted(vars(obj).keys())
82
+ _visitors = self._visitorsFor(obj)
83
+ defaultVisitor = _visitors.get("*", None)
84
+ for key in keys:
85
+ if key[0] == "_":
86
+ continue
87
+ value = getattr(obj, key)
88
+ visitorFunc = _visitors.get(key, defaultVisitor)
89
+ if visitorFunc is not None:
90
+ ret = visitorFunc(self, obj, key, value, *args, **kwargs)
91
+ if ret == False or (ret is None and self.defaultStop):
92
+ continue
93
+ self.visitAttr(obj, key, value, *args, **kwargs)
94
+
95
+ def visitAttr(self, obj, attr, value, *args, **kwargs):
96
+ """Called to visit an attribute of an object."""
97
+ self.visit(value, *args, **kwargs)
98
+
99
+ def visitList(self, obj, *args, **kwargs):
100
+ """Called to visit any value that is a list."""
101
+ for value in obj:
102
+ self.visit(value, *args, **kwargs)
103
+
104
+ def visitDict(self, obj, *args, **kwargs):
105
+ """Called to visit any value that is a dictionary."""
106
+ for value in obj.values():
107
+ self.visit(value, *args, **kwargs)
108
+
109
+ def visitLeaf(self, obj, *args, **kwargs):
110
+ """Called to visit any value that is not an object, list,
111
+ or dictionary."""
112
+ pass
113
+
114
+ def visit(self, obj, *args, **kwargs):
115
+ """This is the main entry to the visitor. The visitor will visit object
116
+ obj.
117
+
118
+ The visitor will first determine if there is a registered (via
119
+ @register()) visit function for the type of object. If there is, it
120
+ will be called, and (visitor, obj, *args, **kwargs) will be passed to
121
+ the user visit function.
122
+
123
+ If there is no user-registered visit function, of if there is and it
124
+ returns True, or it returns None (or doesn't return anything) and
125
+ visitor.defaultStop is False (default), then the visitor will proceed
126
+ to dispatch to one of self.visitObject(), self.visitList(),
127
+ self.visitDict(), or self.visitLeaf() (any of which can be overriden in
128
+ a subclass)."""
129
+
130
+ visitorFunc = self._visitorsFor(obj).get(None, None)
131
+ if visitorFunc is not None:
132
+ ret = visitorFunc(self, obj, *args, **kwargs)
133
+ if ret == False or (ret is None and self.defaultStop):
134
+ return
135
+ if hasattr(obj, "__dict__") and not isinstance(obj, enum.Enum):
136
+ self.visitObject(obj, *args, **kwargs)
137
+ elif isinstance(obj, list):
138
+ self.visitList(obj, *args, **kwargs)
139
+ elif isinstance(obj, dict):
140
+ self.visitDict(obj, *args, **kwargs)
141
+ else:
142
+ self.visitLeaf(obj, *args, **kwargs)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/xmlReader.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools import ttLib
2
+ from fontTools.misc.textTools import safeEval
3
+ from fontTools.ttLib.tables.DefaultTable import DefaultTable
4
+ import sys
5
+ import os
6
+ import logging
7
+
8
+
9
+ log = logging.getLogger(__name__)
10
+
11
+
12
+ class TTXParseError(Exception):
13
+ pass
14
+
15
+
16
+ BUFSIZE = 0x4000
17
+
18
+
19
+ class XMLReader(object):
20
+ def __init__(
21
+ self, fileOrPath, ttFont, progress=None, quiet=None, contentOnly=False
22
+ ):
23
+ if fileOrPath == "-":
24
+ fileOrPath = sys.stdin
25
+ if not hasattr(fileOrPath, "read"):
26
+ self.file = open(fileOrPath, "rb")
27
+ self._closeStream = True
28
+ else:
29
+ # assume readable file object
30
+ self.file = fileOrPath
31
+ self._closeStream = False
32
+ self.ttFont = ttFont
33
+ self.progress = progress
34
+ if quiet is not None:
35
+ from fontTools.misc.loggingTools import deprecateArgument
36
+
37
+ deprecateArgument("quiet", "configure logging instead")
38
+ self.quiet = quiet
39
+ self.root = None
40
+ self.contentStack = []
41
+ self.contentOnly = contentOnly
42
+ self.stackSize = 0
43
+
44
+ def read(self, rootless=False):
45
+ if rootless:
46
+ self.stackSize += 1
47
+ if self.progress:
48
+ self.file.seek(0, 2)
49
+ fileSize = self.file.tell()
50
+ self.progress.set(0, fileSize // 100 or 1)
51
+ self.file.seek(0)
52
+ self._parseFile(self.file)
53
+ if self._closeStream:
54
+ self.close()
55
+ if rootless:
56
+ self.stackSize -= 1
57
+
58
+ def close(self):
59
+ self.file.close()
60
+
61
+ def _parseFile(self, file):
62
+ from xml.parsers.expat import ParserCreate
63
+
64
+ parser = ParserCreate()
65
+ parser.StartElementHandler = self._startElementHandler
66
+ parser.EndElementHandler = self._endElementHandler
67
+ parser.CharacterDataHandler = self._characterDataHandler
68
+
69
+ pos = 0
70
+ while True:
71
+ chunk = file.read(BUFSIZE)
72
+ if not chunk:
73
+ parser.Parse(chunk, 1)
74
+ break
75
+ pos = pos + len(chunk)
76
+ if self.progress:
77
+ self.progress.set(pos // 100)
78
+ parser.Parse(chunk, 0)
79
+
80
+ def _startElementHandler(self, name, attrs):
81
+ if self.stackSize == 1 and self.contentOnly:
82
+ # We already know the table we're parsing, skip
83
+ # parsing the table tag and continue to
84
+ # stack '2' which begins parsing content
85
+ self.contentStack.append([])
86
+ self.stackSize = 2
87
+ return
88
+ stackSize = self.stackSize
89
+ self.stackSize = stackSize + 1
90
+ subFile = attrs.get("src")
91
+ if subFile is not None:
92
+ if hasattr(self.file, "name"):
93
+ # if file has a name, get its parent directory
94
+ dirname = os.path.dirname(self.file.name)
95
+ else:
96
+ # else fall back to using the current working directory
97
+ dirname = os.getcwd()
98
+ subFile = os.path.join(dirname, subFile)
99
+ if not stackSize:
100
+ if name != "ttFont":
101
+ raise TTXParseError("illegal root tag: %s" % name)
102
+ if self.ttFont.reader is None and not self.ttFont.tables:
103
+ sfntVersion = attrs.get("sfntVersion")
104
+ if sfntVersion is not None:
105
+ if len(sfntVersion) != 4:
106
+ sfntVersion = safeEval('"' + sfntVersion + '"')
107
+ self.ttFont.sfntVersion = sfntVersion
108
+ self.contentStack.append([])
109
+ elif stackSize == 1:
110
+ if subFile is not None:
111
+ subReader = XMLReader(subFile, self.ttFont, self.progress)
112
+ subReader.read()
113
+ self.contentStack.append([])
114
+ return
115
+ tag = ttLib.xmlToTag(name)
116
+ msg = "Parsing '%s' table..." % tag
117
+ if self.progress:
118
+ self.progress.setLabel(msg)
119
+ log.info(msg)
120
+ if tag == "GlyphOrder":
121
+ tableClass = ttLib.GlyphOrder
122
+ elif "ERROR" in attrs or ("raw" in attrs and safeEval(attrs["raw"])):
123
+ tableClass = DefaultTable
124
+ else:
125
+ tableClass = ttLib.getTableClass(tag)
126
+ if tableClass is None:
127
+ tableClass = DefaultTable
128
+ if tag == "loca" and tag in self.ttFont:
129
+ # Special-case the 'loca' table as we need the
130
+ # original if the 'glyf' table isn't recompiled.
131
+ self.currentTable = self.ttFont[tag]
132
+ else:
133
+ self.currentTable = tableClass(tag)
134
+ self.ttFont[tag] = self.currentTable
135
+ self.contentStack.append([])
136
+ elif stackSize == 2 and subFile is not None:
137
+ subReader = XMLReader(subFile, self.ttFont, self.progress, contentOnly=True)
138
+ subReader.read()
139
+ self.contentStack.append([])
140
+ self.root = subReader.root
141
+ elif stackSize == 2:
142
+ self.contentStack.append([])
143
+ self.root = (name, attrs, self.contentStack[-1])
144
+ else:
145
+ l = []
146
+ self.contentStack[-1].append((name, attrs, l))
147
+ self.contentStack.append(l)
148
+
149
+ def _characterDataHandler(self, data):
150
+ if self.stackSize > 1:
151
+ # parser parses in chunks, so we may get multiple calls
152
+ # for the same text node; thus we need to append the data
153
+ # to the last item in the content stack:
154
+ # https://github.com/fonttools/fonttools/issues/2614
155
+ if (
156
+ data != "\n"
157
+ and self.contentStack[-1]
158
+ and isinstance(self.contentStack[-1][-1], str)
159
+ and self.contentStack[-1][-1] != "\n"
160
+ ):
161
+ self.contentStack[-1][-1] += data
162
+ else:
163
+ self.contentStack[-1].append(data)
164
+
165
+ def _endElementHandler(self, name):
166
+ self.stackSize = self.stackSize - 1
167
+ del self.contentStack[-1]
168
+ if not self.contentOnly:
169
+ if self.stackSize == 1:
170
+ self.root = None
171
+ elif self.stackSize == 2:
172
+ name, attrs, content = self.root
173
+ self.currentTable.fromXML(name, attrs, content, self.ttFont)
174
+ self.root = None
175
+
176
+
177
+ class ProgressPrinter(object):
178
+ def __init__(self, title, maxval=100):
179
+ print(title)
180
+
181
+ def set(self, val, maxval=None):
182
+ pass
183
+
184
+ def increment(self, val=1):
185
+ pass
186
+
187
+ def setLabel(self, text):
188
+ print(text)
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/misc/xmlWriter.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """xmlWriter.py -- Simple XML authoring class"""
2
+
3
+ from fontTools.misc.textTools import byteord, strjoin, tobytes, tostr
4
+ import sys
5
+ import os
6
+ import string
7
+
8
+ INDENT = " "
9
+
10
+
11
+ class XMLWriter(object):
12
+ def __init__(
13
+ self,
14
+ fileOrPath,
15
+ indentwhite=INDENT,
16
+ idlefunc=None,
17
+ encoding="utf_8",
18
+ newlinestr="\n",
19
+ ):
20
+ if encoding.lower().replace("-", "").replace("_", "") != "utf8":
21
+ raise Exception("Only UTF-8 encoding is supported.")
22
+ if fileOrPath == "-":
23
+ fileOrPath = sys.stdout
24
+ if not hasattr(fileOrPath, "write"):
25
+ self.filename = fileOrPath
26
+ self.file = open(fileOrPath, "wb")
27
+ self._closeStream = True
28
+ else:
29
+ self.filename = None
30
+ # assume writable file object
31
+ self.file = fileOrPath
32
+ self._closeStream = False
33
+
34
+ # Figure out if writer expects bytes or unicodes
35
+ try:
36
+ # The bytes check should be first. See:
37
+ # https://github.com/fonttools/fonttools/pull/233
38
+ self.file.write(b"")
39
+ self.totype = tobytes
40
+ except TypeError:
41
+ # This better not fail.
42
+ self.file.write("")
43
+ self.totype = tostr
44
+ self.indentwhite = self.totype(indentwhite)
45
+ if newlinestr is None:
46
+ self.newlinestr = self.totype(os.linesep)
47
+ else:
48
+ self.newlinestr = self.totype(newlinestr)
49
+ self.indentlevel = 0
50
+ self.stack = []
51
+ self.needindent = 1
52
+ self.idlefunc = idlefunc
53
+ self.idlecounter = 0
54
+ self._writeraw('<?xml version="1.0" encoding="UTF-8"?>')
55
+ self.newline()
56
+
57
+ def __enter__(self):
58
+ return self
59
+
60
+ def __exit__(self, exception_type, exception_value, traceback):
61
+ self.close()
62
+
63
+ def close(self):
64
+ if self._closeStream:
65
+ self.file.close()
66
+
67
+ def write(self, string, indent=True):
68
+ """Writes text."""
69
+ self._writeraw(escape(string), indent=indent)
70
+
71
+ def writecdata(self, string):
72
+ """Writes text in a CDATA section."""
73
+ self._writeraw("<![CDATA[" + string + "]]>")
74
+
75
+ def write8bit(self, data, strip=False):
76
+ """Writes a bytes() sequence into the XML, escaping
77
+ non-ASCII bytes. When this is read in xmlReader,
78
+ the original bytes can be recovered by encoding to
79
+ 'latin-1'."""
80
+ self._writeraw(escape8bit(data), strip=strip)
81
+
82
+ def write_noindent(self, string):
83
+ """Writes text without indentation."""
84
+ self._writeraw(escape(string), indent=False)
85
+
86
+ def _writeraw(self, data, indent=True, strip=False):
87
+ """Writes bytes, possibly indented."""
88
+ if indent and self.needindent:
89
+ self.file.write(self.indentlevel * self.indentwhite)
90
+ self.needindent = 0
91
+ s = self.totype(data, encoding="utf_8")
92
+ if strip:
93
+ s = s.strip()
94
+ self.file.write(s)
95
+
96
+ def newline(self):
97
+ self.file.write(self.newlinestr)
98
+ self.needindent = 1
99
+ idlecounter = self.idlecounter
100
+ if not idlecounter % 100 and self.idlefunc is not None:
101
+ self.idlefunc()
102
+ self.idlecounter = idlecounter + 1
103
+
104
+ def comment(self, data):
105
+ data = escape(data)
106
+ lines = data.split("\n")
107
+ self._writeraw("<!-- " + lines[0])
108
+ for line in lines[1:]:
109
+ self.newline()
110
+ self._writeraw(" " + line)
111
+ self._writeraw(" -->")
112
+
113
+ def simpletag(self, _TAG_, *args, **kwargs):
114
+ attrdata = self.stringifyattrs(*args, **kwargs)
115
+ data = "<%s%s/>" % (_TAG_, attrdata)
116
+ self._writeraw(data)
117
+
118
+ def begintag(self, _TAG_, *args, **kwargs):
119
+ attrdata = self.stringifyattrs(*args, **kwargs)
120
+ data = "<%s%s>" % (_TAG_, attrdata)
121
+ self._writeraw(data)
122
+ self.stack.append(_TAG_)
123
+ self.indent()
124
+
125
+ def endtag(self, _TAG_):
126
+ assert self.stack and self.stack[-1] == _TAG_, "nonmatching endtag"
127
+ del self.stack[-1]
128
+ self.dedent()
129
+ data = "</%s>" % _TAG_
130
+ self._writeraw(data)
131
+
132
+ def dumphex(self, data):
133
+ linelength = 16
134
+ hexlinelength = linelength * 2
135
+ chunksize = 8
136
+ for i in range(0, len(data), linelength):
137
+ hexline = hexStr(data[i : i + linelength])
138
+ line = ""
139
+ white = ""
140
+ for j in range(0, hexlinelength, chunksize):
141
+ line = line + white + hexline[j : j + chunksize]
142
+ white = " "
143
+ self._writeraw(line)
144
+ self.newline()
145
+
146
+ def indent(self):
147
+ self.indentlevel = self.indentlevel + 1
148
+
149
+ def dedent(self):
150
+ assert self.indentlevel > 0
151
+ self.indentlevel = self.indentlevel - 1
152
+
153
+ def stringifyattrs(self, *args, **kwargs):
154
+ if kwargs:
155
+ assert not args
156
+ attributes = sorted(kwargs.items())
157
+ elif args:
158
+ assert len(args) == 1
159
+ attributes = args[0]
160
+ else:
161
+ return ""
162
+ data = ""
163
+ for attr, value in attributes:
164
+ if not isinstance(value, (bytes, str)):
165
+ value = str(value)
166
+ data = data + ' %s="%s"' % (attr, escapeattr(value))
167
+ return data
168
+
169
+
170
+ def escape(data):
171
+ data = tostr(data, "utf_8")
172
+ data = data.replace("&", "&amp;")
173
+ data = data.replace("<", "&lt;")
174
+ data = data.replace(">", "&gt;")
175
+ data = data.replace("\r", "&#13;")
176
+ return data
177
+
178
+
179
+ def escapeattr(data):
180
+ data = escape(data)
181
+ data = data.replace('"', "&quot;")
182
+ return data
183
+
184
+
185
+ def escape8bit(data):
186
+ """Input is Unicode string."""
187
+
188
+ def escapechar(c):
189
+ n = ord(c)
190
+ if 32 <= n <= 127 and c not in "<&>":
191
+ return c
192
+ else:
193
+ return "&#" + repr(n) + ";"
194
+
195
+ return strjoin(map(escapechar, data.decode("latin-1")))
196
+
197
+
198
+ def hexStr(s):
199
+ h = string.hexdigits
200
+ r = ""
201
+ for c in s:
202
+ i = byteord(c)
203
+ r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
204
+ return r
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/otlLib/builder.py ADDED
The diff for this file is too large to render. See raw diff
 
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/subset/__init__.py ADDED
The diff for this file is too large to render. See raw diff
 
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/subset/__main__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import sys
2
+ from fontTools.subset import main
3
+
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())
SUPIR_v56/SUPIR/venv/lib/python3.10/site-packages/fontTools/subset/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (104 kB). View file