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,495 @@
1
+ from fontTools.misc.psCharStrings import (
2
+ SimpleT2Decompiler,
3
+ T2WidthExtractor,
4
+ calcSubrBias,
5
+ )
6
+
7
+
8
+ def _uniq_sort(l):
9
+ return sorted(set(l))
10
+
11
+
12
+ class StopHintCountEvent(Exception):
13
+ pass
14
+
15
+
16
+ class _DesubroutinizingT2Decompiler(SimpleT2Decompiler):
17
+ stop_hintcount_ops = (
18
+ "op_hintmask",
19
+ "op_cntrmask",
20
+ "op_rmoveto",
21
+ "op_hmoveto",
22
+ "op_vmoveto",
23
+ )
24
+
25
+ def __init__(self, localSubrs, globalSubrs, private=None):
26
+ SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, private)
27
+
28
+ def execute(self, charString):
29
+ self.need_hintcount = True # until proven otherwise
30
+ for op_name in self.stop_hintcount_ops:
31
+ setattr(self, op_name, self.stop_hint_count)
32
+
33
+ if hasattr(charString, "_desubroutinized"):
34
+ # If a charstring has already been desubroutinized, we will still
35
+ # need to execute it if we need to count hints in order to
36
+ # compute the byte length for mask arguments, and haven't finished
37
+ # counting hints pairs.
38
+ if self.need_hintcount and self.callingStack:
39
+ try:
40
+ SimpleT2Decompiler.execute(self, charString)
41
+ except StopHintCountEvent:
42
+ del self.callingStack[-1]
43
+ return
44
+
45
+ charString._patches = []
46
+ SimpleT2Decompiler.execute(self, charString)
47
+ desubroutinized = charString.program[:]
48
+ for idx, expansion in reversed(charString._patches):
49
+ assert idx >= 2
50
+ assert desubroutinized[idx - 1] in [
51
+ "callsubr",
52
+ "callgsubr",
53
+ ], desubroutinized[idx - 1]
54
+ assert type(desubroutinized[idx - 2]) == int
55
+ if expansion[-1] == "return":
56
+ expansion = expansion[:-1]
57
+ desubroutinized[idx - 2 : idx] = expansion
58
+ if not self.private.in_cff2:
59
+ if "endchar" in desubroutinized:
60
+ # Cut off after first endchar
61
+ desubroutinized = desubroutinized[
62
+ : desubroutinized.index("endchar") + 1
63
+ ]
64
+
65
+ charString._desubroutinized = desubroutinized
66
+ del charString._patches
67
+
68
+ def op_callsubr(self, index):
69
+ subr = self.localSubrs[self.operandStack[-1] + self.localBias]
70
+ SimpleT2Decompiler.op_callsubr(self, index)
71
+ self.processSubr(index, subr)
72
+
73
+ def op_callgsubr(self, index):
74
+ subr = self.globalSubrs[self.operandStack[-1] + self.globalBias]
75
+ SimpleT2Decompiler.op_callgsubr(self, index)
76
+ self.processSubr(index, subr)
77
+
78
+ def stop_hint_count(self, *args):
79
+ self.need_hintcount = False
80
+ for op_name in self.stop_hintcount_ops:
81
+ setattr(self, op_name, None)
82
+ cs = self.callingStack[-1]
83
+ if hasattr(cs, "_desubroutinized"):
84
+ raise StopHintCountEvent()
85
+
86
+ def op_hintmask(self, index):
87
+ SimpleT2Decompiler.op_hintmask(self, index)
88
+ if self.need_hintcount:
89
+ self.stop_hint_count()
90
+
91
+ def processSubr(self, index, subr):
92
+ cs = self.callingStack[-1]
93
+ if not hasattr(cs, "_desubroutinized"):
94
+ cs._patches.append((index, subr._desubroutinized))
95
+
96
+
97
+ def desubroutinizeCharString(cs):
98
+ """Desubroutinize a charstring in-place."""
99
+ cs.decompile()
100
+ subrs = getattr(cs.private, "Subrs", [])
101
+ decompiler = _DesubroutinizingT2Decompiler(subrs, cs.globalSubrs, cs.private)
102
+ decompiler.execute(cs)
103
+ cs.program = cs._desubroutinized
104
+ del cs._desubroutinized
105
+
106
+
107
+ def desubroutinize(cff):
108
+ for fontName in cff.fontNames:
109
+ font = cff[fontName]
110
+ cs = font.CharStrings
111
+ for c in cs.values():
112
+ desubroutinizeCharString(c)
113
+ # Delete all the local subrs
114
+ if hasattr(font, "FDArray"):
115
+ for fd in font.FDArray:
116
+ pd = fd.Private
117
+ if hasattr(pd, "Subrs"):
118
+ del pd.Subrs
119
+ if "Subrs" in pd.rawDict:
120
+ del pd.rawDict["Subrs"]
121
+ else:
122
+ pd = font.Private
123
+ if hasattr(pd, "Subrs"):
124
+ del pd.Subrs
125
+ if "Subrs" in pd.rawDict:
126
+ del pd.rawDict["Subrs"]
127
+ # as well as the global subrs
128
+ cff.GlobalSubrs.clear()
129
+
130
+
131
+ class _MarkingT2Decompiler(SimpleT2Decompiler):
132
+ def __init__(self, localSubrs, globalSubrs, private):
133
+ SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, private)
134
+ for subrs in [localSubrs, globalSubrs]:
135
+ if subrs and not hasattr(subrs, "_used"):
136
+ subrs._used = set()
137
+
138
+ def op_callsubr(self, index):
139
+ self.localSubrs._used.add(self.operandStack[-1] + self.localBias)
140
+ SimpleT2Decompiler.op_callsubr(self, index)
141
+
142
+ def op_callgsubr(self, index):
143
+ self.globalSubrs._used.add(self.operandStack[-1] + self.globalBias)
144
+ SimpleT2Decompiler.op_callgsubr(self, index)
145
+
146
+
147
+ class _DehintingT2Decompiler(T2WidthExtractor):
148
+ class Hints(object):
149
+ def __init__(self):
150
+ # Whether calling this charstring produces any hint stems
151
+ # Note that if a charstring starts with hintmask, it will
152
+ # have has_hint set to True, because it *might* produce an
153
+ # implicit vstem if called under certain conditions.
154
+ self.has_hint = False
155
+ # Index to start at to drop all hints
156
+ self.last_hint = 0
157
+ # Index up to which we know more hints are possible.
158
+ # Only relevant if status is 0 or 1.
159
+ self.last_checked = 0
160
+ # The status means:
161
+ # 0: after dropping hints, this charstring is empty
162
+ # 1: after dropping hints, there may be more hints
163
+ # continuing after this, or there might be
164
+ # other things. Not clear yet.
165
+ # 2: no more hints possible after this charstring
166
+ self.status = 0
167
+ # Has hintmask instructions; not recursive
168
+ self.has_hintmask = False
169
+ # List of indices of calls to empty subroutines to remove.
170
+ self.deletions = []
171
+
172
+ pass
173
+
174
+ def __init__(
175
+ self, css, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private=None
176
+ ):
177
+ self._css = css
178
+ T2WidthExtractor.__init__(
179
+ self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX
180
+ )
181
+ self.private = private
182
+
183
+ def execute(self, charString):
184
+ old_hints = charString._hints if hasattr(charString, "_hints") else None
185
+ charString._hints = self.Hints()
186
+
187
+ T2WidthExtractor.execute(self, charString)
188
+
189
+ hints = charString._hints
190
+
191
+ if hints.has_hint or hints.has_hintmask:
192
+ self._css.add(charString)
193
+
194
+ if hints.status != 2:
195
+ # Check from last_check, make sure we didn't have any operators.
196
+ for i in range(hints.last_checked, len(charString.program) - 1):
197
+ if isinstance(charString.program[i], str):
198
+ hints.status = 2
199
+ break
200
+ else:
201
+ hints.status = 1 # There's *something* here
202
+ hints.last_checked = len(charString.program)
203
+
204
+ if old_hints:
205
+ assert hints.__dict__ == old_hints.__dict__
206
+
207
+ def op_callsubr(self, index):
208
+ subr = self.localSubrs[self.operandStack[-1] + self.localBias]
209
+ T2WidthExtractor.op_callsubr(self, index)
210
+ self.processSubr(index, subr)
211
+
212
+ def op_callgsubr(self, index):
213
+ subr = self.globalSubrs[self.operandStack[-1] + self.globalBias]
214
+ T2WidthExtractor.op_callgsubr(self, index)
215
+ self.processSubr(index, subr)
216
+
217
+ def op_hstem(self, index):
218
+ T2WidthExtractor.op_hstem(self, index)
219
+ self.processHint(index)
220
+
221
+ def op_vstem(self, index):
222
+ T2WidthExtractor.op_vstem(self, index)
223
+ self.processHint(index)
224
+
225
+ def op_hstemhm(self, index):
226
+ T2WidthExtractor.op_hstemhm(self, index)
227
+ self.processHint(index)
228
+
229
+ def op_vstemhm(self, index):
230
+ T2WidthExtractor.op_vstemhm(self, index)
231
+ self.processHint(index)
232
+
233
+ def op_hintmask(self, index):
234
+ rv = T2WidthExtractor.op_hintmask(self, index)
235
+ self.processHintmask(index)
236
+ return rv
237
+
238
+ def op_cntrmask(self, index):
239
+ rv = T2WidthExtractor.op_cntrmask(self, index)
240
+ self.processHintmask(index)
241
+ return rv
242
+
243
+ def processHintmask(self, index):
244
+ cs = self.callingStack[-1]
245
+ hints = cs._hints
246
+ hints.has_hintmask = True
247
+ if hints.status != 2:
248
+ # Check from last_check, see if we may be an implicit vstem
249
+ for i in range(hints.last_checked, index - 1):
250
+ if isinstance(cs.program[i], str):
251
+ hints.status = 2
252
+ break
253
+ else:
254
+ # We are an implicit vstem
255
+ hints.has_hint = True
256
+ hints.last_hint = index + 1
257
+ hints.status = 0
258
+ hints.last_checked = index + 1
259
+
260
+ def processHint(self, index):
261
+ cs = self.callingStack[-1]
262
+ hints = cs._hints
263
+ hints.has_hint = True
264
+ hints.last_hint = index
265
+ hints.last_checked = index
266
+
267
+ def processSubr(self, index, subr):
268
+ cs = self.callingStack[-1]
269
+ hints = cs._hints
270
+ subr_hints = subr._hints
271
+
272
+ # Check from last_check, make sure we didn't have
273
+ # any operators.
274
+ if hints.status != 2:
275
+ for i in range(hints.last_checked, index - 1):
276
+ if isinstance(cs.program[i], str):
277
+ hints.status = 2
278
+ break
279
+ hints.last_checked = index
280
+
281
+ if hints.status != 2:
282
+ if subr_hints.has_hint:
283
+ hints.has_hint = True
284
+
285
+ # Decide where to chop off from
286
+ if subr_hints.status == 0:
287
+ hints.last_hint = index
288
+ else:
289
+ hints.last_hint = index - 2 # Leave the subr call in
290
+
291
+ elif subr_hints.status == 0:
292
+ hints.deletions.append(index)
293
+
294
+ hints.status = max(hints.status, subr_hints.status)
295
+
296
+
297
+ def _cs_subset_subroutines(charstring, subrs, gsubrs):
298
+ p = charstring.program
299
+ for i in range(1, len(p)):
300
+ if p[i] == "callsubr":
301
+ assert isinstance(p[i - 1], int)
302
+ p[i - 1] = subrs._used.index(p[i - 1] + subrs._old_bias) - subrs._new_bias
303
+ elif p[i] == "callgsubr":
304
+ assert isinstance(p[i - 1], int)
305
+ p[i - 1] = (
306
+ gsubrs._used.index(p[i - 1] + gsubrs._old_bias) - gsubrs._new_bias
307
+ )
308
+
309
+
310
+ def _cs_drop_hints(charstring):
311
+ hints = charstring._hints
312
+
313
+ if hints.deletions:
314
+ p = charstring.program
315
+ for idx in reversed(hints.deletions):
316
+ del p[idx - 2 : idx]
317
+
318
+ if hints.has_hint:
319
+ assert not hints.deletions or hints.last_hint <= hints.deletions[0]
320
+ charstring.program = charstring.program[hints.last_hint :]
321
+ if not charstring.program:
322
+ # TODO CFF2 no need for endchar.
323
+ charstring.program.append("endchar")
324
+ if hasattr(charstring, "width"):
325
+ # Insert width back if needed
326
+ if charstring.width != charstring.private.defaultWidthX:
327
+ # For CFF2 charstrings, this should never happen
328
+ assert (
329
+ charstring.private.defaultWidthX is not None
330
+ ), "CFF2 CharStrings must not have an initial width value"
331
+ charstring.program.insert(
332
+ 0, charstring.width - charstring.private.nominalWidthX
333
+ )
334
+
335
+ if hints.has_hintmask:
336
+ i = 0
337
+ p = charstring.program
338
+ while i < len(p):
339
+ if p[i] in ["hintmask", "cntrmask"]:
340
+ assert i + 1 <= len(p)
341
+ del p[i : i + 2]
342
+ continue
343
+ i += 1
344
+
345
+ assert len(charstring.program)
346
+
347
+ del charstring._hints
348
+
349
+
350
+ def remove_hints(cff, *, removeUnusedSubrs: bool = True):
351
+ for fontname in cff.keys():
352
+ font = cff[fontname]
353
+ cs = font.CharStrings
354
+ # This can be tricky, but doesn't have to. What we do is:
355
+ #
356
+ # - Run all used glyph charstrings and recurse into subroutines,
357
+ # - For each charstring (including subroutines), if it has any
358
+ # of the hint stem operators, we mark it as such.
359
+ # Upon returning, for each charstring we note all the
360
+ # subroutine calls it makes that (recursively) contain a stem,
361
+ # - Dropping hinting then consists of the following two ops:
362
+ # * Drop the piece of the program in each charstring before the
363
+ # last call to a stem op or a stem-calling subroutine,
364
+ # * Drop all hintmask operations.
365
+ # - It's trickier... A hintmask right after hints and a few numbers
366
+ # will act as an implicit vstemhm. As such, we track whether
367
+ # we have seen any non-hint operators so far and do the right
368
+ # thing, recursively... Good luck understanding that :(
369
+ css = set()
370
+ for c in cs.values():
371
+ c.decompile()
372
+ subrs = getattr(c.private, "Subrs", [])
373
+ decompiler = _DehintingT2Decompiler(
374
+ css,
375
+ subrs,
376
+ c.globalSubrs,
377
+ c.private.nominalWidthX,
378
+ c.private.defaultWidthX,
379
+ c.private,
380
+ )
381
+ decompiler.execute(c)
382
+ c.width = decompiler.width
383
+ for charstring in css:
384
+ _cs_drop_hints(charstring)
385
+ del css
386
+
387
+ # Drop font-wide hinting values
388
+ all_privs = []
389
+ if hasattr(font, "FDArray"):
390
+ all_privs.extend(fd.Private for fd in font.FDArray)
391
+ else:
392
+ all_privs.append(font.Private)
393
+ for priv in all_privs:
394
+ for k in [
395
+ "BlueValues",
396
+ "OtherBlues",
397
+ "FamilyBlues",
398
+ "FamilyOtherBlues",
399
+ "BlueScale",
400
+ "BlueShift",
401
+ "BlueFuzz",
402
+ "StemSnapH",
403
+ "StemSnapV",
404
+ "StdHW",
405
+ "StdVW",
406
+ "ForceBold",
407
+ "LanguageGroup",
408
+ "ExpansionFactor",
409
+ ]:
410
+ if hasattr(priv, k):
411
+ setattr(priv, k, None)
412
+ if removeUnusedSubrs:
413
+ remove_unused_subroutines(cff)
414
+
415
+
416
+ def _pd_delete_empty_subrs(private_dict):
417
+ if hasattr(private_dict, "Subrs") and not private_dict.Subrs:
418
+ if "Subrs" in private_dict.rawDict:
419
+ del private_dict.rawDict["Subrs"]
420
+ del private_dict.Subrs
421
+
422
+
423
+ def remove_unused_subroutines(cff):
424
+ for fontname in cff.keys():
425
+ font = cff[fontname]
426
+ cs = font.CharStrings
427
+ # Renumber subroutines to remove unused ones
428
+
429
+ # Mark all used subroutines
430
+ for c in cs.values():
431
+ subrs = getattr(c.private, "Subrs", [])
432
+ decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs, c.private)
433
+ decompiler.execute(c)
434
+
435
+ all_subrs = [font.GlobalSubrs]
436
+ if hasattr(font, "FDArray"):
437
+ all_subrs.extend(
438
+ fd.Private.Subrs
439
+ for fd in font.FDArray
440
+ if hasattr(fd.Private, "Subrs") and fd.Private.Subrs
441
+ )
442
+ elif hasattr(font.Private, "Subrs") and font.Private.Subrs:
443
+ all_subrs.append(font.Private.Subrs)
444
+
445
+ subrs = set(subrs) # Remove duplicates
446
+
447
+ # Prepare
448
+ for subrs in all_subrs:
449
+ if not hasattr(subrs, "_used"):
450
+ subrs._used = set()
451
+ subrs._used = _uniq_sort(subrs._used)
452
+ subrs._old_bias = calcSubrBias(subrs)
453
+ subrs._new_bias = calcSubrBias(subrs._used)
454
+
455
+ # Renumber glyph charstrings
456
+ for c in cs.values():
457
+ subrs = getattr(c.private, "Subrs", None)
458
+ _cs_subset_subroutines(c, subrs, font.GlobalSubrs)
459
+
460
+ # Renumber subroutines themselves
461
+ for subrs in all_subrs:
462
+ if subrs == font.GlobalSubrs:
463
+ if not hasattr(font, "FDArray") and hasattr(font.Private, "Subrs"):
464
+ local_subrs = font.Private.Subrs
465
+ elif (
466
+ hasattr(font, "FDArray")
467
+ and len(font.FDArray) == 1
468
+ and hasattr(font.FDArray[0].Private, "Subrs")
469
+ ):
470
+ # Technically we shouldn't do this. But I've run into fonts that do it.
471
+ local_subrs = font.FDArray[0].Private.Subrs
472
+ else:
473
+ local_subrs = None
474
+ else:
475
+ local_subrs = subrs
476
+
477
+ subrs.items = [subrs.items[i] for i in subrs._used]
478
+ if hasattr(subrs, "file"):
479
+ del subrs.file
480
+ if hasattr(subrs, "offsets"):
481
+ del subrs.offsets
482
+
483
+ for subr in subrs.items:
484
+ _cs_subset_subroutines(subr, local_subrs, font.GlobalSubrs)
485
+
486
+ # Delete local SubrsIndex if empty
487
+ if hasattr(font, "FDArray"):
488
+ for fd in font.FDArray:
489
+ _pd_delete_empty_subrs(fd.Private)
490
+ else:
491
+ _pd_delete_empty_subrs(font.Private)
492
+
493
+ # Cleanup
494
+ for subrs in all_subrs:
495
+ del subrs._used, subrs._old_bias, subrs._new_bias
@@ -0,0 +1,210 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """T2CharString glyph width optimizer.
4
+
5
+ CFF glyphs whose width equals the CFF Private dictionary's ``defaultWidthX``
6
+ value do not need to specify their width in their charstring, saving bytes.
7
+ This module determines the optimum ``defaultWidthX`` and ``nominalWidthX``
8
+ values for a font, when provided with a list of glyph widths."""
9
+
10
+ from fontTools.ttLib import TTFont
11
+ from collections import defaultdict
12
+ from operator import add
13
+ from functools import reduce
14
+
15
+
16
+ __all__ = ["optimizeWidths", "main"]
17
+
18
+
19
+ class missingdict(dict):
20
+ def __init__(self, missing_func):
21
+ self.missing_func = missing_func
22
+
23
+ def __missing__(self, v):
24
+ return self.missing_func(v)
25
+
26
+
27
+ def cumSum(f, op=add, start=0, decreasing=False):
28
+ keys = sorted(f.keys())
29
+ minx, maxx = keys[0], keys[-1]
30
+
31
+ total = reduce(op, f.values(), start)
32
+
33
+ if decreasing:
34
+ missing = lambda x: start if x > maxx else total
35
+ domain = range(maxx, minx - 1, -1)
36
+ else:
37
+ missing = lambda x: start if x < minx else total
38
+ domain = range(minx, maxx + 1)
39
+
40
+ out = missingdict(missing)
41
+
42
+ v = start
43
+ for x in domain:
44
+ v = op(v, f[x])
45
+ out[x] = v
46
+
47
+ return out
48
+
49
+
50
+ def byteCost(widths, default, nominal):
51
+ if not hasattr(widths, "items"):
52
+ d = defaultdict(int)
53
+ for w in widths:
54
+ d[w] += 1
55
+ widths = d
56
+
57
+ cost = 0
58
+ for w, freq in widths.items():
59
+ if w == default:
60
+ continue
61
+ diff = abs(w - nominal)
62
+ if diff <= 107:
63
+ cost += freq
64
+ elif diff <= 1131:
65
+ cost += freq * 2
66
+ else:
67
+ cost += freq * 5
68
+ return cost
69
+
70
+
71
+ def optimizeWidthsBruteforce(widths):
72
+ """Bruteforce version. Veeeeeeeeeeeeeeeeery slow. Only works for smallests of fonts."""
73
+
74
+ d = defaultdict(int)
75
+ for w in widths:
76
+ d[w] += 1
77
+
78
+ # Maximum number of bytes using default can possibly save
79
+ maxDefaultAdvantage = 5 * max(d.values())
80
+
81
+ minw, maxw = min(widths), max(widths)
82
+ domain = list(range(minw, maxw + 1))
83
+
84
+ bestCostWithoutDefault = min(byteCost(widths, None, nominal) for nominal in domain)
85
+
86
+ bestCost = len(widths) * 5 + 1
87
+ for nominal in domain:
88
+ if byteCost(widths, None, nominal) > bestCost + maxDefaultAdvantage:
89
+ continue
90
+ for default in domain:
91
+ cost = byteCost(widths, default, nominal)
92
+ if cost < bestCost:
93
+ bestCost = cost
94
+ bestDefault = default
95
+ bestNominal = nominal
96
+
97
+ return bestDefault, bestNominal
98
+
99
+
100
+ def optimizeWidths(widths):
101
+ """Given a list of glyph widths, or dictionary mapping glyph width to number of
102
+ glyphs having that, returns a tuple of best CFF default and nominal glyph widths.
103
+
104
+ This algorithm is linear in UPEM+numGlyphs."""
105
+
106
+ if not hasattr(widths, "items"):
107
+ d = defaultdict(int)
108
+ for w in widths:
109
+ d[w] += 1
110
+ widths = d
111
+
112
+ keys = sorted(widths.keys())
113
+ minw, maxw = keys[0], keys[-1]
114
+ domain = list(range(minw, maxw + 1))
115
+
116
+ # Cumulative sum/max forward/backward.
117
+ cumFrqU = cumSum(widths, op=add)
118
+ cumMaxU = cumSum(widths, op=max)
119
+ cumFrqD = cumSum(widths, op=add, decreasing=True)
120
+ cumMaxD = cumSum(widths, op=max, decreasing=True)
121
+
122
+ # Cost per nominal choice, without default consideration.
123
+ nomnCostU = missingdict(
124
+ lambda x: cumFrqU[x] + cumFrqU[x - 108] + cumFrqU[x - 1132] * 3
125
+ )
126
+ nomnCostD = missingdict(
127
+ lambda x: cumFrqD[x] + cumFrqD[x + 108] + cumFrqD[x + 1132] * 3
128
+ )
129
+ nomnCost = missingdict(lambda x: nomnCostU[x] + nomnCostD[x] - widths[x])
130
+
131
+ # Cost-saving per nominal choice, by best default choice.
132
+ dfltCostU = missingdict(
133
+ lambda x: max(cumMaxU[x], cumMaxU[x - 108] * 2, cumMaxU[x - 1132] * 5)
134
+ )
135
+ dfltCostD = missingdict(
136
+ lambda x: max(cumMaxD[x], cumMaxD[x + 108] * 2, cumMaxD[x + 1132] * 5)
137
+ )
138
+ dfltCost = missingdict(lambda x: max(dfltCostU[x], dfltCostD[x]))
139
+
140
+ # Combined cost per nominal choice.
141
+ bestCost = missingdict(lambda x: nomnCost[x] - dfltCost[x])
142
+
143
+ # Best nominal.
144
+ nominal = min(domain, key=lambda x: bestCost[x])
145
+
146
+ # Work back the best default.
147
+ bestC = bestCost[nominal]
148
+ dfltC = nomnCost[nominal] - bestCost[nominal]
149
+ ends = []
150
+ if dfltC == dfltCostU[nominal]:
151
+ starts = [nominal, nominal - 108, nominal - 1132]
152
+ for start in starts:
153
+ while cumMaxU[start] and cumMaxU[start] == cumMaxU[start - 1]:
154
+ start -= 1
155
+ ends.append(start)
156
+ else:
157
+ starts = [nominal, nominal + 108, nominal + 1132]
158
+ for start in starts:
159
+ while cumMaxD[start] and cumMaxD[start] == cumMaxD[start + 1]:
160
+ start += 1
161
+ ends.append(start)
162
+ default = min(ends, key=lambda default: byteCost(widths, default, nominal))
163
+
164
+ return default, nominal
165
+
166
+
167
+ def main(args=None):
168
+ """Calculate optimum defaultWidthX/nominalWidthX values"""
169
+
170
+ import argparse
171
+
172
+ parser = argparse.ArgumentParser(
173
+ "fonttools cffLib.width",
174
+ description=main.__doc__,
175
+ )
176
+ parser.add_argument(
177
+ "inputs", metavar="FILE", type=str, nargs="+", help="Input TTF files"
178
+ )
179
+ parser.add_argument(
180
+ "-b",
181
+ "--brute-force",
182
+ dest="brute",
183
+ action="store_true",
184
+ help="Use brute-force approach (VERY slow)",
185
+ )
186
+
187
+ args = parser.parse_args(args)
188
+
189
+ for fontfile in args.inputs:
190
+ font = TTFont(fontfile)
191
+ hmtx = font["hmtx"]
192
+ widths = [m[0] for m in hmtx.metrics.values()]
193
+ if args.brute:
194
+ default, nominal = optimizeWidthsBruteforce(widths)
195
+ else:
196
+ default, nominal = optimizeWidths(widths)
197
+ print(
198
+ "glyphs=%d default=%d nominal=%d byteCost=%d"
199
+ % (len(widths), default, nominal, byteCost(widths, default, nominal))
200
+ )
201
+
202
+
203
+ if __name__ == "__main__":
204
+ import sys
205
+
206
+ if len(sys.argv) == 1:
207
+ import doctest
208
+
209
+ sys.exit(doctest.testmod().failed)
210
+ main()
File without changes