a1o commited on
Commit
3f99a2e
·
verified ·
1 Parent(s): 1b533c3

Upload folder using huggingface_hub (part 4)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +26 -0
  2. .venv/lib/python3.11/site-packages/fontTools/ttLib/sfnt.py +664 -0
  3. .venv/lib/python3.11/site-packages/fontTools/ttLib/standardGlyphOrder.py +271 -0
  4. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/B_A_S_E_.py +14 -0
  5. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/BitmapGlyphMetrics.py +64 -0
  6. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_B_D_T_.py +113 -0
  7. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_B_L_C_.py +19 -0
  8. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_F_F_.py +61 -0
  9. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_F_F__2.py +26 -0
  10. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_O_L_R_.py +165 -0
  11. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_P_A_L_.py +305 -0
  12. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/D_S_I_G_.py +170 -0
  13. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/D__e_b_g.py +35 -0
  14. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/DefaultTable.py +63 -0
  15. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/E_B_D_T_.py +835 -0
  16. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/E_B_L_C_.py +718 -0
  17. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/F_F_T_M_.py +52 -0
  18. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/F__e_a_t.py +159 -0
  19. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_D_E_F_.py +13 -0
  20. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_P_O_S_.py +14 -0
  21. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_S_U_B_.py +13 -0
  22. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  23. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G__l_a_t.py +235 -0
  24. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G__l_o_c.py +85 -0
  25. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/H_V_A_R_.py +13 -0
  26. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/I_F_T_.py +12 -0
  27. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/I_F_T_X_.py +12 -0
  28. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/J_S_T_F_.py +13 -0
  29. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/L_T_S_H_.py +58 -0
  30. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/M_A_T_H_.py +13 -0
  31. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/M_V_A_R_.py +13 -0
  32. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/O_S_2f_2.py +752 -0
  33. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S_T_A_T_.py +15 -0
  34. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S_V_G_.py +223 -0
  35. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S__i_l_f.py +1040 -0
  36. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S__i_l_l.py +92 -0
  37. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_B_.py +13 -0
  38. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_C_.py +14 -0
  39. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_D_.py +13 -0
  40. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_J_.py +13 -0
  41. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_P_.py +13 -0
  42. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_S_.py +13 -0
  43. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_V_.py +26 -0
  44. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__0.py +70 -0
  45. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__1.py +163 -0
  46. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__2.py +17 -0
  47. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__3.py +22 -0
  48. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__5.py +60 -0
  49. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_T_F_A_.py +14 -0
  50. .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/TupleVariation.py +886 -0
.gitattributes CHANGED
@@ -47,3 +47,29 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
47
  .venv/lib/python3.11/site-packages/fontTools/misc/bezierTools.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
48
  .venv/lib/python3.11/site-packages/fontTools/pens/momentsPen.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
49
  .venv/lib/python3.11/site-packages/fontTools/qu2cu/qu2cu.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  .venv/lib/python3.11/site-packages/fontTools/misc/bezierTools.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
48
  .venv/lib/python3.11/site-packages/fontTools/pens/momentsPen.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
49
  .venv/lib/python3.11/site-packages/fontTools/qu2cu/qu2cu.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
50
+ .venv/lib/python3.11/site-packages/fontTools/varLib/iup.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
51
+ .venv/lib/python3.11/site-packages/fsspec/__pycache__/spec.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
52
+ .venv/lib/python3.11/site-packages/h5py/_conv.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
53
+ .venv/lib/python3.11/site-packages/h5py/_errors.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
54
+ .venv/lib/python3.11/site-packages/h5py/_npystrings.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
55
+ .venv/lib/python3.11/site-packages/h5py/_objects.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
56
+ .venv/lib/python3.11/site-packages/h5py/_proxy.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
57
+ .venv/lib/python3.11/site-packages/h5py/_selector.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
58
+ .venv/lib/python3.11/site-packages/h5py/defs.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
59
+ .venv/lib/python3.11/site-packages/h5py/h5.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
60
+ .venv/lib/python3.11/site-packages/h5py/h5a.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
61
+ .venv/lib/python3.11/site-packages/h5py/h5ac.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
62
+ .venv/lib/python3.11/site-packages/h5py/h5d.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
63
+ .venv/lib/python3.11/site-packages/h5py/h5ds.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
64
+ .venv/lib/python3.11/site-packages/h5py/h5f.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
65
+ .venv/lib/python3.11/site-packages/h5py/h5fd.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
66
+ .venv/lib/python3.11/site-packages/h5py/h5g.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
67
+ .venv/lib/python3.11/site-packages/h5py/h5i.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
68
+ .venv/lib/python3.11/site-packages/h5py/h5l.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
69
+ .venv/lib/python3.11/site-packages/h5py/h5o.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
70
+ .venv/lib/python3.11/site-packages/h5py/h5p.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
71
+ .venv/lib/python3.11/site-packages/h5py/h5pl.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
72
+ .venv/lib/python3.11/site-packages/h5py/h5r.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
73
+ .venv/lib/python3.11/site-packages/h5py/h5s.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
74
+ .venv/lib/python3.11/site-packages/h5py/h5t.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
75
+ .venv/lib/python3.11/site-packages/h5py/h5z.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
.venv/lib/python3.11/site-packages/fontTools/ttLib/sfnt.py ADDED
@@ -0,0 +1,664 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ttLib/sfnt.py -- low-level module to deal with the sfnt file format.
2
+
3
+ Defines two public classes:
4
+
5
+ - SFNTReader
6
+ - SFNTWriter
7
+
8
+ (Normally you don't have to use these classes explicitly; they are
9
+ used automatically by ttLib.TTFont.)
10
+
11
+ The reading and writing of sfnt files is separated in two distinct
12
+ classes, since whenever the number of tables changes or whenever
13
+ a table's length changes you need to rewrite the whole file anyway.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from collections.abc import KeysView
19
+ from io import BytesIO
20
+ from types import SimpleNamespace
21
+ from fontTools.misc.textTools import Tag
22
+ from fontTools.misc import sstruct
23
+ from fontTools.ttLib import TTLibError, TTLibFileIsCollectionError
24
+ import struct
25
+ from collections import OrderedDict
26
+ import logging
27
+
28
+
29
+ log = logging.getLogger(__name__)
30
+
31
+
32
+ class SFNTReader(object):
33
+ def __new__(cls, *args, **kwargs):
34
+ """Return an instance of the SFNTReader sub-class which is compatible
35
+ with the input file type.
36
+ """
37
+ if args and cls is SFNTReader:
38
+ infile = args[0]
39
+ infile.seek(0)
40
+ sfntVersion = Tag(infile.read(4))
41
+ infile.seek(0)
42
+ if sfntVersion == "wOF2":
43
+ # return new WOFF2Reader object
44
+ from fontTools.ttLib.woff2 import WOFF2Reader
45
+
46
+ return object.__new__(WOFF2Reader)
47
+ # return default object
48
+ return object.__new__(cls)
49
+
50
+ def __init__(self, file, checkChecksums=0, fontNumber=-1):
51
+ self.file = file
52
+ self.checkChecksums = checkChecksums
53
+
54
+ self.flavor = None
55
+ self.flavorData = None
56
+ self.DirectoryEntry = SFNTDirectoryEntry
57
+ self.file.seek(0)
58
+ self.sfntVersion = self.file.read(4)
59
+ self.file.seek(0)
60
+ if self.sfntVersion == b"ttcf":
61
+ header = readTTCHeader(self.file)
62
+ numFonts = header.numFonts
63
+ if not 0 <= fontNumber < numFonts:
64
+ raise TTLibFileIsCollectionError(
65
+ "specify a font number between 0 and %d (inclusive)"
66
+ % (numFonts - 1)
67
+ )
68
+ self.numFonts = numFonts
69
+ self.file.seek(header.offsetTable[fontNumber])
70
+ data = self.file.read(sfntDirectorySize)
71
+ if len(data) != sfntDirectorySize:
72
+ raise TTLibError("Not a Font Collection (not enough data)")
73
+ sstruct.unpack(sfntDirectoryFormat, data, self)
74
+ elif self.sfntVersion == b"wOFF":
75
+ self.flavor = "woff"
76
+ self.DirectoryEntry = WOFFDirectoryEntry
77
+ data = self.file.read(woffDirectorySize)
78
+ if len(data) != woffDirectorySize:
79
+ raise TTLibError("Not a WOFF font (not enough data)")
80
+ sstruct.unpack(woffDirectoryFormat, data, self)
81
+ else:
82
+ data = self.file.read(sfntDirectorySize)
83
+ if len(data) != sfntDirectorySize:
84
+ raise TTLibError("Not a TrueType or OpenType font (not enough data)")
85
+ sstruct.unpack(sfntDirectoryFormat, data, self)
86
+ self.sfntVersion = Tag(self.sfntVersion)
87
+
88
+ if self.sfntVersion not in ("\x00\x01\x00\x00", "OTTO", "true"):
89
+ raise TTLibError("Not a TrueType or OpenType font (bad sfntVersion)")
90
+ tables: dict[Tag, DirectoryEntry] = {}
91
+ for i in range(self.numTables):
92
+ entry = self.DirectoryEntry()
93
+ entry.fromFile(self.file)
94
+ tag = Tag(entry.tag)
95
+ tables[tag] = entry
96
+ self.tables = OrderedDict(sorted(tables.items(), key=lambda i: i[1].offset))
97
+
98
+ # Load flavor data if any
99
+ if self.flavor == "woff":
100
+ self.flavorData = WOFFFlavorData(self)
101
+
102
+ def has_key(self, tag: str | bytes) -> bool:
103
+ return tag in self.tables
104
+
105
+ __contains__ = has_key
106
+
107
+ def keys(self) -> KeysView[Tag]:
108
+ return self.tables.keys()
109
+
110
+ def __getitem__(self, tag: str | bytes) -> bytes:
111
+ """Fetch the raw table data."""
112
+ entry = self.tables[Tag(tag)]
113
+ data = entry.loadData(self.file)
114
+ if self.checkChecksums:
115
+ if tag == "head":
116
+ # Beh: we have to special-case the 'head' table.
117
+ checksum = calcChecksum(data[:8] + b"\0\0\0\0" + data[12:])
118
+ else:
119
+ checksum = calcChecksum(data)
120
+ if self.checkChecksums > 1:
121
+ # Be obnoxious, and barf when it's wrong
122
+ assert checksum == entry.checkSum, "bad checksum for '%s' table" % tag
123
+ elif checksum != entry.checkSum:
124
+ # Be friendly, and just log a warning.
125
+ log.warning("bad checksum for '%s' table", tag)
126
+ return data
127
+
128
+ def __delitem__(self, tag: str | bytes) -> None:
129
+ del self.tables[Tag(tag)]
130
+
131
+ def close(self) -> None:
132
+ self.file.close()
133
+
134
+ # We define custom __getstate__ and __setstate__ to make SFNTReader pickle-able
135
+ # and deepcopy-able. When a TTFont is loaded as lazy=True, SFNTReader holds a
136
+ # reference to an external file object which is not pickleable. So in __getstate__
137
+ # we store the file name and current position, and in __setstate__ we reopen the
138
+ # same named file after unpickling.
139
+
140
+ def __getstate__(self):
141
+ if isinstance(self.file, BytesIO):
142
+ # BytesIO is already pickleable, return the state unmodified
143
+ return self.__dict__
144
+
145
+ # remove unpickleable file attribute, and only store its name and pos
146
+ state = self.__dict__.copy()
147
+ del state["file"]
148
+ state["_filename"] = self.file.name
149
+ state["_filepos"] = self.file.tell()
150
+ return state
151
+
152
+ def __setstate__(self, state):
153
+ if "file" not in state:
154
+ self.file = open(state.pop("_filename"), "rb")
155
+ self.file.seek(state.pop("_filepos"))
156
+ self.__dict__.update(state)
157
+
158
+
159
+ # default compression level for WOFF 1.0 tables and metadata
160
+ ZLIB_COMPRESSION_LEVEL = 6
161
+
162
+ # if set to True, use zopfli instead of zlib for compressing WOFF 1.0.
163
+ # The Python bindings are available at https://pypi.python.org/pypi/zopfli
164
+ USE_ZOPFLI = False
165
+
166
+ # mapping between zlib's compression levels and zopfli's 'numiterations'.
167
+ # Use lower values for files over several MB in size or it will be too slow
168
+ ZOPFLI_LEVELS = {
169
+ # 0: 0, # can't do 0 iterations...
170
+ 1: 1,
171
+ 2: 3,
172
+ 3: 5,
173
+ 4: 8,
174
+ 5: 10,
175
+ 6: 15,
176
+ 7: 25,
177
+ 8: 50,
178
+ 9: 100,
179
+ }
180
+
181
+
182
+ def compress(data, level=ZLIB_COMPRESSION_LEVEL):
183
+ """Compress 'data' to Zlib format. If 'USE_ZOPFLI' variable is True,
184
+ zopfli is used instead of the zlib module.
185
+ The compression 'level' must be between 0 and 9. 1 gives best speed,
186
+ 9 gives best compression (0 gives no compression at all).
187
+ The default value is a compromise between speed and compression (6).
188
+ """
189
+ if not (0 <= level <= 9):
190
+ raise ValueError("Bad compression level: %s" % level)
191
+ if not USE_ZOPFLI or level == 0:
192
+ from zlib import compress
193
+
194
+ return compress(data, level)
195
+ else:
196
+ from zopfli.zlib import compress
197
+
198
+ return compress(data, numiterations=ZOPFLI_LEVELS[level])
199
+
200
+
201
+ class SFNTWriter(object):
202
+ def __new__(cls, *args, **kwargs):
203
+ """Return an instance of the SFNTWriter sub-class which is compatible
204
+ with the specified 'flavor'.
205
+ """
206
+ flavor = None
207
+ if kwargs and "flavor" in kwargs:
208
+ flavor = kwargs["flavor"]
209
+ elif args and len(args) > 3:
210
+ flavor = args[3]
211
+ if cls is SFNTWriter:
212
+ if flavor == "woff2":
213
+ # return new WOFF2Writer object
214
+ from fontTools.ttLib.woff2 import WOFF2Writer
215
+
216
+ return object.__new__(WOFF2Writer)
217
+ # return default object
218
+ return object.__new__(cls)
219
+
220
+ def __init__(
221
+ self,
222
+ file,
223
+ numTables,
224
+ sfntVersion="\000\001\000\000",
225
+ flavor=None,
226
+ flavorData=None,
227
+ ):
228
+ self.file = file
229
+ self.numTables = numTables
230
+ self.sfntVersion = Tag(sfntVersion)
231
+ self.flavor = flavor
232
+ self.flavorData = flavorData
233
+
234
+ if self.flavor == "woff":
235
+ self.directoryFormat = woffDirectoryFormat
236
+ self.directorySize = woffDirectorySize
237
+ self.DirectoryEntry = WOFFDirectoryEntry
238
+
239
+ self.signature = "wOFF"
240
+
241
+ # to calculate WOFF checksum adjustment, we also need the original SFNT offsets
242
+ self.origNextTableOffset = (
243
+ sfntDirectorySize + numTables * sfntDirectoryEntrySize
244
+ )
245
+ else:
246
+ assert not self.flavor, "Unknown flavor '%s'" % self.flavor
247
+ self.directoryFormat = sfntDirectoryFormat
248
+ self.directorySize = sfntDirectorySize
249
+ self.DirectoryEntry = SFNTDirectoryEntry
250
+
251
+ from fontTools.ttLib import getSearchRange
252
+
253
+ self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(
254
+ numTables, 16
255
+ )
256
+
257
+ self.directoryOffset = self.file.tell()
258
+ self.nextTableOffset = (
259
+ self.directoryOffset
260
+ + self.directorySize
261
+ + numTables * self.DirectoryEntry.formatSize
262
+ )
263
+ # clear out directory area
264
+ self.file.seek(self.nextTableOffset)
265
+ # make sure we're actually where we want to be. (old cStringIO bug)
266
+ self.file.write(b"\0" * (self.nextTableOffset - self.file.tell()))
267
+ self.tables = OrderedDict()
268
+
269
+ def setEntry(self, tag, entry):
270
+ if tag in self.tables:
271
+ raise TTLibError("cannot rewrite '%s' table" % tag)
272
+
273
+ self.tables[tag] = entry
274
+
275
+ def __setitem__(self, tag, data):
276
+ """Write raw table data to disk."""
277
+ if tag in self.tables:
278
+ raise TTLibError("cannot rewrite '%s' table" % tag)
279
+
280
+ entry = self.DirectoryEntry()
281
+ entry.tag = tag
282
+ entry.offset = self.nextTableOffset
283
+ if tag == "head":
284
+ entry.checkSum = calcChecksum(data[:8] + b"\0\0\0\0" + data[12:])
285
+ self.headTable = data
286
+ entry.uncompressed = True
287
+ else:
288
+ entry.checkSum = calcChecksum(data)
289
+ entry.saveData(self.file, data)
290
+
291
+ if self.flavor == "woff":
292
+ entry.origOffset = self.origNextTableOffset
293
+ self.origNextTableOffset += (entry.origLength + 3) & ~3
294
+
295
+ self.nextTableOffset = self.nextTableOffset + ((entry.length + 3) & ~3)
296
+ # Add NUL bytes to pad the table data to a 4-byte boundary.
297
+ # Don't depend on f.seek() as we need to add the padding even if no
298
+ # subsequent write follows (seek is lazy), ie. after the final table
299
+ # in the font.
300
+ self.file.write(b"\0" * (self.nextTableOffset - self.file.tell()))
301
+ assert self.nextTableOffset == self.file.tell()
302
+
303
+ self.setEntry(tag, entry)
304
+
305
+ def __getitem__(self, tag):
306
+ return self.tables[tag]
307
+
308
+ def close(self):
309
+ """All tables must have been written to disk. Now write the
310
+ directory.
311
+ """
312
+ tables = sorted(self.tables.items())
313
+ if len(tables) != self.numTables:
314
+ raise TTLibError(
315
+ "wrong number of tables; expected %d, found %d"
316
+ % (self.numTables, len(tables))
317
+ )
318
+
319
+ if self.flavor == "woff":
320
+ self.signature = b"wOFF"
321
+ self.reserved = 0
322
+
323
+ self.totalSfntSize = 12
324
+ self.totalSfntSize += 16 * len(tables)
325
+ for tag, entry in tables:
326
+ self.totalSfntSize += (entry.origLength + 3) & ~3
327
+
328
+ data = self.flavorData if self.flavorData else WOFFFlavorData()
329
+ if data.majorVersion is not None and data.minorVersion is not None:
330
+ self.majorVersion = data.majorVersion
331
+ self.minorVersion = data.minorVersion
332
+ else:
333
+ if hasattr(self, "headTable"):
334
+ self.majorVersion, self.minorVersion = struct.unpack(
335
+ ">HH", self.headTable[4:8]
336
+ )
337
+ else:
338
+ self.majorVersion = self.minorVersion = 0
339
+ if data.metaData:
340
+ self.metaOrigLength = len(data.metaData)
341
+ self.file.seek(0, 2)
342
+ self.metaOffset = self.file.tell()
343
+ compressedMetaData = compress(data.metaData)
344
+ self.metaLength = len(compressedMetaData)
345
+ self.file.write(compressedMetaData)
346
+ else:
347
+ self.metaOffset = self.metaLength = self.metaOrigLength = 0
348
+ if data.privData:
349
+ self.file.seek(0, 2)
350
+ off = self.file.tell()
351
+ paddedOff = (off + 3) & ~3
352
+ self.file.write(b"\0" * (paddedOff - off))
353
+ self.privOffset = self.file.tell()
354
+ self.privLength = len(data.privData)
355
+ self.file.write(data.privData)
356
+ else:
357
+ self.privOffset = self.privLength = 0
358
+
359
+ self.file.seek(0, 2)
360
+ self.length = self.file.tell()
361
+
362
+ else:
363
+ assert not self.flavor, "Unknown flavor '%s'" % self.flavor
364
+ pass
365
+
366
+ directory = sstruct.pack(self.directoryFormat, self)
367
+
368
+ self.file.seek(self.directoryOffset + self.directorySize)
369
+ seenHead = 0
370
+ for tag, entry in tables:
371
+ if tag == "head":
372
+ seenHead = 1
373
+ directory = directory + entry.toString()
374
+ if seenHead:
375
+ self.writeMasterChecksum(directory)
376
+ self.file.seek(self.directoryOffset)
377
+ self.file.write(directory)
378
+
379
+ def _calcMasterChecksum(self, directory):
380
+ # calculate checkSumAdjustment
381
+ checksums = []
382
+ for tag in self.tables.keys():
383
+ checksums.append(self.tables[tag].checkSum)
384
+
385
+ if self.DirectoryEntry != SFNTDirectoryEntry:
386
+ # Create a SFNT directory for checksum calculation purposes
387
+ from fontTools.ttLib import getSearchRange
388
+
389
+ self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(
390
+ self.numTables, 16
391
+ )
392
+ directory = sstruct.pack(sfntDirectoryFormat, self)
393
+ tables = sorted(self.tables.items())
394
+ for tag, entry in tables:
395
+ sfntEntry = SFNTDirectoryEntry()
396
+ sfntEntry.tag = entry.tag
397
+ sfntEntry.checkSum = entry.checkSum
398
+ sfntEntry.offset = entry.origOffset
399
+ sfntEntry.length = entry.origLength
400
+ directory = directory + sfntEntry.toString()
401
+
402
+ directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize
403
+ assert directory_end == len(directory)
404
+
405
+ checksums.append(calcChecksum(directory))
406
+ checksum = sum(checksums) & 0xFFFFFFFF
407
+ # BiboAfba!
408
+ checksumadjustment = (0xB1B0AFBA - checksum) & 0xFFFFFFFF
409
+ return checksumadjustment
410
+
411
+ def writeMasterChecksum(self, directory):
412
+ checksumadjustment = self._calcMasterChecksum(directory)
413
+ # write the checksum to the file
414
+ self.file.seek(self.tables["head"].offset + 8)
415
+ self.file.write(struct.pack(">L", checksumadjustment))
416
+
417
+ def reordersTables(self):
418
+ return False
419
+
420
+
421
+ # -- sfnt directory helpers and cruft
422
+
423
+ ttcHeaderFormat = """
424
+ > # big endian
425
+ TTCTag: 4s # "ttcf"
426
+ Version: L # 0x00010000 or 0x00020000
427
+ numFonts: L # number of fonts
428
+ # OffsetTable[numFonts]: L # array with offsets from beginning of file
429
+ # ulDsigTag: L # version 2.0 only
430
+ # ulDsigLength: L # version 2.0 only
431
+ # ulDsigOffset: L # version 2.0 only
432
+ """
433
+
434
+ ttcHeaderSize = sstruct.calcsize(ttcHeaderFormat)
435
+
436
+ sfntDirectoryFormat = """
437
+ > # big endian
438
+ sfntVersion: 4s
439
+ numTables: H # number of tables
440
+ searchRange: H # (max2 <= numTables)*16
441
+ entrySelector: H # log2(max2 <= numTables)
442
+ rangeShift: H # numTables*16-searchRange
443
+ """
444
+
445
+ sfntDirectorySize = sstruct.calcsize(sfntDirectoryFormat)
446
+
447
+ sfntDirectoryEntryFormat = """
448
+ > # big endian
449
+ tag: 4s
450
+ checkSum: L
451
+ offset: L
452
+ length: L
453
+ """
454
+
455
+ sfntDirectoryEntrySize = sstruct.calcsize(sfntDirectoryEntryFormat)
456
+
457
+ woffDirectoryFormat = """
458
+ > # big endian
459
+ signature: 4s # "wOFF"
460
+ sfntVersion: 4s
461
+ length: L # total woff file size
462
+ numTables: H # number of tables
463
+ reserved: H # set to 0
464
+ totalSfntSize: L # uncompressed size
465
+ majorVersion: H # major version of WOFF file
466
+ minorVersion: H # minor version of WOFF file
467
+ metaOffset: L # offset to metadata block
468
+ metaLength: L # length of compressed metadata
469
+ metaOrigLength: L # length of uncompressed metadata
470
+ privOffset: L # offset to private data block
471
+ privLength: L # length of private data block
472
+ """
473
+
474
+ woffDirectorySize = sstruct.calcsize(woffDirectoryFormat)
475
+
476
+ woffDirectoryEntryFormat = """
477
+ > # big endian
478
+ tag: 4s
479
+ offset: L
480
+ length: L # compressed length
481
+ origLength: L # original length
482
+ checkSum: L # original checksum
483
+ """
484
+
485
+ woffDirectoryEntrySize = sstruct.calcsize(woffDirectoryEntryFormat)
486
+
487
+
488
+ class DirectoryEntry(object):
489
+ def __init__(self):
490
+ self.uncompressed = False # if True, always embed entry raw
491
+
492
+ def fromFile(self, file):
493
+ sstruct.unpack(self.format, file.read(self.formatSize), self)
494
+
495
+ def fromString(self, str):
496
+ sstruct.unpack(self.format, str, self)
497
+
498
+ def toString(self):
499
+ return sstruct.pack(self.format, self)
500
+
501
+ def __repr__(self):
502
+ if hasattr(self, "tag"):
503
+ return "<%s '%s' at %x>" % (self.__class__.__name__, self.tag, id(self))
504
+ else:
505
+ return "<%s at %x>" % (self.__class__.__name__, id(self))
506
+
507
+ def loadData(self, file):
508
+ file.seek(self.offset)
509
+ data = file.read(self.length)
510
+ assert len(data) == self.length
511
+ if hasattr(self.__class__, "decodeData"):
512
+ data = self.decodeData(data)
513
+ return data
514
+
515
+ def saveData(self, file, data):
516
+ if hasattr(self.__class__, "encodeData"):
517
+ data = self.encodeData(data)
518
+ self.length = len(data)
519
+ file.seek(self.offset)
520
+ file.write(data)
521
+
522
+ def decodeData(self, rawData):
523
+ return rawData
524
+
525
+ def encodeData(self, data):
526
+ return data
527
+
528
+
529
+ class SFNTDirectoryEntry(DirectoryEntry):
530
+ format = sfntDirectoryEntryFormat
531
+ formatSize = sfntDirectoryEntrySize
532
+
533
+
534
+ class WOFFDirectoryEntry(DirectoryEntry):
535
+ format = woffDirectoryEntryFormat
536
+ formatSize = woffDirectoryEntrySize
537
+
538
+ def __init__(self):
539
+ super(WOFFDirectoryEntry, self).__init__()
540
+ # With fonttools<=3.1.2, the only way to set a different zlib
541
+ # compression level for WOFF directory entries was to set the class
542
+ # attribute 'zlibCompressionLevel'. This is now replaced by a globally
543
+ # defined `ZLIB_COMPRESSION_LEVEL`, which is also applied when
544
+ # compressing the metadata. For backward compatibility, we still
545
+ # use the class attribute if it was already set.
546
+ if not hasattr(WOFFDirectoryEntry, "zlibCompressionLevel"):
547
+ self.zlibCompressionLevel = ZLIB_COMPRESSION_LEVEL
548
+
549
+ def decodeData(self, rawData):
550
+ import zlib
551
+
552
+ if self.length == self.origLength:
553
+ data = rawData
554
+ else:
555
+ assert self.length < self.origLength
556
+ data = zlib.decompress(rawData)
557
+ assert len(data) == self.origLength
558
+ return data
559
+
560
+ def encodeData(self, data):
561
+ self.origLength = len(data)
562
+ if not self.uncompressed:
563
+ compressedData = compress(data, self.zlibCompressionLevel)
564
+ if self.uncompressed or len(compressedData) >= self.origLength:
565
+ # Encode uncompressed
566
+ rawData = data
567
+ self.length = self.origLength
568
+ else:
569
+ rawData = compressedData
570
+ self.length = len(rawData)
571
+ return rawData
572
+
573
+
574
+ class WOFFFlavorData:
575
+ Flavor = "woff"
576
+
577
+ def __init__(self, reader=None):
578
+ self.majorVersion = None
579
+ self.minorVersion = None
580
+ self.metaData = None
581
+ self.privData = None
582
+ if reader:
583
+ self.majorVersion = reader.majorVersion
584
+ self.minorVersion = reader.minorVersion
585
+ if reader.metaLength:
586
+ reader.file.seek(reader.metaOffset)
587
+ rawData = reader.file.read(reader.metaLength)
588
+ assert len(rawData) == reader.metaLength
589
+ data = self._decompress(rawData)
590
+ assert len(data) == reader.metaOrigLength
591
+ self.metaData = data
592
+ if reader.privLength:
593
+ reader.file.seek(reader.privOffset)
594
+ data = reader.file.read(reader.privLength)
595
+ assert len(data) == reader.privLength
596
+ self.privData = data
597
+
598
+ def _decompress(self, rawData):
599
+ import zlib
600
+
601
+ return zlib.decompress(rawData)
602
+
603
+
604
+ def calcChecksum(data):
605
+ """Calculate the checksum for an arbitrary block of data.
606
+
607
+ If the data length is not a multiple of four, it assumes
608
+ it is to be padded with null byte.
609
+
610
+ >>> print(calcChecksum(b"abcd"))
611
+ 1633837924
612
+ >>> print(calcChecksum(b"abcdxyz"))
613
+ 3655064932
614
+ """
615
+ remainder = len(data) % 4
616
+ if remainder:
617
+ data += b"\0" * (4 - remainder)
618
+ value = 0
619
+ blockSize = 4096
620
+ assert blockSize % 4 == 0
621
+ for i in range(0, len(data), blockSize):
622
+ block = data[i : i + blockSize]
623
+ longs = struct.unpack(">%dL" % (len(block) // 4), block)
624
+ value = (value + sum(longs)) & 0xFFFFFFFF
625
+ return value
626
+
627
+
628
+ def readTTCHeader(file):
629
+ file.seek(0)
630
+ data = file.read(ttcHeaderSize)
631
+ if len(data) != ttcHeaderSize:
632
+ raise TTLibError("Not a Font Collection (not enough data)")
633
+ self = SimpleNamespace()
634
+ sstruct.unpack(ttcHeaderFormat, data, self)
635
+ if self.TTCTag != "ttcf":
636
+ raise TTLibError("Not a Font Collection")
637
+ assert self.Version == 0x00010000 or self.Version == 0x00020000, (
638
+ "unrecognized TTC version 0x%08x" % self.Version
639
+ )
640
+ self.offsetTable = struct.unpack(
641
+ ">%dL" % self.numFonts, file.read(self.numFonts * 4)
642
+ )
643
+ if self.Version == 0x00020000:
644
+ pass # ignoring version 2.0 signatures
645
+ return self
646
+
647
+
648
+ def writeTTCHeader(file, numFonts):
649
+ self = SimpleNamespace()
650
+ self.TTCTag = "ttcf"
651
+ self.Version = 0x00010000
652
+ self.numFonts = numFonts
653
+ file.seek(0)
654
+ file.write(sstruct.pack(ttcHeaderFormat, self))
655
+ offset = file.tell()
656
+ file.write(struct.pack(">%dL" % self.numFonts, *([0] * self.numFonts)))
657
+ return offset
658
+
659
+
660
+ if __name__ == "__main__":
661
+ import sys
662
+ import doctest
663
+
664
+ sys.exit(doctest.testmod().failed)
.venv/lib/python3.11/site-packages/fontTools/ttLib/standardGlyphOrder.py ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # 'post' table formats 1.0 and 2.0 rely on this list of "standard"
3
+ # glyphs.
4
+ #
5
+ # My list is correct according to the Apple documentation for the 'post' table:
6
+ # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
7
+ # (However, it seems that TTFdump (from MS) and FontLab disagree, at
8
+ # least with respect to the last glyph, which they list as 'dslash'
9
+ # instead of 'dcroat'.)
10
+ #
11
+
12
+ standardGlyphOrder = [
13
+ ".notdef", # 0
14
+ ".null", # 1
15
+ "nonmarkingreturn", # 2
16
+ "space", # 3
17
+ "exclam", # 4
18
+ "quotedbl", # 5
19
+ "numbersign", # 6
20
+ "dollar", # 7
21
+ "percent", # 8
22
+ "ampersand", # 9
23
+ "quotesingle", # 10
24
+ "parenleft", # 11
25
+ "parenright", # 12
26
+ "asterisk", # 13
27
+ "plus", # 14
28
+ "comma", # 15
29
+ "hyphen", # 16
30
+ "period", # 17
31
+ "slash", # 18
32
+ "zero", # 19
33
+ "one", # 20
34
+ "two", # 21
35
+ "three", # 22
36
+ "four", # 23
37
+ "five", # 24
38
+ "six", # 25
39
+ "seven", # 26
40
+ "eight", # 27
41
+ "nine", # 28
42
+ "colon", # 29
43
+ "semicolon", # 30
44
+ "less", # 31
45
+ "equal", # 32
46
+ "greater", # 33
47
+ "question", # 34
48
+ "at", # 35
49
+ "A", # 36
50
+ "B", # 37
51
+ "C", # 38
52
+ "D", # 39
53
+ "E", # 40
54
+ "F", # 41
55
+ "G", # 42
56
+ "H", # 43
57
+ "I", # 44
58
+ "J", # 45
59
+ "K", # 46
60
+ "L", # 47
61
+ "M", # 48
62
+ "N", # 49
63
+ "O", # 50
64
+ "P", # 51
65
+ "Q", # 52
66
+ "R", # 53
67
+ "S", # 54
68
+ "T", # 55
69
+ "U", # 56
70
+ "V", # 57
71
+ "W", # 58
72
+ "X", # 59
73
+ "Y", # 60
74
+ "Z", # 61
75
+ "bracketleft", # 62
76
+ "backslash", # 63
77
+ "bracketright", # 64
78
+ "asciicircum", # 65
79
+ "underscore", # 66
80
+ "grave", # 67
81
+ "a", # 68
82
+ "b", # 69
83
+ "c", # 70
84
+ "d", # 71
85
+ "e", # 72
86
+ "f", # 73
87
+ "g", # 74
88
+ "h", # 75
89
+ "i", # 76
90
+ "j", # 77
91
+ "k", # 78
92
+ "l", # 79
93
+ "m", # 80
94
+ "n", # 81
95
+ "o", # 82
96
+ "p", # 83
97
+ "q", # 84
98
+ "r", # 85
99
+ "s", # 86
100
+ "t", # 87
101
+ "u", # 88
102
+ "v", # 89
103
+ "w", # 90
104
+ "x", # 91
105
+ "y", # 92
106
+ "z", # 93
107
+ "braceleft", # 94
108
+ "bar", # 95
109
+ "braceright", # 96
110
+ "asciitilde", # 97
111
+ "Adieresis", # 98
112
+ "Aring", # 99
113
+ "Ccedilla", # 100
114
+ "Eacute", # 101
115
+ "Ntilde", # 102
116
+ "Odieresis", # 103
117
+ "Udieresis", # 104
118
+ "aacute", # 105
119
+ "agrave", # 106
120
+ "acircumflex", # 107
121
+ "adieresis", # 108
122
+ "atilde", # 109
123
+ "aring", # 110
124
+ "ccedilla", # 111
125
+ "eacute", # 112
126
+ "egrave", # 113
127
+ "ecircumflex", # 114
128
+ "edieresis", # 115
129
+ "iacute", # 116
130
+ "igrave", # 117
131
+ "icircumflex", # 118
132
+ "idieresis", # 119
133
+ "ntilde", # 120
134
+ "oacute", # 121
135
+ "ograve", # 122
136
+ "ocircumflex", # 123
137
+ "odieresis", # 124
138
+ "otilde", # 125
139
+ "uacute", # 126
140
+ "ugrave", # 127
141
+ "ucircumflex", # 128
142
+ "udieresis", # 129
143
+ "dagger", # 130
144
+ "degree", # 131
145
+ "cent", # 132
146
+ "sterling", # 133
147
+ "section", # 134
148
+ "bullet", # 135
149
+ "paragraph", # 136
150
+ "germandbls", # 137
151
+ "registered", # 138
152
+ "copyright", # 139
153
+ "trademark", # 140
154
+ "acute", # 141
155
+ "dieresis", # 142
156
+ "notequal", # 143
157
+ "AE", # 144
158
+ "Oslash", # 145
159
+ "infinity", # 146
160
+ "plusminus", # 147
161
+ "lessequal", # 148
162
+ "greaterequal", # 149
163
+ "yen", # 150
164
+ "mu", # 151
165
+ "partialdiff", # 152
166
+ "summation", # 153
167
+ "product", # 154
168
+ "pi", # 155
169
+ "integral", # 156
170
+ "ordfeminine", # 157
171
+ "ordmasculine", # 158
172
+ "Omega", # 159
173
+ "ae", # 160
174
+ "oslash", # 161
175
+ "questiondown", # 162
176
+ "exclamdown", # 163
177
+ "logicalnot", # 164
178
+ "radical", # 165
179
+ "florin", # 166
180
+ "approxequal", # 167
181
+ "Delta", # 168
182
+ "guillemotleft", # 169
183
+ "guillemotright", # 170
184
+ "ellipsis", # 171
185
+ "nonbreakingspace", # 172
186
+ "Agrave", # 173
187
+ "Atilde", # 174
188
+ "Otilde", # 175
189
+ "OE", # 176
190
+ "oe", # 177
191
+ "endash", # 178
192
+ "emdash", # 179
193
+ "quotedblleft", # 180
194
+ "quotedblright", # 181
195
+ "quoteleft", # 182
196
+ "quoteright", # 183
197
+ "divide", # 184
198
+ "lozenge", # 185
199
+ "ydieresis", # 186
200
+ "Ydieresis", # 187
201
+ "fraction", # 188
202
+ "currency", # 189
203
+ "guilsinglleft", # 190
204
+ "guilsinglright", # 191
205
+ "fi", # 192
206
+ "fl", # 193
207
+ "daggerdbl", # 194
208
+ "periodcentered", # 195
209
+ "quotesinglbase", # 196
210
+ "quotedblbase", # 197
211
+ "perthousand", # 198
212
+ "Acircumflex", # 199
213
+ "Ecircumflex", # 200
214
+ "Aacute", # 201
215
+ "Edieresis", # 202
216
+ "Egrave", # 203
217
+ "Iacute", # 204
218
+ "Icircumflex", # 205
219
+ "Idieresis", # 206
220
+ "Igrave", # 207
221
+ "Oacute", # 208
222
+ "Ocircumflex", # 209
223
+ "apple", # 210
224
+ "Ograve", # 211
225
+ "Uacute", # 212
226
+ "Ucircumflex", # 213
227
+ "Ugrave", # 214
228
+ "dotlessi", # 215
229
+ "circumflex", # 216
230
+ "tilde", # 217
231
+ "macron", # 218
232
+ "breve", # 219
233
+ "dotaccent", # 220
234
+ "ring", # 221
235
+ "cedilla", # 222
236
+ "hungarumlaut", # 223
237
+ "ogonek", # 224
238
+ "caron", # 225
239
+ "Lslash", # 226
240
+ "lslash", # 227
241
+ "Scaron", # 228
242
+ "scaron", # 229
243
+ "Zcaron", # 230
244
+ "zcaron", # 231
245
+ "brokenbar", # 232
246
+ "Eth", # 233
247
+ "eth", # 234
248
+ "Yacute", # 235
249
+ "yacute", # 236
250
+ "Thorn", # 237
251
+ "thorn", # 238
252
+ "minus", # 239
253
+ "multiply", # 240
254
+ "onesuperior", # 241
255
+ "twosuperior", # 242
256
+ "threesuperior", # 243
257
+ "onehalf", # 244
258
+ "onequarter", # 245
259
+ "threequarters", # 246
260
+ "franc", # 247
261
+ "Gbreve", # 248
262
+ "gbreve", # 249
263
+ "Idotaccent", # 250
264
+ "Scedilla", # 251
265
+ "scedilla", # 252
266
+ "Cacute", # 253
267
+ "cacute", # 254
268
+ "Ccaron", # 255
269
+ "ccaron", # 256
270
+ "dcroat", # 257
271
+ ]
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/B_A_S_E_.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .otBase import BaseTTXConverter
2
+
3
+
4
+ class table_B_A_S_E_(BaseTTXConverter):
5
+ """Baseline table
6
+
7
+ The ``BASE`` table contains information needed to align glyphs in
8
+ different scripts, from different fonts, or at different sizes
9
+ within the same line of text.
10
+
11
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/base
12
+ """
13
+
14
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/BitmapGlyphMetrics.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Since bitmap glyph metrics are shared between EBLC and EBDT
2
+ # this class gets its own python file.
3
+ from fontTools.misc import sstruct
4
+ from fontTools.misc.textTools import safeEval
5
+ import logging
6
+
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+ bigGlyphMetricsFormat = """
11
+ > # big endian
12
+ height: B
13
+ width: B
14
+ horiBearingX: b
15
+ horiBearingY: b
16
+ horiAdvance: B
17
+ vertBearingX: b
18
+ vertBearingY: b
19
+ vertAdvance: B
20
+ """
21
+
22
+ smallGlyphMetricsFormat = """
23
+ > # big endian
24
+ height: B
25
+ width: B
26
+ BearingX: b
27
+ BearingY: b
28
+ Advance: B
29
+ """
30
+
31
+
32
+ class BitmapGlyphMetrics(object):
33
+ def toXML(self, writer, ttFont):
34
+ writer.begintag(self.__class__.__name__)
35
+ writer.newline()
36
+ for metricName in sstruct.getformat(self.__class__.binaryFormat)[1]:
37
+ writer.simpletag(metricName, value=getattr(self, metricName))
38
+ writer.newline()
39
+ writer.endtag(self.__class__.__name__)
40
+ writer.newline()
41
+
42
+ def fromXML(self, name, attrs, content, ttFont):
43
+ metricNames = set(sstruct.getformat(self.__class__.binaryFormat)[1])
44
+ for element in content:
45
+ if not isinstance(element, tuple):
46
+ continue
47
+ name, attrs, content = element
48
+ # Make sure this is a metric that is needed by GlyphMetrics.
49
+ if name in metricNames:
50
+ vars(self)[name] = safeEval(attrs["value"])
51
+ else:
52
+ log.warning(
53
+ "unknown name '%s' being ignored in %s.",
54
+ name,
55
+ self.__class__.__name__,
56
+ )
57
+
58
+
59
+ class BigGlyphMetrics(BitmapGlyphMetrics):
60
+ binaryFormat = bigGlyphMetricsFormat
61
+
62
+
63
+ class SmallGlyphMetrics(BitmapGlyphMetrics):
64
+ binaryFormat = smallGlyphMetricsFormat
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_B_D_T_.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2013 Google, Inc. All Rights Reserved.
2
+ #
3
+ # Google Author(s): Matt Fontaine
4
+
5
+
6
+ from fontTools.misc.textTools import bytesjoin
7
+ from fontTools.misc import sstruct
8
+ from . import E_B_D_T_
9
+ from .BitmapGlyphMetrics import (
10
+ BigGlyphMetrics,
11
+ bigGlyphMetricsFormat,
12
+ SmallGlyphMetrics,
13
+ smallGlyphMetricsFormat,
14
+ )
15
+ from .E_B_D_T_ import (
16
+ BitmapGlyph,
17
+ BitmapPlusSmallMetricsMixin,
18
+ BitmapPlusBigMetricsMixin,
19
+ )
20
+ import struct
21
+
22
+
23
+ class table_C_B_D_T_(E_B_D_T_.table_E_B_D_T_):
24
+ """Color Bitmap Data table
25
+
26
+ The ``CBDT`` table contains color bitmap data for glyphs. It must
27
+ be used in concert with the ``CBLC`` table.
28
+
29
+ It is backwards-compatible with the monochrome/grayscale ``EBDT`` table.
30
+
31
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt
32
+ """
33
+
34
+ # Change the data locator table being referenced.
35
+ locatorName = "CBLC"
36
+
37
+ # Modify the format class accessor for color bitmap use.
38
+ def getImageFormatClass(self, imageFormat):
39
+ try:
40
+ return E_B_D_T_.table_E_B_D_T_.getImageFormatClass(self, imageFormat)
41
+ except KeyError:
42
+ return cbdt_bitmap_classes[imageFormat]
43
+
44
+
45
+ # Helper method for removing export features not supported by color bitmaps.
46
+ # Write data in the parent class will default to raw if an option is unsupported.
47
+ def _removeUnsupportedForColor(dataFunctions):
48
+ dataFunctions = dict(dataFunctions)
49
+ del dataFunctions["row"]
50
+ return dataFunctions
51
+
52
+
53
+ class ColorBitmapGlyph(BitmapGlyph):
54
+ fileExtension = ".png"
55
+ xmlDataFunctions = _removeUnsupportedForColor(BitmapGlyph.xmlDataFunctions)
56
+
57
+
58
+ class cbdt_bitmap_format_17(BitmapPlusSmallMetricsMixin, ColorBitmapGlyph):
59
+ def decompile(self):
60
+ self.metrics = SmallGlyphMetrics()
61
+ dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics)
62
+ (dataLen,) = struct.unpack(">L", data[:4])
63
+ data = data[4:]
64
+
65
+ # For the image data cut it to the size specified by dataLen.
66
+ assert dataLen <= len(data), "Data overun in format 17"
67
+ self.imageData = data[:dataLen]
68
+
69
+ def compile(self, ttFont):
70
+ dataList = []
71
+ dataList.append(sstruct.pack(smallGlyphMetricsFormat, self.metrics))
72
+ dataList.append(struct.pack(">L", len(self.imageData)))
73
+ dataList.append(self.imageData)
74
+ return bytesjoin(dataList)
75
+
76
+
77
+ class cbdt_bitmap_format_18(BitmapPlusBigMetricsMixin, ColorBitmapGlyph):
78
+ def decompile(self):
79
+ self.metrics = BigGlyphMetrics()
80
+ dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics)
81
+ (dataLen,) = struct.unpack(">L", data[:4])
82
+ data = data[4:]
83
+
84
+ # For the image data cut it to the size specified by dataLen.
85
+ assert dataLen <= len(data), "Data overun in format 18"
86
+ self.imageData = data[:dataLen]
87
+
88
+ def compile(self, ttFont):
89
+ dataList = []
90
+ dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
91
+ dataList.append(struct.pack(">L", len(self.imageData)))
92
+ dataList.append(self.imageData)
93
+ return bytesjoin(dataList)
94
+
95
+
96
+ class cbdt_bitmap_format_19(ColorBitmapGlyph):
97
+ def decompile(self):
98
+ (dataLen,) = struct.unpack(">L", self.data[:4])
99
+ data = self.data[4:]
100
+
101
+ assert dataLen <= len(data), "Data overun in format 19"
102
+ self.imageData = data[:dataLen]
103
+
104
+ def compile(self, ttFont):
105
+ return struct.pack(">L", len(self.imageData)) + self.imageData
106
+
107
+
108
+ # Dict for CBDT extended formats.
109
+ cbdt_bitmap_classes = {
110
+ 17: cbdt_bitmap_format_17,
111
+ 18: cbdt_bitmap_format_18,
112
+ 19: cbdt_bitmap_format_19,
113
+ }
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_B_L_C_.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2013 Google, Inc. All Rights Reserved.
2
+ #
3
+ # Google Author(s): Matt Fontaine
4
+
5
+ from . import E_B_L_C_
6
+
7
+
8
+ class table_C_B_L_C_(E_B_L_C_.table_E_B_L_C_):
9
+ """Color Bitmap Location table
10
+
11
+ The ``CBLC`` table contains the locations of color bitmaps for glyphs. It must
12
+ be used in concert with the ``CBDT`` table.
13
+
14
+ It is backwards-compatible with the monochrome/grayscale ``EBLC`` table.
15
+
16
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/cblc
17
+ """
18
+
19
+ dependencies = ["CBDT"]
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_F_F_.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from io import BytesIO
2
+ from fontTools import cffLib
3
+ from . import DefaultTable
4
+
5
+
6
+ class table_C_F_F_(DefaultTable.DefaultTable):
7
+ """Compact Font Format table (version 1)
8
+
9
+ The ``CFF`` table embeds a CFF-formatted font. The CFF font format
10
+ predates OpenType and could be used as a standalone font file, but the
11
+ ``CFF`` table is also used to package CFF fonts into an OpenType
12
+ container.
13
+
14
+ .. note::
15
+ ``CFF`` has been succeeded by ``CFF2``, which eliminates much of
16
+ the redundancy incurred by embedding CFF version 1 in an OpenType
17
+ font.
18
+
19
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/cff
20
+ """
21
+
22
+ def __init__(self, tag=None):
23
+ DefaultTable.DefaultTable.__init__(self, tag)
24
+ self.cff = cffLib.CFFFontSet()
25
+ self._gaveGlyphOrder = False
26
+
27
+ def decompile(self, data, otFont):
28
+ self.cff.decompile(BytesIO(data), otFont, isCFF2=False)
29
+ assert len(self.cff) == 1, "can't deal with multi-font CFF tables."
30
+
31
+ def compile(self, otFont):
32
+ f = BytesIO()
33
+ self.cff.compile(f, otFont, isCFF2=False)
34
+ return f.getvalue()
35
+
36
+ def haveGlyphNames(self):
37
+ if hasattr(self.cff[self.cff.fontNames[0]], "ROS"):
38
+ return False # CID-keyed font
39
+ else:
40
+ return True
41
+
42
+ def getGlyphOrder(self):
43
+ if self._gaveGlyphOrder:
44
+ from fontTools import ttLib
45
+
46
+ raise ttLib.TTLibError("illegal use of getGlyphOrder()")
47
+ self._gaveGlyphOrder = True
48
+ return self.cff[self.cff.fontNames[0]].getGlyphOrder()
49
+
50
+ def setGlyphOrder(self, glyphOrder):
51
+ pass
52
+ # XXX
53
+ # self.cff[self.cff.fontNames[0]].setGlyphOrder(glyphOrder)
54
+
55
+ def toXML(self, writer, otFont):
56
+ self.cff.toXML(writer)
57
+
58
+ def fromXML(self, name, attrs, content, otFont):
59
+ if not hasattr(self, "cff"):
60
+ self.cff = cffLib.CFFFontSet()
61
+ self.cff.fromXML(name, attrs, content, otFont)
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_F_F__2.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from io import BytesIO
2
+ from fontTools.ttLib.tables.C_F_F_ import table_C_F_F_
3
+
4
+
5
+ class table_C_F_F__2(table_C_F_F_):
6
+ """Compact Font Format version 2 table
7
+
8
+ The ``CFF2`` table contains glyph data for a CFF2-flavored OpenType
9
+ font.
10
+
11
+ .. note::
12
+ ``CFF2`` is the successor to ``CFF``, and eliminates much of
13
+ the redundancy incurred by embedding CFF version 1 in an OpenType
14
+ font.
15
+
16
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/cff2
17
+ """
18
+
19
+ def decompile(self, data, otFont):
20
+ self.cff.decompile(BytesIO(data), otFont, isCFF2=True)
21
+ assert len(self.cff) == 1, "can't deal with multi-font CFF tables."
22
+
23
+ def compile(self, otFont):
24
+ f = BytesIO()
25
+ self.cff.compile(f, otFont, isCFF2=True)
26
+ return f.getvalue()
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_O_L_R_.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2013 Google, Inc. All Rights Reserved.
2
+ #
3
+ # Google Author(s): Behdad Esfahbod
4
+
5
+ from fontTools.misc.textTools import safeEval
6
+ from . import DefaultTable
7
+
8
+
9
+ class table_C_O_L_R_(DefaultTable.DefaultTable):
10
+ """Color table
11
+
12
+ The ``COLR`` table defines color presentation of outline glyphs. It must
13
+ be used in concert with the ``CPAL`` table, which contains the color
14
+ descriptors used.
15
+
16
+ This table is structured so that you can treat it like a dictionary keyed by glyph name.
17
+
18
+ ``ttFont['COLR'][<glyphName>]`` will return the color layers for any glyph.
19
+
20
+ ``ttFont['COLR'][<glyphName>] = <value>`` will set the color layers for any glyph.
21
+
22
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/colr
23
+ """
24
+
25
+ @staticmethod
26
+ def _decompileColorLayersV0(table):
27
+ if not table.LayerRecordArray:
28
+ return {}
29
+ colorLayerLists = {}
30
+ layerRecords = table.LayerRecordArray.LayerRecord
31
+ numLayerRecords = len(layerRecords)
32
+ for baseRec in table.BaseGlyphRecordArray.BaseGlyphRecord:
33
+ baseGlyph = baseRec.BaseGlyph
34
+ firstLayerIndex = baseRec.FirstLayerIndex
35
+ numLayers = baseRec.NumLayers
36
+ assert firstLayerIndex + numLayers <= numLayerRecords
37
+ layers = []
38
+ for i in range(firstLayerIndex, firstLayerIndex + numLayers):
39
+ layerRec = layerRecords[i]
40
+ layers.append(LayerRecord(layerRec.LayerGlyph, layerRec.PaletteIndex))
41
+ colorLayerLists[baseGlyph] = layers
42
+ return colorLayerLists
43
+
44
+ def _toOTTable(self, ttFont):
45
+ from . import otTables
46
+ from fontTools.colorLib.builder import populateCOLRv0
47
+
48
+ tableClass = getattr(otTables, self.tableTag)
49
+ table = tableClass()
50
+ table.Version = self.version
51
+
52
+ populateCOLRv0(
53
+ table,
54
+ {
55
+ baseGlyph: [(layer.name, layer.colorID) for layer in layers]
56
+ for baseGlyph, layers in self.ColorLayers.items()
57
+ },
58
+ glyphMap=ttFont.getReverseGlyphMap(rebuild=True),
59
+ )
60
+ return table
61
+
62
+ def decompile(self, data, ttFont):
63
+ from .otBase import OTTableReader
64
+ from . import otTables
65
+
66
+ # We use otData to decompile, but we adapt the decompiled otTables to the
67
+ # existing COLR v0 API for backward compatibility.
68
+ reader = OTTableReader(data, tableTag=self.tableTag)
69
+ tableClass = getattr(otTables, self.tableTag)
70
+ table = tableClass()
71
+ table.decompile(reader, ttFont)
72
+
73
+ self.version = table.Version
74
+ if self.version == 0:
75
+ self.ColorLayers = self._decompileColorLayersV0(table)
76
+ else:
77
+ # for new versions, keep the raw otTables around
78
+ self.table = table
79
+
80
+ def compile(self, ttFont):
81
+ from .otBase import OTTableWriter
82
+
83
+ if hasattr(self, "table"):
84
+ table = self.table
85
+ else:
86
+ table = self._toOTTable(ttFont)
87
+
88
+ writer = OTTableWriter(tableTag=self.tableTag)
89
+ table.compile(writer, ttFont)
90
+ return writer.getAllData()
91
+
92
+ def toXML(self, writer, ttFont):
93
+ if hasattr(self, "table"):
94
+ self.table.toXML2(writer, ttFont)
95
+ else:
96
+ writer.simpletag("version", value=self.version)
97
+ writer.newline()
98
+ for baseGlyph in sorted(self.ColorLayers.keys(), key=ttFont.getGlyphID):
99
+ writer.begintag("ColorGlyph", name=baseGlyph)
100
+ writer.newline()
101
+ for layer in self.ColorLayers[baseGlyph]:
102
+ layer.toXML(writer, ttFont)
103
+ writer.endtag("ColorGlyph")
104
+ writer.newline()
105
+
106
+ def fromXML(self, name, attrs, content, ttFont):
107
+ if name == "version": # old COLR v0 API
108
+ setattr(self, name, safeEval(attrs["value"]))
109
+ elif name == "ColorGlyph":
110
+ if not hasattr(self, "ColorLayers"):
111
+ self.ColorLayers = {}
112
+ glyphName = attrs["name"]
113
+ for element in content:
114
+ if isinstance(element, str):
115
+ continue
116
+ layers = []
117
+ for element in content:
118
+ if isinstance(element, str):
119
+ continue
120
+ layer = LayerRecord()
121
+ layer.fromXML(element[0], element[1], element[2], ttFont)
122
+ layers.append(layer)
123
+ self.ColorLayers[glyphName] = layers
124
+ else: # new COLR v1 API
125
+ from . import otTables
126
+
127
+ if not hasattr(self, "table"):
128
+ tableClass = getattr(otTables, self.tableTag)
129
+ self.table = tableClass()
130
+ self.table.fromXML(name, attrs, content, ttFont)
131
+ self.table.populateDefaults()
132
+ self.version = self.table.Version
133
+
134
+ def __getitem__(self, glyphName):
135
+ if not isinstance(glyphName, str):
136
+ raise TypeError(f"expected str, found {type(glyphName).__name__}")
137
+ return self.ColorLayers[glyphName]
138
+
139
+ def __setitem__(self, glyphName, value):
140
+ if not isinstance(glyphName, str):
141
+ raise TypeError(f"expected str, found {type(glyphName).__name__}")
142
+ if value is not None:
143
+ self.ColorLayers[glyphName] = value
144
+ elif glyphName in self.ColorLayers:
145
+ del self.ColorLayers[glyphName]
146
+
147
+ def __delitem__(self, glyphName):
148
+ del self.ColorLayers[glyphName]
149
+
150
+
151
+ class LayerRecord(object):
152
+ def __init__(self, name=None, colorID=None):
153
+ self.name = name
154
+ self.colorID = colorID
155
+
156
+ def toXML(self, writer, ttFont):
157
+ writer.simpletag("layer", name=self.name, colorID=self.colorID)
158
+ writer.newline()
159
+
160
+ def fromXML(self, eltname, attrs, content, ttFont):
161
+ for name, value in attrs.items():
162
+ if name == "name":
163
+ setattr(self, name, value)
164
+ else:
165
+ setattr(self, name, safeEval(value))
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_P_A_L_.py ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2013 Google, Inc. All Rights Reserved.
2
+ #
3
+ # Google Author(s): Behdad Esfahbod
4
+
5
+ from fontTools.misc.textTools import bytesjoin, safeEval
6
+ from . import DefaultTable
7
+ import array
8
+ from collections import namedtuple
9
+ import struct
10
+ import sys
11
+
12
+
13
+ class table_C_P_A_L_(DefaultTable.DefaultTable):
14
+ """Color Palette table
15
+
16
+ The ``CPAL`` table contains a set of one or more color palettes. The color
17
+ records in each palette can be referenced by the ``COLR`` table to specify
18
+ the colors used in a color glyph.
19
+
20
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/cpal
21
+ """
22
+
23
+ NO_NAME_ID = 0xFFFF
24
+ DEFAULT_PALETTE_TYPE = 0
25
+
26
+ def __init__(self, tag=None):
27
+ DefaultTable.DefaultTable.__init__(self, tag)
28
+ self.palettes = []
29
+ self.paletteTypes = []
30
+ self.paletteLabels = []
31
+ self.paletteEntryLabels = []
32
+
33
+ def decompile(self, data, ttFont):
34
+ (
35
+ self.version,
36
+ self.numPaletteEntries,
37
+ numPalettes,
38
+ numColorRecords,
39
+ goffsetFirstColorRecord,
40
+ ) = struct.unpack(">HHHHL", data[:12])
41
+ assert (
42
+ self.version <= 1
43
+ ), "Version of CPAL table is higher than I know how to handle"
44
+ self.palettes = []
45
+ pos = 12
46
+ for i in range(numPalettes):
47
+ startIndex = struct.unpack(">H", data[pos : pos + 2])[0]
48
+ assert startIndex + self.numPaletteEntries <= numColorRecords
49
+ pos += 2
50
+ palette = []
51
+ ppos = goffsetFirstColorRecord + startIndex * 4
52
+ for j in range(self.numPaletteEntries):
53
+ palette.append(Color(*struct.unpack(">BBBB", data[ppos : ppos + 4])))
54
+ ppos += 4
55
+ self.palettes.append(palette)
56
+ if self.version == 0:
57
+ offsetToPaletteTypeArray = 0
58
+ offsetToPaletteLabelArray = 0
59
+ offsetToPaletteEntryLabelArray = 0
60
+ else:
61
+ pos = 12 + numPalettes * 2
62
+ (
63
+ offsetToPaletteTypeArray,
64
+ offsetToPaletteLabelArray,
65
+ offsetToPaletteEntryLabelArray,
66
+ ) = struct.unpack(">LLL", data[pos : pos + 12])
67
+ self.paletteTypes = self._decompileUInt32Array(
68
+ data,
69
+ offsetToPaletteTypeArray,
70
+ numPalettes,
71
+ default=self.DEFAULT_PALETTE_TYPE,
72
+ )
73
+ self.paletteLabels = self._decompileUInt16Array(
74
+ data, offsetToPaletteLabelArray, numPalettes, default=self.NO_NAME_ID
75
+ )
76
+ self.paletteEntryLabels = self._decompileUInt16Array(
77
+ data,
78
+ offsetToPaletteEntryLabelArray,
79
+ self.numPaletteEntries,
80
+ default=self.NO_NAME_ID,
81
+ )
82
+
83
+ def _decompileUInt16Array(self, data, offset, numElements, default=0):
84
+ if offset == 0:
85
+ return [default] * numElements
86
+ result = array.array("H", data[offset : offset + 2 * numElements])
87
+ if sys.byteorder != "big":
88
+ result.byteswap()
89
+ assert len(result) == numElements, result
90
+ return result.tolist()
91
+
92
+ def _decompileUInt32Array(self, data, offset, numElements, default=0):
93
+ if offset == 0:
94
+ return [default] * numElements
95
+ result = array.array("I", data[offset : offset + 4 * numElements])
96
+ if sys.byteorder != "big":
97
+ result.byteswap()
98
+ assert len(result) == numElements, result
99
+ return result.tolist()
100
+
101
+ def compile(self, ttFont):
102
+ colorRecordIndices, colorRecords = self._compileColorRecords()
103
+ paletteTypes = self._compilePaletteTypes()
104
+ paletteLabels = self._compilePaletteLabels()
105
+ paletteEntryLabels = self._compilePaletteEntryLabels()
106
+ numColorRecords = len(colorRecords) // 4
107
+ offsetToFirstColorRecord = 12 + len(colorRecordIndices)
108
+ if self.version >= 1:
109
+ offsetToFirstColorRecord += 12
110
+ header = struct.pack(
111
+ ">HHHHL",
112
+ self.version,
113
+ self.numPaletteEntries,
114
+ len(self.palettes),
115
+ numColorRecords,
116
+ offsetToFirstColorRecord,
117
+ )
118
+ if self.version == 0:
119
+ dataList = [header, colorRecordIndices, colorRecords]
120
+ else:
121
+ pos = offsetToFirstColorRecord + len(colorRecords)
122
+ if len(paletteTypes) == 0:
123
+ offsetToPaletteTypeArray = 0
124
+ else:
125
+ offsetToPaletteTypeArray = pos
126
+ pos += len(paletteTypes)
127
+ if len(paletteLabels) == 0:
128
+ offsetToPaletteLabelArray = 0
129
+ else:
130
+ offsetToPaletteLabelArray = pos
131
+ pos += len(paletteLabels)
132
+ if len(paletteEntryLabels) == 0:
133
+ offsetToPaletteEntryLabelArray = 0
134
+ else:
135
+ offsetToPaletteEntryLabelArray = pos
136
+ pos += len(paletteLabels)
137
+ header1 = struct.pack(
138
+ ">LLL",
139
+ offsetToPaletteTypeArray,
140
+ offsetToPaletteLabelArray,
141
+ offsetToPaletteEntryLabelArray,
142
+ )
143
+ dataList = [
144
+ header,
145
+ colorRecordIndices,
146
+ header1,
147
+ colorRecords,
148
+ paletteTypes,
149
+ paletteLabels,
150
+ paletteEntryLabels,
151
+ ]
152
+ return bytesjoin(dataList)
153
+
154
+ def _compilePalette(self, palette):
155
+ assert len(palette) == self.numPaletteEntries
156
+ pack = lambda c: struct.pack(">BBBB", c.blue, c.green, c.red, c.alpha)
157
+ return bytesjoin([pack(color) for color in palette])
158
+
159
+ def _compileColorRecords(self):
160
+ colorRecords, colorRecordIndices, pool = [], [], {}
161
+ for palette in self.palettes:
162
+ packedPalette = self._compilePalette(palette)
163
+ if packedPalette in pool:
164
+ index = pool[packedPalette]
165
+ else:
166
+ index = len(colorRecords)
167
+ colorRecords.append(packedPalette)
168
+ pool[packedPalette] = index
169
+ colorRecordIndices.append(struct.pack(">H", index * self.numPaletteEntries))
170
+ return bytesjoin(colorRecordIndices), bytesjoin(colorRecords)
171
+
172
+ def _compilePaletteTypes(self):
173
+ if self.version == 0 or not any(self.paletteTypes):
174
+ return b""
175
+ assert len(self.paletteTypes) == len(self.palettes)
176
+ result = bytesjoin([struct.pack(">I", ptype) for ptype in self.paletteTypes])
177
+ assert len(result) == 4 * len(self.palettes)
178
+ return result
179
+
180
+ def _compilePaletteLabels(self):
181
+ if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteLabels):
182
+ return b""
183
+ assert len(self.paletteLabels) == len(self.palettes)
184
+ result = bytesjoin([struct.pack(">H", label) for label in self.paletteLabels])
185
+ assert len(result) == 2 * len(self.palettes)
186
+ return result
187
+
188
+ def _compilePaletteEntryLabels(self):
189
+ if self.version == 0 or all(
190
+ l == self.NO_NAME_ID for l in self.paletteEntryLabels
191
+ ):
192
+ return b""
193
+ assert len(self.paletteEntryLabels) == self.numPaletteEntries
194
+ result = bytesjoin(
195
+ [struct.pack(">H", label) for label in self.paletteEntryLabels]
196
+ )
197
+ assert len(result) == 2 * self.numPaletteEntries
198
+ return result
199
+
200
+ def toXML(self, writer, ttFont):
201
+ numPalettes = len(self.palettes)
202
+ paletteLabels = {i: nameID for (i, nameID) in enumerate(self.paletteLabels)}
203
+ paletteTypes = {i: typ for (i, typ) in enumerate(self.paletteTypes)}
204
+ writer.simpletag("version", value=self.version)
205
+ writer.newline()
206
+ writer.simpletag("numPaletteEntries", value=self.numPaletteEntries)
207
+ writer.newline()
208
+ for index, palette in enumerate(self.palettes):
209
+ attrs = {"index": index}
210
+ paletteType = paletteTypes.get(index, self.DEFAULT_PALETTE_TYPE)
211
+ paletteLabel = paletteLabels.get(index, self.NO_NAME_ID)
212
+ if self.version > 0 and paletteLabel != self.NO_NAME_ID:
213
+ attrs["label"] = paletteLabel
214
+ if self.version > 0 and paletteType != self.DEFAULT_PALETTE_TYPE:
215
+ attrs["type"] = paletteType
216
+ writer.begintag("palette", **attrs)
217
+ writer.newline()
218
+ if (
219
+ self.version > 0
220
+ and paletteLabel != self.NO_NAME_ID
221
+ and ttFont
222
+ and "name" in ttFont
223
+ ):
224
+ name = ttFont["name"].getDebugName(paletteLabel)
225
+ if name is not None:
226
+ writer.comment(name)
227
+ writer.newline()
228
+ assert len(palette) == self.numPaletteEntries
229
+ for cindex, color in enumerate(palette):
230
+ color.toXML(writer, ttFont, cindex)
231
+ writer.endtag("palette")
232
+ writer.newline()
233
+ if self.version > 0 and not all(
234
+ l == self.NO_NAME_ID for l in self.paletteEntryLabels
235
+ ):
236
+ writer.begintag("paletteEntryLabels")
237
+ writer.newline()
238
+ for index, label in enumerate(self.paletteEntryLabels):
239
+ if label != self.NO_NAME_ID:
240
+ writer.simpletag("label", index=index, value=label)
241
+ if self.version > 0 and label and ttFont and "name" in ttFont:
242
+ name = ttFont["name"].getDebugName(label)
243
+ if name is not None:
244
+ writer.comment(name)
245
+ writer.newline()
246
+ writer.endtag("paletteEntryLabels")
247
+ writer.newline()
248
+
249
+ def fromXML(self, name, attrs, content, ttFont):
250
+ if name == "palette":
251
+ self.paletteLabels.append(int(attrs.get("label", self.NO_NAME_ID)))
252
+ self.paletteTypes.append(int(attrs.get("type", self.DEFAULT_PALETTE_TYPE)))
253
+ palette = []
254
+ for element in content:
255
+ if isinstance(element, str):
256
+ continue
257
+ attrs = element[1]
258
+ color = Color.fromHex(attrs["value"])
259
+ palette.append(color)
260
+ self.palettes.append(palette)
261
+ elif name == "paletteEntryLabels":
262
+ colorLabels = {}
263
+ for element in content:
264
+ if isinstance(element, str):
265
+ continue
266
+ elementName, elementAttr, _ = element
267
+ if elementName == "label":
268
+ labelIndex = safeEval(elementAttr["index"])
269
+ nameID = safeEval(elementAttr["value"])
270
+ colorLabels[labelIndex] = nameID
271
+ self.paletteEntryLabels = [
272
+ colorLabels.get(i, self.NO_NAME_ID)
273
+ for i in range(self.numPaletteEntries)
274
+ ]
275
+ elif "value" in attrs:
276
+ value = safeEval(attrs["value"])
277
+ setattr(self, name, value)
278
+ if name == "numPaletteEntries":
279
+ self.paletteEntryLabels = [self.NO_NAME_ID] * self.numPaletteEntries
280
+
281
+
282
+ class Color(namedtuple("Color", "blue green red alpha")):
283
+ def hex(self):
284
+ return "#%02X%02X%02X%02X" % (self.red, self.green, self.blue, self.alpha)
285
+
286
+ def __repr__(self):
287
+ return self.hex()
288
+
289
+ def toXML(self, writer, ttFont, index=None):
290
+ writer.simpletag("color", value=self.hex(), index=index)
291
+ writer.newline()
292
+
293
+ @classmethod
294
+ def fromHex(cls, value):
295
+ if value[0] == "#":
296
+ value = value[1:]
297
+ red = int(value[0:2], 16)
298
+ green = int(value[2:4], 16)
299
+ blue = int(value[4:6], 16)
300
+ alpha = int(value[6:8], 16) if len(value) >= 8 else 0xFF
301
+ return cls(red=red, green=green, blue=blue, alpha=alpha)
302
+
303
+ @classmethod
304
+ def fromRGBA(cls, red, green, blue, alpha):
305
+ return cls(red=red, green=green, blue=blue, alpha=alpha)
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/D_S_I_G_.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ from typing import TYPE_CHECKING
5
+
6
+ from fontTools.misc import sstruct
7
+ from fontTools.misc.textTools import bytesjoin, safeEval, strjoin, tobytes, tostr
8
+
9
+ from . import DefaultTable
10
+
11
+ if TYPE_CHECKING:
12
+ from fontTools.misc.xmlWriter import XMLWriter
13
+ from fontTools.ttLib import TTFont
14
+
15
+ DSIG_HeaderFormat = """
16
+ > # big endian
17
+ ulVersion: L
18
+ usNumSigs: H
19
+ usFlag: H
20
+ """
21
+ # followed by an array of usNumSigs DSIG_Signature records
22
+ DSIG_SignatureFormat = """
23
+ > # big endian
24
+ ulFormat: L
25
+ ulLength: L # length includes DSIG_SignatureBlock header
26
+ ulOffset: L
27
+ """
28
+ # followed by an array of usNumSigs DSIG_SignatureBlock records,
29
+ # each followed immediately by the pkcs7 bytes
30
+ DSIG_SignatureBlockFormat = """
31
+ > # big endian
32
+ usReserved1: H
33
+ usReserved2: H
34
+ cbSignature: l # length of following raw pkcs7 data
35
+ """
36
+
37
+ #
38
+ # NOTE
39
+ # the DSIG table format allows for SignatureBlocks residing
40
+ # anywhere in the table and possibly in a different order as
41
+ # listed in the array after the first table header
42
+ #
43
+ # this implementation does not keep track of any gaps and/or data
44
+ # before or after the actual signature blocks while decompiling,
45
+ # and puts them in the same physical order as listed in the header
46
+ # on compilation with no padding whatsoever.
47
+ #
48
+
49
+
50
+ class table_D_S_I_G_(DefaultTable.DefaultTable):
51
+ """Digital Signature table
52
+
53
+ The ``DSIG`` table contains cryptographic signatures for the font.
54
+
55
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/dsig
56
+ """
57
+
58
+ def decompile(self, data: bytes, ttFont: TTFont) -> None:
59
+ dummy, newData = sstruct.unpack2(DSIG_HeaderFormat, data, self)
60
+ assert self.ulVersion == 1, "DSIG ulVersion must be 1"
61
+ assert self.usFlag & ~1 == 0, "DSIG usFlag must be 0x1 or 0x0"
62
+ self.signatureRecords: list[SignatureRecord] = []
63
+ sigrecs = self.signatureRecords
64
+ for n in range(self.usNumSigs):
65
+ sigrec, newData = sstruct.unpack2(
66
+ DSIG_SignatureFormat, newData, SignatureRecord()
67
+ )
68
+ assert sigrec.ulFormat == 1, (
69
+ "DSIG signature record #%d ulFormat must be 1" % n
70
+ )
71
+ sigrecs.append(sigrec)
72
+ for sigrec in sigrecs:
73
+ dummy, newData = sstruct.unpack2(
74
+ DSIG_SignatureBlockFormat, data[sigrec.ulOffset :], sigrec
75
+ )
76
+ assert sigrec.usReserved1 == 0, (
77
+ "DSIG signature record #%d usReserverd1 must be 0" % n
78
+ )
79
+ assert sigrec.usReserved2 == 0, (
80
+ "DSIG signature record #%d usReserverd2 must be 0" % n
81
+ )
82
+ sigrec.pkcs7 = newData[: sigrec.cbSignature]
83
+
84
+ def compile(self, ttFont: TTFont) -> bytes:
85
+ packed = sstruct.pack(DSIG_HeaderFormat, self)
86
+ headers = [packed]
87
+ offset = len(packed) + self.usNumSigs * sstruct.calcsize(DSIG_SignatureFormat)
88
+ data = []
89
+ for sigrec in self.signatureRecords:
90
+ # first pack signature block
91
+ sigrec.cbSignature = len(sigrec.pkcs7)
92
+ packed = sstruct.pack(DSIG_SignatureBlockFormat, sigrec) + sigrec.pkcs7
93
+ data.append(packed)
94
+ # update redundant length field
95
+ sigrec.ulLength = len(packed)
96
+ # update running table offset
97
+ sigrec.ulOffset = offset
98
+ headers.append(sstruct.pack(DSIG_SignatureFormat, sigrec))
99
+ offset += sigrec.ulLength
100
+ if offset % 2:
101
+ # Pad to even bytes
102
+ data.append(b"\0")
103
+ return bytesjoin(headers + data)
104
+
105
+ def toXML(self, xmlWriter: XMLWriter, ttFont: TTFont) -> None:
106
+ xmlWriter.comment(
107
+ "note that the Digital Signature will be invalid after recompilation!"
108
+ )
109
+ xmlWriter.newline()
110
+ xmlWriter.simpletag(
111
+ "tableHeader",
112
+ version=self.ulVersion,
113
+ numSigs=self.usNumSigs,
114
+ flag="0x%X" % self.usFlag,
115
+ )
116
+ for sigrec in self.signatureRecords:
117
+ xmlWriter.newline()
118
+ sigrec.toXML(xmlWriter, ttFont)
119
+ xmlWriter.newline()
120
+
121
+ def fromXML(self, name, attrs, content, ttFont):
122
+ if name == "tableHeader":
123
+ self.signatureRecords = []
124
+ self.ulVersion = safeEval(attrs["version"])
125
+ self.usNumSigs = safeEval(attrs["numSigs"])
126
+ self.usFlag = safeEval(attrs["flag"])
127
+ return
128
+ if name == "SignatureRecord":
129
+ sigrec = SignatureRecord()
130
+ sigrec.fromXML(name, attrs, content, ttFont)
131
+ self.signatureRecords.append(sigrec)
132
+
133
+
134
+ pem_spam = lambda l, spam={
135
+ "-----BEGIN PKCS7-----": True,
136
+ "-----END PKCS7-----": True,
137
+ "": True,
138
+ }: not spam.get(l.strip())
139
+
140
+
141
+ def b64encode(b):
142
+ s = base64.b64encode(b)
143
+ # Line-break at 76 chars.
144
+ items = []
145
+ while s:
146
+ items.append(tostr(s[:76]))
147
+ items.append("\n")
148
+ s = s[76:]
149
+ return strjoin(items)
150
+
151
+
152
+ class SignatureRecord:
153
+ def __repr__(self) -> str:
154
+ return "<%s: %s>" % (self.__class__.__name__, self.__dict__)
155
+
156
+ def toXML(self, writer, ttFont: TTFont) -> None:
157
+ writer.begintag(self.__class__.__name__, format=self.ulFormat)
158
+ writer.newline()
159
+ writer.write_noindent("-----BEGIN PKCS7-----\n")
160
+ writer.write_noindent(b64encode(self.pkcs7))
161
+ writer.write_noindent("-----END PKCS7-----\n")
162
+ writer.endtag(self.__class__.__name__)
163
+
164
+ def fromXML(
165
+ self, name: str, attrs: dict[str, str], content: str, ttFont: TTFont
166
+ ) -> None:
167
+ self.ulFormat: int = safeEval(attrs["format"])
168
+ self.usReserved1: int = safeEval(attrs.get("reserved1", "0"))
169
+ self.usReserved2: int = safeEval(attrs.get("reserved2", "0"))
170
+ self.pkcs7 = base64.b64decode(tobytes(strjoin(filter(pem_spam, content))))
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/D__e_b_g.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from textwrap import indent
3
+
4
+ from . import DefaultTable
5
+ from fontTools.misc.textTools import tostr
6
+
7
+
8
+ class table_D__e_b_g(DefaultTable.DefaultTable):
9
+ def __init__(self, tag=None):
10
+ DefaultTable.DefaultTable.__init__(self, tag)
11
+ self.data = {}
12
+
13
+ def decompile(self, data, ttFont):
14
+ self.data = json.loads(data)
15
+
16
+ def compile(self, ttFont):
17
+ return json.dumps(self.data).encode("utf-8")
18
+
19
+ def toXML(self, writer, ttFont):
20
+ # make sure json indentation inside CDATA block matches XMLWriter's
21
+ data = json.dumps(self.data, indent=len(writer.indentwhite))
22
+ prefix = tostr(writer.indentwhite) * (writer.indentlevel + 1)
23
+ # but don't indent the first json line so it's adjacent to `<![CDATA[{`
24
+ cdata = indent(data, prefix, lambda ln: ln != "{\n")
25
+
26
+ writer.begintag("json")
27
+ writer.newline()
28
+ writer.writecdata(cdata)
29
+ writer.newline()
30
+ writer.endtag("json")
31
+ writer.newline()
32
+
33
+ def fromXML(self, name, attrs, content, ttFont):
34
+ if name == "json":
35
+ self.data = json.loads("".join(content))
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/DefaultTable.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from fontTools.misc.textTools import Tag
6
+ from fontTools.ttLib import getClassTag
7
+
8
+ if TYPE_CHECKING:
9
+ from typing import Any
10
+
11
+ from fontTools.misc.xmlWriter import XMLWriter
12
+ from fontTools.ttLib import TTFont
13
+
14
+
15
+ class DefaultTable:
16
+ dependencies: list[str] = []
17
+
18
+ def __init__(self, tag: str | bytes | None = None) -> None:
19
+ if tag is None:
20
+ tag = getClassTag(self.__class__)
21
+ self.tableTag = Tag(tag)
22
+
23
+ def decompile(self, data: bytes, ttFont: TTFont) -> None:
24
+ self.data = data
25
+
26
+ def compile(self, ttFont: TTFont) -> bytes:
27
+ return self.data
28
+
29
+ def toXML(
30
+ self, writer: XMLWriter, ttFont: TTFont, **kwargs: dict[str, Any]
31
+ ) -> None:
32
+ if hasattr(self, "ERROR"):
33
+ writer.comment("An error occurred during the decompilation of this table")
34
+ writer.newline()
35
+ writer.comment(self.ERROR)
36
+ writer.newline()
37
+ writer.begintag("hexdata")
38
+ writer.newline()
39
+ writer.dumphex(self.compile(ttFont))
40
+ writer.endtag("hexdata")
41
+ writer.newline()
42
+
43
+ def fromXML(
44
+ self, name: str, attrs: dict[str, str], content: str, ttFont: TTFont
45
+ ) -> None:
46
+ from fontTools import ttLib
47
+ from fontTools.misc.textTools import readHex
48
+
49
+ if name != "hexdata":
50
+ raise ttLib.TTLibError("can't handle '%s' element" % name)
51
+ self.decompile(readHex(content), ttFont)
52
+
53
+ def __repr__(self) -> str:
54
+ return "<'%s' table at %x>" % (self.tableTag, id(self))
55
+
56
+ def __eq__(self, other: Any) -> bool:
57
+ if type(self) != type(other):
58
+ return NotImplemented
59
+ return self.__dict__ == other.__dict__
60
+
61
+ def __ne__(self, other: Any) -> bool:
62
+ result = self.__eq__(other)
63
+ return result if result is NotImplemented else not result
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/E_B_D_T_.py ADDED
@@ -0,0 +1,835 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc import sstruct
2
+ from fontTools.misc.textTools import (
3
+ bytechr,
4
+ byteord,
5
+ bytesjoin,
6
+ strjoin,
7
+ safeEval,
8
+ readHex,
9
+ hexStr,
10
+ deHexStr,
11
+ )
12
+ from .BitmapGlyphMetrics import (
13
+ BigGlyphMetrics,
14
+ bigGlyphMetricsFormat,
15
+ SmallGlyphMetrics,
16
+ smallGlyphMetricsFormat,
17
+ )
18
+ from . import DefaultTable
19
+ import itertools
20
+ import os
21
+ import struct
22
+ import logging
23
+
24
+
25
+ log = logging.getLogger(__name__)
26
+
27
+ ebdtTableVersionFormat = """
28
+ > # big endian
29
+ version: 16.16F
30
+ """
31
+
32
+ ebdtComponentFormat = """
33
+ > # big endian
34
+ glyphCode: H
35
+ xOffset: b
36
+ yOffset: b
37
+ """
38
+
39
+
40
+ class table_E_B_D_T_(DefaultTable.DefaultTable):
41
+ """Embedded Bitmap Data table
42
+
43
+ The ``EBDT`` table contains monochrome or grayscale bitmap data for
44
+ glyphs. It must be used in concert with the ``EBLC`` table.
45
+
46
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt
47
+ """
48
+
49
+ # Keep a reference to the name of the data locator table.
50
+ locatorName = "EBLC"
51
+
52
+ # This method can be overridden in subclasses to support new formats
53
+ # without changing the other implementation. Also can be used as a
54
+ # convenience method for coverting a font file to an alternative format.
55
+ def getImageFormatClass(self, imageFormat):
56
+ return ebdt_bitmap_classes[imageFormat]
57
+
58
+ def decompile(self, data, ttFont):
59
+ # Get the version but don't advance the slice.
60
+ # Most of the lookup for this table is done relative
61
+ # to the begining so slice by the offsets provided
62
+ # in the EBLC table.
63
+ sstruct.unpack2(ebdtTableVersionFormat, data, self)
64
+
65
+ # Keep a dict of glyphs that have been seen so they aren't remade.
66
+ # This dict maps intervals of data to the BitmapGlyph.
67
+ glyphDict = {}
68
+
69
+ # Pull out the EBLC table and loop through glyphs.
70
+ # A strike is a concept that spans both tables.
71
+ # The actual bitmap data is stored in the EBDT.
72
+ locator = ttFont[self.__class__.locatorName]
73
+ self.strikeData = []
74
+ for curStrike in locator.strikes:
75
+ bitmapGlyphDict = {}
76
+ self.strikeData.append(bitmapGlyphDict)
77
+ for indexSubTable in curStrike.indexSubTables:
78
+ dataIter = zip(indexSubTable.names, indexSubTable.locations)
79
+ for curName, curLoc in dataIter:
80
+ # Don't create duplicate data entries for the same glyphs.
81
+ # Instead just use the structures that already exist if they exist.
82
+ if curLoc in glyphDict:
83
+ curGlyph = glyphDict[curLoc]
84
+ else:
85
+ curGlyphData = data[slice(*curLoc)]
86
+ imageFormatClass = self.getImageFormatClass(
87
+ indexSubTable.imageFormat
88
+ )
89
+ curGlyph = imageFormatClass(curGlyphData, ttFont)
90
+ glyphDict[curLoc] = curGlyph
91
+ bitmapGlyphDict[curName] = curGlyph
92
+
93
+ def compile(self, ttFont):
94
+ dataList = []
95
+ dataList.append(sstruct.pack(ebdtTableVersionFormat, self))
96
+ dataSize = len(dataList[0])
97
+
98
+ # Keep a dict of glyphs that have been seen so they aren't remade.
99
+ # This dict maps the id of the BitmapGlyph to the interval
100
+ # in the data.
101
+ glyphDict = {}
102
+
103
+ # Go through the bitmap glyph data. Just in case the data for a glyph
104
+ # changed the size metrics should be recalculated. There are a variety
105
+ # of formats and they get stored in the EBLC table. That is why
106
+ # recalculation is defered to the EblcIndexSubTable class and just
107
+ # pass what is known about bitmap glyphs from this particular table.
108
+ locator = ttFont[self.__class__.locatorName]
109
+ for curStrike, curGlyphDict in zip(locator.strikes, self.strikeData):
110
+ for curIndexSubTable in curStrike.indexSubTables:
111
+ dataLocations = []
112
+ for curName in curIndexSubTable.names:
113
+ # Handle the data placement based on seeing the glyph or not.
114
+ # Just save a reference to the location if the glyph has already
115
+ # been saved in compile. This code assumes that glyphs will only
116
+ # be referenced multiple times from indexFormat5. By luck the
117
+ # code may still work when referencing poorly ordered fonts with
118
+ # duplicate references. If there is a font that is unlucky the
119
+ # respective compile methods for the indexSubTables will fail
120
+ # their assertions. All fonts seem to follow this assumption.
121
+ # More complicated packing may be needed if a counter-font exists.
122
+ glyph = curGlyphDict[curName]
123
+ objectId = id(glyph)
124
+ if objectId not in glyphDict:
125
+ data = glyph.compile(ttFont)
126
+ data = curIndexSubTable.padBitmapData(data)
127
+ startByte = dataSize
128
+ dataSize += len(data)
129
+ endByte = dataSize
130
+ dataList.append(data)
131
+ dataLoc = (startByte, endByte)
132
+ glyphDict[objectId] = dataLoc
133
+ else:
134
+ dataLoc = glyphDict[objectId]
135
+ dataLocations.append(dataLoc)
136
+ # Just use the new data locations in the indexSubTable.
137
+ # The respective compile implementations will take care
138
+ # of any of the problems in the convertion that may arise.
139
+ curIndexSubTable.locations = dataLocations
140
+
141
+ return bytesjoin(dataList)
142
+
143
+ def toXML(self, writer, ttFont):
144
+ # When exporting to XML if one of the data export formats
145
+ # requires metrics then those metrics may be in the locator.
146
+ # In this case populate the bitmaps with "export metrics".
147
+ if ttFont.bitmapGlyphDataFormat in ("row", "bitwise"):
148
+ locator = ttFont[self.__class__.locatorName]
149
+ for curStrike, curGlyphDict in zip(locator.strikes, self.strikeData):
150
+ for curIndexSubTable in curStrike.indexSubTables:
151
+ for curName in curIndexSubTable.names:
152
+ glyph = curGlyphDict[curName]
153
+ # I'm not sure which metrics have priority here.
154
+ # For now if both metrics exist go with glyph metrics.
155
+ if hasattr(glyph, "metrics"):
156
+ glyph.exportMetrics = glyph.metrics
157
+ else:
158
+ glyph.exportMetrics = curIndexSubTable.metrics
159
+ glyph.exportBitDepth = curStrike.bitmapSizeTable.bitDepth
160
+
161
+ writer.simpletag("header", [("version", self.version)])
162
+ writer.newline()
163
+ locator = ttFont[self.__class__.locatorName]
164
+ for strikeIndex, bitmapGlyphDict in enumerate(self.strikeData):
165
+ writer.begintag("strikedata", [("index", strikeIndex)])
166
+ writer.newline()
167
+ for curName, curBitmap in bitmapGlyphDict.items():
168
+ curBitmap.toXML(strikeIndex, curName, writer, ttFont)
169
+ writer.endtag("strikedata")
170
+ writer.newline()
171
+
172
+ def fromXML(self, name, attrs, content, ttFont):
173
+ if name == "header":
174
+ self.version = safeEval(attrs["version"])
175
+ elif name == "strikedata":
176
+ if not hasattr(self, "strikeData"):
177
+ self.strikeData = []
178
+ strikeIndex = safeEval(attrs["index"])
179
+
180
+ bitmapGlyphDict = {}
181
+ for element in content:
182
+ if not isinstance(element, tuple):
183
+ continue
184
+ name, attrs, content = element
185
+ if name[4:].startswith(_bitmapGlyphSubclassPrefix[4:]):
186
+ imageFormat = safeEval(name[len(_bitmapGlyphSubclassPrefix) :])
187
+ glyphName = attrs["name"]
188
+ imageFormatClass = self.getImageFormatClass(imageFormat)
189
+ curGlyph = imageFormatClass(None, None)
190
+ curGlyph.fromXML(name, attrs, content, ttFont)
191
+ assert glyphName not in bitmapGlyphDict, (
192
+ "Duplicate glyphs with the same name '%s' in the same strike."
193
+ % glyphName
194
+ )
195
+ bitmapGlyphDict[glyphName] = curGlyph
196
+ else:
197
+ log.warning("%s being ignored by %s", name, self.__class__.__name__)
198
+
199
+ # Grow the strike data array to the appropriate size. The XML
200
+ # format allows the strike index value to be out of order.
201
+ if strikeIndex >= len(self.strikeData):
202
+ self.strikeData += [None] * (strikeIndex + 1 - len(self.strikeData))
203
+ assert (
204
+ self.strikeData[strikeIndex] is None
205
+ ), "Duplicate strike EBDT indices."
206
+ self.strikeData[strikeIndex] = bitmapGlyphDict
207
+
208
+
209
+ class EbdtComponent(object):
210
+ def toXML(self, writer, ttFont):
211
+ writer.begintag("ebdtComponent", [("name", self.name)])
212
+ writer.newline()
213
+ for componentName in sstruct.getformat(ebdtComponentFormat)[1][1:]:
214
+ writer.simpletag(componentName, value=getattr(self, componentName))
215
+ writer.newline()
216
+ writer.endtag("ebdtComponent")
217
+ writer.newline()
218
+
219
+ def fromXML(self, name, attrs, content, ttFont):
220
+ self.name = attrs["name"]
221
+ componentNames = set(sstruct.getformat(ebdtComponentFormat)[1][1:])
222
+ for element in content:
223
+ if not isinstance(element, tuple):
224
+ continue
225
+ name, attrs, content = element
226
+ if name in componentNames:
227
+ vars(self)[name] = safeEval(attrs["value"])
228
+ else:
229
+ log.warning("unknown name '%s' being ignored by EbdtComponent.", name)
230
+
231
+
232
+ # Helper functions for dealing with binary.
233
+
234
+
235
+ def _data2binary(data, numBits):
236
+ binaryList = []
237
+ for curByte in data:
238
+ value = byteord(curByte)
239
+ numBitsCut = min(8, numBits)
240
+ for i in range(numBitsCut):
241
+ if value & 0x1:
242
+ binaryList.append("1")
243
+ else:
244
+ binaryList.append("0")
245
+ value = value >> 1
246
+ numBits -= numBitsCut
247
+ return strjoin(binaryList)
248
+
249
+
250
+ def _binary2data(binary):
251
+ byteList = []
252
+ for bitLoc in range(0, len(binary), 8):
253
+ byteString = binary[bitLoc : bitLoc + 8]
254
+ curByte = 0
255
+ for curBit in reversed(byteString):
256
+ curByte = curByte << 1
257
+ if curBit == "1":
258
+ curByte |= 1
259
+ byteList.append(bytechr(curByte))
260
+ return bytesjoin(byteList)
261
+
262
+
263
+ def _memoize(f):
264
+ class memodict(dict):
265
+ def __missing__(self, key):
266
+ ret = f(key)
267
+ if isinstance(key, int) or len(key) == 1:
268
+ self[key] = ret
269
+ return ret
270
+
271
+ return memodict().__getitem__
272
+
273
+
274
+ # 00100111 -> 11100100 per byte, not to be confused with little/big endian.
275
+ # Bitmap data per byte is in the order that binary is written on the page
276
+ # with the least significant bit as far right as possible. This is the
277
+ # opposite of what makes sense algorithmically and hence this function.
278
+ @_memoize
279
+ def _reverseBytes(data):
280
+ r"""
281
+ >>> bin(ord(_reverseBytes(0b00100111)))
282
+ '0b11100100'
283
+ >>> _reverseBytes(b'\x00\xf0')
284
+ b'\x00\x0f'
285
+ """
286
+ if isinstance(data, bytes) and len(data) != 1:
287
+ return bytesjoin(map(_reverseBytes, data))
288
+ byte = byteord(data)
289
+ result = 0
290
+ for i in range(8):
291
+ result = result << 1
292
+ result |= byte & 1
293
+ byte = byte >> 1
294
+ return bytechr(result)
295
+
296
+
297
+ # This section of code is for reading and writing image data to/from XML.
298
+
299
+
300
+ def _writeRawImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
301
+ writer.begintag("rawimagedata")
302
+ writer.newline()
303
+ writer.dumphex(bitmapObject.imageData)
304
+ writer.endtag("rawimagedata")
305
+ writer.newline()
306
+
307
+
308
+ def _readRawImageData(bitmapObject, name, attrs, content, ttFont):
309
+ bitmapObject.imageData = readHex(content)
310
+
311
+
312
+ def _writeRowImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
313
+ metrics = bitmapObject.exportMetrics
314
+ del bitmapObject.exportMetrics
315
+ bitDepth = bitmapObject.exportBitDepth
316
+ del bitmapObject.exportBitDepth
317
+
318
+ writer.begintag(
319
+ "rowimagedata", bitDepth=bitDepth, width=metrics.width, height=metrics.height
320
+ )
321
+ writer.newline()
322
+ for curRow in range(metrics.height):
323
+ rowData = bitmapObject.getRow(curRow, bitDepth=bitDepth, metrics=metrics)
324
+ writer.simpletag("row", value=hexStr(rowData))
325
+ writer.newline()
326
+ writer.endtag("rowimagedata")
327
+ writer.newline()
328
+
329
+
330
+ def _readRowImageData(bitmapObject, name, attrs, content, ttFont):
331
+ bitDepth = safeEval(attrs["bitDepth"])
332
+ metrics = SmallGlyphMetrics()
333
+ metrics.width = safeEval(attrs["width"])
334
+ metrics.height = safeEval(attrs["height"])
335
+
336
+ dataRows = []
337
+ for element in content:
338
+ if not isinstance(element, tuple):
339
+ continue
340
+ name, attr, content = element
341
+ # Chop off 'imagedata' from the tag to get just the option.
342
+ if name == "row":
343
+ dataRows.append(deHexStr(attr["value"]))
344
+ bitmapObject.setRows(dataRows, bitDepth=bitDepth, metrics=metrics)
345
+
346
+
347
+ def _writeBitwiseImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
348
+ metrics = bitmapObject.exportMetrics
349
+ del bitmapObject.exportMetrics
350
+ bitDepth = bitmapObject.exportBitDepth
351
+ del bitmapObject.exportBitDepth
352
+
353
+ # A dict for mapping binary to more readable/artistic ASCII characters.
354
+ binaryConv = {"0": ".", "1": "@"}
355
+
356
+ writer.begintag(
357
+ "bitwiseimagedata",
358
+ bitDepth=bitDepth,
359
+ width=metrics.width,
360
+ height=metrics.height,
361
+ )
362
+ writer.newline()
363
+ for curRow in range(metrics.height):
364
+ rowData = bitmapObject.getRow(
365
+ curRow, bitDepth=1, metrics=metrics, reverseBytes=True
366
+ )
367
+ rowData = _data2binary(rowData, metrics.width)
368
+ # Make the output a readable ASCII art form.
369
+ rowData = strjoin(map(binaryConv.get, rowData))
370
+ writer.simpletag("row", value=rowData)
371
+ writer.newline()
372
+ writer.endtag("bitwiseimagedata")
373
+ writer.newline()
374
+
375
+
376
+ def _readBitwiseImageData(bitmapObject, name, attrs, content, ttFont):
377
+ bitDepth = safeEval(attrs["bitDepth"])
378
+ metrics = SmallGlyphMetrics()
379
+ metrics.width = safeEval(attrs["width"])
380
+ metrics.height = safeEval(attrs["height"])
381
+
382
+ # A dict for mapping from ASCII to binary. All characters are considered
383
+ # a '1' except space, period and '0' which maps to '0'.
384
+ binaryConv = {" ": "0", ".": "0", "0": "0"}
385
+
386
+ dataRows = []
387
+ for element in content:
388
+ if not isinstance(element, tuple):
389
+ continue
390
+ name, attr, content = element
391
+ if name == "row":
392
+ mapParams = zip(attr["value"], itertools.repeat("1"))
393
+ rowData = strjoin(itertools.starmap(binaryConv.get, mapParams))
394
+ dataRows.append(_binary2data(rowData))
395
+
396
+ bitmapObject.setRows(
397
+ dataRows, bitDepth=bitDepth, metrics=metrics, reverseBytes=True
398
+ )
399
+
400
+
401
+ def _writeExtFileImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
402
+ try:
403
+ folder = os.path.dirname(writer.file.name)
404
+ except AttributeError:
405
+ # fall back to current directory if output file's directory isn't found
406
+ folder = "."
407
+ folder = os.path.join(folder, "bitmaps")
408
+ filename = glyphName + bitmapObject.fileExtension
409
+ if not os.path.isdir(folder):
410
+ os.makedirs(folder)
411
+ folder = os.path.join(folder, "strike%d" % strikeIndex)
412
+ if not os.path.isdir(folder):
413
+ os.makedirs(folder)
414
+
415
+ fullPath = os.path.join(folder, filename)
416
+ writer.simpletag("extfileimagedata", value=fullPath)
417
+ writer.newline()
418
+
419
+ with open(fullPath, "wb") as file:
420
+ file.write(bitmapObject.imageData)
421
+
422
+
423
+ def _readExtFileImageData(bitmapObject, name, attrs, content, ttFont):
424
+ fullPath = attrs["value"]
425
+ with open(fullPath, "rb") as file:
426
+ bitmapObject.imageData = file.read()
427
+
428
+
429
+ # End of XML writing code.
430
+
431
+ # Important information about the naming scheme. Used for identifying formats
432
+ # in XML.
433
+ _bitmapGlyphSubclassPrefix = "ebdt_bitmap_format_"
434
+
435
+
436
+ class BitmapGlyph(object):
437
+ # For the external file format. This can be changed in subclasses. This way
438
+ # when the extfile option is turned on files have the form: glyphName.ext
439
+ # The default is just a flat binary file with no meaning.
440
+ fileExtension = ".bin"
441
+
442
+ # Keep track of reading and writing of various forms.
443
+ xmlDataFunctions = {
444
+ "raw": (_writeRawImageData, _readRawImageData),
445
+ "row": (_writeRowImageData, _readRowImageData),
446
+ "bitwise": (_writeBitwiseImageData, _readBitwiseImageData),
447
+ "extfile": (_writeExtFileImageData, _readExtFileImageData),
448
+ }
449
+
450
+ def __init__(self, data, ttFont):
451
+ self.data = data
452
+ self.ttFont = ttFont
453
+ # TODO Currently non-lazy decompilation is untested here...
454
+ # if not ttFont.lazy:
455
+ # self.decompile()
456
+ # del self.data
457
+
458
+ def __getattr__(self, attr):
459
+ # Allow lazy decompile.
460
+ if attr[:2] == "__":
461
+ raise AttributeError(attr)
462
+ if attr == "data":
463
+ raise AttributeError(attr)
464
+ self.decompile()
465
+ del self.data
466
+ return getattr(self, attr)
467
+
468
+ def ensureDecompiled(self, recurse=False):
469
+ if hasattr(self, "data"):
470
+ self.decompile()
471
+ del self.data
472
+
473
+ # Not a fan of this but it is needed for safer safety checking.
474
+ def getFormat(self):
475
+ return safeEval(self.__class__.__name__[len(_bitmapGlyphSubclassPrefix) :])
476
+
477
+ def toXML(self, strikeIndex, glyphName, writer, ttFont):
478
+ writer.begintag(self.__class__.__name__, [("name", glyphName)])
479
+ writer.newline()
480
+
481
+ self.writeMetrics(writer, ttFont)
482
+ # Use the internal write method to write using the correct output format.
483
+ self.writeData(strikeIndex, glyphName, writer, ttFont)
484
+
485
+ writer.endtag(self.__class__.__name__)
486
+ writer.newline()
487
+
488
+ def fromXML(self, name, attrs, content, ttFont):
489
+ self.readMetrics(name, attrs, content, ttFont)
490
+ for element in content:
491
+ if not isinstance(element, tuple):
492
+ continue
493
+ name, attr, content = element
494
+ if not name.endswith("imagedata"):
495
+ continue
496
+ # Chop off 'imagedata' from the tag to get just the option.
497
+ option = name[: -len("imagedata")]
498
+ assert option in self.__class__.xmlDataFunctions
499
+ self.readData(name, attr, content, ttFont)
500
+
501
+ # Some of the glyphs have the metrics. This allows for metrics to be
502
+ # added if the glyph format has them. Default behavior is to do nothing.
503
+ def writeMetrics(self, writer, ttFont):
504
+ pass
505
+
506
+ # The opposite of write metrics.
507
+ def readMetrics(self, name, attrs, content, ttFont):
508
+ pass
509
+
510
+ def writeData(self, strikeIndex, glyphName, writer, ttFont):
511
+ try:
512
+ writeFunc, readFunc = self.__class__.xmlDataFunctions[
513
+ ttFont.bitmapGlyphDataFormat
514
+ ]
515
+ except KeyError:
516
+ writeFunc = _writeRawImageData
517
+ writeFunc(strikeIndex, glyphName, self, writer, ttFont)
518
+
519
+ def readData(self, name, attrs, content, ttFont):
520
+ # Chop off 'imagedata' from the tag to get just the option.
521
+ option = name[: -len("imagedata")]
522
+ writeFunc, readFunc = self.__class__.xmlDataFunctions[option]
523
+ readFunc(self, name, attrs, content, ttFont)
524
+
525
+
526
+ # A closure for creating a mixin for the two types of metrics handling.
527
+ # Most of the code is very similar so its easier to deal with here.
528
+ # Everything works just by passing the class that the mixin is for.
529
+ def _createBitmapPlusMetricsMixin(metricsClass):
530
+ # Both metrics names are listed here to make meaningful error messages.
531
+ metricStrings = [BigGlyphMetrics.__name__, SmallGlyphMetrics.__name__]
532
+ curMetricsName = metricsClass.__name__
533
+ # Find which metrics this is for and determine the opposite name.
534
+ metricsId = metricStrings.index(curMetricsName)
535
+ oppositeMetricsName = metricStrings[1 - metricsId]
536
+
537
+ class BitmapPlusMetricsMixin(object):
538
+ def writeMetrics(self, writer, ttFont):
539
+ self.metrics.toXML(writer, ttFont)
540
+
541
+ def readMetrics(self, name, attrs, content, ttFont):
542
+ for element in content:
543
+ if not isinstance(element, tuple):
544
+ continue
545
+ name, attrs, content = element
546
+ if name == curMetricsName:
547
+ self.metrics = metricsClass()
548
+ self.metrics.fromXML(name, attrs, content, ttFont)
549
+ elif name == oppositeMetricsName:
550
+ log.warning(
551
+ "Warning: %s being ignored in format %d.",
552
+ oppositeMetricsName,
553
+ self.getFormat(),
554
+ )
555
+
556
+ return BitmapPlusMetricsMixin
557
+
558
+
559
+ # Since there are only two types of mixin's just create them here.
560
+ BitmapPlusBigMetricsMixin = _createBitmapPlusMetricsMixin(BigGlyphMetrics)
561
+ BitmapPlusSmallMetricsMixin = _createBitmapPlusMetricsMixin(SmallGlyphMetrics)
562
+
563
+
564
+ # Data that is bit aligned can be tricky to deal with. These classes implement
565
+ # helper functionality for dealing with the data and getting a particular row
566
+ # of bitwise data. Also helps implement fancy data export/import in XML.
567
+ class BitAlignedBitmapMixin(object):
568
+ def _getBitRange(self, row, bitDepth, metrics):
569
+ rowBits = bitDepth * metrics.width
570
+ bitOffset = row * rowBits
571
+ return (bitOffset, bitOffset + rowBits)
572
+
573
+ def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False):
574
+ if metrics is None:
575
+ metrics = self.metrics
576
+ assert 0 <= row and row < metrics.height, "Illegal row access in bitmap"
577
+
578
+ # Loop through each byte. This can cover two bytes in the original data or
579
+ # a single byte if things happen to be aligned. The very last entry might
580
+ # not be aligned so take care to trim the binary data to size and pad with
581
+ # zeros in the row data. Bit aligned data is somewhat tricky.
582
+ #
583
+ # Example of data cut. Data cut represented in x's.
584
+ # '|' represents byte boundary.
585
+ # data = ...0XX|XXXXXX00|000... => XXXXXXXX
586
+ # or
587
+ # data = ...0XX|XXXX0000|000... => XXXXXX00
588
+ # or
589
+ # data = ...000|XXXXXXXX|000... => XXXXXXXX
590
+ # or
591
+ # data = ...000|00XXXX00|000... => XXXX0000
592
+ #
593
+ dataList = []
594
+ bitRange = self._getBitRange(row, bitDepth, metrics)
595
+ stepRange = bitRange + (8,)
596
+ for curBit in range(*stepRange):
597
+ endBit = min(curBit + 8, bitRange[1])
598
+ numBits = endBit - curBit
599
+ cutPoint = curBit % 8
600
+ firstByteLoc = curBit // 8
601
+ secondByteLoc = endBit // 8
602
+ if firstByteLoc < secondByteLoc:
603
+ numBitsCut = 8 - cutPoint
604
+ else:
605
+ numBitsCut = endBit - curBit
606
+ curByte = _reverseBytes(self.imageData[firstByteLoc])
607
+ firstHalf = byteord(curByte) >> cutPoint
608
+ firstHalf = ((1 << numBitsCut) - 1) & firstHalf
609
+ newByte = firstHalf
610
+ if firstByteLoc < secondByteLoc and secondByteLoc < len(self.imageData):
611
+ curByte = _reverseBytes(self.imageData[secondByteLoc])
612
+ secondHalf = byteord(curByte) << numBitsCut
613
+ newByte = (firstHalf | secondHalf) & ((1 << numBits) - 1)
614
+ dataList.append(bytechr(newByte))
615
+
616
+ # The way the data is kept is opposite the algorithm used.
617
+ data = bytesjoin(dataList)
618
+ if not reverseBytes:
619
+ data = _reverseBytes(data)
620
+ return data
621
+
622
+ def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False):
623
+ if metrics is None:
624
+ metrics = self.metrics
625
+ if not reverseBytes:
626
+ dataRows = list(map(_reverseBytes, dataRows))
627
+
628
+ # Keep track of a list of ordinal values as they are easier to modify
629
+ # than a list of strings. Map to actual strings later.
630
+ numBytes = (self._getBitRange(len(dataRows), bitDepth, metrics)[0] + 7) // 8
631
+ ordDataList = [0] * numBytes
632
+ for row, data in enumerate(dataRows):
633
+ bitRange = self._getBitRange(row, bitDepth, metrics)
634
+ stepRange = bitRange + (8,)
635
+ for curBit, curByte in zip(range(*stepRange), data):
636
+ endBit = min(curBit + 8, bitRange[1])
637
+ cutPoint = curBit % 8
638
+ firstByteLoc = curBit // 8
639
+ secondByteLoc = endBit // 8
640
+ if firstByteLoc < secondByteLoc:
641
+ numBitsCut = 8 - cutPoint
642
+ else:
643
+ numBitsCut = endBit - curBit
644
+ curByte = byteord(curByte)
645
+ firstByte = curByte & ((1 << numBitsCut) - 1)
646
+ ordDataList[firstByteLoc] |= firstByte << cutPoint
647
+ if firstByteLoc < secondByteLoc and secondByteLoc < numBytes:
648
+ secondByte = (curByte >> numBitsCut) & ((1 << 8 - numBitsCut) - 1)
649
+ ordDataList[secondByteLoc] |= secondByte
650
+
651
+ # Save the image data with the bits going the correct way.
652
+ self.imageData = _reverseBytes(bytesjoin(map(bytechr, ordDataList)))
653
+
654
+
655
+ class ByteAlignedBitmapMixin(object):
656
+ def _getByteRange(self, row, bitDepth, metrics):
657
+ rowBytes = (bitDepth * metrics.width + 7) // 8
658
+ byteOffset = row * rowBytes
659
+ return (byteOffset, byteOffset + rowBytes)
660
+
661
+ def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False):
662
+ if metrics is None:
663
+ metrics = self.metrics
664
+ assert 0 <= row and row < metrics.height, "Illegal row access in bitmap"
665
+ byteRange = self._getByteRange(row, bitDepth, metrics)
666
+ data = self.imageData[slice(*byteRange)]
667
+ if reverseBytes:
668
+ data = _reverseBytes(data)
669
+ return data
670
+
671
+ def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False):
672
+ if metrics is None:
673
+ metrics = self.metrics
674
+ if reverseBytes:
675
+ dataRows = map(_reverseBytes, dataRows)
676
+ self.imageData = bytesjoin(dataRows)
677
+
678
+
679
+ class ebdt_bitmap_format_1(
680
+ ByteAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph
681
+ ):
682
+ def decompile(self):
683
+ self.metrics = SmallGlyphMetrics()
684
+ dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics)
685
+ self.imageData = data
686
+
687
+ def compile(self, ttFont):
688
+ data = sstruct.pack(smallGlyphMetricsFormat, self.metrics)
689
+ return data + self.imageData
690
+
691
+
692
+ class ebdt_bitmap_format_2(
693
+ BitAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph
694
+ ):
695
+ def decompile(self):
696
+ self.metrics = SmallGlyphMetrics()
697
+ dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics)
698
+ self.imageData = data
699
+
700
+ def compile(self, ttFont):
701
+ data = sstruct.pack(smallGlyphMetricsFormat, self.metrics)
702
+ return data + self.imageData
703
+
704
+
705
+ class ebdt_bitmap_format_5(BitAlignedBitmapMixin, BitmapGlyph):
706
+ def decompile(self):
707
+ self.imageData = self.data
708
+
709
+ def compile(self, ttFont):
710
+ return self.imageData
711
+
712
+
713
+ class ebdt_bitmap_format_6(
714
+ ByteAlignedBitmapMixin, BitmapPlusBigMetricsMixin, BitmapGlyph
715
+ ):
716
+ def decompile(self):
717
+ self.metrics = BigGlyphMetrics()
718
+ dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics)
719
+ self.imageData = data
720
+
721
+ def compile(self, ttFont):
722
+ data = sstruct.pack(bigGlyphMetricsFormat, self.metrics)
723
+ return data + self.imageData
724
+
725
+
726
+ class ebdt_bitmap_format_7(
727
+ BitAlignedBitmapMixin, BitmapPlusBigMetricsMixin, BitmapGlyph
728
+ ):
729
+ def decompile(self):
730
+ self.metrics = BigGlyphMetrics()
731
+ dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics)
732
+ self.imageData = data
733
+
734
+ def compile(self, ttFont):
735
+ data = sstruct.pack(bigGlyphMetricsFormat, self.metrics)
736
+ return data + self.imageData
737
+
738
+
739
+ class ComponentBitmapGlyph(BitmapGlyph):
740
+ def toXML(self, strikeIndex, glyphName, writer, ttFont):
741
+ writer.begintag(self.__class__.__name__, [("name", glyphName)])
742
+ writer.newline()
743
+
744
+ self.writeMetrics(writer, ttFont)
745
+
746
+ writer.begintag("components")
747
+ writer.newline()
748
+ for curComponent in self.componentArray:
749
+ curComponent.toXML(writer, ttFont)
750
+ writer.endtag("components")
751
+ writer.newline()
752
+
753
+ writer.endtag(self.__class__.__name__)
754
+ writer.newline()
755
+
756
+ def fromXML(self, name, attrs, content, ttFont):
757
+ self.readMetrics(name, attrs, content, ttFont)
758
+ for element in content:
759
+ if not isinstance(element, tuple):
760
+ continue
761
+ name, attr, content = element
762
+ if name == "components":
763
+ self.componentArray = []
764
+ for compElement in content:
765
+ if not isinstance(compElement, tuple):
766
+ continue
767
+ name, attrs, content = compElement
768
+ if name == "ebdtComponent":
769
+ curComponent = EbdtComponent()
770
+ curComponent.fromXML(name, attrs, content, ttFont)
771
+ self.componentArray.append(curComponent)
772
+ else:
773
+ log.warning("'%s' being ignored in component array.", name)
774
+
775
+
776
+ class ebdt_bitmap_format_8(BitmapPlusSmallMetricsMixin, ComponentBitmapGlyph):
777
+ def decompile(self):
778
+ self.metrics = SmallGlyphMetrics()
779
+ dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics)
780
+ data = data[1:]
781
+
782
+ (numComponents,) = struct.unpack(">H", data[:2])
783
+ data = data[2:]
784
+ self.componentArray = []
785
+ for i in range(numComponents):
786
+ curComponent = EbdtComponent()
787
+ dummy, data = sstruct.unpack2(ebdtComponentFormat, data, curComponent)
788
+ curComponent.name = self.ttFont.getGlyphName(curComponent.glyphCode)
789
+ self.componentArray.append(curComponent)
790
+
791
+ def compile(self, ttFont):
792
+ dataList = []
793
+ dataList.append(sstruct.pack(smallGlyphMetricsFormat, self.metrics))
794
+ dataList.append(b"\0")
795
+ dataList.append(struct.pack(">H", len(self.componentArray)))
796
+ for curComponent in self.componentArray:
797
+ curComponent.glyphCode = ttFont.getGlyphID(curComponent.name)
798
+ dataList.append(sstruct.pack(ebdtComponentFormat, curComponent))
799
+ return bytesjoin(dataList)
800
+
801
+
802
+ class ebdt_bitmap_format_9(BitmapPlusBigMetricsMixin, ComponentBitmapGlyph):
803
+ def decompile(self):
804
+ self.metrics = BigGlyphMetrics()
805
+ dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics)
806
+ (numComponents,) = struct.unpack(">H", data[:2])
807
+ data = data[2:]
808
+ self.componentArray = []
809
+ for i in range(numComponents):
810
+ curComponent = EbdtComponent()
811
+ dummy, data = sstruct.unpack2(ebdtComponentFormat, data, curComponent)
812
+ curComponent.name = self.ttFont.getGlyphName(curComponent.glyphCode)
813
+ self.componentArray.append(curComponent)
814
+
815
+ def compile(self, ttFont):
816
+ dataList = []
817
+ dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
818
+ dataList.append(struct.pack(">H", len(self.componentArray)))
819
+ for curComponent in self.componentArray:
820
+ curComponent.glyphCode = ttFont.getGlyphID(curComponent.name)
821
+ dataList.append(sstruct.pack(ebdtComponentFormat, curComponent))
822
+ return bytesjoin(dataList)
823
+
824
+
825
+ # Dictionary of bitmap formats to the class representing that format
826
+ # currently only the ones listed in this map are the ones supported.
827
+ ebdt_bitmap_classes = {
828
+ 1: ebdt_bitmap_format_1,
829
+ 2: ebdt_bitmap_format_2,
830
+ 5: ebdt_bitmap_format_5,
831
+ 6: ebdt_bitmap_format_6,
832
+ 7: ebdt_bitmap_format_7,
833
+ 8: ebdt_bitmap_format_8,
834
+ 9: ebdt_bitmap_format_9,
835
+ }
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/E_B_L_C_.py ADDED
@@ -0,0 +1,718 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc import sstruct
2
+ from . import DefaultTable
3
+ from fontTools.misc.textTools import bytesjoin, safeEval
4
+ from .BitmapGlyphMetrics import (
5
+ BigGlyphMetrics,
6
+ bigGlyphMetricsFormat,
7
+ SmallGlyphMetrics,
8
+ smallGlyphMetricsFormat,
9
+ )
10
+ import struct
11
+ import itertools
12
+ from collections import deque
13
+ import logging
14
+
15
+
16
+ log = logging.getLogger(__name__)
17
+
18
+ eblcHeaderFormat = """
19
+ > # big endian
20
+ version: 16.16F
21
+ numSizes: I
22
+ """
23
+ # The table format string is split to handle sbitLineMetrics simply.
24
+ bitmapSizeTableFormatPart1 = """
25
+ > # big endian
26
+ indexSubTableArrayOffset: I
27
+ indexTablesSize: I
28
+ numberOfIndexSubTables: I
29
+ colorRef: I
30
+ """
31
+ # The compound type for hori and vert.
32
+ sbitLineMetricsFormat = """
33
+ > # big endian
34
+ ascender: b
35
+ descender: b
36
+ widthMax: B
37
+ caretSlopeNumerator: b
38
+ caretSlopeDenominator: b
39
+ caretOffset: b
40
+ minOriginSB: b
41
+ minAdvanceSB: b
42
+ maxBeforeBL: b
43
+ minAfterBL: b
44
+ pad1: b
45
+ pad2: b
46
+ """
47
+ # hori and vert go between the two parts.
48
+ bitmapSizeTableFormatPart2 = """
49
+ > # big endian
50
+ startGlyphIndex: H
51
+ endGlyphIndex: H
52
+ ppemX: B
53
+ ppemY: B
54
+ bitDepth: B
55
+ flags: b
56
+ """
57
+
58
+ indexSubTableArrayFormat = ">HHL"
59
+ indexSubTableArraySize = struct.calcsize(indexSubTableArrayFormat)
60
+
61
+ indexSubHeaderFormat = ">HHL"
62
+ indexSubHeaderSize = struct.calcsize(indexSubHeaderFormat)
63
+
64
+ codeOffsetPairFormat = ">HH"
65
+ codeOffsetPairSize = struct.calcsize(codeOffsetPairFormat)
66
+
67
+
68
+ class table_E_B_L_C_(DefaultTable.DefaultTable):
69
+ """Embedded Bitmap Location table
70
+
71
+ The ``EBLC`` table contains the locations of monochrome or grayscale
72
+ bitmaps for glyphs. It must be used in concert with the ``EBDT`` table.
73
+
74
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/eblc
75
+ """
76
+
77
+ dependencies = ["EBDT"]
78
+
79
+ # This method can be overridden in subclasses to support new formats
80
+ # without changing the other implementation. Also can be used as a
81
+ # convenience method for coverting a font file to an alternative format.
82
+ def getIndexFormatClass(self, indexFormat):
83
+ return eblc_sub_table_classes[indexFormat]
84
+
85
+ def decompile(self, data, ttFont):
86
+ # Save the original data because offsets are from the start of the table.
87
+ origData = data
88
+ i = 0
89
+
90
+ dummy = sstruct.unpack(eblcHeaderFormat, data[:8], self)
91
+ i += 8
92
+
93
+ self.strikes = []
94
+ for curStrikeIndex in range(self.numSizes):
95
+ curStrike = Strike()
96
+ self.strikes.append(curStrike)
97
+ curTable = curStrike.bitmapSizeTable
98
+ dummy = sstruct.unpack2(
99
+ bitmapSizeTableFormatPart1, data[i : i + 16], curTable
100
+ )
101
+ i += 16
102
+ for metric in ("hori", "vert"):
103
+ metricObj = SbitLineMetrics()
104
+ vars(curTable)[metric] = metricObj
105
+ dummy = sstruct.unpack2(
106
+ sbitLineMetricsFormat, data[i : i + 12], metricObj
107
+ )
108
+ i += 12
109
+ dummy = sstruct.unpack(
110
+ bitmapSizeTableFormatPart2, data[i : i + 8], curTable
111
+ )
112
+ i += 8
113
+
114
+ for curStrike in self.strikes:
115
+ curTable = curStrike.bitmapSizeTable
116
+ for subtableIndex in range(curTable.numberOfIndexSubTables):
117
+ i = (
118
+ curTable.indexSubTableArrayOffset
119
+ + subtableIndex * indexSubTableArraySize
120
+ )
121
+
122
+ tup = struct.unpack(
123
+ indexSubTableArrayFormat, data[i : i + indexSubTableArraySize]
124
+ )
125
+ (firstGlyphIndex, lastGlyphIndex, additionalOffsetToIndexSubtable) = tup
126
+ i = curTable.indexSubTableArrayOffset + additionalOffsetToIndexSubtable
127
+
128
+ tup = struct.unpack(
129
+ indexSubHeaderFormat, data[i : i + indexSubHeaderSize]
130
+ )
131
+ (indexFormat, imageFormat, imageDataOffset) = tup
132
+
133
+ indexFormatClass = self.getIndexFormatClass(indexFormat)
134
+ indexSubTable = indexFormatClass(data[i + indexSubHeaderSize :], ttFont)
135
+ indexSubTable.firstGlyphIndex = firstGlyphIndex
136
+ indexSubTable.lastGlyphIndex = lastGlyphIndex
137
+ indexSubTable.additionalOffsetToIndexSubtable = (
138
+ additionalOffsetToIndexSubtable
139
+ )
140
+ indexSubTable.indexFormat = indexFormat
141
+ indexSubTable.imageFormat = imageFormat
142
+ indexSubTable.imageDataOffset = imageDataOffset
143
+ indexSubTable.decompile() # https://github.com/fonttools/fonttools/issues/317
144
+ curStrike.indexSubTables.append(indexSubTable)
145
+
146
+ def compile(self, ttFont):
147
+ dataList = []
148
+ self.numSizes = len(self.strikes)
149
+ dataList.append(sstruct.pack(eblcHeaderFormat, self))
150
+
151
+ # Data size of the header + bitmapSizeTable needs to be calculated
152
+ # in order to form offsets. This value will hold the size of the data
153
+ # in dataList after all the data is consolidated in dataList.
154
+ dataSize = len(dataList[0])
155
+
156
+ # The table will be structured in the following order:
157
+ # (0) header
158
+ # (1) Each bitmapSizeTable [1 ... self.numSizes]
159
+ # (2) Alternate between indexSubTableArray and indexSubTable
160
+ # for each bitmapSizeTable present.
161
+ #
162
+ # The issue is maintaining the proper offsets when table information
163
+ # gets moved around. All offsets and size information must be recalculated
164
+ # when building the table to allow editing within ttLib and also allow easy
165
+ # import/export to and from XML. All of this offset information is lost
166
+ # when exporting to XML so everything must be calculated fresh so importing
167
+ # from XML will work cleanly. Only byte offset and size information is
168
+ # calculated fresh. Count information like numberOfIndexSubTables is
169
+ # checked through assertions. If the information in this table was not
170
+ # touched or was changed properly then these types of values should match.
171
+ #
172
+ # The table will be rebuilt the following way:
173
+ # (0) Precompute the size of all the bitmapSizeTables. This is needed to
174
+ # compute the offsets properly.
175
+ # (1) For each bitmapSizeTable compute the indexSubTable and
176
+ # indexSubTableArray pair. The indexSubTable must be computed first
177
+ # so that the offset information in indexSubTableArray can be
178
+ # calculated. Update the data size after each pairing.
179
+ # (2) Build each bitmapSizeTable.
180
+ # (3) Consolidate all the data into the main dataList in the correct order.
181
+
182
+ for _ in self.strikes:
183
+ dataSize += sstruct.calcsize(bitmapSizeTableFormatPart1)
184
+ dataSize += len(("hori", "vert")) * sstruct.calcsize(sbitLineMetricsFormat)
185
+ dataSize += sstruct.calcsize(bitmapSizeTableFormatPart2)
186
+
187
+ indexSubTablePairDataList = []
188
+ for curStrike in self.strikes:
189
+ curTable = curStrike.bitmapSizeTable
190
+ curTable.numberOfIndexSubTables = len(curStrike.indexSubTables)
191
+ curTable.indexSubTableArrayOffset = dataSize
192
+
193
+ # Precompute the size of the indexSubTableArray. This information
194
+ # is important for correctly calculating the new value for
195
+ # additionalOffsetToIndexSubtable.
196
+ sizeOfSubTableArray = (
197
+ curTable.numberOfIndexSubTables * indexSubTableArraySize
198
+ )
199
+ lowerBound = dataSize
200
+ dataSize += sizeOfSubTableArray
201
+ upperBound = dataSize
202
+
203
+ indexSubTableDataList = []
204
+ for indexSubTable in curStrike.indexSubTables:
205
+ indexSubTable.additionalOffsetToIndexSubtable = (
206
+ dataSize - curTable.indexSubTableArrayOffset
207
+ )
208
+ glyphIds = list(map(ttFont.getGlyphID, indexSubTable.names))
209
+ indexSubTable.firstGlyphIndex = min(glyphIds)
210
+ indexSubTable.lastGlyphIndex = max(glyphIds)
211
+ data = indexSubTable.compile(ttFont)
212
+ indexSubTableDataList.append(data)
213
+ dataSize += len(data)
214
+ curTable.startGlyphIndex = min(
215
+ ist.firstGlyphIndex for ist in curStrike.indexSubTables
216
+ )
217
+ curTable.endGlyphIndex = max(
218
+ ist.lastGlyphIndex for ist in curStrike.indexSubTables
219
+ )
220
+
221
+ for i in curStrike.indexSubTables:
222
+ data = struct.pack(
223
+ indexSubHeaderFormat,
224
+ i.firstGlyphIndex,
225
+ i.lastGlyphIndex,
226
+ i.additionalOffsetToIndexSubtable,
227
+ )
228
+ indexSubTablePairDataList.append(data)
229
+ indexSubTablePairDataList.extend(indexSubTableDataList)
230
+ curTable.indexTablesSize = dataSize - curTable.indexSubTableArrayOffset
231
+
232
+ for curStrike in self.strikes:
233
+ curTable = curStrike.bitmapSizeTable
234
+ data = sstruct.pack(bitmapSizeTableFormatPart1, curTable)
235
+ dataList.append(data)
236
+ for metric in ("hori", "vert"):
237
+ metricObj = vars(curTable)[metric]
238
+ data = sstruct.pack(sbitLineMetricsFormat, metricObj)
239
+ dataList.append(data)
240
+ data = sstruct.pack(bitmapSizeTableFormatPart2, curTable)
241
+ dataList.append(data)
242
+ dataList.extend(indexSubTablePairDataList)
243
+
244
+ return bytesjoin(dataList)
245
+
246
+ def toXML(self, writer, ttFont):
247
+ writer.simpletag("header", [("version", self.version)])
248
+ writer.newline()
249
+ for curIndex, curStrike in enumerate(self.strikes):
250
+ curStrike.toXML(curIndex, writer, ttFont)
251
+
252
+ def fromXML(self, name, attrs, content, ttFont):
253
+ if name == "header":
254
+ self.version = safeEval(attrs["version"])
255
+ elif name == "strike":
256
+ if not hasattr(self, "strikes"):
257
+ self.strikes = []
258
+ strikeIndex = safeEval(attrs["index"])
259
+ curStrike = Strike()
260
+ curStrike.fromXML(name, attrs, content, ttFont, self)
261
+
262
+ # Grow the strike array to the appropriate size. The XML format
263
+ # allows for the strike index value to be out of order.
264
+ if strikeIndex >= len(self.strikes):
265
+ self.strikes += [None] * (strikeIndex + 1 - len(self.strikes))
266
+ assert self.strikes[strikeIndex] is None, "Duplicate strike EBLC indices."
267
+ self.strikes[strikeIndex] = curStrike
268
+
269
+
270
+ class Strike(object):
271
+ def __init__(self):
272
+ self.bitmapSizeTable = BitmapSizeTable()
273
+ self.indexSubTables = []
274
+
275
+ def toXML(self, strikeIndex, writer, ttFont):
276
+ writer.begintag("strike", [("index", strikeIndex)])
277
+ writer.newline()
278
+ self.bitmapSizeTable.toXML(writer, ttFont)
279
+ writer.comment(
280
+ "GlyphIds are written but not read. The firstGlyphIndex and\nlastGlyphIndex values will be recalculated by the compiler."
281
+ )
282
+ writer.newline()
283
+ for indexSubTable in self.indexSubTables:
284
+ indexSubTable.toXML(writer, ttFont)
285
+ writer.endtag("strike")
286
+ writer.newline()
287
+
288
+ def fromXML(self, name, attrs, content, ttFont, locator):
289
+ for element in content:
290
+ if not isinstance(element, tuple):
291
+ continue
292
+ name, attrs, content = element
293
+ if name == "bitmapSizeTable":
294
+ self.bitmapSizeTable.fromXML(name, attrs, content, ttFont)
295
+ elif name.startswith(_indexSubTableSubclassPrefix):
296
+ indexFormat = safeEval(name[len(_indexSubTableSubclassPrefix) :])
297
+ indexFormatClass = locator.getIndexFormatClass(indexFormat)
298
+ indexSubTable = indexFormatClass(None, None)
299
+ indexSubTable.indexFormat = indexFormat
300
+ indexSubTable.fromXML(name, attrs, content, ttFont)
301
+ self.indexSubTables.append(indexSubTable)
302
+
303
+
304
+ class BitmapSizeTable(object):
305
+ # Returns all the simple metric names that bitmap size table
306
+ # cares about in terms of XML creation.
307
+ def _getXMLMetricNames(self):
308
+ dataNames = sstruct.getformat(bitmapSizeTableFormatPart1)[1]
309
+ dataNames = {**dataNames, **sstruct.getformat(bitmapSizeTableFormatPart2)[1]}
310
+ # Skip the first 3 data names because they are byte offsets and counts.
311
+ return list(dataNames.keys())[3:]
312
+
313
+ def toXML(self, writer, ttFont):
314
+ writer.begintag("bitmapSizeTable")
315
+ writer.newline()
316
+ for metric in ("hori", "vert"):
317
+ getattr(self, metric).toXML(metric, writer, ttFont)
318
+ for metricName in self._getXMLMetricNames():
319
+ writer.simpletag(metricName, value=getattr(self, metricName))
320
+ writer.newline()
321
+ writer.endtag("bitmapSizeTable")
322
+ writer.newline()
323
+
324
+ def fromXML(self, name, attrs, content, ttFont):
325
+ # Create a lookup for all the simple names that make sense to
326
+ # bitmap size table. Only read the information from these names.
327
+ dataNames = set(self._getXMLMetricNames())
328
+ for element in content:
329
+ if not isinstance(element, tuple):
330
+ continue
331
+ name, attrs, content = element
332
+ if name == "sbitLineMetrics":
333
+ direction = attrs["direction"]
334
+ assert direction in (
335
+ "hori",
336
+ "vert",
337
+ ), "SbitLineMetrics direction specified invalid."
338
+ metricObj = SbitLineMetrics()
339
+ metricObj.fromXML(name, attrs, content, ttFont)
340
+ vars(self)[direction] = metricObj
341
+ elif name in dataNames:
342
+ vars(self)[name] = safeEval(attrs["value"])
343
+ else:
344
+ log.warning("unknown name '%s' being ignored in BitmapSizeTable.", name)
345
+
346
+
347
+ class SbitLineMetrics(object):
348
+ def toXML(self, name, writer, ttFont):
349
+ writer.begintag("sbitLineMetrics", [("direction", name)])
350
+ writer.newline()
351
+ for metricName in sstruct.getformat(sbitLineMetricsFormat)[1]:
352
+ writer.simpletag(metricName, value=getattr(self, metricName))
353
+ writer.newline()
354
+ writer.endtag("sbitLineMetrics")
355
+ writer.newline()
356
+
357
+ def fromXML(self, name, attrs, content, ttFont):
358
+ metricNames = set(sstruct.getformat(sbitLineMetricsFormat)[1])
359
+ for element in content:
360
+ if not isinstance(element, tuple):
361
+ continue
362
+ name, attrs, content = element
363
+ if name in metricNames:
364
+ vars(self)[name] = safeEval(attrs["value"])
365
+
366
+
367
+ # Important information about the naming scheme. Used for identifying subtables.
368
+ _indexSubTableSubclassPrefix = "eblc_index_sub_table_"
369
+
370
+
371
+ class EblcIndexSubTable(object):
372
+ def __init__(self, data, ttFont):
373
+ self.data = data
374
+ self.ttFont = ttFont
375
+ # TODO Currently non-lazy decompiling doesn't work for this class...
376
+ # if not ttFont.lazy:
377
+ # self.decompile()
378
+ # del self.data, self.ttFont
379
+
380
+ def __getattr__(self, attr):
381
+ # Allow lazy decompile.
382
+ if attr[:2] == "__":
383
+ raise AttributeError(attr)
384
+ if attr == "data":
385
+ raise AttributeError(attr)
386
+ self.decompile()
387
+ return getattr(self, attr)
388
+
389
+ def ensureDecompiled(self, recurse=False):
390
+ if hasattr(self, "data"):
391
+ self.decompile()
392
+
393
+ # This method just takes care of the indexSubHeader. Implementing subclasses
394
+ # should call it to compile the indexSubHeader and then continue compiling
395
+ # the remainder of their unique format.
396
+ def compile(self, ttFont):
397
+ return struct.pack(
398
+ indexSubHeaderFormat,
399
+ self.indexFormat,
400
+ self.imageFormat,
401
+ self.imageDataOffset,
402
+ )
403
+
404
+ # Creates the XML for bitmap glyphs. Each index sub table basically makes
405
+ # the same XML except for specific metric information that is written
406
+ # out via a method call that a subclass implements optionally.
407
+ def toXML(self, writer, ttFont):
408
+ writer.begintag(
409
+ self.__class__.__name__,
410
+ [
411
+ ("imageFormat", self.imageFormat),
412
+ ("firstGlyphIndex", self.firstGlyphIndex),
413
+ ("lastGlyphIndex", self.lastGlyphIndex),
414
+ ],
415
+ )
416
+ writer.newline()
417
+ self.writeMetrics(writer, ttFont)
418
+ # Write out the names as thats all thats needed to rebuild etc.
419
+ # For font debugging of consecutive formats the ids are also written.
420
+ # The ids are not read when moving from the XML format.
421
+ glyphIds = map(ttFont.getGlyphID, self.names)
422
+ for glyphName, glyphId in zip(self.names, glyphIds):
423
+ writer.simpletag("glyphLoc", name=glyphName, id=glyphId)
424
+ writer.newline()
425
+ writer.endtag(self.__class__.__name__)
426
+ writer.newline()
427
+
428
+ def fromXML(self, name, attrs, content, ttFont):
429
+ # Read all the attributes. Even though the glyph indices are
430
+ # recalculated, they are still read in case there needs to
431
+ # be an immediate export of the data.
432
+ self.imageFormat = safeEval(attrs["imageFormat"])
433
+ self.firstGlyphIndex = safeEval(attrs["firstGlyphIndex"])
434
+ self.lastGlyphIndex = safeEval(attrs["lastGlyphIndex"])
435
+
436
+ self.readMetrics(name, attrs, content, ttFont)
437
+
438
+ self.names = []
439
+ for element in content:
440
+ if not isinstance(element, tuple):
441
+ continue
442
+ name, attrs, content = element
443
+ if name == "glyphLoc":
444
+ self.names.append(attrs["name"])
445
+
446
+ # A helper method that writes the metrics for the index sub table. It also
447
+ # is responsible for writing the image size for fixed size data since fixed
448
+ # size is not recalculated on compile. Default behavior is to do nothing.
449
+ def writeMetrics(self, writer, ttFont):
450
+ pass
451
+
452
+ # A helper method that is the inverse of writeMetrics.
453
+ def readMetrics(self, name, attrs, content, ttFont):
454
+ pass
455
+
456
+ # This method is for fixed glyph data sizes. There are formats where
457
+ # the glyph data is fixed but are actually composite glyphs. To handle
458
+ # this the font spec in indexSubTable makes the data the size of the
459
+ # fixed size by padding the component arrays. This function abstracts
460
+ # out this padding process. Input is data unpadded. Output is data
461
+ # padded only in fixed formats. Default behavior is to return the data.
462
+ def padBitmapData(self, data):
463
+ return data
464
+
465
+ # Remove any of the glyph locations and names that are flagged as skipped.
466
+ # This only occurs in formats {1,3}.
467
+ def removeSkipGlyphs(self):
468
+ # Determines if a name, location pair is a valid data location.
469
+ # Skip glyphs are marked when the size is equal to zero.
470
+ def isValidLocation(args):
471
+ (name, (startByte, endByte)) = args
472
+ return startByte < endByte
473
+
474
+ # Remove all skip glyphs.
475
+ dataPairs = list(filter(isValidLocation, zip(self.names, self.locations)))
476
+ self.names, self.locations = list(map(list, zip(*dataPairs)))
477
+
478
+
479
+ # A closure for creating a custom mixin. This is done because formats 1 and 3
480
+ # are very similar. The only difference between them is the size per offset
481
+ # value. Code put in here should handle both cases generally.
482
+ def _createOffsetArrayIndexSubTableMixin(formatStringForDataType):
483
+ # Prep the data size for the offset array data format.
484
+ dataFormat = ">" + formatStringForDataType
485
+ offsetDataSize = struct.calcsize(dataFormat)
486
+
487
+ class OffsetArrayIndexSubTableMixin(object):
488
+ def decompile(self):
489
+ numGlyphs = self.lastGlyphIndex - self.firstGlyphIndex + 1
490
+ indexingOffsets = [
491
+ glyphIndex * offsetDataSize for glyphIndex in range(numGlyphs + 2)
492
+ ]
493
+ indexingLocations = zip(indexingOffsets, indexingOffsets[1:])
494
+ offsetArray = [
495
+ struct.unpack(dataFormat, self.data[slice(*loc)])[0]
496
+ for loc in indexingLocations
497
+ ]
498
+
499
+ glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1))
500
+ modifiedOffsets = [offset + self.imageDataOffset for offset in offsetArray]
501
+ self.locations = list(zip(modifiedOffsets, modifiedOffsets[1:]))
502
+
503
+ self.names = list(map(self.ttFont.getGlyphName, glyphIds))
504
+ self.removeSkipGlyphs()
505
+ del self.data, self.ttFont
506
+
507
+ def compile(self, ttFont):
508
+ # First make sure that all the data lines up properly. Formats 1 and 3
509
+ # must have all its data lined up consecutively. If not this will fail.
510
+ for curLoc, nxtLoc in zip(self.locations, self.locations[1:]):
511
+ assert (
512
+ curLoc[1] == nxtLoc[0]
513
+ ), "Data must be consecutive in indexSubTable offset formats"
514
+
515
+ glyphIds = list(map(ttFont.getGlyphID, self.names))
516
+ # Make sure that all ids are sorted strictly increasing.
517
+ assert all(glyphIds[i] < glyphIds[i + 1] for i in range(len(glyphIds) - 1))
518
+
519
+ # Run a simple algorithm to add skip glyphs to the data locations at
520
+ # the places where an id is not present.
521
+ idQueue = deque(glyphIds)
522
+ locQueue = deque(self.locations)
523
+ allGlyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1))
524
+ allLocations = []
525
+ for curId in allGlyphIds:
526
+ if curId != idQueue[0]:
527
+ allLocations.append((locQueue[0][0], locQueue[0][0]))
528
+ else:
529
+ idQueue.popleft()
530
+ allLocations.append(locQueue.popleft())
531
+
532
+ # Now that all the locations are collected, pack them appropriately into
533
+ # offsets. This is the form where offset[i] is the location and
534
+ # offset[i+1]-offset[i] is the size of the data location.
535
+ offsets = list(allLocations[0]) + [loc[1] for loc in allLocations[1:]]
536
+ # Image data offset must be less than or equal to the minimum of locations.
537
+ # This offset may change the value for round tripping but is safer and
538
+ # allows imageDataOffset to not be required to be in the XML version.
539
+ self.imageDataOffset = min(offsets)
540
+ offsetArray = [offset - self.imageDataOffset for offset in offsets]
541
+
542
+ dataList = [EblcIndexSubTable.compile(self, ttFont)]
543
+ dataList += [
544
+ struct.pack(dataFormat, offsetValue) for offsetValue in offsetArray
545
+ ]
546
+ # Take care of any padding issues. Only occurs in format 3.
547
+ if offsetDataSize * len(offsetArray) % 4 != 0:
548
+ dataList.append(struct.pack(dataFormat, 0))
549
+ return bytesjoin(dataList)
550
+
551
+ return OffsetArrayIndexSubTableMixin
552
+
553
+
554
+ # A Mixin for functionality shared between the different kinds
555
+ # of fixed sized data handling. Both kinds have big metrics so
556
+ # that kind of special processing is also handled in this mixin.
557
+ class FixedSizeIndexSubTableMixin(object):
558
+ def writeMetrics(self, writer, ttFont):
559
+ writer.simpletag("imageSize", value=self.imageSize)
560
+ writer.newline()
561
+ self.metrics.toXML(writer, ttFont)
562
+
563
+ def readMetrics(self, name, attrs, content, ttFont):
564
+ for element in content:
565
+ if not isinstance(element, tuple):
566
+ continue
567
+ name, attrs, content = element
568
+ if name == "imageSize":
569
+ self.imageSize = safeEval(attrs["value"])
570
+ elif name == BigGlyphMetrics.__name__:
571
+ self.metrics = BigGlyphMetrics()
572
+ self.metrics.fromXML(name, attrs, content, ttFont)
573
+ elif name == SmallGlyphMetrics.__name__:
574
+ log.warning(
575
+ "SmallGlyphMetrics being ignored in format %d.", self.indexFormat
576
+ )
577
+
578
+ def padBitmapData(self, data):
579
+ # Make sure that the data isn't bigger than the fixed size.
580
+ assert len(data) <= self.imageSize, (
581
+ "Data in indexSubTable format %d must be less than the fixed size."
582
+ % self.indexFormat
583
+ )
584
+ # Pad the data so that it matches the fixed size.
585
+ pad = (self.imageSize - len(data)) * b"\0"
586
+ return data + pad
587
+
588
+
589
+ class eblc_index_sub_table_1(
590
+ _createOffsetArrayIndexSubTableMixin("L"), EblcIndexSubTable
591
+ ):
592
+ pass
593
+
594
+
595
+ class eblc_index_sub_table_2(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
596
+ def decompile(self):
597
+ (self.imageSize,) = struct.unpack(">L", self.data[:4])
598
+ self.metrics = BigGlyphMetrics()
599
+ sstruct.unpack2(bigGlyphMetricsFormat, self.data[4:], self.metrics)
600
+ glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1))
601
+ offsets = [
602
+ self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds) + 1)
603
+ ]
604
+ self.locations = list(zip(offsets, offsets[1:]))
605
+ self.names = list(map(self.ttFont.getGlyphName, glyphIds))
606
+ del self.data, self.ttFont
607
+
608
+ def compile(self, ttFont):
609
+ glyphIds = list(map(ttFont.getGlyphID, self.names))
610
+ # Make sure all the ids are consecutive. This is required by Format 2.
611
+ assert glyphIds == list(
612
+ range(self.firstGlyphIndex, self.lastGlyphIndex + 1)
613
+ ), "Format 2 ids must be consecutive."
614
+ self.imageDataOffset = min(next(iter(zip(*self.locations))))
615
+
616
+ dataList = [EblcIndexSubTable.compile(self, ttFont)]
617
+ dataList.append(struct.pack(">L", self.imageSize))
618
+ dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
619
+ return bytesjoin(dataList)
620
+
621
+
622
+ class eblc_index_sub_table_3(
623
+ _createOffsetArrayIndexSubTableMixin("H"), EblcIndexSubTable
624
+ ):
625
+ pass
626
+
627
+
628
+ class eblc_index_sub_table_4(EblcIndexSubTable):
629
+ def decompile(self):
630
+ (numGlyphs,) = struct.unpack(">L", self.data[:4])
631
+ data = self.data[4:]
632
+ indexingOffsets = [
633
+ glyphIndex * codeOffsetPairSize for glyphIndex in range(numGlyphs + 2)
634
+ ]
635
+ indexingLocations = zip(indexingOffsets, indexingOffsets[1:])
636
+ glyphArray = [
637
+ struct.unpack(codeOffsetPairFormat, data[slice(*loc)])
638
+ for loc in indexingLocations
639
+ ]
640
+ glyphIds, offsets = list(map(list, zip(*glyphArray)))
641
+ # There are one too many glyph ids. Get rid of the last one.
642
+ glyphIds.pop()
643
+
644
+ offsets = [offset + self.imageDataOffset for offset in offsets]
645
+ self.locations = list(zip(offsets, offsets[1:]))
646
+ self.names = list(map(self.ttFont.getGlyphName, glyphIds))
647
+ del self.data, self.ttFont
648
+
649
+ def compile(self, ttFont):
650
+ # First make sure that all the data lines up properly. Format 4
651
+ # must have all its data lined up consecutively. If not this will fail.
652
+ for curLoc, nxtLoc in zip(self.locations, self.locations[1:]):
653
+ assert (
654
+ curLoc[1] == nxtLoc[0]
655
+ ), "Data must be consecutive in indexSubTable format 4"
656
+
657
+ offsets = list(self.locations[0]) + [loc[1] for loc in self.locations[1:]]
658
+ # Image data offset must be less than or equal to the minimum of locations.
659
+ # Resetting this offset may change the value for round tripping but is safer
660
+ # and allows imageDataOffset to not be required to be in the XML version.
661
+ self.imageDataOffset = min(offsets)
662
+ offsets = [offset - self.imageDataOffset for offset in offsets]
663
+ glyphIds = list(map(ttFont.getGlyphID, self.names))
664
+ # Create an iterator over the ids plus a padding value.
665
+ idsPlusPad = list(itertools.chain(glyphIds, [0]))
666
+
667
+ dataList = [EblcIndexSubTable.compile(self, ttFont)]
668
+ dataList.append(struct.pack(">L", len(glyphIds)))
669
+ tmp = [
670
+ struct.pack(codeOffsetPairFormat, *cop) for cop in zip(idsPlusPad, offsets)
671
+ ]
672
+ dataList += tmp
673
+ data = bytesjoin(dataList)
674
+ return data
675
+
676
+
677
+ class eblc_index_sub_table_5(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
678
+ def decompile(self):
679
+ self.origDataLen = 0
680
+ (self.imageSize,) = struct.unpack(">L", self.data[:4])
681
+ data = self.data[4:]
682
+ self.metrics, data = sstruct.unpack2(
683
+ bigGlyphMetricsFormat, data, BigGlyphMetrics()
684
+ )
685
+ (numGlyphs,) = struct.unpack(">L", data[:4])
686
+ data = data[4:]
687
+ glyphIds = [
688
+ struct.unpack(">H", data[2 * i : 2 * (i + 1)])[0] for i in range(numGlyphs)
689
+ ]
690
+
691
+ offsets = [
692
+ self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds) + 1)
693
+ ]
694
+ self.locations = list(zip(offsets, offsets[1:]))
695
+ self.names = list(map(self.ttFont.getGlyphName, glyphIds))
696
+ del self.data, self.ttFont
697
+
698
+ def compile(self, ttFont):
699
+ self.imageDataOffset = min(next(iter(zip(*self.locations))))
700
+ dataList = [EblcIndexSubTable.compile(self, ttFont)]
701
+ dataList.append(struct.pack(">L", self.imageSize))
702
+ dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
703
+ glyphIds = list(map(ttFont.getGlyphID, self.names))
704
+ dataList.append(struct.pack(">L", len(glyphIds)))
705
+ dataList += [struct.pack(">H", curId) for curId in glyphIds]
706
+ if len(glyphIds) % 2 == 1:
707
+ dataList.append(struct.pack(">H", 0))
708
+ return bytesjoin(dataList)
709
+
710
+
711
+ # Dictionary of indexFormat to the class representing that format.
712
+ eblc_sub_table_classes = {
713
+ 1: eblc_index_sub_table_1,
714
+ 2: eblc_index_sub_table_2,
715
+ 3: eblc_index_sub_table_3,
716
+ 4: eblc_index_sub_table_4,
717
+ 5: eblc_index_sub_table_5,
718
+ }
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/F_F_T_M_.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc import sstruct
2
+ from fontTools.misc.textTools import safeEval
3
+ from fontTools.misc.timeTools import timestampFromString, timestampToString
4
+ from . import DefaultTable
5
+
6
+ FFTMFormat = """
7
+ > # big endian
8
+ version: I
9
+ FFTimeStamp: Q
10
+ sourceCreated: Q
11
+ sourceModified: Q
12
+ """
13
+
14
+
15
+ class table_F_F_T_M_(DefaultTable.DefaultTable):
16
+ """FontForge Time Stamp table
17
+
18
+ The ``FFTM`` table is used by the free-software font editor
19
+ FontForge to record timestamps for the creation and modification
20
+ of font source (.sfd) files and a timestamp for FontForge's
21
+ own source code.
22
+
23
+ See also https://fontforge.org/docs/techref/non-standard.html
24
+ """
25
+
26
+ def decompile(self, data, ttFont):
27
+ dummy, rest = sstruct.unpack2(FFTMFormat, data, self)
28
+
29
+ def compile(self, ttFont):
30
+ data = sstruct.pack(FFTMFormat, self)
31
+ return data
32
+
33
+ def toXML(self, writer, ttFont):
34
+ writer.comment(
35
+ "FontForge's timestamp, font source creation and modification dates"
36
+ )
37
+ writer.newline()
38
+ formatstring, names, fixes = sstruct.getformat(FFTMFormat)
39
+ for name in names:
40
+ value = getattr(self, name)
41
+ if name in ("FFTimeStamp", "sourceCreated", "sourceModified"):
42
+ value = timestampToString(value)
43
+ writer.simpletag(name, value=value)
44
+ writer.newline()
45
+
46
+ def fromXML(self, name, attrs, content, ttFont):
47
+ value = attrs["value"]
48
+ if name in ("FFTimeStamp", "sourceCreated", "sourceModified"):
49
+ value = timestampFromString(value)
50
+ else:
51
+ value = safeEval(value)
52
+ setattr(self, name, value)
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/F__e_a_t.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc import sstruct
2
+ from fontTools.misc.fixedTools import floatToFixedToStr
3
+ from fontTools.misc.textTools import safeEval
4
+ from . import DefaultTable
5
+ from . import grUtils
6
+ import struct
7
+
8
+ Feat_hdr_format = """
9
+ >
10
+ version: 16.16F
11
+ """
12
+
13
+
14
+ class table_F__e_a_t(DefaultTable.DefaultTable):
15
+ """Feature table
16
+
17
+ The ``Feat`` table is used exclusively by the Graphite shaping engine
18
+ to store features and possible settings specified in GDL. Graphite features
19
+ determine what rules are applied to transform a glyph stream.
20
+
21
+ Not to be confused with ``feat``, or the OpenType Layout tables
22
+ ``GSUB``/``GPOS``.
23
+
24
+ See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
25
+ """
26
+
27
+ def __init__(self, tag=None):
28
+ DefaultTable.DefaultTable.__init__(self, tag)
29
+ self.features = {}
30
+
31
+ def decompile(self, data, ttFont):
32
+ (_, data) = sstruct.unpack2(Feat_hdr_format, data, self)
33
+ self.version = float(floatToFixedToStr(self.version, precisionBits=16))
34
+ (numFeats,) = struct.unpack(">H", data[:2])
35
+ data = data[8:]
36
+ allfeats = []
37
+ maxsetting = 0
38
+ for i in range(numFeats):
39
+ if self.version >= 2.0:
40
+ (fid, nums, _, offset, flags, lid) = struct.unpack(
41
+ ">LHHLHH", data[16 * i : 16 * (i + 1)]
42
+ )
43
+ offset = int((offset - 12 - 16 * numFeats) / 4)
44
+ else:
45
+ (fid, nums, offset, flags, lid) = struct.unpack(
46
+ ">HHLHH", data[12 * i : 12 * (i + 1)]
47
+ )
48
+ offset = int((offset - 12 - 12 * numFeats) / 4)
49
+ allfeats.append((fid, nums, offset, flags, lid))
50
+ maxsetting = max(maxsetting, offset + nums)
51
+ data = data[16 * numFeats :]
52
+ allsettings = []
53
+ for i in range(maxsetting):
54
+ if len(data) >= 4 * (i + 1):
55
+ (val, lid) = struct.unpack(">HH", data[4 * i : 4 * (i + 1)])
56
+ allsettings.append((val, lid))
57
+ for i, f in enumerate(allfeats):
58
+ (fid, nums, offset, flags, lid) = f
59
+ fobj = Feature()
60
+ fobj.flags = flags
61
+ fobj.label = lid
62
+ self.features[grUtils.num2tag(fid)] = fobj
63
+ fobj.settings = {}
64
+ fobj.default = None
65
+ fobj.index = i
66
+ for i in range(offset, offset + nums):
67
+ if i >= len(allsettings):
68
+ continue
69
+ (vid, vlid) = allsettings[i]
70
+ fobj.settings[vid] = vlid
71
+ if fobj.default is None:
72
+ fobj.default = vid
73
+
74
+ def compile(self, ttFont):
75
+ fdat = b""
76
+ vdat = b""
77
+ offset = 0
78
+ for f, v in sorted(self.features.items(), key=lambda x: x[1].index):
79
+ fnum = grUtils.tag2num(f)
80
+ if self.version >= 2.0:
81
+ fdat += struct.pack(
82
+ ">LHHLHH",
83
+ grUtils.tag2num(f),
84
+ len(v.settings),
85
+ 0,
86
+ offset * 4 + 12 + 16 * len(self.features),
87
+ v.flags,
88
+ v.label,
89
+ )
90
+ elif fnum > 65535: # self healing for alphabetic ids
91
+ self.version = 2.0
92
+ return self.compile(ttFont)
93
+ else:
94
+ fdat += struct.pack(
95
+ ">HHLHH",
96
+ grUtils.tag2num(f),
97
+ len(v.settings),
98
+ offset * 4 + 12 + 12 * len(self.features),
99
+ v.flags,
100
+ v.label,
101
+ )
102
+ for s, l in sorted(
103
+ v.settings.items(), key=lambda x: (-1, x[1]) if x[0] == v.default else x
104
+ ):
105
+ vdat += struct.pack(">HH", s, l)
106
+ offset += len(v.settings)
107
+ hdr = sstruct.pack(Feat_hdr_format, self)
108
+ return hdr + struct.pack(">HHL", len(self.features), 0, 0) + fdat + vdat
109
+
110
+ def toXML(self, writer, ttFont):
111
+ writer.simpletag("version", version=self.version)
112
+ writer.newline()
113
+ nameTable = ttFont["name"] if "name" in ttFont else None
114
+ for f, v in sorted(self.features.items(), key=lambda x: x[1].index):
115
+ name = nameTable.getDebugName(v.label) if nameTable else None
116
+ if name is not None:
117
+ writer.newline()
118
+ writer.comment(name)
119
+ writer.newline()
120
+ writer.begintag(
121
+ "feature",
122
+ fid=f,
123
+ label=v.label,
124
+ flags=v.flags,
125
+ default=(v.default if v.default else 0),
126
+ )
127
+ writer.newline()
128
+ for s, l in sorted(v.settings.items()):
129
+ writer.simpletag("setting", value=s, label=l)
130
+ name = nameTable.getDebugName(l) if nameTable else None
131
+ if name is not None:
132
+ writer.write(" ")
133
+ writer.comment(name)
134
+ writer.newline()
135
+ writer.endtag("feature")
136
+ writer.newline()
137
+
138
+ def fromXML(self, name, attrs, content, ttFont):
139
+ if name == "version":
140
+ self.version = float(safeEval(attrs["version"]))
141
+ elif name == "feature":
142
+ fid = attrs["fid"]
143
+ fobj = Feature()
144
+ fobj.flags = int(safeEval(attrs["flags"]))
145
+ fobj.label = int(safeEval(attrs["label"]))
146
+ fobj.default = int(safeEval(attrs.get("default", "0")))
147
+ fobj.index = len(self.features)
148
+ self.features[fid] = fobj
149
+ fobj.settings = {}
150
+ for element in content:
151
+ if not isinstance(element, tuple):
152
+ continue
153
+ tag, a, c = element
154
+ if tag == "setting":
155
+ fobj.settings[int(safeEval(a["value"]))] = int(safeEval(a["label"]))
156
+
157
+
158
+ class Feature(object):
159
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_D_E_F_.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .otBase import BaseTTXConverter
2
+
3
+
4
+ class table_G_D_E_F_(BaseTTXConverter):
5
+ """Glyph Definition table
6
+
7
+ The ``GDEF`` table stores various glyph properties that are used
8
+ by OpenType Layout.
9
+
10
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/gdef
11
+ """
12
+
13
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_P_O_S_.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .otBase import BaseTTXConverter
2
+
3
+
4
+ class table_G_P_O_S_(BaseTTXConverter):
5
+ """Glyph Positioning table
6
+
7
+ The ``GPOS`` table stores advanced glyph-positioning data
8
+ used in OpenType Layout features, such as mark attachment,
9
+ cursive attachment, kerning, and other position adjustments.
10
+
11
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/gpos
12
+ """
13
+
14
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_S_U_B_.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .otBase import BaseTTXConverter
2
+
3
+
4
+ class table_G_S_U_B_(BaseTTXConverter):
5
+ """Glyph Substitution table
6
+
7
+ The ``GSUB`` table contains glyph-substitution rules used in
8
+ OpenType Layout.
9
+
10
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/gsub
11
+ """
12
+
13
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_V_A_R_.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from ._g_v_a_r import table__g_v_a_r
2
+
3
+
4
+ class table_G_V_A_R_(table__g_v_a_r):
5
+ gid_size = 3
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G__l_a_t.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc import sstruct
2
+ from fontTools.misc.fixedTools import floatToFixedToStr
3
+ from fontTools.misc.textTools import safeEval
4
+
5
+ # from itertools import *
6
+ from functools import partial
7
+ from . import DefaultTable
8
+ from . import grUtils
9
+ import struct
10
+
11
+
12
+ Glat_format_0 = """
13
+ > # big endian
14
+ version: 16.16F
15
+ """
16
+
17
+ Glat_format_3 = """
18
+ >
19
+ version: 16.16F
20
+ compression:L # compression scheme or reserved
21
+ """
22
+
23
+ Glat_format_1_entry = """
24
+ >
25
+ attNum: B # Attribute number of first attribute
26
+ num: B # Number of attributes in this run
27
+ """
28
+ Glat_format_23_entry = """
29
+ >
30
+ attNum: H # Attribute number of first attribute
31
+ num: H # Number of attributes in this run
32
+ """
33
+
34
+ Glat_format_3_octabox_metrics = """
35
+ >
36
+ subboxBitmap: H # Which subboxes exist on 4x4 grid
37
+ diagNegMin: B # Defines minimum negatively-sloped diagonal (si)
38
+ diagNegMax: B # Defines maximum negatively-sloped diagonal (sa)
39
+ diagPosMin: B # Defines minimum positively-sloped diagonal (di)
40
+ diagPosMax: B # Defines maximum positively-sloped diagonal (da)
41
+ """
42
+
43
+ Glat_format_3_subbox_entry = """
44
+ >
45
+ left: B # xi
46
+ right: B # xa
47
+ bottom: B # yi
48
+ top: B # ya
49
+ diagNegMin: B # Defines minimum negatively-sloped diagonal (si)
50
+ diagNegMax: B # Defines maximum negatively-sloped diagonal (sa)
51
+ diagPosMin: B # Defines minimum positively-sloped diagonal (di)
52
+ diagPosMax: B # Defines maximum positively-sloped diagonal (da)
53
+ """
54
+
55
+
56
+ class _Object:
57
+ pass
58
+
59
+
60
+ class _Dict(dict):
61
+ pass
62
+
63
+
64
+ class table_G__l_a_t(DefaultTable.DefaultTable):
65
+ """Graphite Glyph Attributes table
66
+
67
+ See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
68
+ """
69
+
70
+ def __init__(self, tag=None):
71
+ DefaultTable.DefaultTable.__init__(self, tag)
72
+ self.scheme = 0
73
+
74
+ def decompile(self, data, ttFont):
75
+ sstruct.unpack2(Glat_format_0, data, self)
76
+ self.version = float(floatToFixedToStr(self.version, precisionBits=16))
77
+ if self.version <= 1.9:
78
+ decoder = partial(self.decompileAttributes12, fmt=Glat_format_1_entry)
79
+ elif self.version <= 2.9:
80
+ decoder = partial(self.decompileAttributes12, fmt=Glat_format_23_entry)
81
+ elif self.version >= 3.0:
82
+ (data, self.scheme) = grUtils.decompress(data)
83
+ sstruct.unpack2(Glat_format_3, data, self)
84
+ self.hasOctaboxes = (self.compression & 1) == 1
85
+ decoder = self.decompileAttributes3
86
+
87
+ gloc = ttFont["Gloc"]
88
+ self.attributes = {}
89
+ count = 0
90
+ for s, e in zip(gloc, gloc[1:]):
91
+ self.attributes[ttFont.getGlyphName(count)] = decoder(data[s:e])
92
+ count += 1
93
+
94
+ def decompileAttributes12(self, data, fmt):
95
+ attributes = _Dict()
96
+ while len(data) > 3:
97
+ e, data = sstruct.unpack2(fmt, data, _Object())
98
+ keys = range(e.attNum, e.attNum + e.num)
99
+ if len(data) >= 2 * e.num:
100
+ vals = struct.unpack_from((">%dh" % e.num), data)
101
+ attributes.update(zip(keys, vals))
102
+ data = data[2 * e.num :]
103
+ return attributes
104
+
105
+ def decompileAttributes3(self, data):
106
+ if self.hasOctaboxes:
107
+ o, data = sstruct.unpack2(Glat_format_3_octabox_metrics, data, _Object())
108
+ numsub = bin(o.subboxBitmap).count("1")
109
+ o.subboxes = []
110
+ for b in range(numsub):
111
+ if len(data) >= 8:
112
+ subbox, data = sstruct.unpack2(
113
+ Glat_format_3_subbox_entry, data, _Object()
114
+ )
115
+ o.subboxes.append(subbox)
116
+ attrs = self.decompileAttributes12(data, Glat_format_23_entry)
117
+ if self.hasOctaboxes:
118
+ attrs.octabox = o
119
+ return attrs
120
+
121
+ def compile(self, ttFont):
122
+ data = sstruct.pack(Glat_format_0, self)
123
+ if self.version <= 1.9:
124
+ encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
125
+ elif self.version <= 2.9:
126
+ encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
127
+ elif self.version >= 3.0:
128
+ self.compression = (self.scheme << 27) + (1 if self.hasOctaboxes else 0)
129
+ data = sstruct.pack(Glat_format_3, self)
130
+ encoder = self.compileAttributes3
131
+
132
+ glocs = []
133
+ for n in range(len(self.attributes)):
134
+ glocs.append(len(data))
135
+ data += encoder(self.attributes[ttFont.getGlyphName(n)])
136
+ glocs.append(len(data))
137
+ ttFont["Gloc"].set(glocs)
138
+
139
+ if self.version >= 3.0:
140
+ data = grUtils.compress(self.scheme, data)
141
+ return data
142
+
143
+ def compileAttributes12(self, attrs, fmt):
144
+ data = b""
145
+ for e in grUtils.entries(attrs):
146
+ data += sstruct.pack(fmt, {"attNum": e[0], "num": e[1]}) + struct.pack(
147
+ (">%dh" % len(e[2])), *e[2]
148
+ )
149
+ return data
150
+
151
+ def compileAttributes3(self, attrs):
152
+ if self.hasOctaboxes:
153
+ o = attrs.octabox
154
+ data = sstruct.pack(Glat_format_3_octabox_metrics, o)
155
+ numsub = bin(o.subboxBitmap).count("1")
156
+ for b in range(numsub):
157
+ data += sstruct.pack(Glat_format_3_subbox_entry, o.subboxes[b])
158
+ else:
159
+ data = ""
160
+ return data + self.compileAttributes12(attrs, Glat_format_23_entry)
161
+
162
+ def toXML(self, writer, ttFont):
163
+ writer.simpletag("version", version=self.version, compressionScheme=self.scheme)
164
+ writer.newline()
165
+ for n, a in sorted(
166
+ self.attributes.items(), key=lambda x: ttFont.getGlyphID(x[0])
167
+ ):
168
+ writer.begintag("glyph", name=n)
169
+ writer.newline()
170
+ if hasattr(a, "octabox"):
171
+ o = a.octabox
172
+ formatstring, names, fixes = sstruct.getformat(
173
+ Glat_format_3_octabox_metrics
174
+ )
175
+ vals = {}
176
+ for k in names:
177
+ if k == "subboxBitmap":
178
+ continue
179
+ vals[k] = "{:.3f}%".format(getattr(o, k) * 100.0 / 255)
180
+ vals["bitmap"] = "{:0X}".format(o.subboxBitmap)
181
+ writer.begintag("octaboxes", **vals)
182
+ writer.newline()
183
+ formatstring, names, fixes = sstruct.getformat(
184
+ Glat_format_3_subbox_entry
185
+ )
186
+ for s in o.subboxes:
187
+ vals = {}
188
+ for k in names:
189
+ vals[k] = "{:.3f}%".format(getattr(s, k) * 100.0 / 255)
190
+ writer.simpletag("octabox", **vals)
191
+ writer.newline()
192
+ writer.endtag("octaboxes")
193
+ writer.newline()
194
+ for k, v in sorted(a.items()):
195
+ writer.simpletag("attribute", index=k, value=v)
196
+ writer.newline()
197
+ writer.endtag("glyph")
198
+ writer.newline()
199
+
200
+ def fromXML(self, name, attrs, content, ttFont):
201
+ if name == "version":
202
+ self.version = float(safeEval(attrs["version"]))
203
+ self.scheme = int(safeEval(attrs["compressionScheme"]))
204
+ if name != "glyph":
205
+ return
206
+ if not hasattr(self, "attributes"):
207
+ self.attributes = {}
208
+ gname = attrs["name"]
209
+ attributes = _Dict()
210
+ for element in content:
211
+ if not isinstance(element, tuple):
212
+ continue
213
+ tag, attrs, subcontent = element
214
+ if tag == "attribute":
215
+ k = int(safeEval(attrs["index"]))
216
+ v = int(safeEval(attrs["value"]))
217
+ attributes[k] = v
218
+ elif tag == "octaboxes":
219
+ self.hasOctaboxes = True
220
+ o = _Object()
221
+ o.subboxBitmap = int(attrs["bitmap"], 16)
222
+ o.subboxes = []
223
+ del attrs["bitmap"]
224
+ for k, v in attrs.items():
225
+ setattr(o, k, int(float(v[:-1]) * 255.0 / 100.0 + 0.5))
226
+ for element in subcontent:
227
+ if not isinstance(element, tuple):
228
+ continue
229
+ (tag, attrs, subcontent) = element
230
+ so = _Object()
231
+ for k, v in attrs.items():
232
+ setattr(so, k, int(float(v[:-1]) * 255.0 / 100.0 + 0.5))
233
+ o.subboxes.append(so)
234
+ attributes.octabox = o
235
+ self.attributes[gname] = attributes
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G__l_o_c.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc import sstruct
2
+ from fontTools.misc.textTools import safeEval
3
+ from . import DefaultTable
4
+ import array
5
+ import sys
6
+
7
+
8
+ Gloc_header = """
9
+ > # big endian
10
+ version: 16.16F # Table version
11
+ flags: H # bit 0: 1=long format, 0=short format
12
+ # bit 1: 1=attribute names, 0=no names
13
+ numAttribs: H # NUmber of attributes
14
+ """
15
+
16
+
17
+ class table_G__l_o_c(DefaultTable.DefaultTable):
18
+ """Graphite Index to Glyph Atttributes table
19
+
20
+ See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
21
+ """
22
+
23
+ dependencies = ["Glat"]
24
+
25
+ def __init__(self, tag=None):
26
+ DefaultTable.DefaultTable.__init__(self, tag)
27
+ self.attribIds = None
28
+ self.numAttribs = 0
29
+
30
+ def decompile(self, data, ttFont):
31
+ _, data = sstruct.unpack2(Gloc_header, data, self)
32
+ flags = self.flags
33
+ del self.flags
34
+ self.locations = array.array("I" if flags & 1 else "H")
35
+ self.locations.frombytes(data[: len(data) - self.numAttribs * (flags & 2)])
36
+ if sys.byteorder != "big":
37
+ self.locations.byteswap()
38
+ self.attribIds = array.array("H")
39
+ if flags & 2:
40
+ self.attribIds.frombytes(data[-self.numAttribs * 2 :])
41
+ if sys.byteorder != "big":
42
+ self.attribIds.byteswap()
43
+
44
+ def compile(self, ttFont):
45
+ data = sstruct.pack(
46
+ Gloc_header,
47
+ dict(
48
+ version=1.0,
49
+ flags=(bool(self.attribIds) << 1) + (self.locations.typecode == "I"),
50
+ numAttribs=self.numAttribs,
51
+ ),
52
+ )
53
+ if sys.byteorder != "big":
54
+ self.locations.byteswap()
55
+ data += self.locations.tobytes()
56
+ if sys.byteorder != "big":
57
+ self.locations.byteswap()
58
+ if self.attribIds:
59
+ if sys.byteorder != "big":
60
+ self.attribIds.byteswap()
61
+ data += self.attribIds.tobytes()
62
+ if sys.byteorder != "big":
63
+ self.attribIds.byteswap()
64
+ return data
65
+
66
+ def set(self, locations):
67
+ long_format = max(locations) >= 65536
68
+ self.locations = array.array("I" if long_format else "H", locations)
69
+
70
+ def toXML(self, writer, ttFont):
71
+ writer.simpletag("attributes", number=self.numAttribs)
72
+ writer.newline()
73
+
74
+ def fromXML(self, name, attrs, content, ttFont):
75
+ if name == "attributes":
76
+ self.numAttribs = int(safeEval(attrs["number"]))
77
+
78
+ def __getitem__(self, index):
79
+ return self.locations[index]
80
+
81
+ def __len__(self):
82
+ return len(self.locations)
83
+
84
+ def __iter__(self):
85
+ return iter(self.locations)
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/H_V_A_R_.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .otBase import BaseTTXConverter
2
+
3
+
4
+ class table_H_V_A_R_(BaseTTXConverter):
5
+ """Horizontal Metrics Variations table
6
+
7
+ The ``HVAR`` table contains variations in horizontal glyph metrics
8
+ in variable fonts.
9
+
10
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/hvar
11
+ """
12
+
13
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/I_F_T_.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """IFT - Incremental Font Transfer table."""
2
+
3
+ # Reference: https://github.com/googlefonts/fontations/blob/main/read-fonts/src/tables/ift.rs
4
+
5
+
6
+ from .otBase import BaseTTXConverter
7
+
8
+
9
+ class table_I_F_T_(BaseTTXConverter):
10
+ """Incremental Font Transfer table."""
11
+
12
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/I_F_T_X_.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """IFT - Incremental Font Transfer table."""
2
+
3
+ # Reference: https://github.com/googlefonts/fontations/blob/main/read-fonts/src/tables/ift.rs
4
+
5
+
6
+ from .otBase import BaseTTXConverter
7
+
8
+
9
+ class table_I_F_T_X_(BaseTTXConverter):
10
+ """Incremental Font Transfer table."""
11
+
12
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/J_S_T_F_.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .otBase import BaseTTXConverter
2
+
3
+
4
+ class table_J_S_T_F_(BaseTTXConverter):
5
+ """Justification table
6
+
7
+ The ``JSTF`` table contains glyph substitution and positioning
8
+ data used to perform text justification.
9
+
10
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/jstf
11
+ """
12
+
13
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/L_T_S_H_.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc.textTools import safeEval
2
+ from . import DefaultTable
3
+ import struct
4
+ import array
5
+
6
+ # XXX I've lowered the strictness, to make sure Apple's own Chicago
7
+ # XXX gets through. They're looking into it, I hope to raise the standards
8
+ # XXX back to normal eventually.
9
+
10
+
11
+ class table_L_T_S_H_(DefaultTable.DefaultTable):
12
+ """Linear Threshold table
13
+
14
+ The ``LTSH`` table contains per-glyph settings indicating the ppem sizes
15
+ at which the advance width metric should be scaled linearly, despite the
16
+ effects of any TrueType instructions that might otherwise alter the
17
+ advance width.
18
+
19
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/ltsh
20
+ """
21
+
22
+ def decompile(self, data, ttFont):
23
+ version, numGlyphs = struct.unpack(">HH", data[:4])
24
+ data = data[4:]
25
+ assert version == 0, "unknown version: %s" % version
26
+ assert (len(data) % numGlyphs) < 4, "numGlyphs doesn't match data length"
27
+ # ouch: the assertion is not true in Chicago!
28
+ # assert numGlyphs == ttFont['maxp'].numGlyphs
29
+ yPels = array.array("B")
30
+ yPels.frombytes(data)
31
+ self.yPels = {}
32
+ for i in range(numGlyphs):
33
+ self.yPels[ttFont.getGlyphName(i)] = yPels[i]
34
+
35
+ def compile(self, ttFont):
36
+ version = 0
37
+ names = list(self.yPels.keys())
38
+ numGlyphs = len(names)
39
+ yPels = [0] * numGlyphs
40
+ # ouch: the assertion is not true in Chicago!
41
+ # assert len(self.yPels) == ttFont['maxp'].numGlyphs == numGlyphs
42
+ for name in names:
43
+ yPels[ttFont.getGlyphID(name)] = self.yPels[name]
44
+ yPels = array.array("B", yPels)
45
+ return struct.pack(">HH", version, numGlyphs) + yPels.tobytes()
46
+
47
+ def toXML(self, writer, ttFont):
48
+ names = sorted(self.yPels.keys())
49
+ for name in names:
50
+ writer.simpletag("yPel", name=name, value=self.yPels[name])
51
+ writer.newline()
52
+
53
+ def fromXML(self, name, attrs, content, ttFont):
54
+ if not hasattr(self, "yPels"):
55
+ self.yPels = {}
56
+ if name != "yPel":
57
+ return # ignore unknown tags
58
+ self.yPels[attrs["name"]] = safeEval(attrs["value"])
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/M_A_T_H_.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .otBase import BaseTTXConverter
2
+
3
+
4
+ class table_M_A_T_H_(BaseTTXConverter):
5
+ """Mathematical Typesetting table
6
+
7
+ The ``MATH`` table contains a variety of information needed to
8
+ typeset glyphs in mathematical formulas and expressions.
9
+
10
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/math
11
+ """
12
+
13
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/M_V_A_R_.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .otBase import BaseTTXConverter
2
+
3
+
4
+ class table_M_V_A_R_(BaseTTXConverter):
5
+ """Metrics Variations table
6
+
7
+ The ``MVAR`` table contains variation information for font-wide
8
+ metrics in a variable font.
9
+
10
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/mvar
11
+ """
12
+
13
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/O_S_2f_2.py ADDED
@@ -0,0 +1,752 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc import sstruct
2
+ from fontTools.misc.roundTools import otRound
3
+ from fontTools.misc.textTools import safeEval, num2binary, binary2num
4
+ from fontTools.ttLib.tables import DefaultTable
5
+ import bisect
6
+ import logging
7
+
8
+
9
+ log = logging.getLogger(__name__)
10
+
11
+ # panose classification
12
+
13
+ panoseFormat = """
14
+ bFamilyType: B
15
+ bSerifStyle: B
16
+ bWeight: B
17
+ bProportion: B
18
+ bContrast: B
19
+ bStrokeVariation: B
20
+ bArmStyle: B
21
+ bLetterForm: B
22
+ bMidline: B
23
+ bXHeight: B
24
+ """
25
+
26
+
27
+ class Panose(object):
28
+ def __init__(self, **kwargs):
29
+ _, names, _ = sstruct.getformat(panoseFormat)
30
+ for name in names:
31
+ setattr(self, name, kwargs.pop(name, 0))
32
+ for k in kwargs:
33
+ raise TypeError(f"Panose() got an unexpected keyword argument {k!r}")
34
+
35
+ def toXML(self, writer, ttFont):
36
+ formatstring, names, fixes = sstruct.getformat(panoseFormat)
37
+ for name in names:
38
+ writer.simpletag(name, value=getattr(self, name))
39
+ writer.newline()
40
+
41
+ def fromXML(self, name, attrs, content, ttFont):
42
+ setattr(self, name, safeEval(attrs["value"]))
43
+
44
+
45
+ # 'sfnt' OS/2 and Windows Metrics table - 'OS/2'
46
+
47
+ OS2_format_0 = """
48
+ > # big endian
49
+ version: H # version
50
+ xAvgCharWidth: h # average character width
51
+ usWeightClass: H # degree of thickness of strokes
52
+ usWidthClass: H # aspect ratio
53
+ fsType: H # type flags
54
+ ySubscriptXSize: h # subscript horizontal font size
55
+ ySubscriptYSize: h # subscript vertical font size
56
+ ySubscriptXOffset: h # subscript x offset
57
+ ySubscriptYOffset: h # subscript y offset
58
+ ySuperscriptXSize: h # superscript horizontal font size
59
+ ySuperscriptYSize: h # superscript vertical font size
60
+ ySuperscriptXOffset: h # superscript x offset
61
+ ySuperscriptYOffset: h # superscript y offset
62
+ yStrikeoutSize: h # strikeout size
63
+ yStrikeoutPosition: h # strikeout position
64
+ sFamilyClass: h # font family class and subclass
65
+ panose: 10s # panose classification number
66
+ ulUnicodeRange1: L # character range
67
+ ulUnicodeRange2: L # character range
68
+ ulUnicodeRange3: L # character range
69
+ ulUnicodeRange4: L # character range
70
+ achVendID: 4s # font vendor identification
71
+ fsSelection: H # font selection flags
72
+ usFirstCharIndex: H # first unicode character index
73
+ usLastCharIndex: H # last unicode character index
74
+ sTypoAscender: h # typographic ascender
75
+ sTypoDescender: h # typographic descender
76
+ sTypoLineGap: h # typographic line gap
77
+ usWinAscent: H # Windows ascender
78
+ usWinDescent: H # Windows descender
79
+ """
80
+
81
+ OS2_format_1_addition = """
82
+ ulCodePageRange1: L
83
+ ulCodePageRange2: L
84
+ """
85
+
86
+ OS2_format_2_addition = (
87
+ OS2_format_1_addition
88
+ + """
89
+ sxHeight: h
90
+ sCapHeight: h
91
+ usDefaultChar: H
92
+ usBreakChar: H
93
+ usMaxContext: H
94
+ """
95
+ )
96
+
97
+ OS2_format_5_addition = (
98
+ OS2_format_2_addition
99
+ + """
100
+ usLowerOpticalPointSize: H
101
+ usUpperOpticalPointSize: H
102
+ """
103
+ )
104
+
105
+ bigendian = " > # big endian\n"
106
+
107
+ OS2_format_1 = OS2_format_0 + OS2_format_1_addition
108
+ OS2_format_2 = OS2_format_0 + OS2_format_2_addition
109
+ OS2_format_5 = OS2_format_0 + OS2_format_5_addition
110
+ OS2_format_1_addition = bigendian + OS2_format_1_addition
111
+ OS2_format_2_addition = bigendian + OS2_format_2_addition
112
+ OS2_format_5_addition = bigendian + OS2_format_5_addition
113
+
114
+
115
+ class table_O_S_2f_2(DefaultTable.DefaultTable):
116
+ """OS/2 and Windows Metrics table
117
+
118
+ The ``OS/2`` table contains a variety of font-wide metrics and
119
+ parameters that may be useful to an operating system or other
120
+ software for system-integration purposes.
121
+
122
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/os2
123
+ """
124
+
125
+ dependencies = ["head"]
126
+
127
+ def decompile(self, data, ttFont):
128
+ dummy, data = sstruct.unpack2(OS2_format_0, data, self)
129
+
130
+ if self.version == 1:
131
+ dummy, data = sstruct.unpack2(OS2_format_1_addition, data, self)
132
+ elif self.version in (2, 3, 4):
133
+ dummy, data = sstruct.unpack2(OS2_format_2_addition, data, self)
134
+ elif self.version == 5:
135
+ dummy, data = sstruct.unpack2(OS2_format_5_addition, data, self)
136
+ self.usLowerOpticalPointSize /= 20
137
+ self.usUpperOpticalPointSize /= 20
138
+ elif self.version != 0:
139
+ from fontTools import ttLib
140
+
141
+ raise ttLib.TTLibError(
142
+ "unknown format for OS/2 table: version %s" % self.version
143
+ )
144
+ if len(data):
145
+ log.warning("too much 'OS/2' table data")
146
+
147
+ self.panose = sstruct.unpack(panoseFormat, self.panose, Panose())
148
+
149
+ def compile(self, ttFont):
150
+ self.updateFirstAndLastCharIndex(ttFont)
151
+ panose = self.panose
152
+ head = ttFont["head"]
153
+ if (self.fsSelection & 1) and not (head.macStyle & 1 << 1):
154
+ log.warning(
155
+ "fsSelection bit 0 (italic) and "
156
+ "head table macStyle bit 1 (italic) should match"
157
+ )
158
+ if (self.fsSelection & 1 << 5) and not (head.macStyle & 1):
159
+ log.warning(
160
+ "fsSelection bit 5 (bold) and "
161
+ "head table macStyle bit 0 (bold) should match"
162
+ )
163
+ if (self.fsSelection & 1 << 6) and (self.fsSelection & 1 + (1 << 5)):
164
+ log.warning(
165
+ "fsSelection bit 6 (regular) is set, "
166
+ "bits 0 (italic) and 5 (bold) must be clear"
167
+ )
168
+ if self.version < 4 and self.fsSelection & 0b1110000000:
169
+ log.warning(
170
+ "fsSelection bits 7, 8 and 9 are only defined in "
171
+ "OS/2 table version 4 and up: version %s",
172
+ self.version,
173
+ )
174
+ self.panose = sstruct.pack(panoseFormat, self.panose)
175
+ if self.version == 0:
176
+ data = sstruct.pack(OS2_format_0, self)
177
+ elif self.version == 1:
178
+ data = sstruct.pack(OS2_format_1, self)
179
+ elif self.version in (2, 3, 4):
180
+ data = sstruct.pack(OS2_format_2, self)
181
+ elif self.version == 5:
182
+ d = self.__dict__.copy()
183
+ d["usLowerOpticalPointSize"] = round(self.usLowerOpticalPointSize * 20)
184
+ d["usUpperOpticalPointSize"] = round(self.usUpperOpticalPointSize * 20)
185
+ data = sstruct.pack(OS2_format_5, d)
186
+ else:
187
+ from fontTools import ttLib
188
+
189
+ raise ttLib.TTLibError(
190
+ "unknown format for OS/2 table: version %s" % self.version
191
+ )
192
+ self.panose = panose
193
+ return data
194
+
195
+ def toXML(self, writer, ttFont):
196
+ writer.comment(
197
+ "The fields 'usFirstCharIndex' and 'usLastCharIndex'\n"
198
+ "will be recalculated by the compiler"
199
+ )
200
+ writer.newline()
201
+ if self.version == 1:
202
+ format = OS2_format_1
203
+ elif self.version in (2, 3, 4):
204
+ format = OS2_format_2
205
+ elif self.version == 5:
206
+ format = OS2_format_5
207
+ else:
208
+ format = OS2_format_0
209
+ formatstring, names, fixes = sstruct.getformat(format)
210
+ for name in names:
211
+ value = getattr(self, name)
212
+ if name == "panose":
213
+ writer.begintag("panose")
214
+ writer.newline()
215
+ value.toXML(writer, ttFont)
216
+ writer.endtag("panose")
217
+ elif name in (
218
+ "ulUnicodeRange1",
219
+ "ulUnicodeRange2",
220
+ "ulUnicodeRange3",
221
+ "ulUnicodeRange4",
222
+ "ulCodePageRange1",
223
+ "ulCodePageRange2",
224
+ ):
225
+ writer.simpletag(name, value=num2binary(value))
226
+ elif name in ("fsType", "fsSelection"):
227
+ writer.simpletag(name, value=num2binary(value, 16))
228
+ elif name == "achVendID":
229
+ writer.simpletag(name, value=repr(value)[1:-1])
230
+ else:
231
+ writer.simpletag(name, value=value)
232
+ writer.newline()
233
+
234
+ def fromXML(self, name, attrs, content, ttFont):
235
+ if name == "panose":
236
+ self.panose = panose = Panose()
237
+ for element in content:
238
+ if isinstance(element, tuple):
239
+ name, attrs, content = element
240
+ panose.fromXML(name, attrs, content, ttFont)
241
+ elif name in (
242
+ "ulUnicodeRange1",
243
+ "ulUnicodeRange2",
244
+ "ulUnicodeRange3",
245
+ "ulUnicodeRange4",
246
+ "ulCodePageRange1",
247
+ "ulCodePageRange2",
248
+ "fsType",
249
+ "fsSelection",
250
+ ):
251
+ setattr(self, name, binary2num(attrs["value"]))
252
+ elif name == "achVendID":
253
+ setattr(self, name, safeEval("'''" + attrs["value"] + "'''"))
254
+ else:
255
+ setattr(self, name, safeEval(attrs["value"]))
256
+
257
+ def updateFirstAndLastCharIndex(self, ttFont):
258
+ if "cmap" not in ttFont:
259
+ return
260
+ codes = set()
261
+ for table in getattr(ttFont["cmap"], "tables", []):
262
+ if table.isUnicode():
263
+ codes.update(table.cmap.keys())
264
+ if codes:
265
+ minCode = min(codes)
266
+ maxCode = max(codes)
267
+ # USHORT cannot hold codepoints greater than 0xFFFF
268
+ self.usFirstCharIndex = min(0xFFFF, minCode)
269
+ self.usLastCharIndex = min(0xFFFF, maxCode)
270
+
271
+ # misspelled attributes kept for legacy reasons
272
+
273
+ @property
274
+ def usMaxContex(self):
275
+ return self.usMaxContext
276
+
277
+ @usMaxContex.setter
278
+ def usMaxContex(self, value):
279
+ self.usMaxContext = value
280
+
281
+ @property
282
+ def fsFirstCharIndex(self):
283
+ return self.usFirstCharIndex
284
+
285
+ @fsFirstCharIndex.setter
286
+ def fsFirstCharIndex(self, value):
287
+ self.usFirstCharIndex = value
288
+
289
+ @property
290
+ def fsLastCharIndex(self):
291
+ return self.usLastCharIndex
292
+
293
+ @fsLastCharIndex.setter
294
+ def fsLastCharIndex(self, value):
295
+ self.usLastCharIndex = value
296
+
297
+ def getUnicodeRanges(self):
298
+ """Return the set of 'ulUnicodeRange*' bits currently enabled."""
299
+ bits = set()
300
+ ul1, ul2 = self.ulUnicodeRange1, self.ulUnicodeRange2
301
+ ul3, ul4 = self.ulUnicodeRange3, self.ulUnicodeRange4
302
+ for i in range(32):
303
+ if ul1 & (1 << i):
304
+ bits.add(i)
305
+ if ul2 & (1 << i):
306
+ bits.add(i + 32)
307
+ if ul3 & (1 << i):
308
+ bits.add(i + 64)
309
+ if ul4 & (1 << i):
310
+ bits.add(i + 96)
311
+ return bits
312
+
313
+ def setUnicodeRanges(self, bits):
314
+ """Set the 'ulUnicodeRange*' fields to the specified 'bits'."""
315
+ ul1, ul2, ul3, ul4 = 0, 0, 0, 0
316
+ for bit in bits:
317
+ if 0 <= bit < 32:
318
+ ul1 |= 1 << bit
319
+ elif 32 <= bit < 64:
320
+ ul2 |= 1 << (bit - 32)
321
+ elif 64 <= bit < 96:
322
+ ul3 |= 1 << (bit - 64)
323
+ elif 96 <= bit < 128:
324
+ ul4 |= 1 << (bit - 96)
325
+ else:
326
+ raise ValueError("expected 0 <= int <= 127, found: %r" % bit)
327
+ self.ulUnicodeRange1, self.ulUnicodeRange2 = ul1, ul2
328
+ self.ulUnicodeRange3, self.ulUnicodeRange4 = ul3, ul4
329
+
330
+ def recalcUnicodeRanges(self, ttFont, pruneOnly=False):
331
+ """Intersect the codepoints in the font's Unicode cmap subtables with
332
+ the Unicode block ranges defined in the OpenType specification (v1.7),
333
+ and set the respective 'ulUnicodeRange*' bits if there is at least ONE
334
+ intersection.
335
+ If 'pruneOnly' is True, only clear unused bits with NO intersection.
336
+ """
337
+ unicodes = set()
338
+ for table in ttFont["cmap"].tables:
339
+ if table.isUnicode():
340
+ unicodes.update(table.cmap.keys())
341
+ if pruneOnly:
342
+ empty = intersectUnicodeRanges(unicodes, inverse=True)
343
+ bits = self.getUnicodeRanges() - empty
344
+ else:
345
+ bits = intersectUnicodeRanges(unicodes)
346
+ self.setUnicodeRanges(bits)
347
+ return bits
348
+
349
+ def getCodePageRanges(self):
350
+ """Return the set of 'ulCodePageRange*' bits currently enabled."""
351
+ bits = set()
352
+ if self.version < 1:
353
+ return bits
354
+ ul1, ul2 = self.ulCodePageRange1, self.ulCodePageRange2
355
+ for i in range(32):
356
+ if ul1 & (1 << i):
357
+ bits.add(i)
358
+ if ul2 & (1 << i):
359
+ bits.add(i + 32)
360
+ return bits
361
+
362
+ def setCodePageRanges(self, bits):
363
+ """Set the 'ulCodePageRange*' fields to the specified 'bits'."""
364
+ ul1, ul2 = 0, 0
365
+ for bit in bits:
366
+ if 0 <= bit < 32:
367
+ ul1 |= 1 << bit
368
+ elif 32 <= bit < 64:
369
+ ul2 |= 1 << (bit - 32)
370
+ else:
371
+ raise ValueError(f"expected 0 <= int <= 63, found: {bit:r}")
372
+ if self.version < 1:
373
+ self.version = 1
374
+ self.ulCodePageRange1, self.ulCodePageRange2 = ul1, ul2
375
+
376
+ def recalcCodePageRanges(self, ttFont, pruneOnly=False):
377
+ unicodes = set()
378
+ for table in ttFont["cmap"].tables:
379
+ if table.isUnicode():
380
+ unicodes.update(table.cmap.keys())
381
+ bits = calcCodePageRanges(unicodes)
382
+ if pruneOnly:
383
+ bits &= self.getCodePageRanges()
384
+ # when no codepage ranges can be enabled, fall back to enabling bit 0
385
+ # (Latin 1) so that the font works in MS Word:
386
+ # https://github.com/googlei18n/fontmake/issues/468
387
+ if not bits:
388
+ bits = {0}
389
+ self.setCodePageRanges(bits)
390
+ return bits
391
+
392
+ def recalcAvgCharWidth(self, ttFont):
393
+ """Recalculate xAvgCharWidth using metrics from ttFont's 'hmtx' table.
394
+
395
+ Set it to 0 if the unlikely event 'hmtx' table is not found.
396
+ """
397
+ avg_width = 0
398
+ hmtx = ttFont.get("hmtx")
399
+ if hmtx is not None:
400
+ widths = [width for width, _ in hmtx.metrics.values() if width > 0]
401
+ if widths:
402
+ avg_width = otRound(sum(widths) / len(widths))
403
+ self.xAvgCharWidth = avg_width
404
+ return avg_width
405
+
406
+
407
+ # Unicode ranges data from the OpenType OS/2 table specification v1.7
408
+
409
+ OS2_UNICODE_RANGES = (
410
+ (("Basic Latin", (0x0000, 0x007F)),),
411
+ (("Latin-1 Supplement", (0x0080, 0x00FF)),),
412
+ (("Latin Extended-A", (0x0100, 0x017F)),),
413
+ (("Latin Extended-B", (0x0180, 0x024F)),),
414
+ (
415
+ ("IPA Extensions", (0x0250, 0x02AF)),
416
+ ("Phonetic Extensions", (0x1D00, 0x1D7F)),
417
+ ("Phonetic Extensions Supplement", (0x1D80, 0x1DBF)),
418
+ ),
419
+ (
420
+ ("Spacing Modifier Letters", (0x02B0, 0x02FF)),
421
+ ("Modifier Tone Letters", (0xA700, 0xA71F)),
422
+ ),
423
+ (
424
+ ("Combining Diacritical Marks", (0x0300, 0x036F)),
425
+ ("Combining Diacritical Marks Supplement", (0x1DC0, 0x1DFF)),
426
+ ),
427
+ (("Greek and Coptic", (0x0370, 0x03FF)),),
428
+ (("Coptic", (0x2C80, 0x2CFF)),),
429
+ (
430
+ ("Cyrillic", (0x0400, 0x04FF)),
431
+ ("Cyrillic Supplement", (0x0500, 0x052F)),
432
+ ("Cyrillic Extended-A", (0x2DE0, 0x2DFF)),
433
+ ("Cyrillic Extended-B", (0xA640, 0xA69F)),
434
+ ),
435
+ (("Armenian", (0x0530, 0x058F)),),
436
+ (("Hebrew", (0x0590, 0x05FF)),),
437
+ (("Vai", (0xA500, 0xA63F)),),
438
+ (("Arabic", (0x0600, 0x06FF)), ("Arabic Supplement", (0x0750, 0x077F))),
439
+ (("NKo", (0x07C0, 0x07FF)),),
440
+ (("Devanagari", (0x0900, 0x097F)),),
441
+ (("Bengali", (0x0980, 0x09FF)),),
442
+ (("Gurmukhi", (0x0A00, 0x0A7F)),),
443
+ (("Gujarati", (0x0A80, 0x0AFF)),),
444
+ (("Oriya", (0x0B00, 0x0B7F)),),
445
+ (("Tamil", (0x0B80, 0x0BFF)),),
446
+ (("Telugu", (0x0C00, 0x0C7F)),),
447
+ (("Kannada", (0x0C80, 0x0CFF)),),
448
+ (("Malayalam", (0x0D00, 0x0D7F)),),
449
+ (("Thai", (0x0E00, 0x0E7F)),),
450
+ (("Lao", (0x0E80, 0x0EFF)),),
451
+ (("Georgian", (0x10A0, 0x10FF)), ("Georgian Supplement", (0x2D00, 0x2D2F))),
452
+ (("Balinese", (0x1B00, 0x1B7F)),),
453
+ (("Hangul Jamo", (0x1100, 0x11FF)),),
454
+ (
455
+ ("Latin Extended Additional", (0x1E00, 0x1EFF)),
456
+ ("Latin Extended-C", (0x2C60, 0x2C7F)),
457
+ ("Latin Extended-D", (0xA720, 0xA7FF)),
458
+ ),
459
+ (("Greek Extended", (0x1F00, 0x1FFF)),),
460
+ (
461
+ ("General Punctuation", (0x2000, 0x206F)),
462
+ ("Supplemental Punctuation", (0x2E00, 0x2E7F)),
463
+ ),
464
+ (("Superscripts And Subscripts", (0x2070, 0x209F)),),
465
+ (("Currency Symbols", (0x20A0, 0x20CF)),),
466
+ (("Combining Diacritical Marks For Symbols", (0x20D0, 0x20FF)),),
467
+ (("Letterlike Symbols", (0x2100, 0x214F)),),
468
+ (("Number Forms", (0x2150, 0x218F)),),
469
+ (
470
+ ("Arrows", (0x2190, 0x21FF)),
471
+ ("Supplemental Arrows-A", (0x27F0, 0x27FF)),
472
+ ("Supplemental Arrows-B", (0x2900, 0x297F)),
473
+ ("Miscellaneous Symbols and Arrows", (0x2B00, 0x2BFF)),
474
+ ),
475
+ (
476
+ ("Mathematical Operators", (0x2200, 0x22FF)),
477
+ ("Supplemental Mathematical Operators", (0x2A00, 0x2AFF)),
478
+ ("Miscellaneous Mathematical Symbols-A", (0x27C0, 0x27EF)),
479
+ ("Miscellaneous Mathematical Symbols-B", (0x2980, 0x29FF)),
480
+ ),
481
+ (("Miscellaneous Technical", (0x2300, 0x23FF)),),
482
+ (("Control Pictures", (0x2400, 0x243F)),),
483
+ (("Optical Character Recognition", (0x2440, 0x245F)),),
484
+ (("Enclosed Alphanumerics", (0x2460, 0x24FF)),),
485
+ (("Box Drawing", (0x2500, 0x257F)),),
486
+ (("Block Elements", (0x2580, 0x259F)),),
487
+ (("Geometric Shapes", (0x25A0, 0x25FF)),),
488
+ (("Miscellaneous Symbols", (0x2600, 0x26FF)),),
489
+ (("Dingbats", (0x2700, 0x27BF)),),
490
+ (("CJK Symbols And Punctuation", (0x3000, 0x303F)),),
491
+ (("Hiragana", (0x3040, 0x309F)),),
492
+ (
493
+ ("Katakana", (0x30A0, 0x30FF)),
494
+ ("Katakana Phonetic Extensions", (0x31F0, 0x31FF)),
495
+ ),
496
+ (("Bopomofo", (0x3100, 0x312F)), ("Bopomofo Extended", (0x31A0, 0x31BF))),
497
+ (("Hangul Compatibility Jamo", (0x3130, 0x318F)),),
498
+ (("Phags-pa", (0xA840, 0xA87F)),),
499
+ (("Enclosed CJK Letters And Months", (0x3200, 0x32FF)),),
500
+ (("CJK Compatibility", (0x3300, 0x33FF)),),
501
+ (("Hangul Syllables", (0xAC00, 0xD7AF)),),
502
+ (("Non-Plane 0 *", (0xD800, 0xDFFF)),),
503
+ (("Phoenician", (0x10900, 0x1091F)),),
504
+ (
505
+ ("CJK Unified Ideographs", (0x4E00, 0x9FFF)),
506
+ ("CJK Radicals Supplement", (0x2E80, 0x2EFF)),
507
+ ("Kangxi Radicals", (0x2F00, 0x2FDF)),
508
+ ("Ideographic Description Characters", (0x2FF0, 0x2FFF)),
509
+ ("CJK Unified Ideographs Extension A", (0x3400, 0x4DBF)),
510
+ ("CJK Unified Ideographs Extension B", (0x20000, 0x2A6DF)),
511
+ ("Kanbun", (0x3190, 0x319F)),
512
+ ),
513
+ (("Private Use Area (plane 0)", (0xE000, 0xF8FF)),),
514
+ (
515
+ ("CJK Strokes", (0x31C0, 0x31EF)),
516
+ ("CJK Compatibility Ideographs", (0xF900, 0xFAFF)),
517
+ ("CJK Compatibility Ideographs Supplement", (0x2F800, 0x2FA1F)),
518
+ ),
519
+ (("Alphabetic Presentation Forms", (0xFB00, 0xFB4F)),),
520
+ (("Arabic Presentation Forms-A", (0xFB50, 0xFDFF)),),
521
+ (("Combining Half Marks", (0xFE20, 0xFE2F)),),
522
+ (
523
+ ("Vertical Forms", (0xFE10, 0xFE1F)),
524
+ ("CJK Compatibility Forms", (0xFE30, 0xFE4F)),
525
+ ),
526
+ (("Small Form Variants", (0xFE50, 0xFE6F)),),
527
+ (("Arabic Presentation Forms-B", (0xFE70, 0xFEFF)),),
528
+ (("Halfwidth And Fullwidth Forms", (0xFF00, 0xFFEF)),),
529
+ (("Specials", (0xFFF0, 0xFFFF)),),
530
+ (("Tibetan", (0x0F00, 0x0FFF)),),
531
+ (("Syriac", (0x0700, 0x074F)),),
532
+ (("Thaana", (0x0780, 0x07BF)),),
533
+ (("Sinhala", (0x0D80, 0x0DFF)),),
534
+ (("Myanmar", (0x1000, 0x109F)),),
535
+ (
536
+ ("Ethiopic", (0x1200, 0x137F)),
537
+ ("Ethiopic Supplement", (0x1380, 0x139F)),
538
+ ("Ethiopic Extended", (0x2D80, 0x2DDF)),
539
+ ),
540
+ (("Cherokee", (0x13A0, 0x13FF)),),
541
+ (("Unified Canadian Aboriginal Syllabics", (0x1400, 0x167F)),),
542
+ (("Ogham", (0x1680, 0x169F)),),
543
+ (("Runic", (0x16A0, 0x16FF)),),
544
+ (("Khmer", (0x1780, 0x17FF)), ("Khmer Symbols", (0x19E0, 0x19FF))),
545
+ (("Mongolian", (0x1800, 0x18AF)),),
546
+ (("Braille Patterns", (0x2800, 0x28FF)),),
547
+ (("Yi Syllables", (0xA000, 0xA48F)), ("Yi Radicals", (0xA490, 0xA4CF))),
548
+ (
549
+ ("Tagalog", (0x1700, 0x171F)),
550
+ ("Hanunoo", (0x1720, 0x173F)),
551
+ ("Buhid", (0x1740, 0x175F)),
552
+ ("Tagbanwa", (0x1760, 0x177F)),
553
+ ),
554
+ (("Old Italic", (0x10300, 0x1032F)),),
555
+ (("Gothic", (0x10330, 0x1034F)),),
556
+ (("Deseret", (0x10400, 0x1044F)),),
557
+ (
558
+ ("Byzantine Musical Symbols", (0x1D000, 0x1D0FF)),
559
+ ("Musical Symbols", (0x1D100, 0x1D1FF)),
560
+ ("Ancient Greek Musical Notation", (0x1D200, 0x1D24F)),
561
+ ),
562
+ (("Mathematical Alphanumeric Symbols", (0x1D400, 0x1D7FF)),),
563
+ (
564
+ ("Private Use (plane 15)", (0xF0000, 0xFFFFD)),
565
+ ("Private Use (plane 16)", (0x100000, 0x10FFFD)),
566
+ ),
567
+ (
568
+ ("Variation Selectors", (0xFE00, 0xFE0F)),
569
+ ("Variation Selectors Supplement", (0xE0100, 0xE01EF)),
570
+ ),
571
+ (("Tags", (0xE0000, 0xE007F)),),
572
+ (("Limbu", (0x1900, 0x194F)),),
573
+ (("Tai Le", (0x1950, 0x197F)),),
574
+ (("New Tai Lue", (0x1980, 0x19DF)),),
575
+ (("Buginese", (0x1A00, 0x1A1F)),),
576
+ (("Glagolitic", (0x2C00, 0x2C5F)),),
577
+ (("Tifinagh", (0x2D30, 0x2D7F)),),
578
+ (("Yijing Hexagram Symbols", (0x4DC0, 0x4DFF)),),
579
+ (("Syloti Nagri", (0xA800, 0xA82F)),),
580
+ (
581
+ ("Linear B Syllabary", (0x10000, 0x1007F)),
582
+ ("Linear B Ideograms", (0x10080, 0x100FF)),
583
+ ("Aegean Numbers", (0x10100, 0x1013F)),
584
+ ),
585
+ (("Ancient Greek Numbers", (0x10140, 0x1018F)),),
586
+ (("Ugaritic", (0x10380, 0x1039F)),),
587
+ (("Old Persian", (0x103A0, 0x103DF)),),
588
+ (("Shavian", (0x10450, 0x1047F)),),
589
+ (("Osmanya", (0x10480, 0x104AF)),),
590
+ (("Cypriot Syllabary", (0x10800, 0x1083F)),),
591
+ (("Kharoshthi", (0x10A00, 0x10A5F)),),
592
+ (("Tai Xuan Jing Symbols", (0x1D300, 0x1D35F)),),
593
+ (
594
+ ("Cuneiform", (0x12000, 0x123FF)),
595
+ ("Cuneiform Numbers and Punctuation", (0x12400, 0x1247F)),
596
+ ),
597
+ (("Counting Rod Numerals", (0x1D360, 0x1D37F)),),
598
+ (("Sundanese", (0x1B80, 0x1BBF)),),
599
+ (("Lepcha", (0x1C00, 0x1C4F)),),
600
+ (("Ol Chiki", (0x1C50, 0x1C7F)),),
601
+ (("Saurashtra", (0xA880, 0xA8DF)),),
602
+ (("Kayah Li", (0xA900, 0xA92F)),),
603
+ (("Rejang", (0xA930, 0xA95F)),),
604
+ (("Cham", (0xAA00, 0xAA5F)),),
605
+ (("Ancient Symbols", (0x10190, 0x101CF)),),
606
+ (("Phaistos Disc", (0x101D0, 0x101FF)),),
607
+ (
608
+ ("Carian", (0x102A0, 0x102DF)),
609
+ ("Lycian", (0x10280, 0x1029F)),
610
+ ("Lydian", (0x10920, 0x1093F)),
611
+ ),
612
+ (("Domino Tiles", (0x1F030, 0x1F09F)), ("Mahjong Tiles", (0x1F000, 0x1F02F))),
613
+ )
614
+
615
+
616
+ _unicodeStarts = []
617
+ _unicodeValues = [None]
618
+
619
+
620
+ def _getUnicodeRanges():
621
+ # build the ranges of codepoints for each unicode range bit, and cache result
622
+ if not _unicodeStarts:
623
+ unicodeRanges = [
624
+ (start, (stop, bit))
625
+ for bit, blocks in enumerate(OS2_UNICODE_RANGES)
626
+ for _, (start, stop) in blocks
627
+ ]
628
+ for start, (stop, bit) in sorted(unicodeRanges):
629
+ _unicodeStarts.append(start)
630
+ _unicodeValues.append((stop, bit))
631
+ return _unicodeStarts, _unicodeValues
632
+
633
+
634
+ def intersectUnicodeRanges(unicodes, inverse=False):
635
+ """Intersect a sequence of (int) Unicode codepoints with the Unicode block
636
+ ranges defined in the OpenType specification v1.7, and return the set of
637
+ 'ulUnicodeRanges' bits for which there is at least ONE intersection.
638
+ If 'inverse' is True, return the the bits for which there is NO intersection.
639
+
640
+ >>> intersectUnicodeRanges([0x0410]) == {9}
641
+ True
642
+ >>> intersectUnicodeRanges([0x0410, 0x1F000]) == {9, 57, 122}
643
+ True
644
+ >>> intersectUnicodeRanges([0x0410, 0x1F000], inverse=True) == (
645
+ ... set(range(len(OS2_UNICODE_RANGES))) - {9, 57, 122})
646
+ True
647
+ """
648
+ unicodes = set(unicodes)
649
+ unicodestarts, unicodevalues = _getUnicodeRanges()
650
+ bits = set()
651
+ for code in unicodes:
652
+ stop, bit = unicodevalues[bisect.bisect(unicodestarts, code)]
653
+ if code <= stop:
654
+ bits.add(bit)
655
+ # The spec says that bit 57 ("Non Plane 0") implies that there's
656
+ # at least one codepoint beyond the BMP; so I also include all
657
+ # the non-BMP codepoints here
658
+ if any(0x10000 <= code < 0x110000 for code in unicodes):
659
+ bits.add(57)
660
+ return set(range(len(OS2_UNICODE_RANGES))) - bits if inverse else bits
661
+
662
+
663
+ def calcCodePageRanges(unicodes):
664
+ """Given a set of Unicode codepoints (integers), calculate the
665
+ corresponding OS/2 CodePage range bits.
666
+ This is a direct translation of FontForge implementation:
667
+ https://github.com/fontforge/fontforge/blob/7b2c074/fontforge/tottf.c#L3158
668
+ """
669
+ bits = set()
670
+ hasAscii = set(range(0x20, 0x7E)).issubset(unicodes)
671
+ hasLineart = ord("┤") in unicodes
672
+
673
+ for uni in unicodes:
674
+ if uni == ord("Þ") and hasAscii:
675
+ bits.add(0) # Latin 1
676
+ elif uni == ord("Ľ") and hasAscii:
677
+ bits.add(1) # Latin 2: Eastern Europe
678
+ if hasLineart:
679
+ bits.add(58) # Latin 2
680
+ elif uni == ord("Б"):
681
+ bits.add(2) # Cyrillic
682
+ if ord("Ѕ") in unicodes and hasLineart:
683
+ bits.add(57) # IBM Cyrillic
684
+ if ord("╜") in unicodes and hasLineart:
685
+ bits.add(49) # MS-DOS Russian
686
+ elif uni == ord("Ά"):
687
+ bits.add(3) # Greek
688
+ if hasLineart and ord("½") in unicodes:
689
+ bits.add(48) # IBM Greek
690
+ if hasLineart and ord("√") in unicodes:
691
+ bits.add(60) # Greek, former 437 G
692
+ elif uni == ord("İ") and hasAscii:
693
+ bits.add(4) # Turkish
694
+ if hasLineart:
695
+ bits.add(56) # IBM turkish
696
+ elif uni == ord("א"):
697
+ bits.add(5) # Hebrew
698
+ if hasLineart and ord("√") in unicodes:
699
+ bits.add(53) # Hebrew
700
+ elif uni == ord("ر"):
701
+ bits.add(6) # Arabic
702
+ if ord("√") in unicodes:
703
+ bits.add(51) # Arabic
704
+ if hasLineart:
705
+ bits.add(61) # Arabic; ASMO 708
706
+ elif uni == ord("ŗ") and hasAscii:
707
+ bits.add(7) # Windows Baltic
708
+ if hasLineart:
709
+ bits.add(59) # MS-DOS Baltic
710
+ elif uni == ord("₫") and hasAscii:
711
+ bits.add(8) # Vietnamese
712
+ elif uni == ord("ๅ"):
713
+ bits.add(16) # Thai
714
+ elif uni == ord("エ"):
715
+ bits.add(17) # JIS/Japan
716
+ elif uni == ord("ㄅ"):
717
+ bits.add(18) # Chinese: Simplified
718
+ elif uni == ord("ㄱ"):
719
+ bits.add(19) # Korean wansung
720
+ elif uni == ord("央"):
721
+ bits.add(20) # Chinese: Traditional
722
+ elif uni == ord("곴"):
723
+ bits.add(21) # Korean Johab
724
+ elif uni == ord("♥") and hasAscii:
725
+ bits.add(30) # OEM Character Set
726
+ # TODO: Symbol bit has a special meaning (check the spec), we need
727
+ # to confirm if this is wanted by default.
728
+ # elif chr(0xF000) <= char <= chr(0xF0FF):
729
+ # codepageRanges.add(31) # Symbol Character Set
730
+ elif uni == ord("þ") and hasAscii and hasLineart:
731
+ bits.add(54) # MS-DOS Icelandic
732
+ elif uni == ord("╚") and hasAscii:
733
+ bits.add(62) # WE/Latin 1
734
+ bits.add(63) # US
735
+ elif hasAscii and hasLineart and ord("√") in unicodes:
736
+ if uni == ord("Å"):
737
+ bits.add(50) # MS-DOS Nordic
738
+ elif uni == ord("é"):
739
+ bits.add(52) # MS-DOS Canadian French
740
+ elif uni == ord("õ"):
741
+ bits.add(55) # MS-DOS Portuguese
742
+
743
+ if hasAscii and ord("‰") in unicodes and ord("∑") in unicodes:
744
+ bits.add(29) # Macintosh Character Set (US Roman)
745
+
746
+ return bits
747
+
748
+
749
+ if __name__ == "__main__":
750
+ import doctest, sys
751
+
752
+ sys.exit(doctest.testmod().failed)
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S_T_A_T_.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .otBase import BaseTTXConverter
2
+
3
+
4
+ class table_S_T_A_T_(BaseTTXConverter):
5
+ """Style Attributes table
6
+
7
+ The ``STAT`` table records stylistic or typeface-design attributes that
8
+ differentiate the individual fonts within a font family from one another.
9
+ Those attributes can be used to assist users when navigating the style
10
+ variations of a variable font or a family of static fonts.
11
+
12
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/stat
13
+ """
14
+
15
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S_V_G_.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compiles/decompiles SVG table.
2
+
3
+ https://docs.microsoft.com/en-us/typography/opentype/spec/svg
4
+
5
+ The XML format is:
6
+
7
+ .. code-block:: xml
8
+
9
+ <SVG>
10
+ <svgDoc endGlyphID="1" startGlyphID="1">
11
+ <![CDATA[ <complete SVG doc> ]]
12
+ </svgDoc>
13
+ ...
14
+ <svgDoc endGlyphID="n" startGlyphID="m">
15
+ <![CDATA[ <complete SVG doc> ]]
16
+ </svgDoc>
17
+ </SVG>
18
+ """
19
+
20
+ from fontTools.misc.textTools import bytesjoin, safeEval, strjoin, tobytes, tostr
21
+ from fontTools.misc import sstruct
22
+ from . import DefaultTable
23
+ from collections.abc import Sequence
24
+ from dataclasses import dataclass, astuple
25
+ from io import BytesIO
26
+ import struct
27
+ import logging
28
+
29
+
30
+ log = logging.getLogger(__name__)
31
+
32
+
33
+ SVG_format_0 = """
34
+ > # big endian
35
+ version: H
36
+ offsetToSVGDocIndex: L
37
+ reserved: L
38
+ """
39
+
40
+ SVG_format_0Size = sstruct.calcsize(SVG_format_0)
41
+
42
+ doc_index_entry_format_0 = """
43
+ > # big endian
44
+ startGlyphID: H
45
+ endGlyphID: H
46
+ svgDocOffset: L
47
+ svgDocLength: L
48
+ """
49
+
50
+ doc_index_entry_format_0Size = sstruct.calcsize(doc_index_entry_format_0)
51
+
52
+
53
+ class table_S_V_G_(DefaultTable.DefaultTable):
54
+ """Scalable Vector Graphics table
55
+
56
+ The ``SVG`` table contains representations for glyphs in the SVG
57
+ image format.
58
+
59
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/stat
60
+ """
61
+
62
+ def decompile(self, data, ttFont):
63
+ self.docList = []
64
+ # Version 0 is the standardized version of the table; and current.
65
+ # https://www.microsoft.com/typography/otspec/svg.htm
66
+ sstruct.unpack(SVG_format_0, data[:SVG_format_0Size], self)
67
+ if self.version != 0:
68
+ log.warning(
69
+ "Unknown SVG table version '%s'. Decompiling as version 0.",
70
+ self.version,
71
+ )
72
+ # read in SVG Documents Index
73
+ # data starts with the first entry of the entry list.
74
+ pos = subTableStart = self.offsetToSVGDocIndex
75
+ self.numEntries = struct.unpack(">H", data[pos : pos + 2])[0]
76
+ pos += 2
77
+ if self.numEntries > 0:
78
+ data2 = data[pos:]
79
+ entries = []
80
+ for i in range(self.numEntries):
81
+ record_data = data2[
82
+ i
83
+ * doc_index_entry_format_0Size : (i + 1)
84
+ * doc_index_entry_format_0Size
85
+ ]
86
+ docIndexEntry = sstruct.unpack(
87
+ doc_index_entry_format_0, record_data, DocumentIndexEntry()
88
+ )
89
+ entries.append(docIndexEntry)
90
+
91
+ for entry in entries:
92
+ start = entry.svgDocOffset + subTableStart
93
+ end = start + entry.svgDocLength
94
+ doc = data[start:end]
95
+ compressed = False
96
+ if doc.startswith(b"\x1f\x8b"):
97
+ import gzip
98
+
99
+ bytesIO = BytesIO(doc)
100
+ with gzip.GzipFile(None, "r", fileobj=bytesIO) as gunzipper:
101
+ doc = gunzipper.read()
102
+ del bytesIO
103
+ compressed = True
104
+ doc = tostr(doc, "utf_8")
105
+ self.docList.append(
106
+ SVGDocument(doc, entry.startGlyphID, entry.endGlyphID, compressed)
107
+ )
108
+
109
+ def compile(self, ttFont):
110
+ version = 0
111
+ offsetToSVGDocIndex = (
112
+ SVG_format_0Size # I start the SVGDocIndex right after the header.
113
+ )
114
+ # get SGVDoc info.
115
+ docList = []
116
+ entryList = []
117
+ numEntries = len(self.docList)
118
+ datum = struct.pack(">H", numEntries)
119
+ entryList.append(datum)
120
+ curOffset = len(datum) + doc_index_entry_format_0Size * numEntries
121
+ seenDocs = {}
122
+ allCompressed = getattr(self, "compressed", False)
123
+ for i, doc in enumerate(self.docList):
124
+ if isinstance(doc, (list, tuple)):
125
+ doc = SVGDocument(*doc)
126
+ self.docList[i] = doc
127
+ docBytes = tobytes(doc.data, encoding="utf_8")
128
+ if (allCompressed or doc.compressed) and not docBytes.startswith(
129
+ b"\x1f\x8b"
130
+ ):
131
+ import gzip
132
+
133
+ bytesIO = BytesIO()
134
+ # mtime=0 strips the useless timestamp and makes gzip output reproducible;
135
+ # equivalent to `gzip -n`
136
+ with gzip.GzipFile(None, "w", fileobj=bytesIO, mtime=0) as gzipper:
137
+ gzipper.write(docBytes)
138
+ gzipped = bytesIO.getvalue()
139
+ if len(gzipped) < len(docBytes):
140
+ docBytes = gzipped
141
+ del gzipped, bytesIO
142
+ docLength = len(docBytes)
143
+ if docBytes in seenDocs:
144
+ docOffset = seenDocs[docBytes]
145
+ else:
146
+ docOffset = curOffset
147
+ curOffset += docLength
148
+ seenDocs[docBytes] = docOffset
149
+ docList.append(docBytes)
150
+ entry = struct.pack(
151
+ ">HHLL", doc.startGlyphID, doc.endGlyphID, docOffset, docLength
152
+ )
153
+ entryList.append(entry)
154
+ entryList.extend(docList)
155
+ svgDocData = bytesjoin(entryList)
156
+
157
+ reserved = 0
158
+ header = struct.pack(">HLL", version, offsetToSVGDocIndex, reserved)
159
+ data = [header, svgDocData]
160
+ data = bytesjoin(data)
161
+ return data
162
+
163
+ def toXML(self, writer, ttFont):
164
+ for i, doc in enumerate(self.docList):
165
+ if isinstance(doc, (list, tuple)):
166
+ doc = SVGDocument(*doc)
167
+ self.docList[i] = doc
168
+ attrs = {"startGlyphID": doc.startGlyphID, "endGlyphID": doc.endGlyphID}
169
+ if doc.compressed:
170
+ attrs["compressed"] = 1
171
+ writer.begintag("svgDoc", **attrs)
172
+ writer.newline()
173
+ writer.writecdata(doc.data)
174
+ writer.newline()
175
+ writer.endtag("svgDoc")
176
+ writer.newline()
177
+
178
+ def fromXML(self, name, attrs, content, ttFont):
179
+ if name == "svgDoc":
180
+ if not hasattr(self, "docList"):
181
+ self.docList = []
182
+ doc = strjoin(content)
183
+ doc = doc.strip()
184
+ startGID = int(attrs["startGlyphID"])
185
+ endGID = int(attrs["endGlyphID"])
186
+ compressed = bool(safeEval(attrs.get("compressed", "0")))
187
+ self.docList.append(SVGDocument(doc, startGID, endGID, compressed))
188
+ else:
189
+ log.warning("Unknown %s %s", name, content)
190
+
191
+
192
+ class DocumentIndexEntry(object):
193
+ def __init__(self):
194
+ self.startGlyphID = None # USHORT
195
+ self.endGlyphID = None # USHORT
196
+ self.svgDocOffset = None # ULONG
197
+ self.svgDocLength = None # ULONG
198
+
199
+ def __repr__(self):
200
+ return (
201
+ "startGlyphID: %s, endGlyphID: %s, svgDocOffset: %s, svgDocLength: %s"
202
+ % (self.startGlyphID, self.endGlyphID, self.svgDocOffset, self.svgDocLength)
203
+ )
204
+
205
+
206
+ @dataclass
207
+ class SVGDocument(Sequence):
208
+ data: str
209
+ startGlyphID: int
210
+ endGlyphID: int
211
+ compressed: bool = False
212
+
213
+ # Previously, the SVG table's docList attribute contained a lists of 3 items:
214
+ # [doc, startGlyphID, endGlyphID]; later, we added a `compressed` attribute.
215
+ # For backward compatibility with code that depends of them being sequences of
216
+ # fixed length=3, we subclass the Sequence abstract base class and pretend only
217
+ # the first three items are present. 'compressed' is only accessible via named
218
+ # attribute lookup like regular dataclasses: i.e. `doc.compressed`, not `doc[3]`
219
+ def __getitem__(self, index):
220
+ return astuple(self)[:3][index]
221
+
222
+ def __len__(self):
223
+ return 3
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S__i_l_f.py ADDED
@@ -0,0 +1,1040 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc import sstruct
2
+ from fontTools.misc.fixedTools import floatToFixedToStr
3
+ from fontTools.misc.textTools import byteord, safeEval
4
+
5
+ # from itertools import *
6
+ from . import DefaultTable
7
+ from . import grUtils
8
+ from array import array
9
+ from functools import reduce
10
+ import struct, re, sys
11
+
12
+ Silf_hdr_format = """
13
+ >
14
+ version: 16.16F
15
+ """
16
+
17
+ Silf_hdr_format_3 = """
18
+ >
19
+ version: 16.16F
20
+ compilerVersion: L
21
+ numSilf: H
22
+ x
23
+ x
24
+ """
25
+
26
+ Silf_part1_format_v3 = """
27
+ >
28
+ ruleVersion: 16.16F
29
+ passOffset: H
30
+ pseudosOffset: H
31
+ """
32
+
33
+ Silf_part1_format = """
34
+ >
35
+ maxGlyphID: H
36
+ extraAscent: h
37
+ extraDescent: h
38
+ numPasses: B
39
+ iSubst: B
40
+ iPos: B
41
+ iJust: B
42
+ iBidi: B
43
+ flags: B
44
+ maxPreContext: B
45
+ maxPostContext: B
46
+ attrPseudo: B
47
+ attrBreakWeight: B
48
+ attrDirectionality: B
49
+ attrMirroring: B
50
+ attrSkipPasses: B
51
+ numJLevels: B
52
+ """
53
+
54
+ Silf_justify_format = """
55
+ >
56
+ attrStretch: B
57
+ attrShrink: B
58
+ attrStep: B
59
+ attrWeight: B
60
+ runto: B
61
+ x
62
+ x
63
+ x
64
+ """
65
+
66
+ Silf_part2_format = """
67
+ >
68
+ numLigComp: H
69
+ numUserDefn: B
70
+ maxCompPerLig: B
71
+ direction: B
72
+ attCollisions: B
73
+ x
74
+ x
75
+ x
76
+ numCritFeatures: B
77
+ """
78
+
79
+ Silf_pseudomap_format = """
80
+ >
81
+ unicode: L
82
+ nPseudo: H
83
+ """
84
+
85
+ Silf_pseudomap_format_h = """
86
+ >
87
+ unicode: H
88
+ nPseudo: H
89
+ """
90
+
91
+ Silf_classmap_format = """
92
+ >
93
+ numClass: H
94
+ numLinear: H
95
+ """
96
+
97
+ Silf_lookupclass_format = """
98
+ >
99
+ numIDs: H
100
+ searchRange: H
101
+ entrySelector: H
102
+ rangeShift: H
103
+ """
104
+
105
+ Silf_lookuppair_format = """
106
+ >
107
+ glyphId: H
108
+ index: H
109
+ """
110
+
111
+ Silf_pass_format = """
112
+ >
113
+ flags: B
114
+ maxRuleLoop: B
115
+ maxRuleContext: B
116
+ maxBackup: B
117
+ numRules: H
118
+ fsmOffset: H
119
+ pcCode: L
120
+ rcCode: L
121
+ aCode: L
122
+ oDebug: L
123
+ numRows: H
124
+ numTransitional: H
125
+ numSuccess: H
126
+ numColumns: H
127
+ """
128
+
129
+ aCode_info = (
130
+ ("NOP", 0),
131
+ ("PUSH_BYTE", "b"),
132
+ ("PUSH_BYTE_U", "B"),
133
+ ("PUSH_SHORT", ">h"),
134
+ ("PUSH_SHORT_U", ">H"),
135
+ ("PUSH_LONG", ">L"),
136
+ ("ADD", 0),
137
+ ("SUB", 0),
138
+ ("MUL", 0),
139
+ ("DIV", 0),
140
+ ("MIN", 0),
141
+ ("MAX", 0),
142
+ ("NEG", 0),
143
+ ("TRUNC8", 0),
144
+ ("TRUNC16", 0),
145
+ ("COND", 0),
146
+ ("AND", 0), # x10
147
+ ("OR", 0),
148
+ ("NOT", 0),
149
+ ("EQUAL", 0),
150
+ ("NOT_EQ", 0),
151
+ ("LESS", 0),
152
+ ("GTR", 0),
153
+ ("LESS_EQ", 0),
154
+ ("GTR_EQ", 0),
155
+ ("NEXT", 0),
156
+ ("NEXT_N", "b"),
157
+ ("COPY_NEXT", 0),
158
+ ("PUT_GLYPH_8BIT_OBS", "B"),
159
+ ("PUT_SUBS_8BIT_OBS", "bBB"),
160
+ ("PUT_COPY", "b"),
161
+ ("INSERT", 0),
162
+ ("DELETE", 0), # x20
163
+ ("ASSOC", -1),
164
+ ("CNTXT_ITEM", "bB"),
165
+ ("ATTR_SET", "B"),
166
+ ("ATTR_ADD", "B"),
167
+ ("ATTR_SUB", "B"),
168
+ ("ATTR_SET_SLOT", "B"),
169
+ ("IATTR_SET_SLOT", "BB"),
170
+ ("PUSH_SLOT_ATTR", "Bb"),
171
+ ("PUSH_GLYPH_ATTR_OBS", "Bb"),
172
+ ("PUSH_GLYPH_METRIC", "Bbb"),
173
+ ("PUSH_FEAT", "Bb"),
174
+ ("PUSH_ATT_TO_GATTR_OBS", "Bb"),
175
+ ("PUSH_ATT_TO_GLYPH_METRIC", "Bbb"),
176
+ ("PUSH_ISLOT_ATTR", "Bbb"),
177
+ ("PUSH_IGLYPH_ATTR", "Bbb"),
178
+ ("POP_RET", 0), # x30
179
+ ("RET_ZERO", 0),
180
+ ("RET_TRUE", 0),
181
+ ("IATTR_SET", "BB"),
182
+ ("IATTR_ADD", "BB"),
183
+ ("IATTR_SUB", "BB"),
184
+ ("PUSH_PROC_STATE", "B"),
185
+ ("PUSH_VERSION", 0),
186
+ ("PUT_SUBS", ">bHH"),
187
+ ("PUT_SUBS2", 0),
188
+ ("PUT_SUBS3", 0),
189
+ ("PUT_GLYPH", ">H"),
190
+ ("PUSH_GLYPH_ATTR", ">Hb"),
191
+ ("PUSH_ATT_TO_GLYPH_ATTR", ">Hb"),
192
+ ("BITOR", 0),
193
+ ("BITAND", 0),
194
+ ("BITNOT", 0), # x40
195
+ ("BITSET", ">HH"),
196
+ ("SET_FEAT", "Bb"),
197
+ )
198
+ aCode_map = dict([(x[0], (i, x[1])) for i, x in enumerate(aCode_info)])
199
+
200
+
201
+ def disassemble(aCode):
202
+ codelen = len(aCode)
203
+ pc = 0
204
+ res = []
205
+ while pc < codelen:
206
+ opcode = byteord(aCode[pc : pc + 1])
207
+ if opcode > len(aCode_info):
208
+ instr = aCode_info[0]
209
+ else:
210
+ instr = aCode_info[opcode]
211
+ pc += 1
212
+ if instr[1] != 0 and pc >= codelen:
213
+ return res
214
+ if instr[1] == -1:
215
+ count = byteord(aCode[pc])
216
+ fmt = "%dB" % count
217
+ pc += 1
218
+ elif instr[1] == 0:
219
+ fmt = ""
220
+ else:
221
+ fmt = instr[1]
222
+ if fmt == "":
223
+ res.append(instr[0])
224
+ continue
225
+ parms = struct.unpack_from(fmt, aCode[pc:])
226
+ res.append(instr[0] + "(" + ", ".join(map(str, parms)) + ")")
227
+ pc += struct.calcsize(fmt)
228
+ return res
229
+
230
+
231
+ instre = re.compile(r"^\s*([^(]+)\s*(?:\(([^)]+)\))?")
232
+
233
+
234
+ def assemble(instrs):
235
+ res = b""
236
+ for inst in instrs:
237
+ m = instre.match(inst)
238
+ if not m or not m.group(1) in aCode_map:
239
+ continue
240
+ opcode, parmfmt = aCode_map[m.group(1)]
241
+ res += struct.pack("B", opcode)
242
+ if m.group(2):
243
+ if parmfmt == 0:
244
+ continue
245
+ parms = [int(x) for x in re.split(r",\s*", m.group(2))]
246
+ if parmfmt == -1:
247
+ l = len(parms)
248
+ res += struct.pack(("%dB" % (l + 1)), l, *parms)
249
+ else:
250
+ res += struct.pack(parmfmt, *parms)
251
+ return res
252
+
253
+
254
+ def writecode(tag, writer, instrs):
255
+ writer.begintag(tag)
256
+ writer.newline()
257
+ for l in disassemble(instrs):
258
+ writer.write(l)
259
+ writer.newline()
260
+ writer.endtag(tag)
261
+ writer.newline()
262
+
263
+
264
+ def readcode(content):
265
+ res = []
266
+ for e in content_string(content).split("\n"):
267
+ e = e.strip()
268
+ if not len(e):
269
+ continue
270
+ res.append(e)
271
+ return assemble(res)
272
+
273
+
274
+ attrs_info = (
275
+ "flags",
276
+ "extraAscent",
277
+ "extraDescent",
278
+ "maxGlyphID",
279
+ "numLigComp",
280
+ "numUserDefn",
281
+ "maxCompPerLig",
282
+ "direction",
283
+ "lbGID",
284
+ )
285
+ attrs_passindexes = ("iSubst", "iPos", "iJust", "iBidi")
286
+ attrs_contexts = ("maxPreContext", "maxPostContext")
287
+ attrs_attributes = (
288
+ "attrPseudo",
289
+ "attrBreakWeight",
290
+ "attrDirectionality",
291
+ "attrMirroring",
292
+ "attrSkipPasses",
293
+ "attCollisions",
294
+ )
295
+ pass_attrs_info = (
296
+ "flags",
297
+ "maxRuleLoop",
298
+ "maxRuleContext",
299
+ "maxBackup",
300
+ "minRulePreContext",
301
+ "maxRulePreContext",
302
+ "collisionThreshold",
303
+ )
304
+ pass_attrs_fsm = ("numRows", "numTransitional", "numSuccess", "numColumns")
305
+
306
+
307
+ def writesimple(tag, self, writer, *attrkeys):
308
+ attrs = dict([(k, getattr(self, k)) for k in attrkeys])
309
+ writer.simpletag(tag, **attrs)
310
+ writer.newline()
311
+
312
+
313
+ def getSimple(self, attrs, *attr_list):
314
+ for k in attr_list:
315
+ if k in attrs:
316
+ setattr(self, k, int(safeEval(attrs[k])))
317
+
318
+
319
+ def content_string(contents):
320
+ res = ""
321
+ for element in contents:
322
+ if isinstance(element, tuple):
323
+ continue
324
+ res += element
325
+ return res.strip()
326
+
327
+
328
+ def wrapline(writer, dat, length=80):
329
+ currline = ""
330
+ for d in dat:
331
+ if len(currline) > length:
332
+ writer.write(currline[:-1])
333
+ writer.newline()
334
+ currline = ""
335
+ currline += d + " "
336
+ if len(currline):
337
+ writer.write(currline[:-1])
338
+ writer.newline()
339
+
340
+
341
+ class _Object:
342
+ pass
343
+
344
+
345
+ class table_S__i_l_f(DefaultTable.DefaultTable):
346
+ """Graphite Rules table
347
+
348
+ See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
349
+ """
350
+
351
+ def __init__(self, tag=None):
352
+ DefaultTable.DefaultTable.__init__(self, tag)
353
+ self.silfs = []
354
+
355
+ def decompile(self, data, ttFont):
356
+ sstruct.unpack2(Silf_hdr_format, data, self)
357
+ self.version = float(floatToFixedToStr(self.version, precisionBits=16))
358
+ if self.version >= 5.0:
359
+ (data, self.scheme) = grUtils.decompress(data)
360
+ sstruct.unpack2(Silf_hdr_format_3, data, self)
361
+ base = sstruct.calcsize(Silf_hdr_format_3)
362
+ elif self.version < 3.0:
363
+ self.numSilf = struct.unpack(">H", data[4:6])
364
+ self.scheme = 0
365
+ self.compilerVersion = 0
366
+ base = 8
367
+ else:
368
+ self.scheme = 0
369
+ sstruct.unpack2(Silf_hdr_format_3, data, self)
370
+ base = sstruct.calcsize(Silf_hdr_format_3)
371
+
372
+ silfoffsets = struct.unpack_from((">%dL" % self.numSilf), data[base:])
373
+ for offset in silfoffsets:
374
+ s = Silf()
375
+ self.silfs.append(s)
376
+ s.decompile(data[offset:], ttFont, self.version)
377
+
378
+ def compile(self, ttFont):
379
+ self.numSilf = len(self.silfs)
380
+ if self.version < 3.0:
381
+ hdr = sstruct.pack(Silf_hdr_format, self)
382
+ hdr += struct.pack(">HH", self.numSilf, 0)
383
+ else:
384
+ hdr = sstruct.pack(Silf_hdr_format_3, self)
385
+ offset = len(hdr) + 4 * self.numSilf
386
+ data = b""
387
+ for s in self.silfs:
388
+ hdr += struct.pack(">L", offset)
389
+ subdata = s.compile(ttFont, self.version)
390
+ offset += len(subdata)
391
+ data += subdata
392
+ if self.version >= 5.0:
393
+ return grUtils.compress(self.scheme, hdr + data)
394
+ return hdr + data
395
+
396
+ def toXML(self, writer, ttFont):
397
+ writer.comment("Attributes starting with _ are informative only")
398
+ writer.newline()
399
+ writer.simpletag(
400
+ "version",
401
+ version=self.version,
402
+ compilerVersion=self.compilerVersion,
403
+ compressionScheme=self.scheme,
404
+ )
405
+ writer.newline()
406
+ for s in self.silfs:
407
+ writer.begintag("silf")
408
+ writer.newline()
409
+ s.toXML(writer, ttFont, self.version)
410
+ writer.endtag("silf")
411
+ writer.newline()
412
+
413
+ def fromXML(self, name, attrs, content, ttFont):
414
+ if name == "version":
415
+ self.scheme = int(safeEval(attrs["compressionScheme"]))
416
+ self.version = float(safeEval(attrs["version"]))
417
+ self.compilerVersion = int(safeEval(attrs["compilerVersion"]))
418
+ return
419
+ if name == "silf":
420
+ s = Silf()
421
+ self.silfs.append(s)
422
+ for element in content:
423
+ if not isinstance(element, tuple):
424
+ continue
425
+ tag, attrs, subcontent = element
426
+ s.fromXML(tag, attrs, subcontent, ttFont, self.version)
427
+
428
+
429
+ class Silf(object):
430
+ """A particular Silf subtable"""
431
+
432
+ def __init__(self):
433
+ self.passes = []
434
+ self.scriptTags = []
435
+ self.critFeatures = []
436
+ self.jLevels = []
437
+ self.pMap = {}
438
+
439
+ def decompile(self, data, ttFont, version=2.0):
440
+ if version >= 3.0:
441
+ _, data = sstruct.unpack2(Silf_part1_format_v3, data, self)
442
+ self.ruleVersion = float(
443
+ floatToFixedToStr(self.ruleVersion, precisionBits=16)
444
+ )
445
+ _, data = sstruct.unpack2(Silf_part1_format, data, self)
446
+ for jlevel in range(self.numJLevels):
447
+ j, data = sstruct.unpack2(Silf_justify_format, data, _Object())
448
+ self.jLevels.append(j)
449
+ _, data = sstruct.unpack2(Silf_part2_format, data, self)
450
+ if self.numCritFeatures:
451
+ self.critFeatures = struct.unpack_from(
452
+ (">%dH" % self.numCritFeatures), data
453
+ )
454
+ data = data[self.numCritFeatures * 2 + 1 :]
455
+ (numScriptTag,) = struct.unpack_from("B", data)
456
+ if numScriptTag:
457
+ self.scriptTags = [
458
+ struct.unpack("4s", data[x : x + 4])[0].decode("ascii")
459
+ for x in range(1, 1 + 4 * numScriptTag, 4)
460
+ ]
461
+ data = data[1 + 4 * numScriptTag :]
462
+ (self.lbGID,) = struct.unpack(">H", data[:2])
463
+ if self.numPasses:
464
+ self.oPasses = struct.unpack(
465
+ (">%dL" % (self.numPasses + 1)), data[2 : 6 + 4 * self.numPasses]
466
+ )
467
+ data = data[6 + 4 * self.numPasses :]
468
+ (numPseudo,) = struct.unpack(">H", data[:2])
469
+ for i in range(numPseudo):
470
+ if version >= 3.0:
471
+ pseudo = sstruct.unpack(
472
+ Silf_pseudomap_format, data[8 + 6 * i : 14 + 6 * i], _Object()
473
+ )
474
+ else:
475
+ pseudo = sstruct.unpack(
476
+ Silf_pseudomap_format_h, data[8 + 4 * i : 12 + 4 * i], _Object()
477
+ )
478
+ self.pMap[pseudo.unicode] = ttFont.getGlyphName(pseudo.nPseudo)
479
+ data = data[8 + 6 * numPseudo :]
480
+ currpos = (
481
+ sstruct.calcsize(Silf_part1_format)
482
+ + sstruct.calcsize(Silf_justify_format) * self.numJLevels
483
+ + sstruct.calcsize(Silf_part2_format)
484
+ + 2 * self.numCritFeatures
485
+ + 1
486
+ + 1
487
+ + 4 * numScriptTag
488
+ + 6
489
+ + 4 * self.numPasses
490
+ + 8
491
+ + 6 * numPseudo
492
+ )
493
+ if version >= 3.0:
494
+ currpos += sstruct.calcsize(Silf_part1_format_v3)
495
+ self.classes = Classes()
496
+ self.classes.decompile(data, ttFont, version)
497
+ for i in range(self.numPasses):
498
+ p = Pass()
499
+ self.passes.append(p)
500
+ p.decompile(
501
+ data[self.oPasses[i] - currpos : self.oPasses[i + 1] - currpos],
502
+ ttFont,
503
+ version,
504
+ )
505
+
506
+ def compile(self, ttFont, version=2.0):
507
+ self.numPasses = len(self.passes)
508
+ self.numJLevels = len(self.jLevels)
509
+ self.numCritFeatures = len(self.critFeatures)
510
+ numPseudo = len(self.pMap)
511
+ data = b""
512
+ if version >= 3.0:
513
+ hdroffset = sstruct.calcsize(Silf_part1_format_v3)
514
+ else:
515
+ hdroffset = 0
516
+ data += sstruct.pack(Silf_part1_format, self)
517
+ for j in self.jLevels:
518
+ data += sstruct.pack(Silf_justify_format, j)
519
+ data += sstruct.pack(Silf_part2_format, self)
520
+ if self.numCritFeatures:
521
+ data += struct.pack((">%dH" % self.numCritFeaturs), *self.critFeatures)
522
+ data += struct.pack("BB", 0, len(self.scriptTags))
523
+ if len(self.scriptTags):
524
+ tdata = [struct.pack("4s", x.encode("ascii")) for x in self.scriptTags]
525
+ data += b"".join(tdata)
526
+ data += struct.pack(">H", self.lbGID)
527
+ self.passOffset = len(data)
528
+
529
+ data1 = grUtils.bininfo(numPseudo, 6)
530
+ currpos = hdroffset + len(data) + 4 * (self.numPasses + 1)
531
+ self.pseudosOffset = currpos + len(data1)
532
+ for u, p in sorted(self.pMap.items()):
533
+ data1 += struct.pack(
534
+ (">LH" if version >= 3.0 else ">HH"), u, ttFont.getGlyphID(p)
535
+ )
536
+ data1 += self.classes.compile(ttFont, version)
537
+ currpos += len(data1)
538
+ data2 = b""
539
+ datao = b""
540
+ for i, p in enumerate(self.passes):
541
+ base = currpos + len(data2)
542
+ datao += struct.pack(">L", base)
543
+ data2 += p.compile(ttFont, base, version)
544
+ datao += struct.pack(">L", currpos + len(data2))
545
+
546
+ if version >= 3.0:
547
+ data3 = sstruct.pack(Silf_part1_format_v3, self)
548
+ else:
549
+ data3 = b""
550
+ return data3 + data + datao + data1 + data2
551
+
552
+ def toXML(self, writer, ttFont, version=2.0):
553
+ if version >= 3.0:
554
+ writer.simpletag("version", ruleVersion=self.ruleVersion)
555
+ writer.newline()
556
+ writesimple("info", self, writer, *attrs_info)
557
+ writesimple("passindexes", self, writer, *attrs_passindexes)
558
+ writesimple("contexts", self, writer, *attrs_contexts)
559
+ writesimple("attributes", self, writer, *attrs_attributes)
560
+ if len(self.jLevels):
561
+ writer.begintag("justifications")
562
+ writer.newline()
563
+ jformat, jnames, jfixes = sstruct.getformat(Silf_justify_format)
564
+ for i, j in enumerate(self.jLevels):
565
+ attrs = dict([(k, getattr(j, k)) for k in jnames])
566
+ writer.simpletag("justify", **attrs)
567
+ writer.newline()
568
+ writer.endtag("justifications")
569
+ writer.newline()
570
+ if len(self.critFeatures):
571
+ writer.begintag("critFeatures")
572
+ writer.newline()
573
+ writer.write(" ".join(map(str, self.critFeatures)))
574
+ writer.newline()
575
+ writer.endtag("critFeatures")
576
+ writer.newline()
577
+ if len(self.scriptTags):
578
+ writer.begintag("scriptTags")
579
+ writer.newline()
580
+ writer.write(" ".join(self.scriptTags))
581
+ writer.newline()
582
+ writer.endtag("scriptTags")
583
+ writer.newline()
584
+ if self.pMap:
585
+ writer.begintag("pseudoMap")
586
+ writer.newline()
587
+ for k, v in sorted(self.pMap.items()):
588
+ writer.simpletag("pseudo", unicode=hex(k), pseudo=v)
589
+ writer.newline()
590
+ writer.endtag("pseudoMap")
591
+ writer.newline()
592
+ self.classes.toXML(writer, ttFont, version)
593
+ if len(self.passes):
594
+ writer.begintag("passes")
595
+ writer.newline()
596
+ for i, p in enumerate(self.passes):
597
+ writer.begintag("pass", _index=i)
598
+ writer.newline()
599
+ p.toXML(writer, ttFont, version)
600
+ writer.endtag("pass")
601
+ writer.newline()
602
+ writer.endtag("passes")
603
+ writer.newline()
604
+
605
+ def fromXML(self, name, attrs, content, ttFont, version=2.0):
606
+ if name == "version":
607
+ self.ruleVersion = float(safeEval(attrs.get("ruleVersion", "0")))
608
+ if name == "info":
609
+ getSimple(self, attrs, *attrs_info)
610
+ elif name == "passindexes":
611
+ getSimple(self, attrs, *attrs_passindexes)
612
+ elif name == "contexts":
613
+ getSimple(self, attrs, *attrs_contexts)
614
+ elif name == "attributes":
615
+ getSimple(self, attrs, *attrs_attributes)
616
+ elif name == "justifications":
617
+ for element in content:
618
+ if not isinstance(element, tuple):
619
+ continue
620
+ (tag, attrs, subcontent) = element
621
+ if tag == "justify":
622
+ j = _Object()
623
+ for k, v in attrs.items():
624
+ setattr(j, k, int(v))
625
+ self.jLevels.append(j)
626
+ elif name == "critFeatures":
627
+ self.critFeatures = []
628
+ element = content_string(content)
629
+ self.critFeatures.extend(map(int, element.split()))
630
+ elif name == "scriptTags":
631
+ self.scriptTags = []
632
+ element = content_string(content)
633
+ for n in element.split():
634
+ self.scriptTags.append(n)
635
+ elif name == "pseudoMap":
636
+ self.pMap = {}
637
+ for element in content:
638
+ if not isinstance(element, tuple):
639
+ continue
640
+ (tag, attrs, subcontent) = element
641
+ if tag == "pseudo":
642
+ k = int(attrs["unicode"], 16)
643
+ v = attrs["pseudo"]
644
+ self.pMap[k] = v
645
+ elif name == "classes":
646
+ self.classes = Classes()
647
+ for element in content:
648
+ if not isinstance(element, tuple):
649
+ continue
650
+ tag, attrs, subcontent = element
651
+ self.classes.fromXML(tag, attrs, subcontent, ttFont, version)
652
+ elif name == "passes":
653
+ for element in content:
654
+ if not isinstance(element, tuple):
655
+ continue
656
+ tag, attrs, subcontent = element
657
+ if tag == "pass":
658
+ p = Pass()
659
+ for e in subcontent:
660
+ if not isinstance(e, tuple):
661
+ continue
662
+ p.fromXML(e[0], e[1], e[2], ttFont, version)
663
+ self.passes.append(p)
664
+
665
+
666
+ class Classes(object):
667
+ def __init__(self):
668
+ self.linear = []
669
+ self.nonLinear = []
670
+
671
+ def decompile(self, data, ttFont, version=2.0):
672
+ sstruct.unpack2(Silf_classmap_format, data, self)
673
+ if version >= 4.0:
674
+ oClasses = struct.unpack(
675
+ (">%dL" % (self.numClass + 1)), data[4 : 8 + 4 * self.numClass]
676
+ )
677
+ else:
678
+ oClasses = struct.unpack(
679
+ (">%dH" % (self.numClass + 1)), data[4 : 6 + 2 * self.numClass]
680
+ )
681
+ for s, e in zip(oClasses[: self.numLinear], oClasses[1 : self.numLinear + 1]):
682
+ self.linear.append(
683
+ ttFont.getGlyphName(x)
684
+ for x in struct.unpack((">%dH" % ((e - s) / 2)), data[s:e])
685
+ )
686
+ for s, e in zip(
687
+ oClasses[self.numLinear : self.numClass],
688
+ oClasses[self.numLinear + 1 : self.numClass + 1],
689
+ ):
690
+ nonLinids = [
691
+ struct.unpack(">HH", data[x : x + 4]) for x in range(s + 8, e, 4)
692
+ ]
693
+ nonLin = dict([(ttFont.getGlyphName(x[0]), x[1]) for x in nonLinids])
694
+ self.nonLinear.append(nonLin)
695
+
696
+ def compile(self, ttFont, version=2.0):
697
+ data = b""
698
+ oClasses = []
699
+ if version >= 4.0:
700
+ offset = 8 + 4 * (len(self.linear) + len(self.nonLinear))
701
+ else:
702
+ offset = 6 + 2 * (len(self.linear) + len(self.nonLinear))
703
+ for l in self.linear:
704
+ oClasses.append(len(data) + offset)
705
+ gs = [ttFont.getGlyphID(x) for x in l]
706
+ data += struct.pack((">%dH" % len(l)), *gs)
707
+ for l in self.nonLinear:
708
+ oClasses.append(len(data) + offset)
709
+ gs = [(ttFont.getGlyphID(x[0]), x[1]) for x in l.items()]
710
+ data += grUtils.bininfo(len(gs))
711
+ data += b"".join([struct.pack(">HH", *x) for x in sorted(gs)])
712
+ oClasses.append(len(data) + offset)
713
+ self.numClass = len(oClasses) - 1
714
+ self.numLinear = len(self.linear)
715
+ return (
716
+ sstruct.pack(Silf_classmap_format, self)
717
+ + struct.pack(
718
+ ((">%dL" if version >= 4.0 else ">%dH") % len(oClasses)), *oClasses
719
+ )
720
+ + data
721
+ )
722
+
723
+ def toXML(self, writer, ttFont, version=2.0):
724
+ writer.begintag("classes")
725
+ writer.newline()
726
+ writer.begintag("linearClasses")
727
+ writer.newline()
728
+ for i, l in enumerate(self.linear):
729
+ writer.begintag("linear", _index=i)
730
+ writer.newline()
731
+ wrapline(writer, l)
732
+ writer.endtag("linear")
733
+ writer.newline()
734
+ writer.endtag("linearClasses")
735
+ writer.newline()
736
+ writer.begintag("nonLinearClasses")
737
+ writer.newline()
738
+ for i, l in enumerate(self.nonLinear):
739
+ writer.begintag("nonLinear", _index=i + self.numLinear)
740
+ writer.newline()
741
+ for inp, ind in l.items():
742
+ writer.simpletag("map", glyph=inp, index=ind)
743
+ writer.newline()
744
+ writer.endtag("nonLinear")
745
+ writer.newline()
746
+ writer.endtag("nonLinearClasses")
747
+ writer.newline()
748
+ writer.endtag("classes")
749
+ writer.newline()
750
+
751
+ def fromXML(self, name, attrs, content, ttFont, version=2.0):
752
+ if name == "linearClasses":
753
+ for element in content:
754
+ if not isinstance(element, tuple):
755
+ continue
756
+ tag, attrs, subcontent = element
757
+ if tag == "linear":
758
+ l = content_string(subcontent).split()
759
+ self.linear.append(l)
760
+ elif name == "nonLinearClasses":
761
+ for element in content:
762
+ if not isinstance(element, tuple):
763
+ continue
764
+ tag, attrs, subcontent = element
765
+ if tag == "nonLinear":
766
+ l = {}
767
+ for e in subcontent:
768
+ if not isinstance(e, tuple):
769
+ continue
770
+ tag, attrs, subsubcontent = e
771
+ if tag == "map":
772
+ l[attrs["glyph"]] = int(safeEval(attrs["index"]))
773
+ self.nonLinear.append(l)
774
+
775
+
776
+ class Pass(object):
777
+ def __init__(self):
778
+ self.colMap = {}
779
+ self.rules = []
780
+ self.rulePreContexts = []
781
+ self.ruleSortKeys = []
782
+ self.ruleConstraints = []
783
+ self.passConstraints = b""
784
+ self.actions = []
785
+ self.stateTrans = []
786
+ self.startStates = []
787
+
788
+ def decompile(self, data, ttFont, version=2.0):
789
+ _, data = sstruct.unpack2(Silf_pass_format, data, self)
790
+ (numRange, _, _, _) = struct.unpack(">4H", data[:8])
791
+ data = data[8:]
792
+ for i in range(numRange):
793
+ (first, last, col) = struct.unpack(">3H", data[6 * i : 6 * i + 6])
794
+ for g in range(first, last + 1):
795
+ self.colMap[ttFont.getGlyphName(g)] = col
796
+ data = data[6 * numRange :]
797
+ oRuleMap = struct.unpack_from((">%dH" % (self.numSuccess + 1)), data)
798
+ data = data[2 + 2 * self.numSuccess :]
799
+ rules = struct.unpack_from((">%dH" % oRuleMap[-1]), data)
800
+ self.rules = [rules[s:e] for (s, e) in zip(oRuleMap, oRuleMap[1:])]
801
+ data = data[2 * oRuleMap[-1] :]
802
+ (self.minRulePreContext, self.maxRulePreContext) = struct.unpack("BB", data[:2])
803
+ numStartStates = self.maxRulePreContext - self.minRulePreContext + 1
804
+ self.startStates = struct.unpack(
805
+ (">%dH" % numStartStates), data[2 : 2 + numStartStates * 2]
806
+ )
807
+ data = data[2 + numStartStates * 2 :]
808
+ self.ruleSortKeys = struct.unpack(
809
+ (">%dH" % self.numRules), data[: 2 * self.numRules]
810
+ )
811
+ data = data[2 * self.numRules :]
812
+ self.rulePreContexts = struct.unpack(
813
+ ("%dB" % self.numRules), data[: self.numRules]
814
+ )
815
+ data = data[self.numRules :]
816
+ (self.collisionThreshold, pConstraint) = struct.unpack(">BH", data[:3])
817
+ oConstraints = list(
818
+ struct.unpack(
819
+ (">%dH" % (self.numRules + 1)), data[3 : 5 + self.numRules * 2]
820
+ )
821
+ )
822
+ data = data[5 + self.numRules * 2 :]
823
+ oActions = list(
824
+ struct.unpack((">%dH" % (self.numRules + 1)), data[: 2 + self.numRules * 2])
825
+ )
826
+ data = data[2 * self.numRules + 2 :]
827
+ for i in range(self.numTransitional):
828
+ a = array(
829
+ "H", data[i * self.numColumns * 2 : (i + 1) * self.numColumns * 2]
830
+ )
831
+ if sys.byteorder != "big":
832
+ a.byteswap()
833
+ self.stateTrans.append(a)
834
+ data = data[self.numTransitional * self.numColumns * 2 + 1 :]
835
+ self.passConstraints = data[:pConstraint]
836
+ data = data[pConstraint:]
837
+ for i in range(len(oConstraints) - 2, -1, -1):
838
+ if oConstraints[i] == 0:
839
+ oConstraints[i] = oConstraints[i + 1]
840
+ self.ruleConstraints = [
841
+ (data[s:e] if (e - s > 1) else b"")
842
+ for (s, e) in zip(oConstraints, oConstraints[1:])
843
+ ]
844
+ data = data[oConstraints[-1] :]
845
+ self.actions = [
846
+ (data[s:e] if (e - s > 1) else "") for (s, e) in zip(oActions, oActions[1:])
847
+ ]
848
+ data = data[oActions[-1] :]
849
+ # not using debug
850
+
851
+ def compile(self, ttFont, base, version=2.0):
852
+ # build it all up backwards
853
+ oActions = reduce(
854
+ lambda a, x: (a[0] + len(x), a[1] + [a[0]]), self.actions + [b""], (0, [])
855
+ )[1]
856
+ oConstraints = reduce(
857
+ lambda a, x: (a[0] + len(x), a[1] + [a[0]]),
858
+ self.ruleConstraints + [b""],
859
+ (1, []),
860
+ )[1]
861
+ constraintCode = b"\000" + b"".join(self.ruleConstraints)
862
+ transes = []
863
+ for t in self.stateTrans:
864
+ if sys.byteorder != "big":
865
+ t.byteswap()
866
+ transes.append(t.tobytes())
867
+ if sys.byteorder != "big":
868
+ t.byteswap()
869
+ if not len(transes):
870
+ self.startStates = [0]
871
+ oRuleMap = reduce(
872
+ lambda a, x: (a[0] + len(x), a[1] + [a[0]]), self.rules + [[]], (0, [])
873
+ )[1]
874
+ passRanges = []
875
+ gidcolmap = dict([(ttFont.getGlyphID(x[0]), x[1]) for x in self.colMap.items()])
876
+ for e in grUtils.entries(gidcolmap, sameval=True):
877
+ if e[1]:
878
+ passRanges.append((e[0], e[0] + e[1] - 1, e[2][0]))
879
+ self.numRules = len(self.actions)
880
+ self.fsmOffset = (
881
+ sstruct.calcsize(Silf_pass_format)
882
+ + 8
883
+ + len(passRanges) * 6
884
+ + len(oRuleMap) * 2
885
+ + 2 * oRuleMap[-1]
886
+ + 2
887
+ + 2 * len(self.startStates)
888
+ + 3 * self.numRules
889
+ + 3
890
+ + 4 * self.numRules
891
+ + 4
892
+ )
893
+ self.pcCode = (
894
+ self.fsmOffset + 2 * self.numTransitional * self.numColumns + 1 + base
895
+ )
896
+ self.rcCode = self.pcCode + len(self.passConstraints)
897
+ self.aCode = self.rcCode + len(constraintCode)
898
+ self.oDebug = 0
899
+ # now generate output
900
+ data = sstruct.pack(Silf_pass_format, self)
901
+ data += grUtils.bininfo(len(passRanges), 6)
902
+ data += b"".join(struct.pack(">3H", *p) for p in passRanges)
903
+ data += struct.pack((">%dH" % len(oRuleMap)), *oRuleMap)
904
+ flatrules = reduce(lambda a, x: a + x, self.rules, [])
905
+ data += struct.pack((">%dH" % oRuleMap[-1]), *flatrules)
906
+ data += struct.pack("BB", self.minRulePreContext, self.maxRulePreContext)
907
+ data += struct.pack((">%dH" % len(self.startStates)), *self.startStates)
908
+ data += struct.pack((">%dH" % self.numRules), *self.ruleSortKeys)
909
+ data += struct.pack(("%dB" % self.numRules), *self.rulePreContexts)
910
+ data += struct.pack(">BH", self.collisionThreshold, len(self.passConstraints))
911
+ data += struct.pack((">%dH" % (self.numRules + 1)), *oConstraints)
912
+ data += struct.pack((">%dH" % (self.numRules + 1)), *oActions)
913
+ return (
914
+ data
915
+ + b"".join(transes)
916
+ + struct.pack("B", 0)
917
+ + self.passConstraints
918
+ + constraintCode
919
+ + b"".join(self.actions)
920
+ )
921
+
922
+ def toXML(self, writer, ttFont, version=2.0):
923
+ writesimple("info", self, writer, *pass_attrs_info)
924
+ writesimple("fsminfo", self, writer, *pass_attrs_fsm)
925
+ writer.begintag("colmap")
926
+ writer.newline()
927
+ wrapline(
928
+ writer,
929
+ [
930
+ "{}={}".format(*x)
931
+ for x in sorted(
932
+ self.colMap.items(), key=lambda x: ttFont.getGlyphID(x[0])
933
+ )
934
+ ],
935
+ )
936
+ writer.endtag("colmap")
937
+ writer.newline()
938
+ writer.begintag("staterulemap")
939
+ writer.newline()
940
+ for i, r in enumerate(self.rules):
941
+ writer.simpletag(
942
+ "state",
943
+ number=self.numRows - self.numSuccess + i,
944
+ rules=" ".join(map(str, r)),
945
+ )
946
+ writer.newline()
947
+ writer.endtag("staterulemap")
948
+ writer.newline()
949
+ writer.begintag("rules")
950
+ writer.newline()
951
+ for i, action in enumerate(self.actions):
952
+ writer.begintag(
953
+ "rule",
954
+ index=i,
955
+ precontext=self.rulePreContexts[i],
956
+ sortkey=self.ruleSortKeys[i],
957
+ )
958
+ writer.newline()
959
+ if len(self.ruleConstraints[i]):
960
+ writecode("constraint", writer, self.ruleConstraints[i])
961
+ writecode("action", writer, action)
962
+ writer.endtag("rule")
963
+ writer.newline()
964
+ writer.endtag("rules")
965
+ writer.newline()
966
+ if len(self.passConstraints):
967
+ writecode("passConstraint", writer, self.passConstraints)
968
+ if len(self.stateTrans):
969
+ writer.begintag("fsm")
970
+ writer.newline()
971
+ writer.begintag("starts")
972
+ writer.write(" ".join(map(str, self.startStates)))
973
+ writer.endtag("starts")
974
+ writer.newline()
975
+ for i, s in enumerate(self.stateTrans):
976
+ writer.begintag("row", _i=i)
977
+ # no newlines here
978
+ writer.write(" ".join(map(str, s)))
979
+ writer.endtag("row")
980
+ writer.newline()
981
+ writer.endtag("fsm")
982
+ writer.newline()
983
+
984
+ def fromXML(self, name, attrs, content, ttFont, version=2.0):
985
+ if name == "info":
986
+ getSimple(self, attrs, *pass_attrs_info)
987
+ elif name == "fsminfo":
988
+ getSimple(self, attrs, *pass_attrs_fsm)
989
+ elif name == "colmap":
990
+ e = content_string(content)
991
+ for w in e.split():
992
+ x = w.split("=")
993
+ if len(x) != 2 or x[0] == "" or x[1] == "":
994
+ continue
995
+ self.colMap[x[0]] = int(x[1])
996
+ elif name == "staterulemap":
997
+ for e in content:
998
+ if not isinstance(e, tuple):
999
+ continue
1000
+ tag, a, c = e
1001
+ if tag == "state":
1002
+ self.rules.append([int(x) for x in a["rules"].split(" ")])
1003
+ elif name == "rules":
1004
+ for element in content:
1005
+ if not isinstance(element, tuple):
1006
+ continue
1007
+ tag, a, c = element
1008
+ if tag != "rule":
1009
+ continue
1010
+ self.rulePreContexts.append(int(a["precontext"]))
1011
+ self.ruleSortKeys.append(int(a["sortkey"]))
1012
+ con = b""
1013
+ act = b""
1014
+ for e in c:
1015
+ if not isinstance(e, tuple):
1016
+ continue
1017
+ tag, a, subc = e
1018
+ if tag == "constraint":
1019
+ con = readcode(subc)
1020
+ elif tag == "action":
1021
+ act = readcode(subc)
1022
+ self.actions.append(act)
1023
+ self.ruleConstraints.append(con)
1024
+ elif name == "passConstraint":
1025
+ self.passConstraints = readcode(content)
1026
+ elif name == "fsm":
1027
+ for element in content:
1028
+ if not isinstance(element, tuple):
1029
+ continue
1030
+ tag, a, c = element
1031
+ if tag == "row":
1032
+ s = array("H")
1033
+ e = content_string(c)
1034
+ s.extend(map(int, e.split()))
1035
+ self.stateTrans.append(s)
1036
+ elif tag == "starts":
1037
+ s = []
1038
+ e = content_string(c)
1039
+ s.extend(map(int, e.split()))
1040
+ self.startStates = s
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S__i_l_l.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc import sstruct
2
+ from fontTools.misc.fixedTools import floatToFixedToStr
3
+ from fontTools.misc.textTools import safeEval
4
+ from . import DefaultTable
5
+ from . import grUtils
6
+ import struct
7
+
8
+ Sill_hdr = """
9
+ >
10
+ version: 16.16F
11
+ """
12
+
13
+
14
+ class table_S__i_l_l(DefaultTable.DefaultTable):
15
+ """Graphite Languages table
16
+
17
+ See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
18
+ """
19
+
20
+ def __init__(self, tag=None):
21
+ DefaultTable.DefaultTable.__init__(self, tag)
22
+ self.langs = {}
23
+
24
+ def decompile(self, data, ttFont):
25
+ (_, data) = sstruct.unpack2(Sill_hdr, data, self)
26
+ self.version = float(floatToFixedToStr(self.version, precisionBits=16))
27
+ (numLangs,) = struct.unpack(">H", data[:2])
28
+ data = data[8:]
29
+ maxsetting = 0
30
+ langinfo = []
31
+ for i in range(numLangs):
32
+ (langcode, numsettings, offset) = struct.unpack(
33
+ ">4sHH", data[i * 8 : (i + 1) * 8]
34
+ )
35
+ offset = int(offset / 8) - (numLangs + 1)
36
+ langcode = langcode.replace(b"\000", b"")
37
+ langinfo.append((langcode.decode("utf-8"), numsettings, offset))
38
+ maxsetting = max(maxsetting, offset + numsettings)
39
+ data = data[numLangs * 8 :]
40
+ finfo = []
41
+ for i in range(maxsetting):
42
+ (fid, val, _) = struct.unpack(">LHH", data[i * 8 : (i + 1) * 8])
43
+ finfo.append((fid, val))
44
+ self.langs = {}
45
+ for c, n, o in langinfo:
46
+ self.langs[c] = []
47
+ for i in range(o, o + n):
48
+ self.langs[c].append(finfo[i])
49
+
50
+ def compile(self, ttFont):
51
+ ldat = b""
52
+ fdat = b""
53
+ offset = len(self.langs)
54
+ for c, inf in sorted(self.langs.items()):
55
+ ldat += struct.pack(">4sHH", c.encode("utf8"), len(inf), 8 * offset + 20)
56
+ for fid, val in inf:
57
+ fdat += struct.pack(">LHH", fid, val, 0)
58
+ offset += len(inf)
59
+ ldat += struct.pack(">LHH", 0x80808080, 0, 8 * offset + 20)
60
+ return (
61
+ sstruct.pack(Sill_hdr, self)
62
+ + grUtils.bininfo(len(self.langs))
63
+ + ldat
64
+ + fdat
65
+ )
66
+
67
+ def toXML(self, writer, ttFont):
68
+ writer.simpletag("version", version=self.version)
69
+ writer.newline()
70
+ for c, inf in sorted(self.langs.items()):
71
+ writer.begintag("lang", name=c)
72
+ writer.newline()
73
+ for fid, val in inf:
74
+ writer.simpletag("feature", fid=grUtils.num2tag(fid), val=val)
75
+ writer.newline()
76
+ writer.endtag("lang")
77
+ writer.newline()
78
+
79
+ def fromXML(self, name, attrs, content, ttFont):
80
+ if name == "version":
81
+ self.version = float(safeEval(attrs["version"]))
82
+ elif name == "lang":
83
+ c = attrs["name"]
84
+ self.langs[c] = []
85
+ for element in content:
86
+ if not isinstance(element, tuple):
87
+ continue
88
+ tag, a, subcontent = element
89
+ if tag == "feature":
90
+ self.langs[c].append(
91
+ (grUtils.tag2num(a["fid"]), int(safeEval(a["val"])))
92
+ )
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_B_.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
2
+ tool to store its table source data.
3
+
4
+ TSIB contains the source text for the ``BASE`` table.
5
+
6
+ See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
7
+ """
8
+
9
+ from .T_S_I_V_ import table_T_S_I_V_
10
+
11
+
12
+ class table_T_S_I_B_(table_T_S_I_V_):
13
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_C_.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
2
+ tool to store its table source data.
3
+
4
+ TSIC contains the source text for the Variation CVT window and data for
5
+ the ``cvar`` table.
6
+
7
+ See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
8
+ """
9
+
10
+ from .otBase import BaseTTXConverter
11
+
12
+
13
+ class table_T_S_I_C_(BaseTTXConverter):
14
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_D_.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
2
+ tool to store its table source data.
3
+
4
+ TSID contains the source text for the ``GDEF`` table.
5
+
6
+ See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
7
+ """
8
+
9
+ from .T_S_I_V_ import table_T_S_I_V_
10
+
11
+
12
+ class table_T_S_I_D_(table_T_S_I_V_):
13
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_J_.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
2
+ tool to store its table source data.
3
+
4
+ TSIJ contains the source text for the ``JSTF`` table.
5
+
6
+ See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
7
+ """
8
+
9
+ from .T_S_I_V_ import table_T_S_I_V_
10
+
11
+
12
+ class table_T_S_I_J_(table_T_S_I_V_):
13
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_P_.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
2
+ tool to store its table source data.
3
+
4
+ TSIP contains the source text for the ``GPOS`` table.
5
+
6
+ See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
7
+ """
8
+
9
+ from .T_S_I_V_ import table_T_S_I_V_
10
+
11
+
12
+ class table_T_S_I_P_(table_T_S_I_V_):
13
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_S_.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
2
+ tool to store its table source data.
3
+
4
+ TSIS contains the source text for the ``GSUB`` table.
5
+
6
+ See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
7
+ """
8
+
9
+ from .T_S_I_V_ import table_T_S_I_V_
10
+
11
+
12
+ class table_T_S_I_S_(table_T_S_I_V_):
13
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_V_.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
2
+ tool to store its table source data.
3
+
4
+ See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
5
+ """
6
+
7
+ from fontTools.misc.textTools import strjoin, tobytes, tostr
8
+ from . import asciiTable
9
+
10
+
11
+ class table_T_S_I_V_(asciiTable.asciiTable):
12
+ def toXML(self, writer, ttFont):
13
+ data = tostr(self.data)
14
+ # removing null bytes. XXX needed??
15
+ data = data.split("\0")
16
+ data = strjoin(data)
17
+ writer.begintag("source")
18
+ writer.newline()
19
+ writer.write_noindent(data.replace("\r", "\n"))
20
+ writer.newline()
21
+ writer.endtag("source")
22
+ writer.newline()
23
+
24
+ def fromXML(self, name, attrs, content, ttFont):
25
+ lines = strjoin(content).split("\n")
26
+ self.data = tobytes("\r".join(lines[1:-1]))
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__0.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
2
+ tool to store its hinting source data.
3
+
4
+ TSI0 is the index table containing the lengths and offsets for the glyph
5
+ programs and 'extra' programs ('fpgm', 'prep', and 'cvt') that are contained
6
+ in the TSI1 table.
7
+
8
+ See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
9
+ """
10
+
11
+ import logging
12
+ import struct
13
+
14
+ from . import DefaultTable
15
+
16
+ log = logging.getLogger(__name__)
17
+
18
+ tsi0Format = ">HHL"
19
+
20
+
21
+ def fixlongs(glyphID, textLength, textOffset):
22
+ return int(glyphID), int(textLength), textOffset
23
+
24
+
25
+ class table_T_S_I__0(DefaultTable.DefaultTable):
26
+ dependencies = ["TSI1"]
27
+
28
+ def decompile(self, data, ttFont):
29
+ numGlyphs = ttFont["maxp"].numGlyphs
30
+ indices = []
31
+ size = struct.calcsize(tsi0Format)
32
+ numEntries = len(data) // size
33
+ if numEntries != numGlyphs + 5:
34
+ diff = numEntries - numGlyphs - 5
35
+ log.warning(
36
+ "Number of glyphPrograms differs from the number of glyphs in the font "
37
+ f"by {abs(diff)} ({numEntries - 5} programs vs. {numGlyphs} glyphs)."
38
+ )
39
+ for _ in range(numEntries):
40
+ glyphID, textLength, textOffset = fixlongs(
41
+ *struct.unpack(tsi0Format, data[:size])
42
+ )
43
+ indices.append((glyphID, textLength, textOffset))
44
+ data = data[size:]
45
+ assert len(data) == 0
46
+ assert indices[-5] == (0xFFFE, 0, 0xABFC1F34), "bad magic number"
47
+ self.indices = indices[:-5]
48
+ self.extra_indices = indices[-4:]
49
+
50
+ def compile(self, ttFont):
51
+ if not hasattr(self, "indices"):
52
+ # We have no corresponding table (TSI1 or TSI3); let's return
53
+ # no data, which effectively means "ignore us".
54
+ return b""
55
+ data = b""
56
+ for index, textLength, textOffset in self.indices:
57
+ data = data + struct.pack(tsi0Format, index, textLength, textOffset)
58
+ data = data + struct.pack(tsi0Format, 0xFFFE, 0, 0xABFC1F34)
59
+ for index, textLength, textOffset in self.extra_indices:
60
+ data = data + struct.pack(tsi0Format, index, textLength, textOffset)
61
+ return data
62
+
63
+ def set(self, indices, extra_indices):
64
+ # gets called by 'TSI1' or 'TSI3'
65
+ self.indices = indices
66
+ self.extra_indices = extra_indices
67
+
68
+ def toXML(self, writer, ttFont):
69
+ writer.comment("This table will be calculated by the compiler")
70
+ writer.newline()
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__1.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
2
+ tool to store its hinting source data.
3
+
4
+ TSI1 contains the text of the glyph programs in the form of low-level assembly
5
+ code, as well as the 'extra' programs 'fpgm', 'ppgm' (i.e. 'prep'), and 'cvt'.
6
+
7
+ See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
8
+ """
9
+
10
+ from . import DefaultTable
11
+ from fontTools.misc.loggingTools import LogMixin
12
+ from fontTools.misc.textTools import strjoin, tobytes, tostr
13
+
14
+
15
+ class table_T_S_I__1(LogMixin, DefaultTable.DefaultTable):
16
+ extras = {0xFFFA: "ppgm", 0xFFFB: "cvt", 0xFFFC: "reserved", 0xFFFD: "fpgm"}
17
+
18
+ indextable = "TSI0"
19
+
20
+ def decompile(self, data, ttFont):
21
+ totalLength = len(data)
22
+ indextable = ttFont[self.indextable]
23
+ for indices, isExtra in zip(
24
+ (indextable.indices, indextable.extra_indices), (False, True)
25
+ ):
26
+ programs = {}
27
+ for i, (glyphID, textLength, textOffset) in enumerate(indices):
28
+ if isExtra:
29
+ name = self.extras[glyphID]
30
+ else:
31
+ name = ttFont.getGlyphName(glyphID)
32
+ if textOffset > totalLength:
33
+ self.log.warning("textOffset > totalLength; %r skipped" % name)
34
+ continue
35
+ if textLength < 0x8000:
36
+ # If the length stored in the record is less than 32768, then use
37
+ # that as the length of the record.
38
+ pass
39
+ elif textLength == 0x8000:
40
+ # If the length is 32768, compute the actual length as follows:
41
+ isLast = i == (len(indices) - 1)
42
+ if isLast:
43
+ if isExtra:
44
+ # For the last "extra" record (the very last record of the
45
+ # table), the length is the difference between the total
46
+ # length of the TSI1 table and the textOffset of the final
47
+ # record.
48
+ nextTextOffset = totalLength
49
+ else:
50
+ # For the last "normal" record (the last record just prior
51
+ # to the record containing the "magic number"), the length
52
+ # is the difference between the textOffset of the record
53
+ # following the "magic number" (0xFFFE) record (i.e. the
54
+ # first "extra" record), and the textOffset of the last
55
+ # "normal" record.
56
+ nextTextOffset = indextable.extra_indices[0][2]
57
+ else:
58
+ # For all other records with a length of 0x8000, the length is
59
+ # the difference between the textOffset of the record in
60
+ # question and the textOffset of the next record.
61
+ nextTextOffset = indices[i + 1][2]
62
+ assert nextTextOffset >= textOffset, "entries not sorted by offset"
63
+ if nextTextOffset > totalLength:
64
+ self.log.warning(
65
+ "nextTextOffset > totalLength; %r truncated" % name
66
+ )
67
+ nextTextOffset = totalLength
68
+ textLength = nextTextOffset - textOffset
69
+ else:
70
+ from fontTools import ttLib
71
+
72
+ raise ttLib.TTLibError(
73
+ "%r textLength (%d) must not be > 32768" % (name, textLength)
74
+ )
75
+ text = data[textOffset : textOffset + textLength]
76
+ assert len(text) == textLength
77
+ text = tostr(text, encoding="utf-8")
78
+ if text:
79
+ programs[name] = text
80
+ if isExtra:
81
+ self.extraPrograms = programs
82
+ else:
83
+ self.glyphPrograms = programs
84
+
85
+ def compile(self, ttFont):
86
+ if not hasattr(self, "glyphPrograms"):
87
+ self.glyphPrograms = {}
88
+ self.extraPrograms = {}
89
+ data = b""
90
+ indextable = ttFont[self.indextable]
91
+ glyphNames = ttFont.getGlyphOrder()
92
+
93
+ indices = []
94
+ for i, name in enumerate(glyphNames):
95
+ if len(data) % 2:
96
+ data = (
97
+ data + b"\015"
98
+ ) # align on 2-byte boundaries, fill with return chars. Yum.
99
+ if name in self.glyphPrograms:
100
+ text = tobytes(self.glyphPrograms[name], encoding="utf-8")
101
+ else:
102
+ text = b""
103
+ textLength = len(text)
104
+ if textLength >= 0x8000:
105
+ textLength = 0x8000
106
+ indices.append((i, textLength, len(data)))
107
+ data = data + text
108
+
109
+ extra_indices = []
110
+ for code, name in sorted(self.extras.items()):
111
+ if len(data) % 2:
112
+ data = (
113
+ data + b"\015"
114
+ ) # align on 2-byte boundaries, fill with return chars.
115
+ if name in self.extraPrograms:
116
+ text = tobytes(self.extraPrograms[name], encoding="utf-8")
117
+ else:
118
+ text = b""
119
+ textLength = len(text)
120
+ if textLength >= 0x8000:
121
+ textLength = 0x8000
122
+ extra_indices.append((code, textLength, len(data)))
123
+ data = data + text
124
+ indextable.set(indices, extra_indices)
125
+ return data
126
+
127
+ def toXML(self, writer, ttFont):
128
+ names = sorted(self.glyphPrograms.keys())
129
+ writer.newline()
130
+ for name in names:
131
+ text = self.glyphPrograms[name]
132
+ if not text:
133
+ continue
134
+ writer.begintag("glyphProgram", name=name)
135
+ writer.newline()
136
+ writer.write_noindent(text.replace("\r", "\n"))
137
+ writer.newline()
138
+ writer.endtag("glyphProgram")
139
+ writer.newline()
140
+ writer.newline()
141
+ extra_names = sorted(self.extraPrograms.keys())
142
+ for name in extra_names:
143
+ text = self.extraPrograms[name]
144
+ if not text:
145
+ continue
146
+ writer.begintag("extraProgram", name=name)
147
+ writer.newline()
148
+ writer.write_noindent(text.replace("\r", "\n"))
149
+ writer.newline()
150
+ writer.endtag("extraProgram")
151
+ writer.newline()
152
+ writer.newline()
153
+
154
+ def fromXML(self, name, attrs, content, ttFont):
155
+ if not hasattr(self, "glyphPrograms"):
156
+ self.glyphPrograms = {}
157
+ self.extraPrograms = {}
158
+ lines = strjoin(content).replace("\r", "\n").split("\n")
159
+ text = "\r".join(lines[1:-1])
160
+ if name == "glyphProgram":
161
+ self.glyphPrograms[attrs["name"]] = text
162
+ elif name == "extraProgram":
163
+ self.extraPrograms[attrs["name"]] = text
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__2.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
2
+ tool to store its hinting source data.
3
+
4
+ TSI2 is the index table containing the lengths and offsets for the glyph
5
+ programs that are contained in the TSI3 table. It uses the same format as
6
+ the TSI0 table.
7
+
8
+ See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
9
+ """
10
+
11
+ from fontTools import ttLib
12
+
13
+ superclass = ttLib.getTableClass("TSI0")
14
+
15
+
16
+ class table_T_S_I__2(superclass):
17
+ dependencies = ["TSI3"]
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__3.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
2
+ tool to store its hinting source data.
3
+
4
+ TSI3 contains the text of the glyph programs in the form of 'VTTTalk' code.
5
+
6
+ See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
7
+ """
8
+
9
+ from fontTools import ttLib
10
+
11
+ superclass = ttLib.getTableClass("TSI1")
12
+
13
+
14
+ class table_T_S_I__3(superclass):
15
+ extras = {
16
+ 0xFFFA: "reserved0",
17
+ 0xFFFB: "reserved1",
18
+ 0xFFFC: "reserved2",
19
+ 0xFFFD: "reserved3",
20
+ }
21
+
22
+ indextable = "TSI2"
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__5.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
2
+ tool to store its hinting source data.
3
+
4
+ TSI5 contains the VTT character groups.
5
+
6
+ See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
7
+ """
8
+
9
+ import array
10
+ import logging
11
+ import sys
12
+
13
+ from fontTools.misc.textTools import safeEval
14
+
15
+ from . import DefaultTable
16
+
17
+ log = logging.getLogger(__name__)
18
+
19
+
20
+ class table_T_S_I__5(DefaultTable.DefaultTable):
21
+ def decompile(self, data, ttFont):
22
+ numGlyphs = ttFont["maxp"].numGlyphs
23
+ a = array.array("H")
24
+ a.frombytes(data)
25
+ if sys.byteorder != "big":
26
+ a.byteswap()
27
+ self.glyphGrouping = {}
28
+ numEntries = len(data) // 2
29
+ if numEntries != numGlyphs:
30
+ diff = numEntries - numGlyphs
31
+ log.warning(
32
+ "Number of entries differs from the number of glyphs in the font "
33
+ f"by {abs(diff)} ({numEntries} entries vs. {numGlyphs} glyphs)."
34
+ )
35
+ for i in range(numEntries):
36
+ self.glyphGrouping[ttFont.getGlyphName(i)] = a[i]
37
+
38
+ def compile(self, ttFont):
39
+ glyphNames = ttFont.getGlyphOrder()
40
+ a = array.array("H")
41
+ for glyphName in glyphNames:
42
+ a.append(self.glyphGrouping.get(glyphName, 0))
43
+ if sys.byteorder != "big":
44
+ a.byteswap()
45
+ return a.tobytes()
46
+
47
+ def toXML(self, writer, ttFont):
48
+ names = sorted(self.glyphGrouping.keys())
49
+ for glyphName in names:
50
+ writer.simpletag(
51
+ "glyphgroup", name=glyphName, value=self.glyphGrouping[glyphName]
52
+ )
53
+ writer.newline()
54
+
55
+ def fromXML(self, name, attrs, content, ttFont):
56
+ if not hasattr(self, "glyphGrouping"):
57
+ self.glyphGrouping = {}
58
+ if name != "glyphgroup":
59
+ return
60
+ self.glyphGrouping[attrs["name"]] = safeEval(attrs["value"])
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_T_F_A_.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from . import asciiTable
2
+
3
+
4
+ class table_T_T_F_A_(asciiTable.asciiTable):
5
+ """ttfautohint parameters table
6
+
7
+ The ``TTFA`` table is used by the free-software `ttfautohint` program
8
+ to record the parameters that `ttfautohint` was called with when it
9
+ was used to auto-hint the font.
10
+
11
+ See also http://freetype.org/ttfautohint/doc/ttfautohint.html#miscellaneous-1
12
+ """
13
+
14
+ pass
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/TupleVariation.py ADDED
@@ -0,0 +1,886 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fontTools.misc.fixedTools import (
2
+ fixedToFloat as fi2fl,
3
+ floatToFixed as fl2fi,
4
+ floatToFixedToStr as fl2str,
5
+ strToFixedToFloat as str2fl,
6
+ otRound,
7
+ )
8
+ from fontTools.misc.textTools import safeEval
9
+ import array
10
+ from collections import Counter, defaultdict
11
+ import logging
12
+ import struct
13
+ import sys
14
+ import warnings
15
+
16
+
17
+ # https://www.microsoft.com/typography/otspec/otvarcommonformats.htm
18
+
19
+ EMBEDDED_PEAK_TUPLE = 0x8000
20
+ INTERMEDIATE_REGION = 0x4000
21
+ PRIVATE_POINT_NUMBERS = 0x2000
22
+
23
+ DELTAS_ARE_ZERO = 0x80
24
+ DELTAS_ARE_WORDS = 0x40
25
+ DELTAS_ARE_LONGS = 0xC0
26
+ DELTAS_SIZE_MASK = 0xC0
27
+ DELTA_RUN_COUNT_MASK = 0x3F
28
+
29
+ POINTS_ARE_WORDS = 0x80
30
+ POINT_RUN_COUNT_MASK = 0x7F
31
+
32
+ TUPLES_SHARE_POINT_NUMBERS = 0x8000
33
+ TUPLE_COUNT_MASK = 0x0FFF
34
+ TUPLE_INDEX_MASK = 0x0FFF
35
+
36
+ log = logging.getLogger(__name__)
37
+
38
+
39
+ class TupleVariation(object):
40
+ def __init__(self, axes, coordinates):
41
+ self.axes = axes.copy()
42
+ self.coordinates = list(coordinates)
43
+
44
+ def __repr__(self):
45
+ axes = ",".join(
46
+ sorted(["%s=%s" % (name, value) for (name, value) in self.axes.items()])
47
+ )
48
+ return "<TupleVariation %s %s>" % (axes, self.coordinates)
49
+
50
+ def __eq__(self, other):
51
+ return self.coordinates == other.coordinates and self.axes == other.axes
52
+
53
+ def getUsedPoints(self):
54
+ # Empty set means "all points used".
55
+ if None not in self.coordinates:
56
+ return frozenset()
57
+ used = frozenset([i for i, p in enumerate(self.coordinates) if p is not None])
58
+ # Return None if no points used.
59
+ return used if used else None
60
+
61
+ def hasImpact(self):
62
+ """Returns True if this TupleVariation has any visible impact.
63
+
64
+ If the result is False, the TupleVariation can be omitted from the font
65
+ without making any visible difference.
66
+ """
67
+ return any(c is not None for c in self.coordinates)
68
+
69
+ def toXML(self, writer, axisTags):
70
+ writer.begintag("tuple")
71
+ writer.newline()
72
+ for axis in axisTags:
73
+ value = self.axes.get(axis)
74
+ if value is not None:
75
+ minValue, value, maxValue = value
76
+ defaultMinValue = min(value, 0.0) # -0.3 --> -0.3; 0.7 --> 0.0
77
+ defaultMaxValue = max(value, 0.0) # -0.3 --> 0.0; 0.7 --> 0.7
78
+ if minValue == defaultMinValue and maxValue == defaultMaxValue:
79
+ writer.simpletag("coord", axis=axis, value=fl2str(value, 14))
80
+ else:
81
+ attrs = [
82
+ ("axis", axis),
83
+ ("min", fl2str(minValue, 14)),
84
+ ("value", fl2str(value, 14)),
85
+ ("max", fl2str(maxValue, 14)),
86
+ ]
87
+ writer.simpletag("coord", attrs)
88
+ writer.newline()
89
+ wrote_any_deltas = False
90
+ for i, delta in enumerate(self.coordinates):
91
+ if type(delta) == tuple and len(delta) == 2:
92
+ writer.simpletag("delta", pt=i, x=delta[0], y=delta[1])
93
+ writer.newline()
94
+ wrote_any_deltas = True
95
+ elif type(delta) == int:
96
+ writer.simpletag("delta", cvt=i, value=delta)
97
+ writer.newline()
98
+ wrote_any_deltas = True
99
+ elif delta is not None:
100
+ log.error("bad delta format")
101
+ writer.comment("bad delta #%d" % i)
102
+ writer.newline()
103
+ wrote_any_deltas = True
104
+ if not wrote_any_deltas:
105
+ writer.comment("no deltas")
106
+ writer.newline()
107
+ writer.endtag("tuple")
108
+ writer.newline()
109
+
110
+ def fromXML(self, name, attrs, _content):
111
+ if name == "coord":
112
+ axis = attrs["axis"]
113
+ value = str2fl(attrs["value"], 14)
114
+ defaultMinValue = min(value, 0.0) # -0.3 --> -0.3; 0.7 --> 0.0
115
+ defaultMaxValue = max(value, 0.0) # -0.3 --> 0.0; 0.7 --> 0.7
116
+ minValue = str2fl(attrs.get("min", defaultMinValue), 14)
117
+ maxValue = str2fl(attrs.get("max", defaultMaxValue), 14)
118
+ self.axes[axis] = (minValue, value, maxValue)
119
+ elif name == "delta":
120
+ if "pt" in attrs:
121
+ point = safeEval(attrs["pt"])
122
+ x = safeEval(attrs["x"])
123
+ y = safeEval(attrs["y"])
124
+ self.coordinates[point] = (x, y)
125
+ elif "cvt" in attrs:
126
+ cvt = safeEval(attrs["cvt"])
127
+ value = safeEval(attrs["value"])
128
+ self.coordinates[cvt] = value
129
+ else:
130
+ log.warning("bad delta format: %s" % ", ".join(sorted(attrs.keys())))
131
+
132
+ def compile(
133
+ self, axisTags, sharedCoordIndices={}, pointData=None, *, optimizeSize=True
134
+ ):
135
+ assert set(self.axes.keys()) <= set(axisTags), (
136
+ "Unknown axis tag found.",
137
+ self.axes.keys(),
138
+ axisTags,
139
+ )
140
+
141
+ tupleData = []
142
+ auxData = []
143
+
144
+ if pointData is None:
145
+ usedPoints = self.getUsedPoints()
146
+ if usedPoints is None: # Nothing to encode
147
+ return b"", b""
148
+ pointData = self.compilePoints(usedPoints)
149
+
150
+ coord = self.compileCoord(axisTags)
151
+ flags = sharedCoordIndices.get(coord)
152
+ if flags is None:
153
+ flags = EMBEDDED_PEAK_TUPLE
154
+ tupleData.append(coord)
155
+
156
+ intermediateCoord = self.compileIntermediateCoord(axisTags)
157
+ if intermediateCoord is not None:
158
+ flags |= INTERMEDIATE_REGION
159
+ tupleData.append(intermediateCoord)
160
+
161
+ # pointData of b'' implies "use shared points".
162
+ if pointData:
163
+ flags |= PRIVATE_POINT_NUMBERS
164
+ auxData.append(pointData)
165
+
166
+ auxData.append(self.compileDeltas(optimizeSize=optimizeSize))
167
+ auxData = b"".join(auxData)
168
+
169
+ tupleData.insert(0, struct.pack(">HH", len(auxData), flags))
170
+ return b"".join(tupleData), auxData
171
+
172
+ def compileCoord(self, axisTags):
173
+ result = []
174
+ axes = self.axes
175
+ for axis in axisTags:
176
+ triple = axes.get(axis)
177
+ if triple is None:
178
+ result.append(b"\0\0")
179
+ else:
180
+ result.append(struct.pack(">h", fl2fi(triple[1], 14)))
181
+ return b"".join(result)
182
+
183
+ def compileIntermediateCoord(self, axisTags):
184
+ needed = False
185
+ for axis in axisTags:
186
+ minValue, value, maxValue = self.axes.get(axis, (0.0, 0.0, 0.0))
187
+ defaultMinValue = min(value, 0.0) # -0.3 --> -0.3; 0.7 --> 0.0
188
+ defaultMaxValue = max(value, 0.0) # -0.3 --> 0.0; 0.7 --> 0.7
189
+ if (minValue != defaultMinValue) or (maxValue != defaultMaxValue):
190
+ needed = True
191
+ break
192
+ if not needed:
193
+ return None
194
+ minCoords = []
195
+ maxCoords = []
196
+ for axis in axisTags:
197
+ minValue, value, maxValue = self.axes.get(axis, (0.0, 0.0, 0.0))
198
+ minCoords.append(struct.pack(">h", fl2fi(minValue, 14)))
199
+ maxCoords.append(struct.pack(">h", fl2fi(maxValue, 14)))
200
+ return b"".join(minCoords + maxCoords)
201
+
202
+ @staticmethod
203
+ def decompileCoord_(axisTags, data, offset):
204
+ coord = {}
205
+ pos = offset
206
+ for axis in axisTags:
207
+ coord[axis] = fi2fl(struct.unpack(">h", data[pos : pos + 2])[0], 14)
208
+ pos += 2
209
+ return coord, pos
210
+
211
+ @staticmethod
212
+ def compilePoints(points):
213
+ # If the set consists of all points in the glyph, it gets encoded with
214
+ # a special encoding: a single zero byte.
215
+ #
216
+ # To use this optimization, points passed in must be empty set.
217
+ # The following two lines are not strictly necessary as the main code
218
+ # below would emit the same. But this is most common and faster.
219
+ if not points:
220
+ return b"\0"
221
+
222
+ # In the 'gvar' table, the packing of point numbers is a little surprising.
223
+ # It consists of multiple runs, each being a delta-encoded list of integers.
224
+ # For example, the point set {17, 18, 19, 20, 21, 22, 23} gets encoded as
225
+ # [6, 17, 1, 1, 1, 1, 1, 1]. The first value (6) is the run length minus 1.
226
+ # There are two types of runs, with values being either 8 or 16 bit unsigned
227
+ # integers.
228
+ points = list(points)
229
+ points.sort()
230
+ numPoints = len(points)
231
+
232
+ result = bytearray()
233
+ # The binary representation starts with the total number of points in the set,
234
+ # encoded into one or two bytes depending on the value.
235
+ if numPoints < 0x80:
236
+ result.append(numPoints)
237
+ else:
238
+ result.append((numPoints >> 8) | 0x80)
239
+ result.append(numPoints & 0xFF)
240
+
241
+ MAX_RUN_LENGTH = 127
242
+ pos = 0
243
+ lastValue = 0
244
+ while pos < numPoints:
245
+ runLength = 0
246
+
247
+ headerPos = len(result)
248
+ result.append(0)
249
+
250
+ useByteEncoding = None
251
+ while pos < numPoints and runLength <= MAX_RUN_LENGTH:
252
+ curValue = points[pos]
253
+ delta = curValue - lastValue
254
+ if useByteEncoding is None:
255
+ useByteEncoding = 0 <= delta <= 0xFF
256
+ if useByteEncoding and (delta > 0xFF or delta < 0):
257
+ # we need to start a new run (which will not use byte encoding)
258
+ break
259
+ # TODO This never switches back to a byte-encoding from a short-encoding.
260
+ # That's suboptimal.
261
+ if useByteEncoding:
262
+ result.append(delta)
263
+ else:
264
+ result.append(delta >> 8)
265
+ result.append(delta & 0xFF)
266
+ lastValue = curValue
267
+ pos += 1
268
+ runLength += 1
269
+ if useByteEncoding:
270
+ result[headerPos] = runLength - 1
271
+ else:
272
+ result[headerPos] = (runLength - 1) | POINTS_ARE_WORDS
273
+
274
+ return result
275
+
276
+ @staticmethod
277
+ def decompilePoints_(numPoints, data, offset, tableTag):
278
+ """(numPoints, data, offset, tableTag) --> ([point1, point2, ...], newOffset)"""
279
+ assert tableTag in ("cvar", "gvar")
280
+ pos = offset
281
+ numPointsInData = data[pos]
282
+ pos += 1
283
+ if (numPointsInData & POINTS_ARE_WORDS) != 0:
284
+ numPointsInData = (numPointsInData & POINT_RUN_COUNT_MASK) << 8 | data[pos]
285
+ pos += 1
286
+ if numPointsInData == 0:
287
+ return (range(numPoints), pos)
288
+
289
+ result = []
290
+ while len(result) < numPointsInData:
291
+ runHeader = data[pos]
292
+ pos += 1
293
+ numPointsInRun = (runHeader & POINT_RUN_COUNT_MASK) + 1
294
+ point = 0
295
+ if (runHeader & POINTS_ARE_WORDS) != 0:
296
+ points = array.array("H")
297
+ pointsSize = numPointsInRun * 2
298
+ else:
299
+ points = array.array("B")
300
+ pointsSize = numPointsInRun
301
+ points.frombytes(data[pos : pos + pointsSize])
302
+ if sys.byteorder != "big":
303
+ points.byteswap()
304
+
305
+ assert len(points) == numPointsInRun
306
+ pos += pointsSize
307
+
308
+ result.extend(points)
309
+
310
+ # Convert relative to absolute
311
+ absolute = []
312
+ current = 0
313
+ for delta in result:
314
+ current += delta
315
+ absolute.append(current)
316
+ result = absolute
317
+ del absolute
318
+
319
+ badPoints = {str(p) for p in result if p < 0 or p >= numPoints}
320
+ if badPoints:
321
+ log.warning(
322
+ "point %s out of range in '%s' table"
323
+ % (",".join(sorted(badPoints)), tableTag)
324
+ )
325
+ return (result, pos)
326
+
327
+ def compileDeltas(self, optimizeSize=True):
328
+ deltaX = []
329
+ deltaY = []
330
+ if self.getCoordWidth() == 2:
331
+ for c in self.coordinates:
332
+ if c is None:
333
+ continue
334
+ deltaX.append(c[0])
335
+ deltaY.append(c[1])
336
+ else:
337
+ for c in self.coordinates:
338
+ if c is None:
339
+ continue
340
+ deltaX.append(c)
341
+ bytearr = bytearray()
342
+ self.compileDeltaValues_(deltaX, bytearr, optimizeSize=optimizeSize)
343
+ self.compileDeltaValues_(deltaY, bytearr, optimizeSize=optimizeSize)
344
+ return bytearr
345
+
346
+ @staticmethod
347
+ def compileDeltaValues_(deltas, bytearr=None, *, optimizeSize=True):
348
+ """[value1, value2, value3, ...] --> bytearray
349
+
350
+ Emits a sequence of runs. Each run starts with a
351
+ byte-sized header whose 6 least significant bits
352
+ (header & 0x3F) indicate how many values are encoded
353
+ in this run. The stored length is the actual length
354
+ minus one; run lengths are thus in the range [1..64].
355
+ If the header byte has its most significant bit (0x80)
356
+ set, all values in this run are zero, and no data
357
+ follows. Otherwise, the header byte is followed by
358
+ ((header & 0x3F) + 1) signed values. If (header &
359
+ 0x40) is clear, the delta values are stored as signed
360
+ bytes; if (header & 0x40) is set, the delta values are
361
+ signed 16-bit integers.
362
+ """ # Explaining the format because the 'gvar' spec is hard to understand.
363
+ if bytearr is None:
364
+ bytearr = bytearray()
365
+
366
+ pos = 0
367
+ numDeltas = len(deltas)
368
+
369
+ if optimizeSize:
370
+ while pos < numDeltas:
371
+ value = deltas[pos]
372
+ if value == 0:
373
+ pos = TupleVariation.encodeDeltaRunAsZeroes_(deltas, pos, bytearr)
374
+ elif -128 <= value <= 127:
375
+ pos = TupleVariation.encodeDeltaRunAsBytes_(deltas, pos, bytearr)
376
+ elif -32768 <= value <= 32767:
377
+ pos = TupleVariation.encodeDeltaRunAsWords_(deltas, pos, bytearr)
378
+ else:
379
+ pos = TupleVariation.encodeDeltaRunAsLongs_(deltas, pos, bytearr)
380
+ else:
381
+ minVal, maxVal = min(deltas), max(deltas)
382
+ if minVal == 0 == maxVal:
383
+ pos = TupleVariation.encodeDeltaRunAsZeroes_(deltas, pos, bytearr)
384
+ elif -128 <= minVal <= maxVal <= 127:
385
+ pos = TupleVariation.encodeDeltaRunAsBytes_(
386
+ deltas, pos, bytearr, optimizeSize=False
387
+ )
388
+ elif -32768 <= minVal <= maxVal <= 32767:
389
+ pos = TupleVariation.encodeDeltaRunAsWords_(
390
+ deltas, pos, bytearr, optimizeSize=False
391
+ )
392
+ else:
393
+ pos = TupleVariation.encodeDeltaRunAsLongs_(
394
+ deltas, pos, bytearr, optimizeSize=False
395
+ )
396
+
397
+ assert pos == numDeltas, (pos, numDeltas)
398
+
399
+ return bytearr
400
+
401
+ @staticmethod
402
+ def encodeDeltaRunAsZeroes_(deltas, offset, bytearr):
403
+ pos = offset
404
+ numDeltas = len(deltas)
405
+ while pos < numDeltas and deltas[pos] == 0:
406
+ pos += 1
407
+ runLength = pos - offset
408
+ while runLength >= 64:
409
+ bytearr.append(DELTAS_ARE_ZERO | 63)
410
+ runLength -= 64
411
+ if runLength:
412
+ bytearr.append(DELTAS_ARE_ZERO | (runLength - 1))
413
+ return pos
414
+
415
+ @staticmethod
416
+ def encodeDeltaRunAsBytes_(deltas, offset, bytearr, optimizeSize=True):
417
+ pos = offset
418
+ numDeltas = len(deltas)
419
+ while pos < numDeltas:
420
+ value = deltas[pos]
421
+ if not (-128 <= value <= 127):
422
+ break
423
+ # Within a byte-encoded run of deltas, a single zero
424
+ # is best stored literally as 0x00 value. However,
425
+ # if are two or more zeroes in a sequence, it is
426
+ # better to start a new run. For example, the sequence
427
+ # of deltas [15, 15, 0, 15, 15] becomes 6 bytes
428
+ # (04 0F 0F 00 0F 0F) when storing the zero value
429
+ # literally, but 7 bytes (01 0F 0F 80 01 0F 0F)
430
+ # when starting a new run.
431
+ if (
432
+ optimizeSize
433
+ and value == 0
434
+ and pos + 1 < numDeltas
435
+ and deltas[pos + 1] == 0
436
+ ):
437
+ break
438
+ pos += 1
439
+ runLength = pos - offset
440
+ while runLength >= 64:
441
+ bytearr.append(63)
442
+ bytearr.extend(array.array("b", deltas[offset : offset + 64]))
443
+ offset += 64
444
+ runLength -= 64
445
+ if runLength:
446
+ bytearr.append(runLength - 1)
447
+ bytearr.extend(array.array("b", deltas[offset:pos]))
448
+ return pos
449
+
450
+ @staticmethod
451
+ def encodeDeltaRunAsWords_(deltas, offset, bytearr, optimizeSize=True):
452
+ pos = offset
453
+ numDeltas = len(deltas)
454
+ while pos < numDeltas:
455
+ value = deltas[pos]
456
+
457
+ # Within a word-encoded run of deltas, it is easiest
458
+ # to start a new run (with a different encoding)
459
+ # whenever we encounter a zero value. For example,
460
+ # the sequence [0x6666, 0, 0x7777] needs 7 bytes when
461
+ # storing the zero literally (42 66 66 00 00 77 77),
462
+ # and equally 7 bytes when starting a new run
463
+ # (40 66 66 80 40 77 77).
464
+ if optimizeSize and value == 0:
465
+ break
466
+
467
+ # Within a word-encoded run of deltas, a single value
468
+ # in the range (-128..127) should be encoded literally
469
+ # because it is more compact. For example, the sequence
470
+ # [0x6666, 2, 0x7777] becomes 7 bytes when storing
471
+ # the value literally (42 66 66 00 02 77 77), but 8 bytes
472
+ # when starting a new run (40 66 66 00 02 40 77 77).
473
+ if (
474
+ optimizeSize
475
+ and (-128 <= value <= 127)
476
+ and pos + 1 < numDeltas
477
+ and (-128 <= deltas[pos + 1] <= 127)
478
+ ):
479
+ break
480
+
481
+ if not (-32768 <= value <= 32767):
482
+ break
483
+
484
+ pos += 1
485
+ runLength = pos - offset
486
+ while runLength >= 64:
487
+ bytearr.append(DELTAS_ARE_WORDS | 63)
488
+ a = array.array("h", deltas[offset : offset + 64])
489
+ if sys.byteorder != "big":
490
+ a.byteswap()
491
+ bytearr.extend(a)
492
+ offset += 64
493
+ runLength -= 64
494
+ if runLength:
495
+ bytearr.append(DELTAS_ARE_WORDS | (runLength - 1))
496
+ a = array.array("h", deltas[offset:pos])
497
+ if sys.byteorder != "big":
498
+ a.byteswap()
499
+ bytearr.extend(a)
500
+ return pos
501
+
502
+ @staticmethod
503
+ def encodeDeltaRunAsLongs_(deltas, offset, bytearr, optimizeSize=True):
504
+ pos = offset
505
+ numDeltas = len(deltas)
506
+ while pos < numDeltas:
507
+ value = deltas[pos]
508
+ if optimizeSize and -32768 <= value <= 32767:
509
+ break
510
+ pos += 1
511
+ runLength = pos - offset
512
+ while runLength >= 64:
513
+ bytearr.append(DELTAS_ARE_LONGS | 63)
514
+ a = array.array("i", deltas[offset : offset + 64])
515
+ if sys.byteorder != "big":
516
+ a.byteswap()
517
+ bytearr.extend(a)
518
+ offset += 64
519
+ runLength -= 64
520
+ if runLength:
521
+ bytearr.append(DELTAS_ARE_LONGS | (runLength - 1))
522
+ a = array.array("i", deltas[offset:pos])
523
+ if sys.byteorder != "big":
524
+ a.byteswap()
525
+ bytearr.extend(a)
526
+ return pos
527
+
528
+ @staticmethod
529
+ def decompileDeltas_(numDeltas, data, offset=0):
530
+ """(numDeltas, data, offset) --> ([delta, delta, ...], newOffset)"""
531
+ result = []
532
+ pos = offset
533
+ while len(result) < numDeltas if numDeltas is not None else pos < len(data):
534
+ runHeader = data[pos]
535
+ pos += 1
536
+ numDeltasInRun = (runHeader & DELTA_RUN_COUNT_MASK) + 1
537
+ if (runHeader & DELTAS_SIZE_MASK) == DELTAS_ARE_ZERO:
538
+ result.extend([0] * numDeltasInRun)
539
+ else:
540
+ if (runHeader & DELTAS_SIZE_MASK) == DELTAS_ARE_LONGS:
541
+ deltas = array.array("i")
542
+ deltasSize = numDeltasInRun * 4
543
+ elif (runHeader & DELTAS_SIZE_MASK) == DELTAS_ARE_WORDS:
544
+ deltas = array.array("h")
545
+ deltasSize = numDeltasInRun * 2
546
+ else:
547
+ deltas = array.array("b")
548
+ deltasSize = numDeltasInRun
549
+ deltas.frombytes(data[pos : pos + deltasSize])
550
+ if sys.byteorder != "big":
551
+ deltas.byteswap()
552
+ assert len(deltas) == numDeltasInRun, (len(deltas), numDeltasInRun)
553
+ pos += deltasSize
554
+ result.extend(deltas)
555
+ assert numDeltas is None or len(result) == numDeltas
556
+ return (result, pos)
557
+
558
+ @staticmethod
559
+ def getTupleSize_(flags, axisCount):
560
+ size = 4
561
+ if (flags & EMBEDDED_PEAK_TUPLE) != 0:
562
+ size += axisCount * 2
563
+ if (flags & INTERMEDIATE_REGION) != 0:
564
+ size += axisCount * 4
565
+ return size
566
+
567
+ def getCoordWidth(self):
568
+ """Return 2 if coordinates are (x, y) as in gvar, 1 if single values
569
+ as in cvar, or 0 if empty.
570
+ """
571
+ firstDelta = next((c for c in self.coordinates if c is not None), None)
572
+ if firstDelta is None:
573
+ return 0 # empty or has no impact
574
+ if type(firstDelta) in (int, float):
575
+ return 1
576
+ if type(firstDelta) is tuple and len(firstDelta) == 2:
577
+ return 2
578
+ raise TypeError(
579
+ "invalid type of delta; expected (int or float) number, or "
580
+ "Tuple[number, number]: %r" % firstDelta
581
+ )
582
+
583
+ def scaleDeltas(self, scalar):
584
+ if scalar == 1.0:
585
+ return # no change
586
+ coordWidth = self.getCoordWidth()
587
+ self.coordinates = [
588
+ (
589
+ None
590
+ if d is None
591
+ else d * scalar if coordWidth == 1 else (d[0] * scalar, d[1] * scalar)
592
+ )
593
+ for d in self.coordinates
594
+ ]
595
+
596
+ def roundDeltas(self):
597
+ coordWidth = self.getCoordWidth()
598
+ self.coordinates = [
599
+ (
600
+ None
601
+ if d is None
602
+ else otRound(d) if coordWidth == 1 else (otRound(d[0]), otRound(d[1]))
603
+ )
604
+ for d in self.coordinates
605
+ ]
606
+
607
+ def calcInferredDeltas(self, origCoords, endPts):
608
+ from fontTools.varLib.iup import iup_delta
609
+
610
+ if self.getCoordWidth() == 1:
611
+ raise TypeError("Only 'gvar' TupleVariation can have inferred deltas")
612
+ if None in self.coordinates:
613
+ if len(self.coordinates) != len(origCoords):
614
+ raise ValueError(
615
+ "Expected len(origCoords) == %d; found %d"
616
+ % (len(self.coordinates), len(origCoords))
617
+ )
618
+ self.coordinates = iup_delta(self.coordinates, origCoords, endPts)
619
+
620
+ def optimize(self, origCoords, endPts, tolerance=0.5, isComposite=False):
621
+ from fontTools.varLib.iup import iup_delta_optimize
622
+
623
+ if isComposite:
624
+ warnings.warn(
625
+ "The isComposite argument is deprecated and may be removed in a future version",
626
+ DeprecationWarning,
627
+ )
628
+
629
+ if None in self.coordinates:
630
+ return # already optimized
631
+
632
+ deltaOpt = iup_delta_optimize(
633
+ self.coordinates, origCoords, endPts, tolerance=tolerance
634
+ )
635
+ if None in deltaOpt:
636
+ # Use "optimized" version only if smaller...
637
+ varOpt = TupleVariation(self.axes, deltaOpt)
638
+
639
+ # Shouldn't matter that this is different from fvar...?
640
+ axisTags = sorted(self.axes.keys())
641
+ tupleData, auxData = self.compile(axisTags)
642
+ unoptimizedLength = len(tupleData) + len(auxData)
643
+ tupleData, auxData = varOpt.compile(axisTags)
644
+ optimizedLength = len(tupleData) + len(auxData)
645
+
646
+ if optimizedLength < unoptimizedLength:
647
+ self.coordinates = varOpt.coordinates
648
+
649
+ def __imul__(self, scalar):
650
+ self.scaleDeltas(scalar)
651
+ return self
652
+
653
+ def __iadd__(self, other):
654
+ if not isinstance(other, TupleVariation):
655
+ return NotImplemented
656
+ deltas1 = self.coordinates
657
+ length = len(deltas1)
658
+ deltas2 = other.coordinates
659
+ if len(deltas2) != length:
660
+ raise ValueError("cannot sum TupleVariation deltas with different lengths")
661
+ # 'None' values have different meanings in gvar vs cvar TupleVariations:
662
+ # within the gvar, when deltas are not provided explicitly for some points,
663
+ # they need to be inferred; whereas for the 'cvar' table, if deltas are not
664
+ # provided for some CVT values, then no adjustments are made (i.e. None == 0).
665
+ # Thus, we cannot sum deltas for gvar TupleVariations if they contain
666
+ # inferred inferred deltas (the latter need to be computed first using
667
+ # 'calcInferredDeltas' method), but we can treat 'None' values in cvar
668
+ # deltas as if they are zeros.
669
+ if self.getCoordWidth() == 2:
670
+ for i, d2 in zip(range(length), deltas2):
671
+ d1 = deltas1[i]
672
+ try:
673
+ deltas1[i] = (d1[0] + d2[0], d1[1] + d2[1])
674
+ except TypeError:
675
+ raise ValueError("cannot sum gvar deltas with inferred points")
676
+ else:
677
+ for i, d2 in zip(range(length), deltas2):
678
+ d1 = deltas1[i]
679
+ if d1 is not None and d2 is not None:
680
+ deltas1[i] = d1 + d2
681
+ elif d1 is None and d2 is not None:
682
+ deltas1[i] = d2
683
+ # elif d2 is None do nothing
684
+ return self
685
+
686
+
687
+ def decompileSharedTuples(axisTags, sharedTupleCount, data, offset):
688
+ result = []
689
+ for _ in range(sharedTupleCount):
690
+ t, offset = TupleVariation.decompileCoord_(axisTags, data, offset)
691
+ result.append(t)
692
+ return result
693
+
694
+
695
+ def compileSharedTuples(
696
+ axisTags, variations, MAX_NUM_SHARED_COORDS=TUPLE_INDEX_MASK + 1
697
+ ):
698
+ coordCount = Counter()
699
+ for var in variations:
700
+ coord = var.compileCoord(axisTags)
701
+ coordCount[coord] += 1
702
+ # In python < 3.7, most_common() ordering is non-deterministic
703
+ # so apply a sort to make sure the ordering is consistent.
704
+ sharedCoords = sorted(
705
+ coordCount.most_common(MAX_NUM_SHARED_COORDS),
706
+ key=lambda item: (-item[1], item[0]),
707
+ )
708
+ return [c[0] for c in sharedCoords if c[1] > 1]
709
+
710
+
711
+ def compileTupleVariationStore(
712
+ variations,
713
+ pointCount,
714
+ axisTags,
715
+ sharedTupleIndices,
716
+ useSharedPoints=True,
717
+ *,
718
+ optimizeSize=True,
719
+ ):
720
+ # pointCount is actually unused. Keeping for API compat.
721
+ del pointCount
722
+ newVariations = []
723
+ pointDatas = []
724
+ # Compile all points and figure out sharing if desired
725
+ sharedPoints = None
726
+
727
+ # Collect, count, and compile point-sets for all variation sets
728
+ pointSetCount = defaultdict(int)
729
+ for v in variations:
730
+ points = v.getUsedPoints()
731
+ if points is None: # Empty variations
732
+ continue
733
+ pointSetCount[points] += 1
734
+ newVariations.append(v)
735
+ pointDatas.append(points)
736
+ variations = newVariations
737
+ del newVariations
738
+
739
+ if not variations:
740
+ return (0, b"", b"")
741
+
742
+ n = len(variations[0].coordinates)
743
+ assert all(
744
+ len(v.coordinates) == n for v in variations
745
+ ), "Variation sets have different sizes"
746
+
747
+ compiledPoints = {
748
+ pointSet: TupleVariation.compilePoints(pointSet) for pointSet in pointSetCount
749
+ }
750
+
751
+ tupleVariationCount = len(variations)
752
+ tuples = []
753
+ data = []
754
+
755
+ if useSharedPoints:
756
+ # Find point-set which saves most bytes.
757
+ def key(pn):
758
+ pointSet = pn[0]
759
+ count = pn[1]
760
+ return len(compiledPoints[pointSet]) * (count - 1)
761
+
762
+ sharedPoints = max(pointSetCount.items(), key=key)[0]
763
+
764
+ data.append(compiledPoints[sharedPoints])
765
+ tupleVariationCount |= TUPLES_SHARE_POINT_NUMBERS
766
+
767
+ # b'' implies "use shared points"
768
+ pointDatas = [
769
+ compiledPoints[points] if points != sharedPoints else b""
770
+ for points in pointDatas
771
+ ]
772
+
773
+ for v, p in zip(variations, pointDatas):
774
+ thisTuple, thisData = v.compile(
775
+ axisTags, sharedTupleIndices, pointData=p, optimizeSize=optimizeSize
776
+ )
777
+
778
+ tuples.append(thisTuple)
779
+ data.append(thisData)
780
+
781
+ tuples = b"".join(tuples)
782
+ data = b"".join(data)
783
+ return tupleVariationCount, tuples, data
784
+
785
+
786
+ def decompileTupleVariationStore(
787
+ tableTag,
788
+ axisTags,
789
+ tupleVariationCount,
790
+ pointCount,
791
+ sharedTuples,
792
+ data,
793
+ pos,
794
+ dataPos,
795
+ ):
796
+ numAxes = len(axisTags)
797
+ result = []
798
+ if (tupleVariationCount & TUPLES_SHARE_POINT_NUMBERS) != 0:
799
+ sharedPoints, dataPos = TupleVariation.decompilePoints_(
800
+ pointCount, data, dataPos, tableTag
801
+ )
802
+ else:
803
+ sharedPoints = []
804
+ for _ in range(tupleVariationCount & TUPLE_COUNT_MASK):
805
+ dataSize, flags = struct.unpack(">HH", data[pos : pos + 4])
806
+ tupleSize = TupleVariation.getTupleSize_(flags, numAxes)
807
+ tupleData = data[pos : pos + tupleSize]
808
+ pointDeltaData = data[dataPos : dataPos + dataSize]
809
+ result.append(
810
+ decompileTupleVariation_(
811
+ pointCount,
812
+ sharedTuples,
813
+ sharedPoints,
814
+ tableTag,
815
+ axisTags,
816
+ tupleData,
817
+ pointDeltaData,
818
+ )
819
+ )
820
+ pos += tupleSize
821
+ dataPos += dataSize
822
+ return result
823
+
824
+
825
+ def decompileTupleVariation_(
826
+ pointCount, sharedTuples, sharedPoints, tableTag, axisTags, data, tupleData
827
+ ):
828
+ assert tableTag in ("cvar", "gvar"), tableTag
829
+ flags = struct.unpack(">H", data[2:4])[0]
830
+ pos = 4
831
+ if (flags & EMBEDDED_PEAK_TUPLE) == 0:
832
+ peak = sharedTuples[flags & TUPLE_INDEX_MASK]
833
+ else:
834
+ peak, pos = TupleVariation.decompileCoord_(axisTags, data, pos)
835
+ if (flags & INTERMEDIATE_REGION) != 0:
836
+ start, pos = TupleVariation.decompileCoord_(axisTags, data, pos)
837
+ end, pos = TupleVariation.decompileCoord_(axisTags, data, pos)
838
+ else:
839
+ start, end = inferRegion_(peak)
840
+ axes = {}
841
+ for axis in axisTags:
842
+ region = start[axis], peak[axis], end[axis]
843
+ if region != (0.0, 0.0, 0.0):
844
+ axes[axis] = region
845
+ pos = 0
846
+ if (flags & PRIVATE_POINT_NUMBERS) != 0:
847
+ points, pos = TupleVariation.decompilePoints_(
848
+ pointCount, tupleData, pos, tableTag
849
+ )
850
+ else:
851
+ points = sharedPoints
852
+
853
+ deltas = [None] * pointCount
854
+
855
+ if tableTag == "cvar":
856
+ deltas_cvt, pos = TupleVariation.decompileDeltas_(len(points), tupleData, pos)
857
+ for p, delta in zip(points, deltas_cvt):
858
+ if 0 <= p < pointCount:
859
+ deltas[p] = delta
860
+
861
+ elif tableTag == "gvar":
862
+ deltas_x, pos = TupleVariation.decompileDeltas_(len(points), tupleData, pos)
863
+ deltas_y, pos = TupleVariation.decompileDeltas_(len(points), tupleData, pos)
864
+ for p, x, y in zip(points, deltas_x, deltas_y):
865
+ if 0 <= p < pointCount:
866
+ deltas[p] = (x, y)
867
+
868
+ return TupleVariation(axes, deltas)
869
+
870
+
871
+ def inferRegion_(peak):
872
+ """Infer start and end for a (non-intermediate) region
873
+
874
+ This helper function computes the applicability region for
875
+ variation tuples whose INTERMEDIATE_REGION flag is not set in the
876
+ TupleVariationHeader structure. Variation tuples apply only to
877
+ certain regions of the variation space; outside that region, the
878
+ tuple has no effect. To make the binary encoding more compact,
879
+ TupleVariationHeaders can omit the intermediateStartTuple and
880
+ intermediateEndTuple fields.
881
+ """
882
+ start, end = {}, {}
883
+ for axis, value in peak.items():
884
+ start[axis] = min(value, 0.0) # -0.3 --> -0.3; 0.7 --> 0.0
885
+ end[axis] = max(value, 0.0) # -0.3 --> 0.0; 0.7 --> 0.7
886
+ return (start, end)