fonttools 4.60.2__cp311-cp311-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. fontTools/__init__.py +8 -0
  2. fontTools/__main__.py +35 -0
  3. fontTools/afmLib.py +439 -0
  4. fontTools/agl.py +5233 -0
  5. fontTools/annotations.py +30 -0
  6. fontTools/cffLib/CFF2ToCFF.py +258 -0
  7. fontTools/cffLib/CFFToCFF2.py +305 -0
  8. fontTools/cffLib/__init__.py +3694 -0
  9. fontTools/cffLib/specializer.py +927 -0
  10. fontTools/cffLib/transforms.py +495 -0
  11. fontTools/cffLib/width.py +210 -0
  12. fontTools/colorLib/__init__.py +0 -0
  13. fontTools/colorLib/builder.py +664 -0
  14. fontTools/colorLib/errors.py +2 -0
  15. fontTools/colorLib/geometry.py +143 -0
  16. fontTools/colorLib/table_builder.py +223 -0
  17. fontTools/colorLib/unbuilder.py +81 -0
  18. fontTools/config/__init__.py +90 -0
  19. fontTools/cu2qu/__init__.py +15 -0
  20. fontTools/cu2qu/__main__.py +6 -0
  21. fontTools/cu2qu/benchmark.py +54 -0
  22. fontTools/cu2qu/cli.py +198 -0
  23. fontTools/cu2qu/cu2qu.c +15817 -0
  24. fontTools/cu2qu/cu2qu.cp311-win32.pyd +0 -0
  25. fontTools/cu2qu/cu2qu.py +563 -0
  26. fontTools/cu2qu/errors.py +77 -0
  27. fontTools/cu2qu/ufo.py +363 -0
  28. fontTools/designspaceLib/__init__.py +3343 -0
  29. fontTools/designspaceLib/__main__.py +6 -0
  30. fontTools/designspaceLib/split.py +475 -0
  31. fontTools/designspaceLib/statNames.py +260 -0
  32. fontTools/designspaceLib/types.py +147 -0
  33. fontTools/encodings/MacRoman.py +258 -0
  34. fontTools/encodings/StandardEncoding.py +258 -0
  35. fontTools/encodings/__init__.py +1 -0
  36. fontTools/encodings/codecs.py +135 -0
  37. fontTools/feaLib/__init__.py +4 -0
  38. fontTools/feaLib/__main__.py +78 -0
  39. fontTools/feaLib/ast.py +2143 -0
  40. fontTools/feaLib/builder.py +1814 -0
  41. fontTools/feaLib/error.py +22 -0
  42. fontTools/feaLib/lexer.c +17029 -0
  43. fontTools/feaLib/lexer.cp311-win32.pyd +0 -0
  44. fontTools/feaLib/lexer.py +287 -0
  45. fontTools/feaLib/location.py +12 -0
  46. fontTools/feaLib/lookupDebugInfo.py +12 -0
  47. fontTools/feaLib/parser.py +2394 -0
  48. fontTools/feaLib/variableScalar.py +118 -0
  49. fontTools/fontBuilder.py +1014 -0
  50. fontTools/help.py +36 -0
  51. fontTools/merge/__init__.py +248 -0
  52. fontTools/merge/__main__.py +6 -0
  53. fontTools/merge/base.py +81 -0
  54. fontTools/merge/cmap.py +173 -0
  55. fontTools/merge/layout.py +526 -0
  56. fontTools/merge/options.py +85 -0
  57. fontTools/merge/tables.py +352 -0
  58. fontTools/merge/unicode.py +78 -0
  59. fontTools/merge/util.py +143 -0
  60. fontTools/misc/__init__.py +1 -0
  61. fontTools/misc/arrayTools.py +424 -0
  62. fontTools/misc/bezierTools.c +39731 -0
  63. fontTools/misc/bezierTools.cp311-win32.pyd +0 -0
  64. fontTools/misc/bezierTools.py +1500 -0
  65. fontTools/misc/classifyTools.py +170 -0
  66. fontTools/misc/cliTools.py +53 -0
  67. fontTools/misc/configTools.py +349 -0
  68. fontTools/misc/cython.py +27 -0
  69. fontTools/misc/dictTools.py +83 -0
  70. fontTools/misc/eexec.py +119 -0
  71. fontTools/misc/encodingTools.py +72 -0
  72. fontTools/misc/enumTools.py +23 -0
  73. fontTools/misc/etree.py +456 -0
  74. fontTools/misc/filenames.py +245 -0
  75. fontTools/misc/filesystem/__init__.py +68 -0
  76. fontTools/misc/filesystem/_base.py +134 -0
  77. fontTools/misc/filesystem/_copy.py +45 -0
  78. fontTools/misc/filesystem/_errors.py +54 -0
  79. fontTools/misc/filesystem/_info.py +75 -0
  80. fontTools/misc/filesystem/_osfs.py +164 -0
  81. fontTools/misc/filesystem/_path.py +67 -0
  82. fontTools/misc/filesystem/_subfs.py +92 -0
  83. fontTools/misc/filesystem/_tempfs.py +34 -0
  84. fontTools/misc/filesystem/_tools.py +34 -0
  85. fontTools/misc/filesystem/_walk.py +55 -0
  86. fontTools/misc/filesystem/_zipfs.py +204 -0
  87. fontTools/misc/fixedTools.py +253 -0
  88. fontTools/misc/intTools.py +25 -0
  89. fontTools/misc/iterTools.py +12 -0
  90. fontTools/misc/lazyTools.py +42 -0
  91. fontTools/misc/loggingTools.py +543 -0
  92. fontTools/misc/macCreatorType.py +56 -0
  93. fontTools/misc/macRes.py +261 -0
  94. fontTools/misc/plistlib/__init__.py +681 -0
  95. fontTools/misc/plistlib/py.typed +0 -0
  96. fontTools/misc/psCharStrings.py +1511 -0
  97. fontTools/misc/psLib.py +398 -0
  98. fontTools/misc/psOperators.py +572 -0
  99. fontTools/misc/py23.py +96 -0
  100. fontTools/misc/roundTools.py +110 -0
  101. fontTools/misc/sstruct.py +227 -0
  102. fontTools/misc/symfont.py +242 -0
  103. fontTools/misc/testTools.py +233 -0
  104. fontTools/misc/textTools.py +156 -0
  105. fontTools/misc/timeTools.py +88 -0
  106. fontTools/misc/transform.py +516 -0
  107. fontTools/misc/treeTools.py +45 -0
  108. fontTools/misc/vector.py +147 -0
  109. fontTools/misc/visitor.py +158 -0
  110. fontTools/misc/xmlReader.py +188 -0
  111. fontTools/misc/xmlWriter.py +231 -0
  112. fontTools/mtiLib/__init__.py +1400 -0
  113. fontTools/mtiLib/__main__.py +5 -0
  114. fontTools/otlLib/__init__.py +1 -0
  115. fontTools/otlLib/builder.py +3465 -0
  116. fontTools/otlLib/error.py +11 -0
  117. fontTools/otlLib/maxContextCalc.py +96 -0
  118. fontTools/otlLib/optimize/__init__.py +53 -0
  119. fontTools/otlLib/optimize/__main__.py +6 -0
  120. fontTools/otlLib/optimize/gpos.py +439 -0
  121. fontTools/pens/__init__.py +1 -0
  122. fontTools/pens/areaPen.py +52 -0
  123. fontTools/pens/basePen.py +475 -0
  124. fontTools/pens/boundsPen.py +98 -0
  125. fontTools/pens/cairoPen.py +26 -0
  126. fontTools/pens/cocoaPen.py +26 -0
  127. fontTools/pens/cu2quPen.py +325 -0
  128. fontTools/pens/explicitClosingLinePen.py +101 -0
  129. fontTools/pens/filterPen.py +433 -0
  130. fontTools/pens/freetypePen.py +462 -0
  131. fontTools/pens/hashPointPen.py +89 -0
  132. fontTools/pens/momentsPen.c +13378 -0
  133. fontTools/pens/momentsPen.cp311-win32.pyd +0 -0
  134. fontTools/pens/momentsPen.py +879 -0
  135. fontTools/pens/perimeterPen.py +69 -0
  136. fontTools/pens/pointInsidePen.py +192 -0
  137. fontTools/pens/pointPen.py +643 -0
  138. fontTools/pens/qtPen.py +29 -0
  139. fontTools/pens/qu2cuPen.py +105 -0
  140. fontTools/pens/quartzPen.py +43 -0
  141. fontTools/pens/recordingPen.py +335 -0
  142. fontTools/pens/reportLabPen.py +79 -0
  143. fontTools/pens/reverseContourPen.py +96 -0
  144. fontTools/pens/roundingPen.py +130 -0
  145. fontTools/pens/statisticsPen.py +312 -0
  146. fontTools/pens/svgPathPen.py +310 -0
  147. fontTools/pens/t2CharStringPen.py +88 -0
  148. fontTools/pens/teePen.py +55 -0
  149. fontTools/pens/transformPen.py +115 -0
  150. fontTools/pens/ttGlyphPen.py +335 -0
  151. fontTools/pens/wxPen.py +29 -0
  152. fontTools/qu2cu/__init__.py +15 -0
  153. fontTools/qu2cu/__main__.py +7 -0
  154. fontTools/qu2cu/benchmark.py +56 -0
  155. fontTools/qu2cu/cli.py +125 -0
  156. fontTools/qu2cu/qu2cu.c +16682 -0
  157. fontTools/qu2cu/qu2cu.cp311-win32.pyd +0 -0
  158. fontTools/qu2cu/qu2cu.py +405 -0
  159. fontTools/subset/__init__.py +4096 -0
  160. fontTools/subset/__main__.py +6 -0
  161. fontTools/subset/cff.py +184 -0
  162. fontTools/subset/svg.py +253 -0
  163. fontTools/subset/util.py +25 -0
  164. fontTools/svgLib/__init__.py +3 -0
  165. fontTools/svgLib/path/__init__.py +65 -0
  166. fontTools/svgLib/path/arc.py +154 -0
  167. fontTools/svgLib/path/parser.py +322 -0
  168. fontTools/svgLib/path/shapes.py +183 -0
  169. fontTools/t1Lib/__init__.py +648 -0
  170. fontTools/tfmLib.py +460 -0
  171. fontTools/ttLib/__init__.py +30 -0
  172. fontTools/ttLib/__main__.py +148 -0
  173. fontTools/ttLib/macUtils.py +54 -0
  174. fontTools/ttLib/removeOverlaps.py +395 -0
  175. fontTools/ttLib/reorderGlyphs.py +285 -0
  176. fontTools/ttLib/scaleUpem.py +436 -0
  177. fontTools/ttLib/sfnt.py +661 -0
  178. fontTools/ttLib/standardGlyphOrder.py +271 -0
  179. fontTools/ttLib/tables/B_A_S_E_.py +14 -0
  180. fontTools/ttLib/tables/BitmapGlyphMetrics.py +64 -0
  181. fontTools/ttLib/tables/C_B_D_T_.py +113 -0
  182. fontTools/ttLib/tables/C_B_L_C_.py +19 -0
  183. fontTools/ttLib/tables/C_F_F_.py +61 -0
  184. fontTools/ttLib/tables/C_F_F__2.py +26 -0
  185. fontTools/ttLib/tables/C_O_L_R_.py +165 -0
  186. fontTools/ttLib/tables/C_P_A_L_.py +305 -0
  187. fontTools/ttLib/tables/D_S_I_G_.py +158 -0
  188. fontTools/ttLib/tables/D__e_b_g.py +35 -0
  189. fontTools/ttLib/tables/DefaultTable.py +49 -0
  190. fontTools/ttLib/tables/E_B_D_T_.py +835 -0
  191. fontTools/ttLib/tables/E_B_L_C_.py +718 -0
  192. fontTools/ttLib/tables/F_F_T_M_.py +52 -0
  193. fontTools/ttLib/tables/F__e_a_t.py +149 -0
  194. fontTools/ttLib/tables/G_D_E_F_.py +13 -0
  195. fontTools/ttLib/tables/G_M_A_P_.py +148 -0
  196. fontTools/ttLib/tables/G_P_K_G_.py +133 -0
  197. fontTools/ttLib/tables/G_P_O_S_.py +14 -0
  198. fontTools/ttLib/tables/G_S_U_B_.py +13 -0
  199. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  200. fontTools/ttLib/tables/G__l_a_t.py +235 -0
  201. fontTools/ttLib/tables/G__l_o_c.py +85 -0
  202. fontTools/ttLib/tables/H_V_A_R_.py +13 -0
  203. fontTools/ttLib/tables/J_S_T_F_.py +13 -0
  204. fontTools/ttLib/tables/L_T_S_H_.py +58 -0
  205. fontTools/ttLib/tables/M_A_T_H_.py +13 -0
  206. fontTools/ttLib/tables/M_E_T_A_.py +352 -0
  207. fontTools/ttLib/tables/M_V_A_R_.py +13 -0
  208. fontTools/ttLib/tables/O_S_2f_2.py +752 -0
  209. fontTools/ttLib/tables/S_I_N_G_.py +99 -0
  210. fontTools/ttLib/tables/S_T_A_T_.py +15 -0
  211. fontTools/ttLib/tables/S_V_G_.py +223 -0
  212. fontTools/ttLib/tables/S__i_l_f.py +1040 -0
  213. fontTools/ttLib/tables/S__i_l_l.py +92 -0
  214. fontTools/ttLib/tables/T_S_I_B_.py +13 -0
  215. fontTools/ttLib/tables/T_S_I_C_.py +14 -0
  216. fontTools/ttLib/tables/T_S_I_D_.py +13 -0
  217. fontTools/ttLib/tables/T_S_I_J_.py +13 -0
  218. fontTools/ttLib/tables/T_S_I_P_.py +13 -0
  219. fontTools/ttLib/tables/T_S_I_S_.py +13 -0
  220. fontTools/ttLib/tables/T_S_I_V_.py +26 -0
  221. fontTools/ttLib/tables/T_S_I__0.py +70 -0
  222. fontTools/ttLib/tables/T_S_I__1.py +163 -0
  223. fontTools/ttLib/tables/T_S_I__2.py +17 -0
  224. fontTools/ttLib/tables/T_S_I__3.py +22 -0
  225. fontTools/ttLib/tables/T_S_I__5.py +60 -0
  226. fontTools/ttLib/tables/T_T_F_A_.py +14 -0
  227. fontTools/ttLib/tables/TupleVariation.py +884 -0
  228. fontTools/ttLib/tables/V_A_R_C_.py +12 -0
  229. fontTools/ttLib/tables/V_D_M_X_.py +249 -0
  230. fontTools/ttLib/tables/V_O_R_G_.py +165 -0
  231. fontTools/ttLib/tables/V_V_A_R_.py +13 -0
  232. fontTools/ttLib/tables/__init__.py +98 -0
  233. fontTools/ttLib/tables/_a_n_k_r.py +15 -0
  234. fontTools/ttLib/tables/_a_v_a_r.py +193 -0
  235. fontTools/ttLib/tables/_b_s_l_n.py +15 -0
  236. fontTools/ttLib/tables/_c_i_d_g.py +24 -0
  237. fontTools/ttLib/tables/_c_m_a_p.py +1591 -0
  238. fontTools/ttLib/tables/_c_v_a_r.py +94 -0
  239. fontTools/ttLib/tables/_c_v_t.py +56 -0
  240. fontTools/ttLib/tables/_f_e_a_t.py +15 -0
  241. fontTools/ttLib/tables/_f_p_g_m.py +62 -0
  242. fontTools/ttLib/tables/_f_v_a_r.py +261 -0
  243. fontTools/ttLib/tables/_g_a_s_p.py +63 -0
  244. fontTools/ttLib/tables/_g_c_i_d.py +13 -0
  245. fontTools/ttLib/tables/_g_l_y_f.py +2311 -0
  246. fontTools/ttLib/tables/_g_v_a_r.py +340 -0
  247. fontTools/ttLib/tables/_h_d_m_x.py +127 -0
  248. fontTools/ttLib/tables/_h_e_a_d.py +130 -0
  249. fontTools/ttLib/tables/_h_h_e_a.py +147 -0
  250. fontTools/ttLib/tables/_h_m_t_x.py +164 -0
  251. fontTools/ttLib/tables/_k_e_r_n.py +289 -0
  252. fontTools/ttLib/tables/_l_c_a_r.py +13 -0
  253. fontTools/ttLib/tables/_l_o_c_a.py +70 -0
  254. fontTools/ttLib/tables/_l_t_a_g.py +72 -0
  255. fontTools/ttLib/tables/_m_a_x_p.py +147 -0
  256. fontTools/ttLib/tables/_m_e_t_a.py +112 -0
  257. fontTools/ttLib/tables/_m_o_r_t.py +14 -0
  258. fontTools/ttLib/tables/_m_o_r_x.py +15 -0
  259. fontTools/ttLib/tables/_n_a_m_e.py +1242 -0
  260. fontTools/ttLib/tables/_o_p_b_d.py +14 -0
  261. fontTools/ttLib/tables/_p_o_s_t.py +319 -0
  262. fontTools/ttLib/tables/_p_r_e_p.py +16 -0
  263. fontTools/ttLib/tables/_p_r_o_p.py +12 -0
  264. fontTools/ttLib/tables/_s_b_i_x.py +129 -0
  265. fontTools/ttLib/tables/_t_r_a_k.py +332 -0
  266. fontTools/ttLib/tables/_v_h_e_a.py +139 -0
  267. fontTools/ttLib/tables/_v_m_t_x.py +19 -0
  268. fontTools/ttLib/tables/asciiTable.py +20 -0
  269. fontTools/ttLib/tables/grUtils.py +92 -0
  270. fontTools/ttLib/tables/otBase.py +1458 -0
  271. fontTools/ttLib/tables/otConverters.py +2068 -0
  272. fontTools/ttLib/tables/otData.py +6400 -0
  273. fontTools/ttLib/tables/otTables.py +2703 -0
  274. fontTools/ttLib/tables/otTraverse.py +163 -0
  275. fontTools/ttLib/tables/sbixGlyph.py +149 -0
  276. fontTools/ttLib/tables/sbixStrike.py +177 -0
  277. fontTools/ttLib/tables/table_API_readme.txt +91 -0
  278. fontTools/ttLib/tables/ttProgram.py +594 -0
  279. fontTools/ttLib/ttCollection.py +125 -0
  280. fontTools/ttLib/ttFont.py +1148 -0
  281. fontTools/ttLib/ttGlyphSet.py +490 -0
  282. fontTools/ttLib/ttVisitor.py +32 -0
  283. fontTools/ttLib/woff2.py +1680 -0
  284. fontTools/ttx.py +479 -0
  285. fontTools/ufoLib/__init__.py +2575 -0
  286. fontTools/ufoLib/converters.py +407 -0
  287. fontTools/ufoLib/errors.py +30 -0
  288. fontTools/ufoLib/etree.py +6 -0
  289. fontTools/ufoLib/filenames.py +356 -0
  290. fontTools/ufoLib/glifLib.py +2120 -0
  291. fontTools/ufoLib/kerning.py +141 -0
  292. fontTools/ufoLib/plistlib.py +47 -0
  293. fontTools/ufoLib/pointPen.py +6 -0
  294. fontTools/ufoLib/utils.py +107 -0
  295. fontTools/ufoLib/validators.py +1208 -0
  296. fontTools/unicode.py +50 -0
  297. fontTools/unicodedata/Blocks.py +817 -0
  298. fontTools/unicodedata/Mirrored.py +446 -0
  299. fontTools/unicodedata/OTTags.py +50 -0
  300. fontTools/unicodedata/ScriptExtensions.py +832 -0
  301. fontTools/unicodedata/Scripts.py +3639 -0
  302. fontTools/unicodedata/__init__.py +306 -0
  303. fontTools/varLib/__init__.py +1600 -0
  304. fontTools/varLib/__main__.py +6 -0
  305. fontTools/varLib/avar/__init__.py +0 -0
  306. fontTools/varLib/avar/__main__.py +72 -0
  307. fontTools/varLib/avar/build.py +79 -0
  308. fontTools/varLib/avar/map.py +108 -0
  309. fontTools/varLib/avar/plan.py +1004 -0
  310. fontTools/varLib/avar/unbuild.py +271 -0
  311. fontTools/varLib/avarPlanner.py +8 -0
  312. fontTools/varLib/builder.py +215 -0
  313. fontTools/varLib/cff.py +631 -0
  314. fontTools/varLib/errors.py +219 -0
  315. fontTools/varLib/featureVars.py +703 -0
  316. fontTools/varLib/hvar.py +113 -0
  317. fontTools/varLib/instancer/__init__.py +2052 -0
  318. fontTools/varLib/instancer/__main__.py +5 -0
  319. fontTools/varLib/instancer/featureVars.py +190 -0
  320. fontTools/varLib/instancer/names.py +388 -0
  321. fontTools/varLib/instancer/solver.py +309 -0
  322. fontTools/varLib/interpolatable.py +1209 -0
  323. fontTools/varLib/interpolatableHelpers.py +399 -0
  324. fontTools/varLib/interpolatablePlot.py +1269 -0
  325. fontTools/varLib/interpolatableTestContourOrder.py +82 -0
  326. fontTools/varLib/interpolatableTestStartingPoint.py +107 -0
  327. fontTools/varLib/interpolate_layout.py +124 -0
  328. fontTools/varLib/iup.c +19815 -0
  329. fontTools/varLib/iup.cp311-win32.pyd +0 -0
  330. fontTools/varLib/iup.py +490 -0
  331. fontTools/varLib/merger.py +1717 -0
  332. fontTools/varLib/models.py +642 -0
  333. fontTools/varLib/multiVarStore.py +253 -0
  334. fontTools/varLib/mutator.py +529 -0
  335. fontTools/varLib/mvar.py +40 -0
  336. fontTools/varLib/plot.py +238 -0
  337. fontTools/varLib/stat.py +149 -0
  338. fontTools/varLib/varStore.py +739 -0
  339. fontTools/voltLib/__init__.py +5 -0
  340. fontTools/voltLib/__main__.py +206 -0
  341. fontTools/voltLib/ast.py +452 -0
  342. fontTools/voltLib/error.py +12 -0
  343. fontTools/voltLib/lexer.py +99 -0
  344. fontTools/voltLib/parser.py +664 -0
  345. fontTools/voltLib/voltToFea.py +911 -0
  346. fonttools-4.60.2.data/data/share/man/man1/ttx.1 +225 -0
  347. fonttools-4.60.2.dist-info/METADATA +2250 -0
  348. fonttools-4.60.2.dist-info/RECORD +353 -0
  349. fonttools-4.60.2.dist-info/WHEEL +5 -0
  350. fonttools-4.60.2.dist-info/entry_points.txt +5 -0
  351. fonttools-4.60.2.dist-info/licenses/LICENSE +21 -0
  352. fonttools-4.60.2.dist-info/licenses/LICENSE.external +388 -0
  353. fonttools-4.60.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,739 @@
1
+ from fontTools.misc.roundTools import noRound, otRound
2
+ from fontTools.misc.intTools import bit_count
3
+ from fontTools.ttLib.tables import otTables as ot
4
+ from fontTools.varLib.models import supportScalar
5
+ from fontTools.varLib.builder import (
6
+ buildVarRegionList,
7
+ buildVarStore,
8
+ buildVarRegion,
9
+ buildVarData,
10
+ )
11
+ from functools import partial
12
+ from collections import defaultdict
13
+ from heapq import heappush, heappop
14
+
15
+
16
+ NO_VARIATION_INDEX = ot.NO_VARIATION_INDEX
17
+ ot.VarStore.NO_VARIATION_INDEX = NO_VARIATION_INDEX
18
+
19
+
20
+ def _getLocationKey(loc):
21
+ return tuple(sorted(loc.items(), key=lambda kv: kv[0]))
22
+
23
+
24
+ class OnlineVarStoreBuilder(object):
25
+ def __init__(self, axisTags):
26
+ self._axisTags = axisTags
27
+ self._regionMap = {}
28
+ self._regionList = buildVarRegionList([], axisTags)
29
+ self._store = buildVarStore(self._regionList, [])
30
+ self._data = None
31
+ self._model = None
32
+ self._supports = None
33
+ self._varDataIndices = {}
34
+ self._varDataCaches = {}
35
+ self._cache = None
36
+
37
+ def setModel(self, model):
38
+ self.setSupports(model.supports)
39
+ self._model = model
40
+
41
+ def setSupports(self, supports):
42
+ self._model = None
43
+ self._supports = list(supports)
44
+ if self._supports and not self._supports[0]:
45
+ del self._supports[0] # Drop base master support
46
+ self._cache = None
47
+ self._data = None
48
+
49
+ def finish(self, optimize=True):
50
+ self._regionList.RegionCount = len(self._regionList.Region)
51
+ self._store.VarDataCount = len(self._store.VarData)
52
+ for data in self._store.VarData:
53
+ data.ItemCount = len(data.Item)
54
+ data.calculateNumShorts(optimize=optimize)
55
+ return self._store
56
+
57
+ def _add_VarData(self, num_items=1):
58
+ regionMap = self._regionMap
59
+ regionList = self._regionList
60
+
61
+ regions = self._supports
62
+ regionIndices = []
63
+ for region in regions:
64
+ key = _getLocationKey(region)
65
+ idx = regionMap.get(key)
66
+ if idx is None:
67
+ varRegion = buildVarRegion(region, self._axisTags)
68
+ idx = regionMap[key] = len(regionList.Region)
69
+ regionList.Region.append(varRegion)
70
+ regionIndices.append(idx)
71
+
72
+ # Check if we have one already...
73
+ key = tuple(regionIndices)
74
+ varDataIdx = self._varDataIndices.get(key)
75
+ if varDataIdx is not None:
76
+ self._outer = varDataIdx
77
+ self._data = self._store.VarData[varDataIdx]
78
+ self._cache = self._varDataCaches[key]
79
+ if len(self._data.Item) + num_items > 0xFFFF:
80
+ # This is full. Need new one.
81
+ varDataIdx = None
82
+
83
+ if varDataIdx is None:
84
+ self._data = buildVarData(regionIndices, [], optimize=False)
85
+ self._outer = len(self._store.VarData)
86
+ self._store.VarData.append(self._data)
87
+ self._varDataIndices[key] = self._outer
88
+ if key not in self._varDataCaches:
89
+ self._varDataCaches[key] = {}
90
+ self._cache = self._varDataCaches[key]
91
+
92
+ def storeMasters(self, master_values, *, round=round):
93
+ deltas = self._model.getDeltas(master_values, round=round)
94
+ base = deltas.pop(0)
95
+ return base, self.storeDeltas(deltas, round=noRound)
96
+
97
+ def storeMastersMany(self, master_values_list, *, round=round):
98
+ deltas_list = [
99
+ self._model.getDeltas(master_values, round=round)
100
+ for master_values in master_values_list
101
+ ]
102
+ base_list = [deltas.pop(0) for deltas in deltas_list]
103
+ return base_list, self.storeDeltasMany(deltas_list, round=noRound)
104
+
105
+ def storeDeltas(self, deltas, *, round=round):
106
+ deltas = [round(d) for d in deltas]
107
+ if len(deltas) == len(self._supports) + 1:
108
+ deltas = tuple(deltas[1:])
109
+ else:
110
+ assert len(deltas) == len(self._supports)
111
+ deltas = tuple(deltas)
112
+
113
+ if not self._data:
114
+ self._add_VarData()
115
+
116
+ varIdx = self._cache.get(deltas)
117
+ if varIdx is not None:
118
+ return varIdx
119
+
120
+ inner = len(self._data.Item)
121
+ if inner == 0xFFFF:
122
+ # Full array. Start new one.
123
+ self._add_VarData()
124
+ return self.storeDeltas(deltas, round=noRound)
125
+ self._data.addItem(deltas, round=noRound)
126
+
127
+ varIdx = (self._outer << 16) + inner
128
+ self._cache[deltas] = varIdx
129
+ return varIdx
130
+
131
+ def storeDeltasMany(self, deltas_list, *, round=round):
132
+ deltas_list = [[round(d) for d in deltas] for deltas in deltas_list]
133
+ deltas_list = tuple(tuple(deltas) for deltas in deltas_list)
134
+
135
+ if not self._data:
136
+ self._add_VarData(len(deltas_list))
137
+
138
+ varIdx = self._cache.get(deltas_list)
139
+ if varIdx is not None:
140
+ return varIdx
141
+
142
+ inner = len(self._data.Item)
143
+ if inner + len(deltas_list) > 0xFFFF:
144
+ # Full array. Start new one.
145
+ self._add_VarData(len(deltas_list))
146
+ return self.storeDeltasMany(deltas_list, round=noRound)
147
+ for i, deltas in enumerate(deltas_list):
148
+ self._data.addItem(deltas, round=noRound)
149
+
150
+ varIdx = (self._outer << 16) + inner + i
151
+ self._cache[deltas] = varIdx
152
+
153
+ varIdx = (self._outer << 16) + inner
154
+ self._cache[deltas_list] = varIdx
155
+
156
+ return varIdx
157
+
158
+
159
+ def VarData_addItem(self, deltas, *, round=round):
160
+ deltas = [round(d) for d in deltas]
161
+
162
+ countUs = self.VarRegionCount
163
+ countThem = len(deltas)
164
+ if countUs + 1 == countThem:
165
+ deltas = list(deltas[1:])
166
+ else:
167
+ assert countUs == countThem, (countUs, countThem)
168
+ deltas = list(deltas)
169
+ self.Item.append(deltas)
170
+ self.ItemCount = len(self.Item)
171
+
172
+
173
+ ot.VarData.addItem = VarData_addItem
174
+
175
+
176
+ def VarRegion_get_support(self, fvar_axes):
177
+ return {
178
+ fvar_axes[i].axisTag: (reg.StartCoord, reg.PeakCoord, reg.EndCoord)
179
+ for i, reg in enumerate(self.VarRegionAxis)
180
+ if reg.PeakCoord != 0
181
+ }
182
+
183
+
184
+ ot.VarRegion.get_support = VarRegion_get_support
185
+
186
+
187
+ def VarStore___bool__(self):
188
+ return bool(self.VarData)
189
+
190
+
191
+ ot.VarStore.__bool__ = VarStore___bool__
192
+
193
+
194
+ class VarStoreInstancer(object):
195
+ def __init__(self, varstore, fvar_axes, location={}):
196
+ self.fvar_axes = fvar_axes
197
+ assert varstore is None or varstore.Format == 1
198
+ self._varData = varstore.VarData if varstore else []
199
+ self._regions = varstore.VarRegionList.Region if varstore else []
200
+ self.setLocation(location)
201
+
202
+ def setLocation(self, location):
203
+ self.location = dict(location)
204
+ self._clearCaches()
205
+
206
+ def _clearCaches(self):
207
+ self._scalars = {}
208
+
209
+ def _getScalar(self, regionIdx):
210
+ scalar = self._scalars.get(regionIdx)
211
+ if scalar is None:
212
+ support = self._regions[regionIdx].get_support(self.fvar_axes)
213
+ scalar = supportScalar(self.location, support)
214
+ self._scalars[regionIdx] = scalar
215
+ return scalar
216
+
217
+ @staticmethod
218
+ def interpolateFromDeltasAndScalars(deltas, scalars):
219
+ delta = 0.0
220
+ for d, s in zip(deltas, scalars):
221
+ if not s:
222
+ continue
223
+ delta += d * s
224
+ return delta
225
+
226
+ def __getitem__(self, varidx):
227
+ major, minor = varidx >> 16, varidx & 0xFFFF
228
+ if varidx == NO_VARIATION_INDEX:
229
+ return 0.0
230
+ varData = self._varData
231
+ scalars = [self._getScalar(ri) for ri in varData[major].VarRegionIndex]
232
+ deltas = varData[major].Item[minor]
233
+ return self.interpolateFromDeltasAndScalars(deltas, scalars)
234
+
235
+ def interpolateFromDeltas(self, varDataIndex, deltas):
236
+ varData = self._varData
237
+ scalars = [self._getScalar(ri) for ri in varData[varDataIndex].VarRegionIndex]
238
+ return self.interpolateFromDeltasAndScalars(deltas, scalars)
239
+
240
+
241
+ #
242
+ # Optimizations
243
+ #
244
+ # retainFirstMap - If true, major 0 mappings are retained. Deltas for unused indices are zeroed
245
+ # advIdxes - Set of major 0 indices for advance deltas to be listed first. Other major 0 indices follow.
246
+
247
+
248
+ def VarStore_subset_varidxes(
249
+ self,
250
+ varIdxes,
251
+ optimize=True,
252
+ retainFirstMap=False,
253
+ advIdxes=set(),
254
+ *,
255
+ VarData="VarData",
256
+ ):
257
+ # Sort out used varIdxes by major/minor.
258
+ used = defaultdict(set)
259
+ for varIdx in varIdxes:
260
+ if varIdx == NO_VARIATION_INDEX:
261
+ continue
262
+ major = varIdx >> 16
263
+ minor = varIdx & 0xFFFF
264
+ used[major].add(minor)
265
+ del varIdxes
266
+
267
+ #
268
+ # Subset VarData
269
+ #
270
+
271
+ varData = getattr(self, VarData)
272
+ newVarData = []
273
+ varDataMap = {NO_VARIATION_INDEX: NO_VARIATION_INDEX}
274
+ for major, data in enumerate(varData):
275
+ usedMinors = used.get(major)
276
+ if usedMinors is None:
277
+ continue
278
+ newMajor = len(newVarData)
279
+ newVarData.append(data)
280
+
281
+ items = data.Item
282
+ newItems = []
283
+ if major == 0 and retainFirstMap:
284
+ for minor in range(len(items)):
285
+ newItems.append(
286
+ items[minor] if minor in usedMinors else [0] * len(items[minor])
287
+ )
288
+ varDataMap[minor] = minor
289
+ else:
290
+ if major == 0:
291
+ minors = sorted(advIdxes) + sorted(usedMinors - advIdxes)
292
+ else:
293
+ minors = sorted(usedMinors)
294
+ for minor in minors:
295
+ newMinor = len(newItems)
296
+ newItems.append(items[minor])
297
+ varDataMap[(major << 16) + minor] = (newMajor << 16) + newMinor
298
+
299
+ data.Item = newItems
300
+ data.ItemCount = len(data.Item)
301
+
302
+ if VarData == "VarData":
303
+ data.calculateNumShorts(optimize=optimize)
304
+
305
+ setattr(self, VarData, newVarData)
306
+ setattr(self, VarData + "Count", len(newVarData))
307
+
308
+ self.prune_regions()
309
+
310
+ return varDataMap
311
+
312
+
313
+ ot.VarStore.subset_varidxes = VarStore_subset_varidxes
314
+
315
+
316
+ def VarStore_prune_regions(self, *, VarData="VarData", VarRegionList="VarRegionList"):
317
+ """Remove unused VarRegions."""
318
+ #
319
+ # Subset VarRegionList
320
+ #
321
+
322
+ # Collect.
323
+ usedRegions = set()
324
+ for data in getattr(self, VarData):
325
+ usedRegions.update(data.VarRegionIndex)
326
+ # Subset.
327
+ regionList = getattr(self, VarRegionList)
328
+ regions = regionList.Region
329
+ newRegions = []
330
+ regionMap = {}
331
+ for i in sorted(usedRegions):
332
+ regionMap[i] = len(newRegions)
333
+ newRegions.append(regions[i])
334
+ regionList.Region = newRegions
335
+ regionList.RegionCount = len(regionList.Region)
336
+ # Map.
337
+ for data in getattr(self, VarData):
338
+ data.VarRegionIndex = [regionMap[i] for i in data.VarRegionIndex]
339
+
340
+
341
+ ot.VarStore.prune_regions = VarStore_prune_regions
342
+
343
+
344
+ def _visit(self, func):
345
+ """Recurse down from self, if type of an object is ot.Device,
346
+ call func() on it. Works on otData-style classes."""
347
+
348
+ if type(self) == ot.Device:
349
+ func(self)
350
+
351
+ elif isinstance(self, list):
352
+ for that in self:
353
+ _visit(that, func)
354
+
355
+ elif hasattr(self, "getConverters") and not hasattr(self, "postRead"):
356
+ for conv in self.getConverters():
357
+ that = getattr(self, conv.name, None)
358
+ if that is not None:
359
+ _visit(that, func)
360
+
361
+ elif isinstance(self, ot.ValueRecord):
362
+ for that in self.__dict__.values():
363
+ _visit(that, func)
364
+
365
+
366
+ def _Device_recordVarIdx(self, s):
367
+ """Add VarIdx in this Device table (if any) to the set s."""
368
+ if self.DeltaFormat == 0x8000:
369
+ s.add((self.StartSize << 16) + self.EndSize)
370
+
371
+
372
+ def Object_collect_device_varidxes(self, varidxes):
373
+ adder = partial(_Device_recordVarIdx, s=varidxes)
374
+ _visit(self, adder)
375
+
376
+
377
+ ot.GDEF.collect_device_varidxes = Object_collect_device_varidxes
378
+ ot.GPOS.collect_device_varidxes = Object_collect_device_varidxes
379
+
380
+
381
+ def _Device_mapVarIdx(self, mapping, done):
382
+ """Map VarIdx in this Device table (if any) through mapping."""
383
+ if id(self) in done:
384
+ return
385
+ done.add(id(self))
386
+ if self.DeltaFormat == 0x8000:
387
+ varIdx = mapping[(self.StartSize << 16) + self.EndSize]
388
+ self.StartSize = varIdx >> 16
389
+ self.EndSize = varIdx & 0xFFFF
390
+
391
+
392
+ def Object_remap_device_varidxes(self, varidxes_map):
393
+ mapper = partial(_Device_mapVarIdx, mapping=varidxes_map, done=set())
394
+ _visit(self, mapper)
395
+
396
+
397
+ ot.GDEF.remap_device_varidxes = Object_remap_device_varidxes
398
+ ot.GPOS.remap_device_varidxes = Object_remap_device_varidxes
399
+
400
+
401
+ class _Encoding(object):
402
+ def __init__(self, chars):
403
+ self.chars = chars
404
+ self.width = bit_count(chars)
405
+ self.columns = self._columns(chars)
406
+ self.overhead = self._characteristic_overhead(self.columns)
407
+ self.items = set()
408
+
409
+ def append(self, row):
410
+ self.items.add(row)
411
+
412
+ def extend(self, lst):
413
+ self.items.update(lst)
414
+
415
+ def width_sort_key(self):
416
+ return self.width, self.chars
417
+
418
+ @staticmethod
419
+ def _characteristic_overhead(columns):
420
+ """Returns overhead in bytes of encoding this characteristic
421
+ as a VarData."""
422
+ c = 4 + 6 # 4 bytes for LOffset, 6 bytes for VarData header
423
+ c += bit_count(columns) * 2
424
+ return c
425
+
426
+ @staticmethod
427
+ def _columns(chars):
428
+ cols = 0
429
+ i = 1
430
+ while chars:
431
+ if chars & 0b1111:
432
+ cols |= i
433
+ chars >>= 4
434
+ i <<= 1
435
+ return cols
436
+
437
+ def gain_from_merging(self, other_encoding):
438
+ combined_chars = other_encoding.chars | self.chars
439
+ combined_width = bit_count(combined_chars)
440
+ combined_columns = self.columns | other_encoding.columns
441
+ combined_overhead = _Encoding._characteristic_overhead(combined_columns)
442
+ combined_gain = (
443
+ +self.overhead
444
+ + other_encoding.overhead
445
+ - combined_overhead
446
+ - (combined_width - self.width) * len(self.items)
447
+ - (combined_width - other_encoding.width) * len(other_encoding.items)
448
+ )
449
+ return combined_gain
450
+
451
+
452
+ class _EncodingDict(dict):
453
+ def __missing__(self, chars):
454
+ r = self[chars] = _Encoding(chars)
455
+ return r
456
+
457
+ def add_row(self, row):
458
+ chars = self._row_characteristics(row)
459
+ self[chars].append(row)
460
+
461
+ @staticmethod
462
+ def _row_characteristics(row):
463
+ """Returns encoding characteristics for a row."""
464
+ longWords = False
465
+
466
+ chars = 0
467
+ i = 1
468
+ for v in row:
469
+ if v:
470
+ chars += i
471
+ if not (-128 <= v <= 127):
472
+ chars += i * 0b0010
473
+ if not (-32768 <= v <= 32767):
474
+ longWords = True
475
+ break
476
+ i <<= 4
477
+
478
+ if longWords:
479
+ # Redo; only allow 2byte/4byte encoding
480
+ chars = 0
481
+ i = 1
482
+ for v in row:
483
+ if v:
484
+ chars += i * 0b0011
485
+ if not (-32768 <= v <= 32767):
486
+ chars += i * 0b1100
487
+ i <<= 4
488
+
489
+ return chars
490
+
491
+
492
+ def VarStore_optimize(self, use_NO_VARIATION_INDEX=True, quantization=1):
493
+ """Optimize storage. Returns mapping from old VarIdxes to new ones."""
494
+
495
+ # Overview:
496
+ #
497
+ # For each VarData row, we first extend it with zeroes to have
498
+ # one column per region in VarRegionList. We then group the
499
+ # rows into _Encoding objects, by their "characteristic" bitmap.
500
+ # The characteristic bitmap is a binary number representing how
501
+ # many bytes each column of the data takes up to encode. Each
502
+ # column is encoded in four bits. For example, if a column has
503
+ # only values in the range -128..127, it would only have a single
504
+ # bit set in the characteristic bitmap for that column. If it has
505
+ # values in the range -32768..32767, it would have two bits set.
506
+ # The number of ones in the characteristic bitmap is the "width"
507
+ # of the encoding.
508
+ #
509
+ # Each encoding as such has a number of "active" (ie. non-zero)
510
+ # columns. The overhead of encoding the characteristic bitmap
511
+ # is 10 bytes, plus 2 bytes per active column.
512
+ #
513
+ # When an encoding is merged into another one, if the characteristic
514
+ # of the old encoding is a subset of the new one, then the overhead
515
+ # of the old encoding is completely eliminated. However, each row
516
+ # now would require more bytes to encode, to the tune of one byte
517
+ # per characteristic bit that is active in the new encoding but not
518
+ # in the old one.
519
+ #
520
+ # The "gain" of merging two encodings is how many bytes we save by doing so.
521
+ #
522
+ # High-level algorithm:
523
+ #
524
+ # - Each encoding has a minimal way to encode it. However, because
525
+ # of the overhead of encoding the characteristic bitmap, it may
526
+ # be beneficial to merge two encodings together, if there is
527
+ # gain in doing so. As such, we need to search for the best
528
+ # such successive merges.
529
+ #
530
+ # Algorithm:
531
+ #
532
+ # - Put all encodings into a "todo" list.
533
+ #
534
+ # - Sort todo list (for stability) by width_sort_key(), which is a tuple
535
+ # of the following items:
536
+ # * The "width" of the encoding.
537
+ # * The characteristic bitmap of the encoding, with higher-numbered
538
+ # columns compared first.
539
+ #
540
+ # - Make a priority-queue of the gain from combining each two
541
+ # encodings in the todo list. The priority queue is sorted by
542
+ # decreasing gain. Only positive gains are included.
543
+ #
544
+ # - While priority queue is not empty:
545
+ # - Pop the first item from the priority queue,
546
+ # - Merge the two encodings it represents,
547
+ # - Remove the two encodings from the todo list,
548
+ # - Insert positive gains from combining the new encoding with
549
+ # all existing todo list items into the priority queue,
550
+ # - If a todo list item with the same characteristic bitmap as
551
+ # the new encoding exists, remove it from the todo list and
552
+ # merge it into the new encoding.
553
+ # - Insert the new encoding into the todo list,
554
+ #
555
+ # - Encode all remaining items in the todo list.
556
+ #
557
+ # The output is then sorted for stability, in the following way:
558
+ # - The VarRegionList of the input is kept intact.
559
+ # - The VarData is sorted by the same width_sort_key() used at the beginning.
560
+ # - Within each VarData, the items are sorted as vectors of numbers.
561
+ #
562
+ # Finally, each VarData is optimized to remove the empty columns and
563
+ # reorder columns as needed.
564
+
565
+ # TODO
566
+ # Check that no two VarRegions are the same; if they are, fold them.
567
+
568
+ n = len(self.VarRegionList.Region) # Number of columns
569
+ zeroes = [0] * n
570
+
571
+ front_mapping = {} # Map from old VarIdxes to full row tuples
572
+
573
+ encodings = _EncodingDict()
574
+
575
+ # Collect all items into a set of full rows (with lots of zeroes.)
576
+ for major, data in enumerate(self.VarData):
577
+ regionIndices = data.VarRegionIndex
578
+
579
+ for minor, item in enumerate(data.Item):
580
+ row = list(zeroes)
581
+
582
+ if quantization == 1:
583
+ for regionIdx, v in zip(regionIndices, item):
584
+ row[regionIdx] += v
585
+ else:
586
+ for regionIdx, v in zip(regionIndices, item):
587
+ row[regionIdx] += (
588
+ round(v / quantization) * quantization
589
+ ) # TODO https://github.com/fonttools/fonttools/pull/3126#discussion_r1205439785
590
+
591
+ row = tuple(row)
592
+
593
+ if use_NO_VARIATION_INDEX and not any(row):
594
+ front_mapping[(major << 16) + minor] = None
595
+ continue
596
+
597
+ encodings.add_row(row)
598
+ front_mapping[(major << 16) + minor] = row
599
+
600
+ # Prepare for the main algorithm.
601
+ todo = sorted(encodings.values(), key=_Encoding.width_sort_key)
602
+ del encodings
603
+
604
+ # Repeatedly pick two best encodings to combine, and combine them.
605
+
606
+ heap = []
607
+ for i, encoding in enumerate(todo):
608
+ for j in range(i + 1, len(todo)):
609
+ other_encoding = todo[j]
610
+ combining_gain = encoding.gain_from_merging(other_encoding)
611
+ if combining_gain > 0:
612
+ heappush(heap, (-combining_gain, i, j))
613
+
614
+ while heap:
615
+ _, i, j = heappop(heap)
616
+ if todo[i] is None or todo[j] is None:
617
+ continue
618
+
619
+ encoding, other_encoding = todo[i], todo[j]
620
+ todo[i], todo[j] = None, None
621
+
622
+ # Combine the two encodings
623
+ combined_chars = other_encoding.chars | encoding.chars
624
+ combined_encoding = _Encoding(combined_chars)
625
+ combined_encoding.extend(encoding.items)
626
+ combined_encoding.extend(other_encoding.items)
627
+
628
+ for k, enc in enumerate(todo):
629
+ if enc is None:
630
+ continue
631
+
632
+ # In the unlikely event that the same encoding exists already,
633
+ # combine it.
634
+ if enc.chars == combined_chars:
635
+ combined_encoding.extend(enc.items)
636
+ todo[k] = None
637
+ continue
638
+
639
+ combining_gain = combined_encoding.gain_from_merging(enc)
640
+ if combining_gain > 0:
641
+ heappush(heap, (-combining_gain, k, len(todo)))
642
+
643
+ todo.append(combined_encoding)
644
+
645
+ encodings = [encoding for encoding in todo if encoding is not None]
646
+
647
+ # Assemble final store.
648
+ back_mapping = {} # Mapping from full rows to new VarIdxes
649
+ encodings.sort(key=_Encoding.width_sort_key)
650
+ self.VarData = []
651
+ for encoding in encodings:
652
+ items = sorted(encoding.items)
653
+
654
+ while items:
655
+ major = len(self.VarData)
656
+ data = ot.VarData()
657
+ self.VarData.append(data)
658
+ data.VarRegionIndex = range(n)
659
+ data.VarRegionCount = len(data.VarRegionIndex)
660
+
661
+ # Each major can only encode up to 0xFFFF entries.
662
+ data.Item, items = items[:0xFFFF], items[0xFFFF:]
663
+
664
+ for minor, item in enumerate(data.Item):
665
+ back_mapping[item] = (major << 16) + minor
666
+
667
+ # Compile final mapping.
668
+ varidx_map = {NO_VARIATION_INDEX: NO_VARIATION_INDEX}
669
+ for k, v in front_mapping.items():
670
+ varidx_map[k] = back_mapping[v] if v is not None else NO_VARIATION_INDEX
671
+
672
+ # Recalculate things and go home.
673
+ self.VarRegionList.RegionCount = len(self.VarRegionList.Region)
674
+ self.VarDataCount = len(self.VarData)
675
+ for data in self.VarData:
676
+ data.ItemCount = len(data.Item)
677
+ data.optimize()
678
+
679
+ # Remove unused regions.
680
+ self.prune_regions()
681
+
682
+ return varidx_map
683
+
684
+
685
+ ot.VarStore.optimize = VarStore_optimize
686
+
687
+
688
+ def main(args=None):
689
+ """Optimize a font's GDEF variation store"""
690
+ from argparse import ArgumentParser
691
+ from fontTools import configLogger
692
+ from fontTools.ttLib import TTFont
693
+ from fontTools.ttLib.tables.otBase import OTTableWriter
694
+
695
+ parser = ArgumentParser(prog="varLib.varStore", description=main.__doc__)
696
+ parser.add_argument("--quantization", type=int, default=1)
697
+ parser.add_argument("fontfile")
698
+ parser.add_argument("outfile", nargs="?")
699
+ options = parser.parse_args(args)
700
+
701
+ # TODO: allow user to configure logging via command-line options
702
+ configLogger(level="INFO")
703
+
704
+ quantization = options.quantization
705
+ fontfile = options.fontfile
706
+ outfile = options.outfile
707
+
708
+ font = TTFont(fontfile)
709
+ gdef = font["GDEF"]
710
+ store = gdef.table.VarStore
711
+
712
+ writer = OTTableWriter()
713
+ store.compile(writer, font)
714
+ size = len(writer.getAllData())
715
+ print("Before: %7d bytes" % size)
716
+
717
+ varidx_map = store.optimize(quantization=quantization)
718
+
719
+ writer = OTTableWriter()
720
+ store.compile(writer, font)
721
+ size = len(writer.getAllData())
722
+ print("After: %7d bytes" % size)
723
+
724
+ if outfile is not None:
725
+ gdef.table.remap_device_varidxes(varidx_map)
726
+ if "GPOS" in font:
727
+ font["GPOS"].table.remap_device_varidxes(varidx_map)
728
+
729
+ font.save(outfile)
730
+
731
+
732
+ if __name__ == "__main__":
733
+ import sys
734
+
735
+ if len(sys.argv) > 1:
736
+ sys.exit(main())
737
+ import doctest
738
+
739
+ sys.exit(doctest.testmod().failed)