fonttools 4.58.3__cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fonttools might be problematic. Click here for more details.

Files changed (334) 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/cffLib/CFF2ToCFF.py +203 -0
  6. fontTools/cffLib/CFFToCFF2.py +305 -0
  7. fontTools/cffLib/__init__.py +3694 -0
  8. fontTools/cffLib/specializer.py +927 -0
  9. fontTools/cffLib/transforms.py +490 -0
  10. fontTools/cffLib/width.py +210 -0
  11. fontTools/colorLib/__init__.py +0 -0
  12. fontTools/colorLib/builder.py +664 -0
  13. fontTools/colorLib/errors.py +2 -0
  14. fontTools/colorLib/geometry.py +143 -0
  15. fontTools/colorLib/table_builder.py +223 -0
  16. fontTools/colorLib/unbuilder.py +81 -0
  17. fontTools/config/__init__.py +90 -0
  18. fontTools/cu2qu/__init__.py +15 -0
  19. fontTools/cu2qu/__main__.py +6 -0
  20. fontTools/cu2qu/benchmark.py +54 -0
  21. fontTools/cu2qu/cli.py +198 -0
  22. fontTools/cu2qu/cu2qu.c +15545 -0
  23. fontTools/cu2qu/cu2qu.cpython-39-x86_64-linux-gnu.so +0 -0
  24. fontTools/cu2qu/cu2qu.py +531 -0
  25. fontTools/cu2qu/errors.py +77 -0
  26. fontTools/cu2qu/ufo.py +349 -0
  27. fontTools/designspaceLib/__init__.py +3338 -0
  28. fontTools/designspaceLib/__main__.py +6 -0
  29. fontTools/designspaceLib/split.py +475 -0
  30. fontTools/designspaceLib/statNames.py +260 -0
  31. fontTools/designspaceLib/types.py +147 -0
  32. fontTools/encodings/MacRoman.py +258 -0
  33. fontTools/encodings/StandardEncoding.py +258 -0
  34. fontTools/encodings/__init__.py +1 -0
  35. fontTools/encodings/codecs.py +135 -0
  36. fontTools/feaLib/__init__.py +4 -0
  37. fontTools/feaLib/__main__.py +78 -0
  38. fontTools/feaLib/ast.py +2142 -0
  39. fontTools/feaLib/builder.py +1796 -0
  40. fontTools/feaLib/error.py +22 -0
  41. fontTools/feaLib/lexer.c +17336 -0
  42. fontTools/feaLib/lexer.cpython-39-x86_64-linux-gnu.so +0 -0
  43. fontTools/feaLib/lexer.py +287 -0
  44. fontTools/feaLib/location.py +12 -0
  45. fontTools/feaLib/lookupDebugInfo.py +12 -0
  46. fontTools/feaLib/parser.py +2379 -0
  47. fontTools/feaLib/variableScalar.py +113 -0
  48. fontTools/fontBuilder.py +1014 -0
  49. fontTools/help.py +36 -0
  50. fontTools/merge/__init__.py +248 -0
  51. fontTools/merge/__main__.py +6 -0
  52. fontTools/merge/base.py +81 -0
  53. fontTools/merge/cmap.py +173 -0
  54. fontTools/merge/layout.py +526 -0
  55. fontTools/merge/options.py +85 -0
  56. fontTools/merge/tables.py +352 -0
  57. fontTools/merge/unicode.py +78 -0
  58. fontTools/merge/util.py +143 -0
  59. fontTools/misc/__init__.py +1 -0
  60. fontTools/misc/arrayTools.py +424 -0
  61. fontTools/misc/bezierTools.c +40136 -0
  62. fontTools/misc/bezierTools.cpython-39-x86_64-linux-gnu.so +0 -0
  63. fontTools/misc/bezierTools.py +1497 -0
  64. fontTools/misc/classifyTools.py +170 -0
  65. fontTools/misc/cliTools.py +53 -0
  66. fontTools/misc/configTools.py +349 -0
  67. fontTools/misc/cython.py +27 -0
  68. fontTools/misc/dictTools.py +83 -0
  69. fontTools/misc/eexec.py +119 -0
  70. fontTools/misc/encodingTools.py +72 -0
  71. fontTools/misc/etree.py +456 -0
  72. fontTools/misc/filenames.py +245 -0
  73. fontTools/misc/fixedTools.py +253 -0
  74. fontTools/misc/intTools.py +25 -0
  75. fontTools/misc/iterTools.py +12 -0
  76. fontTools/misc/lazyTools.py +42 -0
  77. fontTools/misc/loggingTools.py +543 -0
  78. fontTools/misc/macCreatorType.py +56 -0
  79. fontTools/misc/macRes.py +261 -0
  80. fontTools/misc/plistlib/__init__.py +681 -0
  81. fontTools/misc/plistlib/py.typed +0 -0
  82. fontTools/misc/psCharStrings.py +1496 -0
  83. fontTools/misc/psLib.py +398 -0
  84. fontTools/misc/psOperators.py +572 -0
  85. fontTools/misc/py23.py +96 -0
  86. fontTools/misc/roundTools.py +110 -0
  87. fontTools/misc/sstruct.py +231 -0
  88. fontTools/misc/symfont.py +242 -0
  89. fontTools/misc/testTools.py +233 -0
  90. fontTools/misc/textTools.py +154 -0
  91. fontTools/misc/timeTools.py +88 -0
  92. fontTools/misc/transform.py +516 -0
  93. fontTools/misc/treeTools.py +45 -0
  94. fontTools/misc/vector.py +147 -0
  95. fontTools/misc/visitor.py +142 -0
  96. fontTools/misc/xmlReader.py +188 -0
  97. fontTools/misc/xmlWriter.py +204 -0
  98. fontTools/mtiLib/__init__.py +1400 -0
  99. fontTools/mtiLib/__main__.py +5 -0
  100. fontTools/otlLib/__init__.py +1 -0
  101. fontTools/otlLib/builder.py +3435 -0
  102. fontTools/otlLib/error.py +11 -0
  103. fontTools/otlLib/maxContextCalc.py +96 -0
  104. fontTools/otlLib/optimize/__init__.py +53 -0
  105. fontTools/otlLib/optimize/__main__.py +6 -0
  106. fontTools/otlLib/optimize/gpos.py +439 -0
  107. fontTools/pens/__init__.py +1 -0
  108. fontTools/pens/areaPen.py +52 -0
  109. fontTools/pens/basePen.py +475 -0
  110. fontTools/pens/boundsPen.py +98 -0
  111. fontTools/pens/cairoPen.py +26 -0
  112. fontTools/pens/cocoaPen.py +26 -0
  113. fontTools/pens/cu2quPen.py +325 -0
  114. fontTools/pens/explicitClosingLinePen.py +101 -0
  115. fontTools/pens/filterPen.py +241 -0
  116. fontTools/pens/freetypePen.py +462 -0
  117. fontTools/pens/hashPointPen.py +89 -0
  118. fontTools/pens/momentsPen.c +13459 -0
  119. fontTools/pens/momentsPen.cpython-39-x86_64-linux-gnu.so +0 -0
  120. fontTools/pens/momentsPen.py +879 -0
  121. fontTools/pens/perimeterPen.py +69 -0
  122. fontTools/pens/pointInsidePen.py +192 -0
  123. fontTools/pens/pointPen.py +609 -0
  124. fontTools/pens/qtPen.py +29 -0
  125. fontTools/pens/qu2cuPen.py +105 -0
  126. fontTools/pens/quartzPen.py +43 -0
  127. fontTools/pens/recordingPen.py +335 -0
  128. fontTools/pens/reportLabPen.py +79 -0
  129. fontTools/pens/reverseContourPen.py +96 -0
  130. fontTools/pens/roundingPen.py +130 -0
  131. fontTools/pens/statisticsPen.py +312 -0
  132. fontTools/pens/svgPathPen.py +310 -0
  133. fontTools/pens/t2CharStringPen.py +88 -0
  134. fontTools/pens/teePen.py +55 -0
  135. fontTools/pens/transformPen.py +115 -0
  136. fontTools/pens/ttGlyphPen.py +335 -0
  137. fontTools/pens/wxPen.py +29 -0
  138. fontTools/qu2cu/__init__.py +15 -0
  139. fontTools/qu2cu/__main__.py +7 -0
  140. fontTools/qu2cu/benchmark.py +56 -0
  141. fontTools/qu2cu/cli.py +125 -0
  142. fontTools/qu2cu/qu2cu.c +16738 -0
  143. fontTools/qu2cu/qu2cu.cpython-39-x86_64-linux-gnu.so +0 -0
  144. fontTools/qu2cu/qu2cu.py +405 -0
  145. fontTools/subset/__init__.py +3929 -0
  146. fontTools/subset/__main__.py +6 -0
  147. fontTools/subset/cff.py +184 -0
  148. fontTools/subset/svg.py +253 -0
  149. fontTools/subset/util.py +25 -0
  150. fontTools/svgLib/__init__.py +3 -0
  151. fontTools/svgLib/path/__init__.py +65 -0
  152. fontTools/svgLib/path/arc.py +154 -0
  153. fontTools/svgLib/path/parser.py +322 -0
  154. fontTools/svgLib/path/shapes.py +183 -0
  155. fontTools/t1Lib/__init__.py +648 -0
  156. fontTools/tfmLib.py +460 -0
  157. fontTools/ttLib/__init__.py +30 -0
  158. fontTools/ttLib/__main__.py +148 -0
  159. fontTools/ttLib/macUtils.py +54 -0
  160. fontTools/ttLib/removeOverlaps.py +393 -0
  161. fontTools/ttLib/reorderGlyphs.py +285 -0
  162. fontTools/ttLib/scaleUpem.py +436 -0
  163. fontTools/ttLib/sfnt.py +662 -0
  164. fontTools/ttLib/standardGlyphOrder.py +271 -0
  165. fontTools/ttLib/tables/B_A_S_E_.py +14 -0
  166. fontTools/ttLib/tables/BitmapGlyphMetrics.py +64 -0
  167. fontTools/ttLib/tables/C_B_D_T_.py +113 -0
  168. fontTools/ttLib/tables/C_B_L_C_.py +19 -0
  169. fontTools/ttLib/tables/C_F_F_.py +61 -0
  170. fontTools/ttLib/tables/C_F_F__2.py +26 -0
  171. fontTools/ttLib/tables/C_O_L_R_.py +165 -0
  172. fontTools/ttLib/tables/C_P_A_L_.py +305 -0
  173. fontTools/ttLib/tables/D_S_I_G_.py +158 -0
  174. fontTools/ttLib/tables/D__e_b_g.py +35 -0
  175. fontTools/ttLib/tables/DefaultTable.py +49 -0
  176. fontTools/ttLib/tables/E_B_D_T_.py +835 -0
  177. fontTools/ttLib/tables/E_B_L_C_.py +718 -0
  178. fontTools/ttLib/tables/F_F_T_M_.py +52 -0
  179. fontTools/ttLib/tables/F__e_a_t.py +149 -0
  180. fontTools/ttLib/tables/G_D_E_F_.py +13 -0
  181. fontTools/ttLib/tables/G_M_A_P_.py +148 -0
  182. fontTools/ttLib/tables/G_P_K_G_.py +133 -0
  183. fontTools/ttLib/tables/G_P_O_S_.py +14 -0
  184. fontTools/ttLib/tables/G_S_U_B_.py +13 -0
  185. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  186. fontTools/ttLib/tables/G__l_a_t.py +235 -0
  187. fontTools/ttLib/tables/G__l_o_c.py +85 -0
  188. fontTools/ttLib/tables/H_V_A_R_.py +13 -0
  189. fontTools/ttLib/tables/J_S_T_F_.py +13 -0
  190. fontTools/ttLib/tables/L_T_S_H_.py +58 -0
  191. fontTools/ttLib/tables/M_A_T_H_.py +13 -0
  192. fontTools/ttLib/tables/M_E_T_A_.py +352 -0
  193. fontTools/ttLib/tables/M_V_A_R_.py +13 -0
  194. fontTools/ttLib/tables/O_S_2f_2.py +752 -0
  195. fontTools/ttLib/tables/S_I_N_G_.py +99 -0
  196. fontTools/ttLib/tables/S_T_A_T_.py +15 -0
  197. fontTools/ttLib/tables/S_V_G_.py +223 -0
  198. fontTools/ttLib/tables/S__i_l_f.py +1040 -0
  199. fontTools/ttLib/tables/S__i_l_l.py +92 -0
  200. fontTools/ttLib/tables/T_S_I_B_.py +13 -0
  201. fontTools/ttLib/tables/T_S_I_C_.py +14 -0
  202. fontTools/ttLib/tables/T_S_I_D_.py +13 -0
  203. fontTools/ttLib/tables/T_S_I_J_.py +13 -0
  204. fontTools/ttLib/tables/T_S_I_P_.py +13 -0
  205. fontTools/ttLib/tables/T_S_I_S_.py +13 -0
  206. fontTools/ttLib/tables/T_S_I_V_.py +26 -0
  207. fontTools/ttLib/tables/T_S_I__0.py +70 -0
  208. fontTools/ttLib/tables/T_S_I__1.py +166 -0
  209. fontTools/ttLib/tables/T_S_I__2.py +17 -0
  210. fontTools/ttLib/tables/T_S_I__3.py +22 -0
  211. fontTools/ttLib/tables/T_S_I__5.py +60 -0
  212. fontTools/ttLib/tables/T_T_F_A_.py +14 -0
  213. fontTools/ttLib/tables/TupleVariation.py +884 -0
  214. fontTools/ttLib/tables/V_A_R_C_.py +12 -0
  215. fontTools/ttLib/tables/V_D_M_X_.py +249 -0
  216. fontTools/ttLib/tables/V_O_R_G_.py +165 -0
  217. fontTools/ttLib/tables/V_V_A_R_.py +13 -0
  218. fontTools/ttLib/tables/__init__.py +98 -0
  219. fontTools/ttLib/tables/_a_n_k_r.py +15 -0
  220. fontTools/ttLib/tables/_a_v_a_r.py +191 -0
  221. fontTools/ttLib/tables/_b_s_l_n.py +15 -0
  222. fontTools/ttLib/tables/_c_i_d_g.py +24 -0
  223. fontTools/ttLib/tables/_c_m_a_p.py +1591 -0
  224. fontTools/ttLib/tables/_c_v_a_r.py +94 -0
  225. fontTools/ttLib/tables/_c_v_t.py +57 -0
  226. fontTools/ttLib/tables/_f_e_a_t.py +15 -0
  227. fontTools/ttLib/tables/_f_p_g_m.py +62 -0
  228. fontTools/ttLib/tables/_f_v_a_r.py +261 -0
  229. fontTools/ttLib/tables/_g_a_s_p.py +63 -0
  230. fontTools/ttLib/tables/_g_c_i_d.py +13 -0
  231. fontTools/ttLib/tables/_g_l_y_f.py +2312 -0
  232. fontTools/ttLib/tables/_g_v_a_r.py +337 -0
  233. fontTools/ttLib/tables/_h_d_m_x.py +127 -0
  234. fontTools/ttLib/tables/_h_e_a_d.py +130 -0
  235. fontTools/ttLib/tables/_h_h_e_a.py +147 -0
  236. fontTools/ttLib/tables/_h_m_t_x.py +160 -0
  237. fontTools/ttLib/tables/_k_e_r_n.py +289 -0
  238. fontTools/ttLib/tables/_l_c_a_r.py +13 -0
  239. fontTools/ttLib/tables/_l_o_c_a.py +70 -0
  240. fontTools/ttLib/tables/_l_t_a_g.py +72 -0
  241. fontTools/ttLib/tables/_m_a_x_p.py +147 -0
  242. fontTools/ttLib/tables/_m_e_t_a.py +112 -0
  243. fontTools/ttLib/tables/_m_o_r_t.py +14 -0
  244. fontTools/ttLib/tables/_m_o_r_x.py +15 -0
  245. fontTools/ttLib/tables/_n_a_m_e.py +1237 -0
  246. fontTools/ttLib/tables/_o_p_b_d.py +14 -0
  247. fontTools/ttLib/tables/_p_o_s_t.py +320 -0
  248. fontTools/ttLib/tables/_p_r_e_p.py +16 -0
  249. fontTools/ttLib/tables/_p_r_o_p.py +12 -0
  250. fontTools/ttLib/tables/_s_b_i_x.py +129 -0
  251. fontTools/ttLib/tables/_t_r_a_k.py +332 -0
  252. fontTools/ttLib/tables/_v_h_e_a.py +139 -0
  253. fontTools/ttLib/tables/_v_m_t_x.py +19 -0
  254. fontTools/ttLib/tables/asciiTable.py +20 -0
  255. fontTools/ttLib/tables/grUtils.py +92 -0
  256. fontTools/ttLib/tables/otBase.py +1466 -0
  257. fontTools/ttLib/tables/otConverters.py +2068 -0
  258. fontTools/ttLib/tables/otData.py +6400 -0
  259. fontTools/ttLib/tables/otTables.py +2708 -0
  260. fontTools/ttLib/tables/otTraverse.py +163 -0
  261. fontTools/ttLib/tables/sbixGlyph.py +149 -0
  262. fontTools/ttLib/tables/sbixStrike.py +177 -0
  263. fontTools/ttLib/tables/table_API_readme.txt +91 -0
  264. fontTools/ttLib/tables/ttProgram.py +594 -0
  265. fontTools/ttLib/ttCollection.py +125 -0
  266. fontTools/ttLib/ttFont.py +1157 -0
  267. fontTools/ttLib/ttGlyphSet.py +490 -0
  268. fontTools/ttLib/ttVisitor.py +32 -0
  269. fontTools/ttLib/woff2.py +1683 -0
  270. fontTools/ttx.py +479 -0
  271. fontTools/ufoLib/__init__.py +2477 -0
  272. fontTools/ufoLib/converters.py +398 -0
  273. fontTools/ufoLib/errors.py +30 -0
  274. fontTools/ufoLib/etree.py +6 -0
  275. fontTools/ufoLib/filenames.py +346 -0
  276. fontTools/ufoLib/glifLib.py +2029 -0
  277. fontTools/ufoLib/kerning.py +121 -0
  278. fontTools/ufoLib/plistlib.py +47 -0
  279. fontTools/ufoLib/pointPen.py +6 -0
  280. fontTools/ufoLib/utils.py +79 -0
  281. fontTools/ufoLib/validators.py +1186 -0
  282. fontTools/unicode.py +50 -0
  283. fontTools/unicodedata/Blocks.py +801 -0
  284. fontTools/unicodedata/Mirrored.py +446 -0
  285. fontTools/unicodedata/OTTags.py +50 -0
  286. fontTools/unicodedata/ScriptExtensions.py +826 -0
  287. fontTools/unicodedata/Scripts.py +3617 -0
  288. fontTools/unicodedata/__init__.py +302 -0
  289. fontTools/varLib/__init__.py +1517 -0
  290. fontTools/varLib/__main__.py +6 -0
  291. fontTools/varLib/avar.py +260 -0
  292. fontTools/varLib/avarPlanner.py +1004 -0
  293. fontTools/varLib/builder.py +215 -0
  294. fontTools/varLib/cff.py +631 -0
  295. fontTools/varLib/errors.py +219 -0
  296. fontTools/varLib/featureVars.py +695 -0
  297. fontTools/varLib/hvar.py +113 -0
  298. fontTools/varLib/instancer/__init__.py +1946 -0
  299. fontTools/varLib/instancer/__main__.py +5 -0
  300. fontTools/varLib/instancer/featureVars.py +190 -0
  301. fontTools/varLib/instancer/names.py +388 -0
  302. fontTools/varLib/instancer/solver.py +309 -0
  303. fontTools/varLib/interpolatable.py +1209 -0
  304. fontTools/varLib/interpolatableHelpers.py +396 -0
  305. fontTools/varLib/interpolatablePlot.py +1269 -0
  306. fontTools/varLib/interpolatableTestContourOrder.py +82 -0
  307. fontTools/varLib/interpolatableTestStartingPoint.py +107 -0
  308. fontTools/varLib/interpolate_layout.py +124 -0
  309. fontTools/varLib/iup.c +19830 -0
  310. fontTools/varLib/iup.cpython-39-x86_64-linux-gnu.so +0 -0
  311. fontTools/varLib/iup.py +490 -0
  312. fontTools/varLib/merger.py +1717 -0
  313. fontTools/varLib/models.py +642 -0
  314. fontTools/varLib/multiVarStore.py +253 -0
  315. fontTools/varLib/mutator.py +518 -0
  316. fontTools/varLib/mvar.py +40 -0
  317. fontTools/varLib/plot.py +238 -0
  318. fontTools/varLib/stat.py +149 -0
  319. fontTools/varLib/varStore.py +739 -0
  320. fontTools/voltLib/__init__.py +5 -0
  321. fontTools/voltLib/__main__.py +206 -0
  322. fontTools/voltLib/ast.py +452 -0
  323. fontTools/voltLib/error.py +12 -0
  324. fontTools/voltLib/lexer.py +99 -0
  325. fontTools/voltLib/parser.py +664 -0
  326. fontTools/voltLib/voltToFea.py +911 -0
  327. fonttools-4.58.3.data/data/share/man/man1/ttx.1 +225 -0
  328. fonttools-4.58.3.dist-info/METADATA +2133 -0
  329. fonttools-4.58.3.dist-info/RECORD +334 -0
  330. fonttools-4.58.3.dist-info/WHEEL +6 -0
  331. fonttools-4.58.3.dist-info/entry_points.txt +5 -0
  332. fonttools-4.58.3.dist-info/licenses/LICENSE +21 -0
  333. fonttools-4.58.3.dist-info/licenses/LICENSE.external +359 -0
  334. fonttools-4.58.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,490 @@
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 desubroutinize(cff):
98
+ for fontName in cff.fontNames:
99
+ font = cff[fontName]
100
+ cs = font.CharStrings
101
+ for c in cs.values():
102
+ c.decompile()
103
+ subrs = getattr(c.private, "Subrs", [])
104
+ decompiler = _DesubroutinizingT2Decompiler(subrs, c.globalSubrs, c.private)
105
+ decompiler.execute(c)
106
+ c.program = c._desubroutinized
107
+ del c._desubroutinized
108
+ # Delete all the local subrs
109
+ if hasattr(font, "FDArray"):
110
+ for fd in font.FDArray:
111
+ pd = fd.Private
112
+ if hasattr(pd, "Subrs"):
113
+ del pd.Subrs
114
+ if "Subrs" in pd.rawDict:
115
+ del pd.rawDict["Subrs"]
116
+ else:
117
+ pd = font.Private
118
+ if hasattr(pd, "Subrs"):
119
+ del pd.Subrs
120
+ if "Subrs" in pd.rawDict:
121
+ del pd.rawDict["Subrs"]
122
+ # as well as the global subrs
123
+ cff.GlobalSubrs.clear()
124
+
125
+
126
+ class _MarkingT2Decompiler(SimpleT2Decompiler):
127
+ def __init__(self, localSubrs, globalSubrs, private):
128
+ SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, private)
129
+ for subrs in [localSubrs, globalSubrs]:
130
+ if subrs and not hasattr(subrs, "_used"):
131
+ subrs._used = set()
132
+
133
+ def op_callsubr(self, index):
134
+ self.localSubrs._used.add(self.operandStack[-1] + self.localBias)
135
+ SimpleT2Decompiler.op_callsubr(self, index)
136
+
137
+ def op_callgsubr(self, index):
138
+ self.globalSubrs._used.add(self.operandStack[-1] + self.globalBias)
139
+ SimpleT2Decompiler.op_callgsubr(self, index)
140
+
141
+
142
+ class _DehintingT2Decompiler(T2WidthExtractor):
143
+ class Hints(object):
144
+ def __init__(self):
145
+ # Whether calling this charstring produces any hint stems
146
+ # Note that if a charstring starts with hintmask, it will
147
+ # have has_hint set to True, because it *might* produce an
148
+ # implicit vstem if called under certain conditions.
149
+ self.has_hint = False
150
+ # Index to start at to drop all hints
151
+ self.last_hint = 0
152
+ # Index up to which we know more hints are possible.
153
+ # Only relevant if status is 0 or 1.
154
+ self.last_checked = 0
155
+ # The status means:
156
+ # 0: after dropping hints, this charstring is empty
157
+ # 1: after dropping hints, there may be more hints
158
+ # continuing after this, or there might be
159
+ # other things. Not clear yet.
160
+ # 2: no more hints possible after this charstring
161
+ self.status = 0
162
+ # Has hintmask instructions; not recursive
163
+ self.has_hintmask = False
164
+ # List of indices of calls to empty subroutines to remove.
165
+ self.deletions = []
166
+
167
+ pass
168
+
169
+ def __init__(
170
+ self, css, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private=None
171
+ ):
172
+ self._css = css
173
+ T2WidthExtractor.__init__(
174
+ self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX
175
+ )
176
+ self.private = private
177
+
178
+ def execute(self, charString):
179
+ old_hints = charString._hints if hasattr(charString, "_hints") else None
180
+ charString._hints = self.Hints()
181
+
182
+ T2WidthExtractor.execute(self, charString)
183
+
184
+ hints = charString._hints
185
+
186
+ if hints.has_hint or hints.has_hintmask:
187
+ self._css.add(charString)
188
+
189
+ if hints.status != 2:
190
+ # Check from last_check, make sure we didn't have any operators.
191
+ for i in range(hints.last_checked, len(charString.program) - 1):
192
+ if isinstance(charString.program[i], str):
193
+ hints.status = 2
194
+ break
195
+ else:
196
+ hints.status = 1 # There's *something* here
197
+ hints.last_checked = len(charString.program)
198
+
199
+ if old_hints:
200
+ assert hints.__dict__ == old_hints.__dict__
201
+
202
+ def op_callsubr(self, index):
203
+ subr = self.localSubrs[self.operandStack[-1] + self.localBias]
204
+ T2WidthExtractor.op_callsubr(self, index)
205
+ self.processSubr(index, subr)
206
+
207
+ def op_callgsubr(self, index):
208
+ subr = self.globalSubrs[self.operandStack[-1] + self.globalBias]
209
+ T2WidthExtractor.op_callgsubr(self, index)
210
+ self.processSubr(index, subr)
211
+
212
+ def op_hstem(self, index):
213
+ T2WidthExtractor.op_hstem(self, index)
214
+ self.processHint(index)
215
+
216
+ def op_vstem(self, index):
217
+ T2WidthExtractor.op_vstem(self, index)
218
+ self.processHint(index)
219
+
220
+ def op_hstemhm(self, index):
221
+ T2WidthExtractor.op_hstemhm(self, index)
222
+ self.processHint(index)
223
+
224
+ def op_vstemhm(self, index):
225
+ T2WidthExtractor.op_vstemhm(self, index)
226
+ self.processHint(index)
227
+
228
+ def op_hintmask(self, index):
229
+ rv = T2WidthExtractor.op_hintmask(self, index)
230
+ self.processHintmask(index)
231
+ return rv
232
+
233
+ def op_cntrmask(self, index):
234
+ rv = T2WidthExtractor.op_cntrmask(self, index)
235
+ self.processHintmask(index)
236
+ return rv
237
+
238
+ def processHintmask(self, index):
239
+ cs = self.callingStack[-1]
240
+ hints = cs._hints
241
+ hints.has_hintmask = True
242
+ if hints.status != 2:
243
+ # Check from last_check, see if we may be an implicit vstem
244
+ for i in range(hints.last_checked, index - 1):
245
+ if isinstance(cs.program[i], str):
246
+ hints.status = 2
247
+ break
248
+ else:
249
+ # We are an implicit vstem
250
+ hints.has_hint = True
251
+ hints.last_hint = index + 1
252
+ hints.status = 0
253
+ hints.last_checked = index + 1
254
+
255
+ def processHint(self, index):
256
+ cs = self.callingStack[-1]
257
+ hints = cs._hints
258
+ hints.has_hint = True
259
+ hints.last_hint = index
260
+ hints.last_checked = index
261
+
262
+ def processSubr(self, index, subr):
263
+ cs = self.callingStack[-1]
264
+ hints = cs._hints
265
+ subr_hints = subr._hints
266
+
267
+ # Check from last_check, make sure we didn't have
268
+ # any operators.
269
+ if hints.status != 2:
270
+ for i in range(hints.last_checked, index - 1):
271
+ if isinstance(cs.program[i], str):
272
+ hints.status = 2
273
+ break
274
+ hints.last_checked = index
275
+
276
+ if hints.status != 2:
277
+ if subr_hints.has_hint:
278
+ hints.has_hint = True
279
+
280
+ # Decide where to chop off from
281
+ if subr_hints.status == 0:
282
+ hints.last_hint = index
283
+ else:
284
+ hints.last_hint = index - 2 # Leave the subr call in
285
+
286
+ elif subr_hints.status == 0:
287
+ hints.deletions.append(index)
288
+
289
+ hints.status = max(hints.status, subr_hints.status)
290
+
291
+
292
+ def _cs_subset_subroutines(charstring, subrs, gsubrs):
293
+ p = charstring.program
294
+ for i in range(1, len(p)):
295
+ if p[i] == "callsubr":
296
+ assert isinstance(p[i - 1], int)
297
+ p[i - 1] = subrs._used.index(p[i - 1] + subrs._old_bias) - subrs._new_bias
298
+ elif p[i] == "callgsubr":
299
+ assert isinstance(p[i - 1], int)
300
+ p[i - 1] = (
301
+ gsubrs._used.index(p[i - 1] + gsubrs._old_bias) - gsubrs._new_bias
302
+ )
303
+
304
+
305
+ def _cs_drop_hints(charstring):
306
+ hints = charstring._hints
307
+
308
+ if hints.deletions:
309
+ p = charstring.program
310
+ for idx in reversed(hints.deletions):
311
+ del p[idx - 2 : idx]
312
+
313
+ if hints.has_hint:
314
+ assert not hints.deletions or hints.last_hint <= hints.deletions[0]
315
+ charstring.program = charstring.program[hints.last_hint :]
316
+ if not charstring.program:
317
+ # TODO CFF2 no need for endchar.
318
+ charstring.program.append("endchar")
319
+ if hasattr(charstring, "width"):
320
+ # Insert width back if needed
321
+ if charstring.width != charstring.private.defaultWidthX:
322
+ # For CFF2 charstrings, this should never happen
323
+ assert (
324
+ charstring.private.defaultWidthX is not None
325
+ ), "CFF2 CharStrings must not have an initial width value"
326
+ charstring.program.insert(
327
+ 0, charstring.width - charstring.private.nominalWidthX
328
+ )
329
+
330
+ if hints.has_hintmask:
331
+ i = 0
332
+ p = charstring.program
333
+ while i < len(p):
334
+ if p[i] in ["hintmask", "cntrmask"]:
335
+ assert i + 1 <= len(p)
336
+ del p[i : i + 2]
337
+ continue
338
+ i += 1
339
+
340
+ assert len(charstring.program)
341
+
342
+ del charstring._hints
343
+
344
+
345
+ def remove_hints(cff, *, removeUnusedSubrs: bool = True):
346
+ for fontname in cff.keys():
347
+ font = cff[fontname]
348
+ cs = font.CharStrings
349
+ # This can be tricky, but doesn't have to. What we do is:
350
+ #
351
+ # - Run all used glyph charstrings and recurse into subroutines,
352
+ # - For each charstring (including subroutines), if it has any
353
+ # of the hint stem operators, we mark it as such.
354
+ # Upon returning, for each charstring we note all the
355
+ # subroutine calls it makes that (recursively) contain a stem,
356
+ # - Dropping hinting then consists of the following two ops:
357
+ # * Drop the piece of the program in each charstring before the
358
+ # last call to a stem op or a stem-calling subroutine,
359
+ # * Drop all hintmask operations.
360
+ # - It's trickier... A hintmask right after hints and a few numbers
361
+ # will act as an implicit vstemhm. As such, we track whether
362
+ # we have seen any non-hint operators so far and do the right
363
+ # thing, recursively... Good luck understanding that :(
364
+ css = set()
365
+ for c in cs.values():
366
+ c.decompile()
367
+ subrs = getattr(c.private, "Subrs", [])
368
+ decompiler = _DehintingT2Decompiler(
369
+ css,
370
+ subrs,
371
+ c.globalSubrs,
372
+ c.private.nominalWidthX,
373
+ c.private.defaultWidthX,
374
+ c.private,
375
+ )
376
+ decompiler.execute(c)
377
+ c.width = decompiler.width
378
+ for charstring in css:
379
+ _cs_drop_hints(charstring)
380
+ del css
381
+
382
+ # Drop font-wide hinting values
383
+ all_privs = []
384
+ if hasattr(font, "FDArray"):
385
+ all_privs.extend(fd.Private for fd in font.FDArray)
386
+ else:
387
+ all_privs.append(font.Private)
388
+ for priv in all_privs:
389
+ for k in [
390
+ "BlueValues",
391
+ "OtherBlues",
392
+ "FamilyBlues",
393
+ "FamilyOtherBlues",
394
+ "BlueScale",
395
+ "BlueShift",
396
+ "BlueFuzz",
397
+ "StemSnapH",
398
+ "StemSnapV",
399
+ "StdHW",
400
+ "StdVW",
401
+ "ForceBold",
402
+ "LanguageGroup",
403
+ "ExpansionFactor",
404
+ ]:
405
+ if hasattr(priv, k):
406
+ setattr(priv, k, None)
407
+ if removeUnusedSubrs:
408
+ remove_unused_subroutines(cff)
409
+
410
+
411
+ def _pd_delete_empty_subrs(private_dict):
412
+ if hasattr(private_dict, "Subrs") and not private_dict.Subrs:
413
+ if "Subrs" in private_dict.rawDict:
414
+ del private_dict.rawDict["Subrs"]
415
+ del private_dict.Subrs
416
+
417
+
418
+ def remove_unused_subroutines(cff):
419
+ for fontname in cff.keys():
420
+ font = cff[fontname]
421
+ cs = font.CharStrings
422
+ # Renumber subroutines to remove unused ones
423
+
424
+ # Mark all used subroutines
425
+ for c in cs.values():
426
+ subrs = getattr(c.private, "Subrs", [])
427
+ decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs, c.private)
428
+ decompiler.execute(c)
429
+
430
+ all_subrs = [font.GlobalSubrs]
431
+ if hasattr(font, "FDArray"):
432
+ all_subrs.extend(
433
+ fd.Private.Subrs
434
+ for fd in font.FDArray
435
+ if hasattr(fd.Private, "Subrs") and fd.Private.Subrs
436
+ )
437
+ elif hasattr(font.Private, "Subrs") and font.Private.Subrs:
438
+ all_subrs.append(font.Private.Subrs)
439
+
440
+ subrs = set(subrs) # Remove duplicates
441
+
442
+ # Prepare
443
+ for subrs in all_subrs:
444
+ if not hasattr(subrs, "_used"):
445
+ subrs._used = set()
446
+ subrs._used = _uniq_sort(subrs._used)
447
+ subrs._old_bias = calcSubrBias(subrs)
448
+ subrs._new_bias = calcSubrBias(subrs._used)
449
+
450
+ # Renumber glyph charstrings
451
+ for c in cs.values():
452
+ subrs = getattr(c.private, "Subrs", None)
453
+ _cs_subset_subroutines(c, subrs, font.GlobalSubrs)
454
+
455
+ # Renumber subroutines themselves
456
+ for subrs in all_subrs:
457
+ if subrs == font.GlobalSubrs:
458
+ if not hasattr(font, "FDArray") and hasattr(font.Private, "Subrs"):
459
+ local_subrs = font.Private.Subrs
460
+ elif (
461
+ hasattr(font, "FDArray")
462
+ and len(font.FDArray) == 1
463
+ and hasattr(font.FDArray[0].Private, "Subrs")
464
+ ):
465
+ # Technically we shouldn't do this. But I've run into fonts that do it.
466
+ local_subrs = font.FDArray[0].Private.Subrs
467
+ else:
468
+ local_subrs = None
469
+ else:
470
+ local_subrs = subrs
471
+
472
+ subrs.items = [subrs.items[i] for i in subrs._used]
473
+ if hasattr(subrs, "file"):
474
+ del subrs.file
475
+ if hasattr(subrs, "offsets"):
476
+ del subrs.offsets
477
+
478
+ for subr in subrs.items:
479
+ _cs_subset_subroutines(subr, local_subrs, font.GlobalSubrs)
480
+
481
+ # Delete local SubrsIndex if empty
482
+ if hasattr(font, "FDArray"):
483
+ for fd in font.FDArray:
484
+ _pd_delete_empty_subrs(fd.Private)
485
+ else:
486
+ _pd_delete_empty_subrs(font.Private)
487
+
488
+ # Cleanup
489
+ for subrs in all_subrs:
490
+ 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