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,3694 @@
1
+ """cffLib: read/write Adobe CFF fonts
2
+
3
+ OpenType fonts with PostScript outlines embed a completely independent
4
+ font file in Adobe's *Compact Font Format*. So dealing with OpenType fonts
5
+ requires also dealing with CFF. This module allows you to read and write
6
+ fonts written in the CFF format.
7
+
8
+ In 2016, OpenType 1.8 introduced the `CFF2 <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2>`_
9
+ format which, along with other changes, extended the CFF format to deal with
10
+ the demands of variable fonts. This module parses both original CFF and CFF2.
11
+
12
+ """
13
+
14
+ from fontTools.misc import sstruct
15
+ from fontTools.misc import psCharStrings
16
+ from fontTools.misc.arrayTools import unionRect, intRect
17
+ from fontTools.misc.textTools import (
18
+ bytechr,
19
+ byteord,
20
+ bytesjoin,
21
+ tobytes,
22
+ tostr,
23
+ safeEval,
24
+ )
25
+ from fontTools.ttLib import TTFont
26
+ from fontTools.ttLib.tables.otBase import OTTableWriter
27
+ from fontTools.ttLib.tables.otBase import OTTableReader
28
+ from fontTools.ttLib.tables import otTables as ot
29
+ from io import BytesIO
30
+ import struct
31
+ import logging
32
+ import re
33
+
34
+ # mute cffLib debug messages when running ttx in verbose mode
35
+ DEBUG = logging.DEBUG - 1
36
+ log = logging.getLogger(__name__)
37
+
38
+ cffHeaderFormat = """
39
+ major: B
40
+ minor: B
41
+ hdrSize: B
42
+ """
43
+
44
+ maxStackLimit = 513
45
+ # maxstack operator has been deprecated. max stack is now always 513.
46
+
47
+
48
+ class CFFFontSet(object):
49
+ """A CFF font "file" can contain more than one font, although this is
50
+ extremely rare (and not allowed within OpenType fonts).
51
+
52
+ This class is the entry point for parsing a CFF table. To actually
53
+ manipulate the data inside the CFF font, you will want to access the
54
+ ``CFFFontSet``'s :class:`TopDict` object. To do this, a ``CFFFontSet``
55
+ object can either be treated as a dictionary (with appropriate
56
+ ``keys()`` and ``values()`` methods) mapping font names to :class:`TopDict`
57
+ objects, or as a list.
58
+
59
+ .. code:: python
60
+
61
+ from fontTools import ttLib
62
+ tt = ttLib.TTFont("Tests/cffLib/data/LinLibertine_RBI.otf")
63
+ tt["CFF "].cff
64
+ # <fontTools.cffLib.CFFFontSet object at 0x101e24c90>
65
+ tt["CFF "].cff[0] # Here's your actual font data
66
+ # <fontTools.cffLib.TopDict object at 0x1020f1fd0>
67
+
68
+ """
69
+
70
+ def decompile(self, file, otFont, isCFF2=None):
71
+ """Parse a binary CFF file into an internal representation. ``file``
72
+ should be a file handle object. ``otFont`` is the top-level
73
+ :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.
74
+
75
+ If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
76
+ library makes an assertion that the CFF header is of the appropriate
77
+ version.
78
+ """
79
+
80
+ self.otFont = otFont
81
+ sstruct.unpack(cffHeaderFormat, file.read(3), self)
82
+ if isCFF2 is not None:
83
+ # called from ttLib: assert 'major' as read from file matches the
84
+ # expected version
85
+ expected_major = 2 if isCFF2 else 1
86
+ if self.major != expected_major:
87
+ raise ValueError(
88
+ "Invalid CFF 'major' version: expected %d, found %d"
89
+ % (expected_major, self.major)
90
+ )
91
+ else:
92
+ # use 'major' version from file to determine if isCFF2
93
+ assert self.major in (1, 2), "Unknown CFF format"
94
+ isCFF2 = self.major == 2
95
+ if not isCFF2:
96
+ self.offSize = struct.unpack("B", file.read(1))[0]
97
+ file.seek(self.hdrSize)
98
+ self.fontNames = list(tostr(s) for s in Index(file, isCFF2=isCFF2))
99
+ self.topDictIndex = TopDictIndex(file, isCFF2=isCFF2)
100
+ self.strings = IndexedStrings(file)
101
+ else: # isCFF2
102
+ self.topDictSize = struct.unpack(">H", file.read(2))[0]
103
+ file.seek(self.hdrSize)
104
+ self.fontNames = ["CFF2Font"]
105
+ cff2GetGlyphOrder = otFont.getGlyphOrder
106
+ # in CFF2, offsetSize is the size of the TopDict data.
107
+ self.topDictIndex = TopDictIndex(
108
+ file, cff2GetGlyphOrder, self.topDictSize, isCFF2=isCFF2
109
+ )
110
+ self.strings = None
111
+ self.GlobalSubrs = GlobalSubrsIndex(file, isCFF2=isCFF2)
112
+ self.topDictIndex.strings = self.strings
113
+ self.topDictIndex.GlobalSubrs = self.GlobalSubrs
114
+
115
+ def __len__(self):
116
+ return len(self.fontNames)
117
+
118
+ def keys(self):
119
+ return list(self.fontNames)
120
+
121
+ def values(self):
122
+ return self.topDictIndex
123
+
124
+ def __getitem__(self, nameOrIndex):
125
+ """Return TopDict instance identified by name (str) or index (int
126
+ or any object that implements `__index__`).
127
+ """
128
+ if hasattr(nameOrIndex, "__index__"):
129
+ index = nameOrIndex.__index__()
130
+ elif isinstance(nameOrIndex, str):
131
+ name = nameOrIndex
132
+ try:
133
+ index = self.fontNames.index(name)
134
+ except ValueError:
135
+ raise KeyError(nameOrIndex)
136
+ else:
137
+ raise TypeError(nameOrIndex)
138
+ return self.topDictIndex[index]
139
+
140
+ def compile(self, file, otFont, isCFF2=None):
141
+ """Write the object back into binary representation onto the given file.
142
+ ``file`` should be a file handle object. ``otFont`` is the top-level
143
+ :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.
144
+
145
+ If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
146
+ library makes an assertion that the CFF header is of the appropriate
147
+ version.
148
+ """
149
+ self.otFont = otFont
150
+ if isCFF2 is not None:
151
+ # called from ttLib: assert 'major' value matches expected version
152
+ expected_major = 2 if isCFF2 else 1
153
+ if self.major != expected_major:
154
+ raise ValueError(
155
+ "Invalid CFF 'major' version: expected %d, found %d"
156
+ % (expected_major, self.major)
157
+ )
158
+ else:
159
+ # use current 'major' value to determine output format
160
+ assert self.major in (1, 2), "Unknown CFF format"
161
+ isCFF2 = self.major == 2
162
+
163
+ if otFont.recalcBBoxes and not isCFF2:
164
+ for topDict in self.topDictIndex:
165
+ topDict.recalcFontBBox()
166
+
167
+ if not isCFF2:
168
+ strings = IndexedStrings()
169
+ else:
170
+ strings = None
171
+ writer = CFFWriter(isCFF2)
172
+ topCompiler = self.topDictIndex.getCompiler(strings, self, isCFF2=isCFF2)
173
+ if isCFF2:
174
+ self.hdrSize = 5
175
+ writer.add(sstruct.pack(cffHeaderFormat, self))
176
+ # Note: topDictSize will most likely change in CFFWriter.toFile().
177
+ self.topDictSize = topCompiler.getDataLength()
178
+ writer.add(struct.pack(">H", self.topDictSize))
179
+ else:
180
+ self.hdrSize = 4
181
+ self.offSize = 4 # will most likely change in CFFWriter.toFile().
182
+ writer.add(sstruct.pack(cffHeaderFormat, self))
183
+ writer.add(struct.pack("B", self.offSize))
184
+ if not isCFF2:
185
+ fontNames = Index()
186
+ for name in self.fontNames:
187
+ fontNames.append(name)
188
+ writer.add(fontNames.getCompiler(strings, self, isCFF2=isCFF2))
189
+ writer.add(topCompiler)
190
+ if not isCFF2:
191
+ writer.add(strings.getCompiler())
192
+ writer.add(self.GlobalSubrs.getCompiler(strings, self, isCFF2=isCFF2))
193
+
194
+ for topDict in self.topDictIndex:
195
+ if not hasattr(topDict, "charset") or topDict.charset is None:
196
+ charset = otFont.getGlyphOrder()
197
+ topDict.charset = charset
198
+ children = topCompiler.getChildren(strings)
199
+ for child in children:
200
+ writer.add(child)
201
+
202
+ writer.toFile(file)
203
+
204
+ def toXML(self, xmlWriter):
205
+ """Write the object into XML representation onto the given
206
+ :class:`fontTools.misc.xmlWriter.XMLWriter`.
207
+
208
+ .. code:: python
209
+
210
+ writer = xmlWriter.XMLWriter(sys.stdout)
211
+ tt["CFF "].cff.toXML(writer)
212
+
213
+ """
214
+
215
+ xmlWriter.simpletag("major", value=self.major)
216
+ xmlWriter.newline()
217
+ xmlWriter.simpletag("minor", value=self.minor)
218
+ xmlWriter.newline()
219
+ for fontName in self.fontNames:
220
+ xmlWriter.begintag("CFFFont", name=tostr(fontName))
221
+ xmlWriter.newline()
222
+ font = self[fontName]
223
+ font.toXML(xmlWriter)
224
+ xmlWriter.endtag("CFFFont")
225
+ xmlWriter.newline()
226
+ xmlWriter.newline()
227
+ xmlWriter.begintag("GlobalSubrs")
228
+ xmlWriter.newline()
229
+ self.GlobalSubrs.toXML(xmlWriter)
230
+ xmlWriter.endtag("GlobalSubrs")
231
+ xmlWriter.newline()
232
+
233
+ def fromXML(self, name, attrs, content, otFont=None):
234
+ """Reads data from the XML element into the ``CFFFontSet`` object."""
235
+ self.otFont = otFont
236
+
237
+ # set defaults. These will be replaced if there are entries for them
238
+ # in the XML file.
239
+ if not hasattr(self, "major"):
240
+ self.major = 1
241
+ if not hasattr(self, "minor"):
242
+ self.minor = 0
243
+
244
+ if name == "CFFFont":
245
+ if self.major == 1:
246
+ if not hasattr(self, "offSize"):
247
+ # this will be recalculated when the cff is compiled.
248
+ self.offSize = 4
249
+ if not hasattr(self, "hdrSize"):
250
+ self.hdrSize = 4
251
+ if not hasattr(self, "GlobalSubrs"):
252
+ self.GlobalSubrs = GlobalSubrsIndex()
253
+ if not hasattr(self, "fontNames"):
254
+ self.fontNames = []
255
+ self.topDictIndex = TopDictIndex()
256
+ fontName = attrs["name"]
257
+ self.fontNames.append(fontName)
258
+ topDict = TopDict(GlobalSubrs=self.GlobalSubrs)
259
+ topDict.charset = None # gets filled in later
260
+ elif self.major == 2:
261
+ if not hasattr(self, "hdrSize"):
262
+ self.hdrSize = 5
263
+ if not hasattr(self, "GlobalSubrs"):
264
+ self.GlobalSubrs = GlobalSubrsIndex()
265
+ if not hasattr(self, "fontNames"):
266
+ self.fontNames = ["CFF2Font"]
267
+ cff2GetGlyphOrder = self.otFont.getGlyphOrder
268
+ topDict = TopDict(
269
+ GlobalSubrs=self.GlobalSubrs, cff2GetGlyphOrder=cff2GetGlyphOrder
270
+ )
271
+ self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder)
272
+ self.topDictIndex.append(topDict)
273
+ for element in content:
274
+ if isinstance(element, str):
275
+ continue
276
+ name, attrs, content = element
277
+ topDict.fromXML(name, attrs, content)
278
+
279
+ if hasattr(topDict, "VarStore") and topDict.FDArray[0].vstore is None:
280
+ fdArray = topDict.FDArray
281
+ for fontDict in fdArray:
282
+ if hasattr(fontDict, "Private"):
283
+ fontDict.Private.vstore = topDict.VarStore
284
+
285
+ elif name == "GlobalSubrs":
286
+ subrCharStringClass = psCharStrings.T2CharString
287
+ if not hasattr(self, "GlobalSubrs"):
288
+ self.GlobalSubrs = GlobalSubrsIndex()
289
+ for element in content:
290
+ if isinstance(element, str):
291
+ continue
292
+ name, attrs, content = element
293
+ subr = subrCharStringClass()
294
+ subr.fromXML(name, attrs, content)
295
+ self.GlobalSubrs.append(subr)
296
+ elif name == "major":
297
+ self.major = int(attrs["value"])
298
+ elif name == "minor":
299
+ self.minor = int(attrs["value"])
300
+
301
+ def convertCFFToCFF2(self, otFont):
302
+ from .CFFToCFF2 import _convertCFFToCFF2
303
+
304
+ _convertCFFToCFF2(self, otFont)
305
+
306
+ def convertCFF2ToCFF(self, otFont):
307
+ from .CFF2ToCFF import _convertCFF2ToCFF
308
+
309
+ _convertCFF2ToCFF(self, otFont)
310
+
311
+ def desubroutinize(self):
312
+ from .transforms import desubroutinize
313
+
314
+ desubroutinize(self)
315
+
316
+ def remove_hints(self):
317
+ from .transforms import remove_hints
318
+
319
+ remove_hints(self)
320
+
321
+ def remove_unused_subroutines(self):
322
+ from .transforms import remove_unused_subroutines
323
+
324
+ remove_unused_subroutines(self)
325
+
326
+
327
+ class CFFWriter(object):
328
+ """Helper class for serializing CFF data to binary. Used by
329
+ :meth:`CFFFontSet.compile`."""
330
+
331
+ def __init__(self, isCFF2):
332
+ self.data = []
333
+ self.isCFF2 = isCFF2
334
+
335
+ def add(self, table):
336
+ self.data.append(table)
337
+
338
+ def toFile(self, file):
339
+ lastPosList = None
340
+ count = 1
341
+ while True:
342
+ log.log(DEBUG, "CFFWriter.toFile() iteration: %d", count)
343
+ count = count + 1
344
+ pos = 0
345
+ posList = [pos]
346
+ for item in self.data:
347
+ if hasattr(item, "getDataLength"):
348
+ endPos = pos + item.getDataLength()
349
+ if isinstance(item, TopDictIndexCompiler) and item.isCFF2:
350
+ self.topDictSize = item.getDataLength()
351
+ else:
352
+ endPos = pos + len(item)
353
+ if hasattr(item, "setPos"):
354
+ item.setPos(pos, endPos)
355
+ pos = endPos
356
+ posList.append(pos)
357
+ if posList == lastPosList:
358
+ break
359
+ lastPosList = posList
360
+ log.log(DEBUG, "CFFWriter.toFile() writing to file.")
361
+ begin = file.tell()
362
+ if self.isCFF2:
363
+ self.data[1] = struct.pack(">H", self.topDictSize)
364
+ else:
365
+ self.offSize = calcOffSize(lastPosList[-1])
366
+ self.data[1] = struct.pack("B", self.offSize)
367
+ posList = [0]
368
+ for item in self.data:
369
+ if hasattr(item, "toFile"):
370
+ item.toFile(file)
371
+ else:
372
+ file.write(item)
373
+ posList.append(file.tell() - begin)
374
+ assert posList == lastPosList
375
+
376
+
377
+ def calcOffSize(largestOffset):
378
+ if largestOffset < 0x100:
379
+ offSize = 1
380
+ elif largestOffset < 0x10000:
381
+ offSize = 2
382
+ elif largestOffset < 0x1000000:
383
+ offSize = 3
384
+ else:
385
+ offSize = 4
386
+ return offSize
387
+
388
+
389
+ class IndexCompiler(object):
390
+ """Base class for writing CFF `INDEX data <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#5-index-data>`_
391
+ to binary."""
392
+
393
+ def __init__(self, items, strings, parent, isCFF2=None):
394
+ if isCFF2 is None and hasattr(parent, "isCFF2"):
395
+ isCFF2 = parent.isCFF2
396
+ assert isCFF2 is not None
397
+ self.isCFF2 = isCFF2
398
+ self.items = self.getItems(items, strings)
399
+ self.parent = parent
400
+
401
+ def getItems(self, items, strings):
402
+ return items
403
+
404
+ def getOffsets(self):
405
+ # An empty INDEX contains only the count field.
406
+ if self.items:
407
+ pos = 1
408
+ offsets = [pos]
409
+ for item in self.items:
410
+ if hasattr(item, "getDataLength"):
411
+ pos = pos + item.getDataLength()
412
+ else:
413
+ pos = pos + len(item)
414
+ offsets.append(pos)
415
+ else:
416
+ offsets = []
417
+ return offsets
418
+
419
+ def getDataLength(self):
420
+ if self.isCFF2:
421
+ countSize = 4
422
+ else:
423
+ countSize = 2
424
+
425
+ if self.items:
426
+ lastOffset = self.getOffsets()[-1]
427
+ offSize = calcOffSize(lastOffset)
428
+ dataLength = (
429
+ countSize
430
+ + 1 # count
431
+ + (len(self.items) + 1) * offSize # offSize
432
+ + lastOffset # the offsets
433
+ - 1 # size of object data
434
+ )
435
+ else:
436
+ # count. For empty INDEX tables, this is the only entry.
437
+ dataLength = countSize
438
+
439
+ return dataLength
440
+
441
+ def toFile(self, file):
442
+ offsets = self.getOffsets()
443
+ if self.isCFF2:
444
+ writeCard32(file, len(self.items))
445
+ else:
446
+ writeCard16(file, len(self.items))
447
+ # An empty INDEX contains only the count field.
448
+ if self.items:
449
+ offSize = calcOffSize(offsets[-1])
450
+ writeCard8(file, offSize)
451
+ offSize = -offSize
452
+ pack = struct.pack
453
+ for offset in offsets:
454
+ binOffset = pack(">l", offset)[offSize:]
455
+ assert len(binOffset) == -offSize
456
+ file.write(binOffset)
457
+ for item in self.items:
458
+ if hasattr(item, "toFile"):
459
+ item.toFile(file)
460
+ else:
461
+ data = tobytes(item, encoding="latin1")
462
+ file.write(data)
463
+
464
+
465
+ class IndexedStringsCompiler(IndexCompiler):
466
+ def getItems(self, items, strings):
467
+ return items.strings
468
+
469
+
470
+ class TopDictIndexCompiler(IndexCompiler):
471
+ """Helper class for writing the TopDict to binary."""
472
+
473
+ def getItems(self, items, strings):
474
+ out = []
475
+ for item in items:
476
+ out.append(item.getCompiler(strings, self))
477
+ return out
478
+
479
+ def getChildren(self, strings):
480
+ children = []
481
+ for topDict in self.items:
482
+ children.extend(topDict.getChildren(strings))
483
+ return children
484
+
485
+ def getOffsets(self):
486
+ if self.isCFF2:
487
+ offsets = [0, self.items[0].getDataLength()]
488
+ return offsets
489
+ else:
490
+ return super(TopDictIndexCompiler, self).getOffsets()
491
+
492
+ def getDataLength(self):
493
+ if self.isCFF2:
494
+ dataLength = self.items[0].getDataLength()
495
+ return dataLength
496
+ else:
497
+ return super(TopDictIndexCompiler, self).getDataLength()
498
+
499
+ def toFile(self, file):
500
+ if self.isCFF2:
501
+ self.items[0].toFile(file)
502
+ else:
503
+ super(TopDictIndexCompiler, self).toFile(file)
504
+
505
+
506
+ class FDArrayIndexCompiler(IndexCompiler):
507
+ """Helper class for writing the
508
+ `Font DICT INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#10-font-dict-index-font-dicts-and-fdselect>`_
509
+ to binary."""
510
+
511
+ def getItems(self, items, strings):
512
+ out = []
513
+ for item in items:
514
+ out.append(item.getCompiler(strings, self))
515
+ return out
516
+
517
+ def getChildren(self, strings):
518
+ children = []
519
+ for fontDict in self.items:
520
+ children.extend(fontDict.getChildren(strings))
521
+ return children
522
+
523
+ def toFile(self, file):
524
+ offsets = self.getOffsets()
525
+ if self.isCFF2:
526
+ writeCard32(file, len(self.items))
527
+ else:
528
+ writeCard16(file, len(self.items))
529
+ offSize = calcOffSize(offsets[-1])
530
+ writeCard8(file, offSize)
531
+ offSize = -offSize
532
+ pack = struct.pack
533
+ for offset in offsets:
534
+ binOffset = pack(">l", offset)[offSize:]
535
+ assert len(binOffset) == -offSize
536
+ file.write(binOffset)
537
+ for item in self.items:
538
+ if hasattr(item, "toFile"):
539
+ item.toFile(file)
540
+ else:
541
+ file.write(item)
542
+
543
+ def setPos(self, pos, endPos):
544
+ self.parent.rawDict["FDArray"] = pos
545
+
546
+
547
+ class GlobalSubrsCompiler(IndexCompiler):
548
+ """Helper class for writing the `global subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
549
+ to binary."""
550
+
551
+ def getItems(self, items, strings):
552
+ out = []
553
+ for cs in items:
554
+ cs.compile(self.isCFF2)
555
+ out.append(cs.bytecode)
556
+ return out
557
+
558
+
559
+ class SubrsCompiler(GlobalSubrsCompiler):
560
+ """Helper class for writing the `local subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
561
+ to binary."""
562
+
563
+ def setPos(self, pos, endPos):
564
+ offset = pos - self.parent.pos
565
+ self.parent.rawDict["Subrs"] = offset
566
+
567
+
568
+ class CharStringsCompiler(GlobalSubrsCompiler):
569
+ """Helper class for writing the `CharStrings INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
570
+ to binary."""
571
+
572
+ def getItems(self, items, strings):
573
+ out = []
574
+ for cs in items:
575
+ cs.compile(self.isCFF2)
576
+ out.append(cs.bytecode)
577
+ return out
578
+
579
+ def setPos(self, pos, endPos):
580
+ self.parent.rawDict["CharStrings"] = pos
581
+
582
+
583
+ class Index(object):
584
+ """This class represents what the CFF spec calls an INDEX (an array of
585
+ variable-sized objects). `Index` items can be addressed and set using
586
+ Python list indexing."""
587
+
588
+ compilerClass = IndexCompiler
589
+
590
+ def __init__(self, file=None, isCFF2=None):
591
+ self.items = []
592
+ self.offsets = offsets = []
593
+ name = self.__class__.__name__
594
+ if file is None:
595
+ return
596
+ self._isCFF2 = isCFF2
597
+ log.log(DEBUG, "loading %s at %s", name, file.tell())
598
+ self.file = file
599
+ if isCFF2:
600
+ count = readCard32(file)
601
+ else:
602
+ count = readCard16(file)
603
+ if count == 0:
604
+ return
605
+ self.items = [None] * count
606
+ offSize = readCard8(file)
607
+ log.log(DEBUG, " index count: %s offSize: %s", count, offSize)
608
+ assert offSize <= 4, "offSize too large: %s" % offSize
609
+ pad = b"\0" * (4 - offSize)
610
+ for index in range(count + 1):
611
+ chunk = file.read(offSize)
612
+ chunk = pad + chunk
613
+ (offset,) = struct.unpack(">L", chunk)
614
+ offsets.append(int(offset))
615
+ self.offsetBase = file.tell() - 1
616
+ file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot
617
+ log.log(DEBUG, " end of %s at %s", name, file.tell())
618
+
619
+ def __len__(self):
620
+ return len(self.items)
621
+
622
+ def __getitem__(self, index):
623
+ item = self.items[index]
624
+ if item is not None:
625
+ return item
626
+ offset = self.offsets[index] + self.offsetBase
627
+ size = self.offsets[index + 1] - self.offsets[index]
628
+ file = self.file
629
+ file.seek(offset)
630
+ data = file.read(size)
631
+ assert len(data) == size
632
+ item = self.produceItem(index, data, file, offset)
633
+ self.items[index] = item
634
+ return item
635
+
636
+ def __setitem__(self, index, item):
637
+ self.items[index] = item
638
+
639
+ def produceItem(self, index, data, file, offset):
640
+ return data
641
+
642
+ def append(self, item):
643
+ """Add an item to an INDEX."""
644
+ self.items.append(item)
645
+
646
+ def getCompiler(self, strings, parent, isCFF2=None):
647
+ return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
648
+
649
+ def clear(self):
650
+ """Empty the INDEX."""
651
+ del self.items[:]
652
+
653
+
654
+ class GlobalSubrsIndex(Index):
655
+ """This index contains all the global subroutines in the font. A global
656
+ subroutine is a set of ``CharString`` data which is accessible to any
657
+ glyph in the font, and are used to store repeated instructions - for
658
+ example, components may be encoded as global subroutines, but so could
659
+ hinting instructions.
660
+
661
+ Remember that when interpreting a ``callgsubr`` instruction (or indeed
662
+ a ``callsubr`` instruction) that you will need to add the "subroutine
663
+ number bias" to number given:
664
+
665
+ .. code:: python
666
+
667
+ tt = ttLib.TTFont("Almendra-Bold.otf")
668
+ u = tt["CFF "].cff[0].CharStrings["udieresis"]
669
+ u.decompile()
670
+
671
+ u.toXML(XMLWriter(sys.stdout))
672
+ # <some stuff>
673
+ # -64 callgsubr <-- Subroutine which implements the dieresis mark
674
+ # <other stuff>
675
+
676
+ tt["CFF "].cff[0].GlobalSubrs[-64] # <-- WRONG
677
+ # <T2CharString (bytecode) at 103451d10>
678
+
679
+ tt["CFF "].cff[0].GlobalSubrs[-64 + 107] # <-- RIGHT
680
+ # <T2CharString (source) at 103451390>
681
+
682
+ ("The bias applied depends on the number of subrs (gsubrs). If the number of
683
+ subrs (gsubrs) is less than 1240, the bias is 107. Otherwise if it is less
684
+ than 33900, it is 1131; otherwise it is 32768.",
685
+ `Subroutine Operators <https://docs.microsoft.com/en-us/typography/opentype/otspec180/cff2charstr#section4.4>`)
686
+ """
687
+
688
+ compilerClass = GlobalSubrsCompiler
689
+ subrClass = psCharStrings.T2CharString
690
+ charStringClass = psCharStrings.T2CharString
691
+
692
+ def __init__(
693
+ self,
694
+ file=None,
695
+ globalSubrs=None,
696
+ private=None,
697
+ fdSelect=None,
698
+ fdArray=None,
699
+ isCFF2=None,
700
+ ):
701
+ super(GlobalSubrsIndex, self).__init__(file, isCFF2=isCFF2)
702
+ self.globalSubrs = globalSubrs
703
+ self.private = private
704
+ if fdSelect:
705
+ self.fdSelect = fdSelect
706
+ if fdArray:
707
+ self.fdArray = fdArray
708
+
709
+ def produceItem(self, index, data, file, offset):
710
+ if self.private is not None:
711
+ private = self.private
712
+ elif hasattr(self, "fdArray") and self.fdArray is not None:
713
+ if hasattr(self, "fdSelect") and self.fdSelect is not None:
714
+ fdIndex = self.fdSelect[index]
715
+ else:
716
+ fdIndex = 0
717
+ private = self.fdArray[fdIndex].Private
718
+ else:
719
+ private = None
720
+ return self.subrClass(data, private=private, globalSubrs=self.globalSubrs)
721
+
722
+ def toXML(self, xmlWriter):
723
+ """Write the subroutines index into XML representation onto the given
724
+ :class:`fontTools.misc.xmlWriter.XMLWriter`.
725
+
726
+ .. code:: python
727
+
728
+ writer = xmlWriter.XMLWriter(sys.stdout)
729
+ tt["CFF "].cff[0].GlobalSubrs.toXML(writer)
730
+
731
+ """
732
+ xmlWriter.comment(
733
+ "The 'index' attribute is only for humans; " "it is ignored when parsed."
734
+ )
735
+ xmlWriter.newline()
736
+ for i in range(len(self)):
737
+ subr = self[i]
738
+ if subr.needsDecompilation():
739
+ xmlWriter.begintag("CharString", index=i, raw=1)
740
+ else:
741
+ xmlWriter.begintag("CharString", index=i)
742
+ xmlWriter.newline()
743
+ subr.toXML(xmlWriter)
744
+ xmlWriter.endtag("CharString")
745
+ xmlWriter.newline()
746
+
747
+ def fromXML(self, name, attrs, content):
748
+ if name != "CharString":
749
+ return
750
+ subr = self.subrClass()
751
+ subr.fromXML(name, attrs, content)
752
+ self.append(subr)
753
+
754
+ def getItemAndSelector(self, index):
755
+ sel = None
756
+ if hasattr(self, "fdSelect"):
757
+ sel = self.fdSelect[index]
758
+ return self[index], sel
759
+
760
+
761
+ class SubrsIndex(GlobalSubrsIndex):
762
+ """This index contains a glyph's local subroutines. A local subroutine is a
763
+ private set of ``CharString`` data which is accessible only to the glyph to
764
+ which the index is attached."""
765
+
766
+ compilerClass = SubrsCompiler
767
+
768
+
769
+ class TopDictIndex(Index):
770
+ """This index represents the array of ``TopDict`` structures in the font
771
+ (again, usually only one entry is present). Hence the following calls are
772
+ equivalent:
773
+
774
+ .. code:: python
775
+
776
+ tt["CFF "].cff[0]
777
+ # <fontTools.cffLib.TopDict object at 0x102ed6e50>
778
+ tt["CFF "].cff.topDictIndex[0]
779
+ # <fontTools.cffLib.TopDict object at 0x102ed6e50>
780
+
781
+ """
782
+
783
+ compilerClass = TopDictIndexCompiler
784
+
785
+ def __init__(self, file=None, cff2GetGlyphOrder=None, topSize=0, isCFF2=None):
786
+ self.cff2GetGlyphOrder = cff2GetGlyphOrder
787
+ if file is not None and isCFF2:
788
+ self._isCFF2 = isCFF2
789
+ self.items = []
790
+ name = self.__class__.__name__
791
+ log.log(DEBUG, "loading %s at %s", name, file.tell())
792
+ self.file = file
793
+ count = 1
794
+ self.items = [None] * count
795
+ self.offsets = [0, topSize]
796
+ self.offsetBase = file.tell()
797
+ # pretend we've read the whole lot
798
+ file.seek(self.offsetBase + topSize)
799
+ log.log(DEBUG, " end of %s at %s", name, file.tell())
800
+ else:
801
+ super(TopDictIndex, self).__init__(file, isCFF2=isCFF2)
802
+
803
+ def produceItem(self, index, data, file, offset):
804
+ top = TopDict(
805
+ self.strings,
806
+ file,
807
+ offset,
808
+ self.GlobalSubrs,
809
+ self.cff2GetGlyphOrder,
810
+ isCFF2=self._isCFF2,
811
+ )
812
+ top.decompile(data)
813
+ return top
814
+
815
+ def toXML(self, xmlWriter):
816
+ for i in range(len(self)):
817
+ xmlWriter.begintag("FontDict", index=i)
818
+ xmlWriter.newline()
819
+ self[i].toXML(xmlWriter)
820
+ xmlWriter.endtag("FontDict")
821
+ xmlWriter.newline()
822
+
823
+
824
+ class FDArrayIndex(Index):
825
+ compilerClass = FDArrayIndexCompiler
826
+
827
+ def toXML(self, xmlWriter):
828
+ for i in range(len(self)):
829
+ xmlWriter.begintag("FontDict", index=i)
830
+ xmlWriter.newline()
831
+ self[i].toXML(xmlWriter)
832
+ xmlWriter.endtag("FontDict")
833
+ xmlWriter.newline()
834
+
835
+ def produceItem(self, index, data, file, offset):
836
+ fontDict = FontDict(
837
+ self.strings,
838
+ file,
839
+ offset,
840
+ self.GlobalSubrs,
841
+ isCFF2=self._isCFF2,
842
+ vstore=self.vstore,
843
+ )
844
+ fontDict.decompile(data)
845
+ return fontDict
846
+
847
+ def fromXML(self, name, attrs, content):
848
+ if name != "FontDict":
849
+ return
850
+ fontDict = FontDict()
851
+ for element in content:
852
+ if isinstance(element, str):
853
+ continue
854
+ name, attrs, content = element
855
+ fontDict.fromXML(name, attrs, content)
856
+ self.append(fontDict)
857
+
858
+
859
+ class VarStoreData(object):
860
+ def __init__(self, file=None, otVarStore=None):
861
+ self.file = file
862
+ self.data = None
863
+ self.otVarStore = otVarStore
864
+ self.font = TTFont() # dummy font for the decompile function.
865
+
866
+ def decompile(self):
867
+ if self.file:
868
+ # read data in from file. Assume position is correct.
869
+ length = readCard16(self.file)
870
+ # https://github.com/fonttools/fonttools/issues/3673
871
+ if length == 65535:
872
+ self.data = self.file.read()
873
+ else:
874
+ self.data = self.file.read(length)
875
+ globalState = {}
876
+ reader = OTTableReader(self.data, globalState)
877
+ self.otVarStore = ot.VarStore()
878
+ self.otVarStore.decompile(reader, self.font)
879
+ self.data = None
880
+ return self
881
+
882
+ def compile(self):
883
+ writer = OTTableWriter()
884
+ self.otVarStore.compile(writer, self.font)
885
+ # Note that this omits the initial Card16 length from the CFF2
886
+ # VarStore data block
887
+ self.data = writer.getAllData()
888
+
889
+ def writeXML(self, xmlWriter, name):
890
+ self.otVarStore.toXML(xmlWriter, self.font)
891
+
892
+ def xmlRead(self, name, attrs, content, parent):
893
+ self.otVarStore = ot.VarStore()
894
+ for element in content:
895
+ if isinstance(element, tuple):
896
+ name, attrs, content = element
897
+ self.otVarStore.fromXML(name, attrs, content, self.font)
898
+ else:
899
+ pass
900
+ return None
901
+
902
+ def __len__(self):
903
+ return len(self.data)
904
+
905
+ def getNumRegions(self, vsIndex):
906
+ if vsIndex is None:
907
+ vsIndex = 0
908
+ varData = self.otVarStore.VarData[vsIndex]
909
+ numRegions = varData.VarRegionCount
910
+ return numRegions
911
+
912
+
913
+ class FDSelect(object):
914
+ def __init__(self, file=None, numGlyphs=None, format=None):
915
+ if file:
916
+ # read data in from file
917
+ self.format = readCard8(file)
918
+ if self.format == 0:
919
+ from array import array
920
+
921
+ self.gidArray = array("B", file.read(numGlyphs)).tolist()
922
+ elif self.format == 3:
923
+ gidArray = [None] * numGlyphs
924
+ nRanges = readCard16(file)
925
+ fd = None
926
+ prev = None
927
+ for i in range(nRanges):
928
+ first = readCard16(file)
929
+ if prev is not None:
930
+ for glyphID in range(prev, first):
931
+ gidArray[glyphID] = fd
932
+ prev = first
933
+ fd = readCard8(file)
934
+ if prev is not None:
935
+ first = readCard16(file)
936
+ for glyphID in range(prev, first):
937
+ gidArray[glyphID] = fd
938
+ self.gidArray = gidArray
939
+ elif self.format == 4:
940
+ gidArray = [None] * numGlyphs
941
+ nRanges = readCard32(file)
942
+ fd = None
943
+ prev = None
944
+ for i in range(nRanges):
945
+ first = readCard32(file)
946
+ if prev is not None:
947
+ for glyphID in range(prev, first):
948
+ gidArray[glyphID] = fd
949
+ prev = first
950
+ fd = readCard16(file)
951
+ if prev is not None:
952
+ first = readCard32(file)
953
+ for glyphID in range(prev, first):
954
+ gidArray[glyphID] = fd
955
+ self.gidArray = gidArray
956
+ else:
957
+ assert False, "unsupported FDSelect format: %s" % format
958
+ else:
959
+ # reading from XML. Make empty gidArray, and leave format as passed in.
960
+ # format is None will result in the smallest representation being used.
961
+ self.format = format
962
+ self.gidArray = []
963
+
964
+ def __len__(self):
965
+ return len(self.gidArray)
966
+
967
+ def __getitem__(self, index):
968
+ return self.gidArray[index]
969
+
970
+ def __setitem__(self, index, fdSelectValue):
971
+ self.gidArray[index] = fdSelectValue
972
+
973
+ def append(self, fdSelectValue):
974
+ self.gidArray.append(fdSelectValue)
975
+
976
+
977
+ class CharStrings(object):
978
+ """The ``CharStrings`` in the font represent the instructions for drawing
979
+ each glyph. This object presents a dictionary interface to the font's
980
+ CharStrings, indexed by glyph name:
981
+
982
+ .. code:: python
983
+
984
+ tt["CFF "].cff[0].CharStrings["a"]
985
+ # <T2CharString (bytecode) at 103451e90>
986
+
987
+ See :class:`fontTools.misc.psCharStrings.T1CharString` and
988
+ :class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile,
989
+ compile and interpret the glyph drawing instructions in the returned objects.
990
+
991
+ """
992
+
993
+ def __init__(
994
+ self,
995
+ file,
996
+ charset,
997
+ globalSubrs,
998
+ private,
999
+ fdSelect,
1000
+ fdArray,
1001
+ isCFF2=None,
1002
+ varStore=None,
1003
+ ):
1004
+ self.globalSubrs = globalSubrs
1005
+ self.varStore = varStore
1006
+ if file is not None:
1007
+ self.charStringsIndex = SubrsIndex(
1008
+ file, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2
1009
+ )
1010
+ self.charStrings = charStrings = {}
1011
+ for i in range(len(charset)):
1012
+ charStrings[charset[i]] = i
1013
+ # read from OTF file: charStrings.values() are indices into
1014
+ # charStringsIndex.
1015
+ self.charStringsAreIndexed = 1
1016
+ else:
1017
+ self.charStrings = {}
1018
+ # read from ttx file: charStrings.values() are actual charstrings
1019
+ self.charStringsAreIndexed = 0
1020
+ self.private = private
1021
+ if fdSelect is not None:
1022
+ self.fdSelect = fdSelect
1023
+ if fdArray is not None:
1024
+ self.fdArray = fdArray
1025
+
1026
+ def keys(self):
1027
+ return list(self.charStrings.keys())
1028
+
1029
+ def values(self):
1030
+ if self.charStringsAreIndexed:
1031
+ return self.charStringsIndex
1032
+ else:
1033
+ return list(self.charStrings.values())
1034
+
1035
+ def has_key(self, name):
1036
+ return name in self.charStrings
1037
+
1038
+ __contains__ = has_key
1039
+
1040
+ def __len__(self):
1041
+ return len(self.charStrings)
1042
+
1043
+ def __getitem__(self, name):
1044
+ charString = self.charStrings[name]
1045
+ if self.charStringsAreIndexed:
1046
+ charString = self.charStringsIndex[charString]
1047
+ return charString
1048
+
1049
+ def __setitem__(self, name, charString):
1050
+ if self.charStringsAreIndexed:
1051
+ index = self.charStrings[name]
1052
+ self.charStringsIndex[index] = charString
1053
+ else:
1054
+ self.charStrings[name] = charString
1055
+
1056
+ def getItemAndSelector(self, name):
1057
+ if self.charStringsAreIndexed:
1058
+ index = self.charStrings[name]
1059
+ return self.charStringsIndex.getItemAndSelector(index)
1060
+ else:
1061
+ if hasattr(self, "fdArray"):
1062
+ if hasattr(self, "fdSelect"):
1063
+ sel = self.charStrings[name].fdSelectIndex
1064
+ else:
1065
+ sel = 0
1066
+ else:
1067
+ sel = None
1068
+ return self.charStrings[name], sel
1069
+
1070
+ def toXML(self, xmlWriter):
1071
+ names = sorted(self.keys())
1072
+ for name in names:
1073
+ charStr, fdSelectIndex = self.getItemAndSelector(name)
1074
+ if charStr.needsDecompilation():
1075
+ raw = [("raw", 1)]
1076
+ else:
1077
+ raw = []
1078
+ if fdSelectIndex is None:
1079
+ xmlWriter.begintag("CharString", [("name", name)] + raw)
1080
+ else:
1081
+ xmlWriter.begintag(
1082
+ "CharString",
1083
+ [("name", name), ("fdSelectIndex", fdSelectIndex)] + raw,
1084
+ )
1085
+ xmlWriter.newline()
1086
+ charStr.toXML(xmlWriter)
1087
+ xmlWriter.endtag("CharString")
1088
+ xmlWriter.newline()
1089
+
1090
+ def fromXML(self, name, attrs, content):
1091
+ for element in content:
1092
+ if isinstance(element, str):
1093
+ continue
1094
+ name, attrs, content = element
1095
+ if name != "CharString":
1096
+ continue
1097
+ fdID = -1
1098
+ if hasattr(self, "fdArray"):
1099
+ try:
1100
+ fdID = safeEval(attrs["fdSelectIndex"])
1101
+ except KeyError:
1102
+ fdID = 0
1103
+ private = self.fdArray[fdID].Private
1104
+ else:
1105
+ private = self.private
1106
+
1107
+ glyphName = attrs["name"]
1108
+ charStringClass = psCharStrings.T2CharString
1109
+ charString = charStringClass(private=private, globalSubrs=self.globalSubrs)
1110
+ charString.fromXML(name, attrs, content)
1111
+ if fdID >= 0:
1112
+ charString.fdSelectIndex = fdID
1113
+ self[glyphName] = charString
1114
+
1115
+
1116
+ def readCard8(file):
1117
+ return byteord(file.read(1))
1118
+
1119
+
1120
+ def readCard16(file):
1121
+ (value,) = struct.unpack(">H", file.read(2))
1122
+ return value
1123
+
1124
+
1125
+ def readCard32(file):
1126
+ (value,) = struct.unpack(">L", file.read(4))
1127
+ return value
1128
+
1129
+
1130
+ def writeCard8(file, value):
1131
+ file.write(bytechr(value))
1132
+
1133
+
1134
+ def writeCard16(file, value):
1135
+ file.write(struct.pack(">H", value))
1136
+
1137
+
1138
+ def writeCard32(file, value):
1139
+ file.write(struct.pack(">L", value))
1140
+
1141
+
1142
+ def packCard8(value):
1143
+ return bytechr(value)
1144
+
1145
+
1146
+ def packCard16(value):
1147
+ return struct.pack(">H", value)
1148
+
1149
+
1150
+ def packCard32(value):
1151
+ return struct.pack(">L", value)
1152
+
1153
+
1154
+ def buildOperatorDict(table):
1155
+ d = {}
1156
+ for op, name, arg, default, conv in table:
1157
+ d[op] = (name, arg)
1158
+ return d
1159
+
1160
+
1161
+ def buildOpcodeDict(table):
1162
+ d = {}
1163
+ for op, name, arg, default, conv in table:
1164
+ if isinstance(op, tuple):
1165
+ op = bytechr(op[0]) + bytechr(op[1])
1166
+ else:
1167
+ op = bytechr(op)
1168
+ d[name] = (op, arg)
1169
+ return d
1170
+
1171
+
1172
+ def buildOrder(table):
1173
+ l = []
1174
+ for op, name, arg, default, conv in table:
1175
+ l.append(name)
1176
+ return l
1177
+
1178
+
1179
+ def buildDefaults(table):
1180
+ d = {}
1181
+ for op, name, arg, default, conv in table:
1182
+ if default is not None:
1183
+ d[name] = default
1184
+ return d
1185
+
1186
+
1187
+ def buildConverters(table):
1188
+ d = {}
1189
+ for op, name, arg, default, conv in table:
1190
+ d[name] = conv
1191
+ return d
1192
+
1193
+
1194
+ class SimpleConverter(object):
1195
+ def read(self, parent, value):
1196
+ if not hasattr(parent, "file"):
1197
+ return self._read(parent, value)
1198
+ file = parent.file
1199
+ pos = file.tell()
1200
+ try:
1201
+ return self._read(parent, value)
1202
+ finally:
1203
+ file.seek(pos)
1204
+
1205
+ def _read(self, parent, value):
1206
+ return value
1207
+
1208
+ def write(self, parent, value):
1209
+ return value
1210
+
1211
+ def xmlWrite(self, xmlWriter, name, value):
1212
+ xmlWriter.simpletag(name, value=value)
1213
+ xmlWriter.newline()
1214
+
1215
+ def xmlRead(self, name, attrs, content, parent):
1216
+ return attrs["value"]
1217
+
1218
+
1219
+ class ASCIIConverter(SimpleConverter):
1220
+ def _read(self, parent, value):
1221
+ return tostr(value, encoding="ascii")
1222
+
1223
+ def write(self, parent, value):
1224
+ return tobytes(value, encoding="ascii")
1225
+
1226
+ def xmlWrite(self, xmlWriter, name, value):
1227
+ xmlWriter.simpletag(name, value=tostr(value, encoding="ascii"))
1228
+ xmlWriter.newline()
1229
+
1230
+ def xmlRead(self, name, attrs, content, parent):
1231
+ return tobytes(attrs["value"], encoding=("ascii"))
1232
+
1233
+
1234
+ class Latin1Converter(SimpleConverter):
1235
+ def _read(self, parent, value):
1236
+ return tostr(value, encoding="latin1")
1237
+
1238
+ def write(self, parent, value):
1239
+ return tobytes(value, encoding="latin1")
1240
+
1241
+ def xmlWrite(self, xmlWriter, name, value):
1242
+ value = tostr(value, encoding="latin1")
1243
+ if name in ["Notice", "Copyright"]:
1244
+ value = re.sub(r"[\r\n]\s+", " ", value)
1245
+ xmlWriter.simpletag(name, value=value)
1246
+ xmlWriter.newline()
1247
+
1248
+ def xmlRead(self, name, attrs, content, parent):
1249
+ return tobytes(attrs["value"], encoding=("latin1"))
1250
+
1251
+
1252
+ def parseNum(s):
1253
+ try:
1254
+ value = int(s)
1255
+ except:
1256
+ value = float(s)
1257
+ return value
1258
+
1259
+
1260
+ def parseBlendList(s):
1261
+ valueList = []
1262
+ for element in s:
1263
+ if isinstance(element, str):
1264
+ continue
1265
+ name, attrs, content = element
1266
+ blendList = attrs["value"].split()
1267
+ blendList = [eval(val) for val in blendList]
1268
+ valueList.append(blendList)
1269
+ if len(valueList) == 1:
1270
+ valueList = valueList[0]
1271
+ return valueList
1272
+
1273
+
1274
+ class NumberConverter(SimpleConverter):
1275
+ def xmlWrite(self, xmlWriter, name, value):
1276
+ if isinstance(value, list):
1277
+ xmlWriter.begintag(name)
1278
+ xmlWriter.newline()
1279
+ xmlWriter.indent()
1280
+ blendValue = " ".join([str(val) for val in value])
1281
+ xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
1282
+ xmlWriter.newline()
1283
+ xmlWriter.dedent()
1284
+ xmlWriter.endtag(name)
1285
+ xmlWriter.newline()
1286
+ else:
1287
+ xmlWriter.simpletag(name, value=value)
1288
+ xmlWriter.newline()
1289
+
1290
+ def xmlRead(self, name, attrs, content, parent):
1291
+ valueString = attrs.get("value", None)
1292
+ if valueString is None:
1293
+ value = parseBlendList(content)
1294
+ else:
1295
+ value = parseNum(attrs["value"])
1296
+ return value
1297
+
1298
+
1299
+ class ArrayConverter(SimpleConverter):
1300
+ def xmlWrite(self, xmlWriter, name, value):
1301
+ if value and isinstance(value[0], list):
1302
+ xmlWriter.begintag(name)
1303
+ xmlWriter.newline()
1304
+ xmlWriter.indent()
1305
+ for valueList in value:
1306
+ blendValue = " ".join([str(val) for val in valueList])
1307
+ xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
1308
+ xmlWriter.newline()
1309
+ xmlWriter.dedent()
1310
+ xmlWriter.endtag(name)
1311
+ xmlWriter.newline()
1312
+ else:
1313
+ value = " ".join([str(val) for val in value])
1314
+ xmlWriter.simpletag(name, value=value)
1315
+ xmlWriter.newline()
1316
+
1317
+ def xmlRead(self, name, attrs, content, parent):
1318
+ valueString = attrs.get("value", None)
1319
+ if valueString is None:
1320
+ valueList = parseBlendList(content)
1321
+ else:
1322
+ values = valueString.split()
1323
+ valueList = [parseNum(value) for value in values]
1324
+ return valueList
1325
+
1326
+
1327
+ class TableConverter(SimpleConverter):
1328
+ def xmlWrite(self, xmlWriter, name, value):
1329
+ xmlWriter.begintag(name)
1330
+ xmlWriter.newline()
1331
+ value.toXML(xmlWriter)
1332
+ xmlWriter.endtag(name)
1333
+ xmlWriter.newline()
1334
+
1335
+ def xmlRead(self, name, attrs, content, parent):
1336
+ ob = self.getClass()()
1337
+ for element in content:
1338
+ if isinstance(element, str):
1339
+ continue
1340
+ name, attrs, content = element
1341
+ ob.fromXML(name, attrs, content)
1342
+ return ob
1343
+
1344
+
1345
+ class PrivateDictConverter(TableConverter):
1346
+ def getClass(self):
1347
+ return PrivateDict
1348
+
1349
+ def _read(self, parent, value):
1350
+ size, offset = value
1351
+ file = parent.file
1352
+ isCFF2 = parent._isCFF2
1353
+ try:
1354
+ vstore = parent.vstore
1355
+ except AttributeError:
1356
+ vstore = None
1357
+ priv = PrivateDict(parent.strings, file, offset, isCFF2=isCFF2, vstore=vstore)
1358
+ file.seek(offset)
1359
+ data = file.read(size)
1360
+ assert len(data) == size
1361
+ priv.decompile(data)
1362
+ return priv
1363
+
1364
+ def write(self, parent, value):
1365
+ return (0, 0) # dummy value
1366
+
1367
+
1368
+ class SubrsConverter(TableConverter):
1369
+ def getClass(self):
1370
+ return SubrsIndex
1371
+
1372
+ def _read(self, parent, value):
1373
+ file = parent.file
1374
+ isCFF2 = parent._isCFF2
1375
+ file.seek(parent.offset + value) # Offset(self)
1376
+ return SubrsIndex(file, isCFF2=isCFF2)
1377
+
1378
+ def write(self, parent, value):
1379
+ return 0 # dummy value
1380
+
1381
+
1382
+ class CharStringsConverter(TableConverter):
1383
+ def _read(self, parent, value):
1384
+ file = parent.file
1385
+ isCFF2 = parent._isCFF2
1386
+ charset = parent.charset
1387
+ varStore = getattr(parent, "VarStore", None)
1388
+ globalSubrs = parent.GlobalSubrs
1389
+ if hasattr(parent, "FDArray"):
1390
+ fdArray = parent.FDArray
1391
+ if hasattr(parent, "FDSelect"):
1392
+ fdSelect = parent.FDSelect
1393
+ else:
1394
+ fdSelect = None
1395
+ private = None
1396
+ else:
1397
+ fdSelect, fdArray = None, None
1398
+ private = parent.Private
1399
+ file.seek(value) # Offset(0)
1400
+ charStrings = CharStrings(
1401
+ file,
1402
+ charset,
1403
+ globalSubrs,
1404
+ private,
1405
+ fdSelect,
1406
+ fdArray,
1407
+ isCFF2=isCFF2,
1408
+ varStore=varStore,
1409
+ )
1410
+ return charStrings
1411
+
1412
+ def write(self, parent, value):
1413
+ return 0 # dummy value
1414
+
1415
+ def xmlRead(self, name, attrs, content, parent):
1416
+ if hasattr(parent, "FDArray"):
1417
+ # if it is a CID-keyed font, then the private Dict is extracted from the
1418
+ # parent.FDArray
1419
+ fdArray = parent.FDArray
1420
+ if hasattr(parent, "FDSelect"):
1421
+ fdSelect = parent.FDSelect
1422
+ else:
1423
+ fdSelect = None
1424
+ private = None
1425
+ else:
1426
+ # if it is a name-keyed font, then the private dict is in the top dict,
1427
+ # and
1428
+ # there is no fdArray.
1429
+ private, fdSelect, fdArray = parent.Private, None, None
1430
+ charStrings = CharStrings(
1431
+ None,
1432
+ None,
1433
+ parent.GlobalSubrs,
1434
+ private,
1435
+ fdSelect,
1436
+ fdArray,
1437
+ varStore=getattr(parent, "VarStore", None),
1438
+ )
1439
+ charStrings.fromXML(name, attrs, content)
1440
+ return charStrings
1441
+
1442
+
1443
+ class CharsetConverter(SimpleConverter):
1444
+ def _read(self, parent, value):
1445
+ isCID = hasattr(parent, "ROS")
1446
+ if value > 2:
1447
+ numGlyphs = parent.numGlyphs
1448
+ file = parent.file
1449
+ file.seek(value)
1450
+ log.log(DEBUG, "loading charset at %s", value)
1451
+ format = readCard8(file)
1452
+ if format == 0:
1453
+ charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
1454
+ elif format == 1 or format == 2:
1455
+ charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
1456
+ else:
1457
+ raise NotImplementedError
1458
+ assert len(charset) == numGlyphs
1459
+ log.log(DEBUG, " charset end at %s", file.tell())
1460
+ # make sure glyph names are unique
1461
+ allNames = {}
1462
+ newCharset = []
1463
+ for glyphName in charset:
1464
+ if glyphName in allNames:
1465
+ # make up a new glyphName that's unique
1466
+ n = allNames[glyphName]
1467
+ names = set(allNames) | set(charset)
1468
+ while (glyphName + "." + str(n)) in names:
1469
+ n += 1
1470
+ allNames[glyphName] = n + 1
1471
+ glyphName = glyphName + "." + str(n)
1472
+ allNames[glyphName] = 1
1473
+ newCharset.append(glyphName)
1474
+ charset = newCharset
1475
+ else: # offset == 0 -> no charset data.
1476
+ if isCID or "CharStrings" not in parent.rawDict:
1477
+ # We get here only when processing fontDicts from the FDArray of
1478
+ # CFF-CID fonts. Only the real topDict references the charset.
1479
+ assert value == 0
1480
+ charset = None
1481
+ elif value == 0:
1482
+ charset = cffISOAdobeStrings
1483
+ elif value == 1:
1484
+ charset = cffIExpertStrings
1485
+ elif value == 2:
1486
+ charset = cffExpertSubsetStrings
1487
+ if charset and (len(charset) != parent.numGlyphs):
1488
+ charset = charset[: parent.numGlyphs]
1489
+ return charset
1490
+
1491
+ def write(self, parent, value):
1492
+ return 0 # dummy value
1493
+
1494
+ def xmlWrite(self, xmlWriter, name, value):
1495
+ # XXX only write charset when not in OT/TTX context, where we
1496
+ # dump charset as a separate "GlyphOrder" table.
1497
+ # # xmlWriter.simpletag("charset")
1498
+ xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
1499
+ xmlWriter.newline()
1500
+
1501
+ def xmlRead(self, name, attrs, content, parent):
1502
+ pass
1503
+
1504
+
1505
+ class CharsetCompiler(object):
1506
+ def __init__(self, strings, charset, parent):
1507
+ assert charset[0] == ".notdef"
1508
+ isCID = hasattr(parent.dictObj, "ROS")
1509
+ data0 = packCharset0(charset, isCID, strings)
1510
+ data = packCharset(charset, isCID, strings)
1511
+ if len(data) < len(data0):
1512
+ self.data = data
1513
+ else:
1514
+ self.data = data0
1515
+ self.parent = parent
1516
+
1517
+ def setPos(self, pos, endPos):
1518
+ self.parent.rawDict["charset"] = pos
1519
+
1520
+ def getDataLength(self):
1521
+ return len(self.data)
1522
+
1523
+ def toFile(self, file):
1524
+ file.write(self.data)
1525
+
1526
+
1527
+ def getStdCharSet(charset):
1528
+ # check to see if we can use a predefined charset value.
1529
+ predefinedCharSetVal = None
1530
+ predefinedCharSets = [
1531
+ (cffISOAdobeStringCount, cffISOAdobeStrings, 0),
1532
+ (cffExpertStringCount, cffIExpertStrings, 1),
1533
+ (cffExpertSubsetStringCount, cffExpertSubsetStrings, 2),
1534
+ ]
1535
+ lcs = len(charset)
1536
+ for cnt, pcs, csv in predefinedCharSets:
1537
+ if predefinedCharSetVal is not None:
1538
+ break
1539
+ if lcs > cnt:
1540
+ continue
1541
+ predefinedCharSetVal = csv
1542
+ for i in range(lcs):
1543
+ if charset[i] != pcs[i]:
1544
+ predefinedCharSetVal = None
1545
+ break
1546
+ return predefinedCharSetVal
1547
+
1548
+
1549
+ def getCIDfromName(name, strings):
1550
+ return int(name[3:])
1551
+
1552
+
1553
+ def getSIDfromName(name, strings):
1554
+ return strings.getSID(name)
1555
+
1556
+
1557
+ def packCharset0(charset, isCID, strings):
1558
+ fmt = 0
1559
+ data = [packCard8(fmt)]
1560
+ if isCID:
1561
+ getNameID = getCIDfromName
1562
+ else:
1563
+ getNameID = getSIDfromName
1564
+
1565
+ for name in charset[1:]:
1566
+ data.append(packCard16(getNameID(name, strings)))
1567
+ return bytesjoin(data)
1568
+
1569
+
1570
+ def packCharset(charset, isCID, strings):
1571
+ fmt = 1
1572
+ ranges = []
1573
+ first = None
1574
+ end = 0
1575
+ if isCID:
1576
+ getNameID = getCIDfromName
1577
+ else:
1578
+ getNameID = getSIDfromName
1579
+
1580
+ for name in charset[1:]:
1581
+ SID = getNameID(name, strings)
1582
+ if first is None:
1583
+ first = SID
1584
+ elif end + 1 != SID:
1585
+ nLeft = end - first
1586
+ if nLeft > 255:
1587
+ fmt = 2
1588
+ ranges.append((first, nLeft))
1589
+ first = SID
1590
+ end = SID
1591
+ if end:
1592
+ nLeft = end - first
1593
+ if nLeft > 255:
1594
+ fmt = 2
1595
+ ranges.append((first, nLeft))
1596
+
1597
+ data = [packCard8(fmt)]
1598
+ if fmt == 1:
1599
+ nLeftFunc = packCard8
1600
+ else:
1601
+ nLeftFunc = packCard16
1602
+ for first, nLeft in ranges:
1603
+ data.append(packCard16(first) + nLeftFunc(nLeft))
1604
+ return bytesjoin(data)
1605
+
1606
+
1607
+ def parseCharset0(numGlyphs, file, strings, isCID):
1608
+ charset = [".notdef"]
1609
+ if isCID:
1610
+ for i in range(numGlyphs - 1):
1611
+ CID = readCard16(file)
1612
+ charset.append("cid" + str(CID).zfill(5))
1613
+ else:
1614
+ for i in range(numGlyphs - 1):
1615
+ SID = readCard16(file)
1616
+ charset.append(strings[SID])
1617
+ return charset
1618
+
1619
+
1620
+ def parseCharset(numGlyphs, file, strings, isCID, fmt):
1621
+ charset = [".notdef"]
1622
+ count = 1
1623
+ if fmt == 1:
1624
+ nLeftFunc = readCard8
1625
+ else:
1626
+ nLeftFunc = readCard16
1627
+ while count < numGlyphs:
1628
+ first = readCard16(file)
1629
+ nLeft = nLeftFunc(file)
1630
+ if isCID:
1631
+ for CID in range(first, first + nLeft + 1):
1632
+ charset.append("cid" + str(CID).zfill(5))
1633
+ else:
1634
+ for SID in range(first, first + nLeft + 1):
1635
+ charset.append(strings[SID])
1636
+ count = count + nLeft + 1
1637
+ return charset
1638
+
1639
+
1640
+ class EncodingCompiler(object):
1641
+ def __init__(self, strings, encoding, parent):
1642
+ assert not isinstance(encoding, str)
1643
+ data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
1644
+ data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
1645
+ if len(data0) < len(data1):
1646
+ self.data = data0
1647
+ else:
1648
+ self.data = data1
1649
+ self.parent = parent
1650
+
1651
+ def setPos(self, pos, endPos):
1652
+ self.parent.rawDict["Encoding"] = pos
1653
+
1654
+ def getDataLength(self):
1655
+ return len(self.data)
1656
+
1657
+ def toFile(self, file):
1658
+ file.write(self.data)
1659
+
1660
+
1661
+ class EncodingConverter(SimpleConverter):
1662
+ def _read(self, parent, value):
1663
+ if value == 0:
1664
+ return "StandardEncoding"
1665
+ elif value == 1:
1666
+ return "ExpertEncoding"
1667
+ # custom encoding at offset `value`
1668
+ assert value > 1
1669
+ file = parent.file
1670
+ file.seek(value)
1671
+ log.log(DEBUG, "loading Encoding at %s", value)
1672
+ fmt = readCard8(file)
1673
+ haveSupplement = bool(fmt & 0x80)
1674
+ fmt = fmt & 0x7F
1675
+
1676
+ if fmt == 0:
1677
+ encoding = parseEncoding0(parent.charset, file)
1678
+ elif fmt == 1:
1679
+ encoding = parseEncoding1(parent.charset, file)
1680
+ else:
1681
+ raise ValueError(f"Unknown Encoding format: {fmt}")
1682
+
1683
+ if haveSupplement:
1684
+ parseEncodingSupplement(file, encoding, parent.strings)
1685
+
1686
+ return encoding
1687
+
1688
+ def write(self, parent, value):
1689
+ if value == "StandardEncoding":
1690
+ return 0
1691
+ elif value == "ExpertEncoding":
1692
+ return 1
1693
+ return 0 # dummy value
1694
+
1695
+ def xmlWrite(self, xmlWriter, name, value):
1696
+ if value in ("StandardEncoding", "ExpertEncoding"):
1697
+ xmlWriter.simpletag(name, name=value)
1698
+ xmlWriter.newline()
1699
+ return
1700
+ xmlWriter.begintag(name)
1701
+ xmlWriter.newline()
1702
+ for code in range(len(value)):
1703
+ glyphName = value[code]
1704
+ if glyphName != ".notdef":
1705
+ xmlWriter.simpletag("map", code=hex(code), name=glyphName)
1706
+ xmlWriter.newline()
1707
+ xmlWriter.endtag(name)
1708
+ xmlWriter.newline()
1709
+
1710
+ def xmlRead(self, name, attrs, content, parent):
1711
+ if "name" in attrs:
1712
+ return attrs["name"]
1713
+ encoding = [".notdef"] * 256
1714
+ for element in content:
1715
+ if isinstance(element, str):
1716
+ continue
1717
+ name, attrs, content = element
1718
+ code = safeEval(attrs["code"])
1719
+ glyphName = attrs["name"]
1720
+ encoding[code] = glyphName
1721
+ return encoding
1722
+
1723
+
1724
+ def readSID(file):
1725
+ """Read a String ID (SID) — 2-byte unsigned integer."""
1726
+ data = file.read(2)
1727
+ if len(data) != 2:
1728
+ raise EOFError("Unexpected end of file while reading SID")
1729
+ return struct.unpack(">H", data)[0] # big-endian uint16
1730
+
1731
+
1732
+ def parseEncodingSupplement(file, encoding, strings):
1733
+ """
1734
+ Parse the CFF Encoding supplement data:
1735
+ - nSups: number of supplementary mappings
1736
+ - each mapping: (code, SID) pair
1737
+ and apply them to the `encoding` list in place.
1738
+ """
1739
+ nSups = readCard8(file)
1740
+ for _ in range(nSups):
1741
+ code = readCard8(file)
1742
+ sid = readSID(file)
1743
+ name = strings[sid]
1744
+ encoding[code] = name
1745
+
1746
+
1747
+ def parseEncoding0(charset, file):
1748
+ """
1749
+ Format 0: simple list of codes.
1750
+ After reading the base table, optionally parse the supplement.
1751
+ """
1752
+ nCodes = readCard8(file)
1753
+ encoding = [".notdef"] * 256
1754
+ for glyphID in range(1, nCodes + 1):
1755
+ code = readCard8(file)
1756
+ if code != 0:
1757
+ encoding[code] = charset[glyphID]
1758
+
1759
+ return encoding
1760
+
1761
+
1762
+ def parseEncoding1(charset, file):
1763
+ """
1764
+ Format 1: range-based encoding.
1765
+ After reading the base ranges, optionally parse the supplement.
1766
+ """
1767
+ nRanges = readCard8(file)
1768
+ encoding = [".notdef"] * 256
1769
+ glyphID = 1
1770
+ for _ in range(nRanges):
1771
+ code = readCard8(file)
1772
+ nLeft = readCard8(file)
1773
+ for _ in range(nLeft + 1):
1774
+ encoding[code] = charset[glyphID]
1775
+ code += 1
1776
+ glyphID += 1
1777
+
1778
+ return encoding
1779
+
1780
+
1781
+ def packEncoding0(charset, encoding, strings):
1782
+ fmt = 0
1783
+ m = {}
1784
+ for code in range(len(encoding)):
1785
+ name = encoding[code]
1786
+ if name != ".notdef":
1787
+ m[name] = code
1788
+ codes = []
1789
+ for name in charset[1:]:
1790
+ code = m.get(name)
1791
+ codes.append(code)
1792
+
1793
+ while codes and codes[-1] is None:
1794
+ codes.pop()
1795
+
1796
+ data = [packCard8(fmt), packCard8(len(codes))]
1797
+ for code in codes:
1798
+ if code is None:
1799
+ code = 0
1800
+ data.append(packCard8(code))
1801
+ return bytesjoin(data)
1802
+
1803
+
1804
+ def packEncoding1(charset, encoding, strings):
1805
+ fmt = 1
1806
+ m = {}
1807
+ for code in range(len(encoding)):
1808
+ name = encoding[code]
1809
+ if name != ".notdef":
1810
+ m[name] = code
1811
+ ranges = []
1812
+ first = None
1813
+ end = 0
1814
+ for name in charset[1:]:
1815
+ code = m.get(name, -1)
1816
+ if first is None:
1817
+ first = code
1818
+ elif end + 1 != code:
1819
+ nLeft = end - first
1820
+ ranges.append((first, nLeft))
1821
+ first = code
1822
+ end = code
1823
+ nLeft = end - first
1824
+ ranges.append((first, nLeft))
1825
+
1826
+ # remove unencoded glyphs at the end.
1827
+ while ranges and ranges[-1][0] == -1:
1828
+ ranges.pop()
1829
+
1830
+ data = [packCard8(fmt), packCard8(len(ranges))]
1831
+ for first, nLeft in ranges:
1832
+ if first == -1: # unencoded
1833
+ first = 0
1834
+ data.append(packCard8(first) + packCard8(nLeft))
1835
+ return bytesjoin(data)
1836
+
1837
+
1838
+ class FDArrayConverter(TableConverter):
1839
+ def _read(self, parent, value):
1840
+ try:
1841
+ vstore = parent.VarStore
1842
+ except AttributeError:
1843
+ vstore = None
1844
+ file = parent.file
1845
+ isCFF2 = parent._isCFF2
1846
+ file.seek(value)
1847
+ fdArray = FDArrayIndex(file, isCFF2=isCFF2)
1848
+ fdArray.vstore = vstore
1849
+ fdArray.strings = parent.strings
1850
+ fdArray.GlobalSubrs = parent.GlobalSubrs
1851
+ return fdArray
1852
+
1853
+ def write(self, parent, value):
1854
+ return 0 # dummy value
1855
+
1856
+ def xmlRead(self, name, attrs, content, parent):
1857
+ fdArray = FDArrayIndex()
1858
+ for element in content:
1859
+ if isinstance(element, str):
1860
+ continue
1861
+ name, attrs, content = element
1862
+ fdArray.fromXML(name, attrs, content)
1863
+ return fdArray
1864
+
1865
+
1866
+ class FDSelectConverter(SimpleConverter):
1867
+ def _read(self, parent, value):
1868
+ file = parent.file
1869
+ file.seek(value)
1870
+ fdSelect = FDSelect(file, parent.numGlyphs)
1871
+ return fdSelect
1872
+
1873
+ def write(self, parent, value):
1874
+ return 0 # dummy value
1875
+
1876
+ # The FDSelect glyph data is written out to XML in the charstring keys,
1877
+ # so we write out only the format selector
1878
+ def xmlWrite(self, xmlWriter, name, value):
1879
+ xmlWriter.simpletag(name, [("format", value.format)])
1880
+ xmlWriter.newline()
1881
+
1882
+ def xmlRead(self, name, attrs, content, parent):
1883
+ fmt = safeEval(attrs["format"])
1884
+ file = None
1885
+ numGlyphs = None
1886
+ fdSelect = FDSelect(file, numGlyphs, fmt)
1887
+ return fdSelect
1888
+
1889
+
1890
+ class VarStoreConverter(SimpleConverter):
1891
+ def _read(self, parent, value):
1892
+ file = parent.file
1893
+ file.seek(value)
1894
+ varStore = VarStoreData(file)
1895
+ varStore.decompile()
1896
+ return varStore
1897
+
1898
+ def write(self, parent, value):
1899
+ return 0 # dummy value
1900
+
1901
+ def xmlWrite(self, xmlWriter, name, value):
1902
+ value.writeXML(xmlWriter, name)
1903
+
1904
+ def xmlRead(self, name, attrs, content, parent):
1905
+ varStore = VarStoreData()
1906
+ varStore.xmlRead(name, attrs, content, parent)
1907
+ return varStore
1908
+
1909
+
1910
+ def packFDSelect0(fdSelectArray):
1911
+ fmt = 0
1912
+ data = [packCard8(fmt)]
1913
+ for index in fdSelectArray:
1914
+ data.append(packCard8(index))
1915
+ return bytesjoin(data)
1916
+
1917
+
1918
+ def packFDSelect3(fdSelectArray):
1919
+ fmt = 3
1920
+ fdRanges = []
1921
+ lenArray = len(fdSelectArray)
1922
+ lastFDIndex = -1
1923
+ for i in range(lenArray):
1924
+ fdIndex = fdSelectArray[i]
1925
+ if lastFDIndex != fdIndex:
1926
+ fdRanges.append([i, fdIndex])
1927
+ lastFDIndex = fdIndex
1928
+ sentinelGID = i + 1
1929
+
1930
+ data = [packCard8(fmt)]
1931
+ data.append(packCard16(len(fdRanges)))
1932
+ for fdRange in fdRanges:
1933
+ data.append(packCard16(fdRange[0]))
1934
+ data.append(packCard8(fdRange[1]))
1935
+ data.append(packCard16(sentinelGID))
1936
+ return bytesjoin(data)
1937
+
1938
+
1939
+ def packFDSelect4(fdSelectArray):
1940
+ fmt = 4
1941
+ fdRanges = []
1942
+ lenArray = len(fdSelectArray)
1943
+ lastFDIndex = -1
1944
+ for i in range(lenArray):
1945
+ fdIndex = fdSelectArray[i]
1946
+ if lastFDIndex != fdIndex:
1947
+ fdRanges.append([i, fdIndex])
1948
+ lastFDIndex = fdIndex
1949
+ sentinelGID = i + 1
1950
+
1951
+ data = [packCard8(fmt)]
1952
+ data.append(packCard32(len(fdRanges)))
1953
+ for fdRange in fdRanges:
1954
+ data.append(packCard32(fdRange[0]))
1955
+ data.append(packCard16(fdRange[1]))
1956
+ data.append(packCard32(sentinelGID))
1957
+ return bytesjoin(data)
1958
+
1959
+
1960
+ class FDSelectCompiler(object):
1961
+ def __init__(self, fdSelect, parent):
1962
+ fmt = fdSelect.format
1963
+ fdSelectArray = fdSelect.gidArray
1964
+ if fmt == 0:
1965
+ self.data = packFDSelect0(fdSelectArray)
1966
+ elif fmt == 3:
1967
+ self.data = packFDSelect3(fdSelectArray)
1968
+ elif fmt == 4:
1969
+ self.data = packFDSelect4(fdSelectArray)
1970
+ else:
1971
+ # choose smaller of the two formats
1972
+ data0 = packFDSelect0(fdSelectArray)
1973
+ data3 = packFDSelect3(fdSelectArray)
1974
+ if len(data0) < len(data3):
1975
+ self.data = data0
1976
+ fdSelect.format = 0
1977
+ else:
1978
+ self.data = data3
1979
+ fdSelect.format = 3
1980
+
1981
+ self.parent = parent
1982
+
1983
+ def setPos(self, pos, endPos):
1984
+ self.parent.rawDict["FDSelect"] = pos
1985
+
1986
+ def getDataLength(self):
1987
+ return len(self.data)
1988
+
1989
+ def toFile(self, file):
1990
+ file.write(self.data)
1991
+
1992
+
1993
+ class VarStoreCompiler(object):
1994
+ def __init__(self, varStoreData, parent):
1995
+ self.parent = parent
1996
+ if not varStoreData.data:
1997
+ varStoreData.compile()
1998
+ varStoreDataLen = min(0xFFFF, len(varStoreData.data))
1999
+ data = [packCard16(varStoreDataLen), varStoreData.data]
2000
+ self.data = bytesjoin(data)
2001
+
2002
+ def setPos(self, pos, endPos):
2003
+ self.parent.rawDict["VarStore"] = pos
2004
+
2005
+ def getDataLength(self):
2006
+ return len(self.data)
2007
+
2008
+ def toFile(self, file):
2009
+ file.write(self.data)
2010
+
2011
+
2012
+ class ROSConverter(SimpleConverter):
2013
+ def xmlWrite(self, xmlWriter, name, value):
2014
+ registry, order, supplement = value
2015
+ xmlWriter.simpletag(
2016
+ name,
2017
+ [
2018
+ ("Registry", tostr(registry)),
2019
+ ("Order", tostr(order)),
2020
+ ("Supplement", supplement),
2021
+ ],
2022
+ )
2023
+ xmlWriter.newline()
2024
+
2025
+ def xmlRead(self, name, attrs, content, parent):
2026
+ return (attrs["Registry"], attrs["Order"], safeEval(attrs["Supplement"]))
2027
+
2028
+
2029
+ topDictOperators = [
2030
+ # opcode name argument type default converter
2031
+ (25, "maxstack", "number", None, None),
2032
+ ((12, 30), "ROS", ("SID", "SID", "number"), None, ROSConverter()),
2033
+ ((12, 20), "SyntheticBase", "number", None, None),
2034
+ (0, "version", "SID", None, None),
2035
+ (1, "Notice", "SID", None, Latin1Converter()),
2036
+ ((12, 0), "Copyright", "SID", None, Latin1Converter()),
2037
+ (2, "FullName", "SID", None, Latin1Converter()),
2038
+ ((12, 38), "FontName", "SID", None, Latin1Converter()),
2039
+ (3, "FamilyName", "SID", None, Latin1Converter()),
2040
+ (4, "Weight", "SID", None, None),
2041
+ ((12, 1), "isFixedPitch", "number", 0, None),
2042
+ ((12, 2), "ItalicAngle", "number", 0, None),
2043
+ ((12, 3), "UnderlinePosition", "number", -100, None),
2044
+ ((12, 4), "UnderlineThickness", "number", 50, None),
2045
+ ((12, 5), "PaintType", "number", 0, None),
2046
+ ((12, 6), "CharstringType", "number", 2, None),
2047
+ ((12, 7), "FontMatrix", "array", [0.001, 0, 0, 0.001, 0, 0], None),
2048
+ (13, "UniqueID", "number", None, None),
2049
+ (5, "FontBBox", "array", [0, 0, 0, 0], None),
2050
+ ((12, 8), "StrokeWidth", "number", 0, None),
2051
+ (14, "XUID", "array", None, None),
2052
+ ((12, 21), "PostScript", "SID", None, None),
2053
+ ((12, 22), "BaseFontName", "SID", None, None),
2054
+ ((12, 23), "BaseFontBlend", "delta", None, None),
2055
+ ((12, 31), "CIDFontVersion", "number", 0, None),
2056
+ ((12, 32), "CIDFontRevision", "number", 0, None),
2057
+ ((12, 33), "CIDFontType", "number", 0, None),
2058
+ ((12, 34), "CIDCount", "number", 8720, None),
2059
+ (15, "charset", "number", None, CharsetConverter()),
2060
+ ((12, 35), "UIDBase", "number", None, None),
2061
+ (16, "Encoding", "number", 0, EncodingConverter()),
2062
+ (18, "Private", ("number", "number"), None, PrivateDictConverter()),
2063
+ ((12, 37), "FDSelect", "number", None, FDSelectConverter()),
2064
+ ((12, 36), "FDArray", "number", None, FDArrayConverter()),
2065
+ (17, "CharStrings", "number", None, CharStringsConverter()),
2066
+ (24, "VarStore", "number", None, VarStoreConverter()),
2067
+ ]
2068
+
2069
+ topDictOperators2 = [
2070
+ # opcode name argument type default converter
2071
+ (25, "maxstack", "number", None, None),
2072
+ ((12, 7), "FontMatrix", "array", [0.001, 0, 0, 0.001, 0, 0], None),
2073
+ ((12, 37), "FDSelect", "number", None, FDSelectConverter()),
2074
+ ((12, 36), "FDArray", "number", None, FDArrayConverter()),
2075
+ (17, "CharStrings", "number", None, CharStringsConverter()),
2076
+ (24, "VarStore", "number", None, VarStoreConverter()),
2077
+ ]
2078
+
2079
+ # Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
2080
+ # in order for the font to compile back from xml.
2081
+
2082
+ kBlendDictOpName = "blend"
2083
+ blendOp = 23
2084
+
2085
+ privateDictOperators = [
2086
+ # opcode name argument type default converter
2087
+ (22, "vsindex", "number", None, None),
2088
+ (
2089
+ blendOp,
2090
+ kBlendDictOpName,
2091
+ "blendList",
2092
+ None,
2093
+ None,
2094
+ ), # This is for reading to/from XML: it not written to CFF.
2095
+ (6, "BlueValues", "delta", None, None),
2096
+ (7, "OtherBlues", "delta", None, None),
2097
+ (8, "FamilyBlues", "delta", None, None),
2098
+ (9, "FamilyOtherBlues", "delta", None, None),
2099
+ ((12, 9), "BlueScale", "number", 0.039625, None),
2100
+ ((12, 10), "BlueShift", "number", 7, None),
2101
+ ((12, 11), "BlueFuzz", "number", 1, None),
2102
+ (10, "StdHW", "number", None, None),
2103
+ (11, "StdVW", "number", None, None),
2104
+ ((12, 12), "StemSnapH", "delta", None, None),
2105
+ ((12, 13), "StemSnapV", "delta", None, None),
2106
+ ((12, 14), "ForceBold", "number", 0, None),
2107
+ ((12, 15), "ForceBoldThreshold", "number", None, None), # deprecated
2108
+ ((12, 16), "lenIV", "number", None, None), # deprecated
2109
+ ((12, 17), "LanguageGroup", "number", 0, None),
2110
+ ((12, 18), "ExpansionFactor", "number", 0.06, None),
2111
+ ((12, 19), "initialRandomSeed", "number", 0, None),
2112
+ (20, "defaultWidthX", "number", 0, None),
2113
+ (21, "nominalWidthX", "number", 0, None),
2114
+ (19, "Subrs", "number", None, SubrsConverter()),
2115
+ ]
2116
+
2117
+ privateDictOperators2 = [
2118
+ # opcode name argument type default converter
2119
+ (22, "vsindex", "number", None, None),
2120
+ (
2121
+ blendOp,
2122
+ kBlendDictOpName,
2123
+ "blendList",
2124
+ None,
2125
+ None,
2126
+ ), # This is for reading to/from XML: it not written to CFF.
2127
+ (6, "BlueValues", "delta", None, None),
2128
+ (7, "OtherBlues", "delta", None, None),
2129
+ (8, "FamilyBlues", "delta", None, None),
2130
+ (9, "FamilyOtherBlues", "delta", None, None),
2131
+ ((12, 9), "BlueScale", "number", 0.039625, None),
2132
+ ((12, 10), "BlueShift", "number", 7, None),
2133
+ ((12, 11), "BlueFuzz", "number", 1, None),
2134
+ (10, "StdHW", "number", None, None),
2135
+ (11, "StdVW", "number", None, None),
2136
+ ((12, 12), "StemSnapH", "delta", None, None),
2137
+ ((12, 13), "StemSnapV", "delta", None, None),
2138
+ ((12, 17), "LanguageGroup", "number", 0, None),
2139
+ ((12, 18), "ExpansionFactor", "number", 0.06, None),
2140
+ (19, "Subrs", "number", None, SubrsConverter()),
2141
+ ]
2142
+
2143
+
2144
+ def addConverters(table):
2145
+ for i in range(len(table)):
2146
+ op, name, arg, default, conv = table[i]
2147
+ if conv is not None:
2148
+ continue
2149
+ if arg in ("delta", "array"):
2150
+ conv = ArrayConverter()
2151
+ elif arg == "number":
2152
+ conv = NumberConverter()
2153
+ elif arg == "SID":
2154
+ conv = ASCIIConverter()
2155
+ elif arg == "blendList":
2156
+ conv = None
2157
+ else:
2158
+ assert False
2159
+ table[i] = op, name, arg, default, conv
2160
+
2161
+
2162
+ addConverters(privateDictOperators)
2163
+ addConverters(topDictOperators)
2164
+
2165
+
2166
+ class TopDictDecompiler(psCharStrings.DictDecompiler):
2167
+ operators = buildOperatorDict(topDictOperators)
2168
+
2169
+
2170
+ class PrivateDictDecompiler(psCharStrings.DictDecompiler):
2171
+ operators = buildOperatorDict(privateDictOperators)
2172
+
2173
+
2174
+ class DictCompiler(object):
2175
+ maxBlendStack = 0
2176
+
2177
+ def __init__(self, dictObj, strings, parent, isCFF2=None):
2178
+ if strings:
2179
+ assert isinstance(strings, IndexedStrings)
2180
+ if isCFF2 is None and hasattr(parent, "isCFF2"):
2181
+ isCFF2 = parent.isCFF2
2182
+ assert isCFF2 is not None
2183
+ self.isCFF2 = isCFF2
2184
+ self.dictObj = dictObj
2185
+ self.strings = strings
2186
+ self.parent = parent
2187
+ rawDict = {}
2188
+ for name in dictObj.order:
2189
+ value = getattr(dictObj, name, None)
2190
+ if value is None:
2191
+ continue
2192
+ conv = dictObj.converters[name]
2193
+ value = conv.write(dictObj, value)
2194
+ if value == dictObj.defaults.get(name):
2195
+ continue
2196
+ rawDict[name] = value
2197
+ self.rawDict = rawDict
2198
+
2199
+ def setPos(self, pos, endPos):
2200
+ pass
2201
+
2202
+ def getDataLength(self):
2203
+ return len(self.compile("getDataLength"))
2204
+
2205
+ def compile(self, reason):
2206
+ log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason)
2207
+ rawDict = self.rawDict
2208
+ data = []
2209
+ for name in self.dictObj.order:
2210
+ value = rawDict.get(name)
2211
+ if value is None:
2212
+ continue
2213
+ op, argType = self.opcodes[name]
2214
+ if isinstance(argType, tuple):
2215
+ l = len(argType)
2216
+ assert len(value) == l, "value doesn't match arg type"
2217
+ for i in range(l):
2218
+ arg = argType[i]
2219
+ v = value[i]
2220
+ arghandler = getattr(self, "arg_" + arg)
2221
+ data.append(arghandler(v))
2222
+ else:
2223
+ arghandler = getattr(self, "arg_" + argType)
2224
+ data.append(arghandler(value))
2225
+ data.append(op)
2226
+ data = bytesjoin(data)
2227
+ return data
2228
+
2229
+ def toFile(self, file):
2230
+ data = self.compile("toFile")
2231
+ file.write(data)
2232
+
2233
+ def arg_number(self, num):
2234
+ if isinstance(num, list):
2235
+ data = [encodeNumber(val) for val in num]
2236
+ data.append(encodeNumber(1))
2237
+ data.append(bytechr(blendOp))
2238
+ datum = bytesjoin(data)
2239
+ else:
2240
+ datum = encodeNumber(num)
2241
+ return datum
2242
+
2243
+ def arg_SID(self, s):
2244
+ return psCharStrings.encodeIntCFF(self.strings.getSID(s))
2245
+
2246
+ def arg_array(self, value):
2247
+ data = []
2248
+ for num in value:
2249
+ data.append(self.arg_number(num))
2250
+ return bytesjoin(data)
2251
+
2252
+ def arg_delta(self, value):
2253
+ if not value:
2254
+ return b""
2255
+ val0 = value[0]
2256
+ if isinstance(val0, list):
2257
+ data = self.arg_delta_blend(value)
2258
+ else:
2259
+ out = []
2260
+ last = 0
2261
+ for v in value:
2262
+ out.append(v - last)
2263
+ last = v
2264
+ data = []
2265
+ for num in out:
2266
+ data.append(encodeNumber(num))
2267
+ return bytesjoin(data)
2268
+
2269
+ def arg_delta_blend(self, value):
2270
+ """A delta list with blend lists has to be *all* blend lists.
2271
+
2272
+ The value is a list is arranged as follows::
2273
+
2274
+ [
2275
+ [V0, d0..dn]
2276
+ [V1, d0..dn]
2277
+ ...
2278
+ [Vm, d0..dn]
2279
+ ]
2280
+
2281
+ ``V`` is the absolute coordinate value from the default font, and ``d0-dn``
2282
+ are the delta values from the *n* regions. Each ``V`` is an absolute
2283
+ coordinate from the default font.
2284
+
2285
+ We want to return a list::
2286
+
2287
+ [
2288
+ [v0, v1..vm]
2289
+ [d0..dn]
2290
+ ...
2291
+ [d0..dn]
2292
+ numBlends
2293
+ blendOp
2294
+ ]
2295
+
2296
+ where each ``v`` is relative to the previous default font value.
2297
+ """
2298
+ numMasters = len(value[0])
2299
+ numBlends = len(value)
2300
+ numStack = (numBlends * numMasters) + 1
2301
+ if numStack > self.maxBlendStack:
2302
+ # Figure out the max number of value we can blend
2303
+ # and divide this list up into chunks of that size.
2304
+
2305
+ numBlendValues = int((self.maxBlendStack - 1) / numMasters)
2306
+ out = []
2307
+ while True:
2308
+ numVal = min(len(value), numBlendValues)
2309
+ if numVal == 0:
2310
+ break
2311
+ valList = value[0:numVal]
2312
+ out1 = self.arg_delta_blend(valList)
2313
+ out.extend(out1)
2314
+ value = value[numVal:]
2315
+ else:
2316
+ firstList = [0] * numBlends
2317
+ deltaList = [None] * numBlends
2318
+ i = 0
2319
+ prevVal = 0
2320
+ while i < numBlends:
2321
+ # For PrivateDict BlueValues, the default font
2322
+ # values are absolute, not relative.
2323
+ # Must convert these back to relative coordinates
2324
+ # before writing to CFF2.
2325
+ defaultValue = value[i][0]
2326
+ firstList[i] = defaultValue - prevVal
2327
+ prevVal = defaultValue
2328
+ deltaList[i] = value[i][1:]
2329
+ i += 1
2330
+
2331
+ relValueList = firstList
2332
+ for blendList in deltaList:
2333
+ relValueList.extend(blendList)
2334
+ out = [encodeNumber(val) for val in relValueList]
2335
+ out.append(encodeNumber(numBlends))
2336
+ out.append(bytechr(blendOp))
2337
+ return out
2338
+
2339
+
2340
+ def encodeNumber(num):
2341
+ if isinstance(num, float):
2342
+ return psCharStrings.encodeFloat(num)
2343
+ else:
2344
+ return psCharStrings.encodeIntCFF(num)
2345
+
2346
+
2347
+ class TopDictCompiler(DictCompiler):
2348
+ opcodes = buildOpcodeDict(topDictOperators)
2349
+
2350
+ def getChildren(self, strings):
2351
+ isCFF2 = self.isCFF2
2352
+ children = []
2353
+ if self.dictObj.cff2GetGlyphOrder is None:
2354
+ if hasattr(self.dictObj, "charset") and self.dictObj.charset:
2355
+ if hasattr(self.dictObj, "ROS"): # aka isCID
2356
+ charsetCode = None
2357
+ else:
2358
+ charsetCode = getStdCharSet(self.dictObj.charset)
2359
+ if charsetCode is None:
2360
+ children.append(
2361
+ CharsetCompiler(strings, self.dictObj.charset, self)
2362
+ )
2363
+ else:
2364
+ self.rawDict["charset"] = charsetCode
2365
+ if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding:
2366
+ encoding = self.dictObj.Encoding
2367
+ if not isinstance(encoding, str):
2368
+ children.append(EncodingCompiler(strings, encoding, self))
2369
+ else:
2370
+ if hasattr(self.dictObj, "VarStore"):
2371
+ varStoreData = self.dictObj.VarStore
2372
+ varStoreComp = VarStoreCompiler(varStoreData, self)
2373
+ children.append(varStoreComp)
2374
+ if hasattr(self.dictObj, "FDSelect"):
2375
+ # I have not yet supported merging a ttx CFF-CID font, as there are
2376
+ # interesting issues about merging the FDArrays. Here I assume that
2377
+ # either the font was read from XML, and the FDSelect indices are all
2378
+ # in the charstring data, or the FDSelect array is already fully defined.
2379
+ fdSelect = self.dictObj.FDSelect
2380
+ # probably read in from XML; assume fdIndex in CharString data
2381
+ if len(fdSelect) == 0:
2382
+ charStrings = self.dictObj.CharStrings
2383
+ for name in self.dictObj.charset:
2384
+ fdSelect.append(charStrings[name].fdSelectIndex)
2385
+ fdSelectComp = FDSelectCompiler(fdSelect, self)
2386
+ children.append(fdSelectComp)
2387
+ if hasattr(self.dictObj, "CharStrings"):
2388
+ items = []
2389
+ charStrings = self.dictObj.CharStrings
2390
+ for name in self.dictObj.charset:
2391
+ items.append(charStrings[name])
2392
+ charStringsComp = CharStringsCompiler(items, strings, self, isCFF2=isCFF2)
2393
+ children.append(charStringsComp)
2394
+ if hasattr(self.dictObj, "FDArray"):
2395
+ # I have not yet supported merging a ttx CFF-CID font, as there are
2396
+ # interesting issues about merging the FDArrays. Here I assume that the
2397
+ # FDArray info is correct and complete.
2398
+ fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
2399
+ children.append(fdArrayIndexComp)
2400
+ children.extend(fdArrayIndexComp.getChildren(strings))
2401
+ if hasattr(self.dictObj, "Private"):
2402
+ privComp = self.dictObj.Private.getCompiler(strings, self)
2403
+ children.append(privComp)
2404
+ children.extend(privComp.getChildren(strings))
2405
+ return children
2406
+
2407
+
2408
+ class FontDictCompiler(DictCompiler):
2409
+ opcodes = buildOpcodeDict(topDictOperators)
2410
+
2411
+ def __init__(self, dictObj, strings, parent, isCFF2=None):
2412
+ super(FontDictCompiler, self).__init__(dictObj, strings, parent, isCFF2=isCFF2)
2413
+ #
2414
+ # We now take some effort to detect if there were any key/value pairs
2415
+ # supplied that were ignored in the FontDict context, and issue a warning
2416
+ # for those cases.
2417
+ #
2418
+ ignoredNames = []
2419
+ dictObj = self.dictObj
2420
+ for name in sorted(set(dictObj.converters) - set(dictObj.order)):
2421
+ if name in dictObj.rawDict:
2422
+ # The font was directly read from binary. In this
2423
+ # case, we want to report *all* "useless" key/value
2424
+ # pairs that are in the font, not just the ones that
2425
+ # are different from the default.
2426
+ ignoredNames.append(name)
2427
+ else:
2428
+ # The font was probably read from a TTX file. We only
2429
+ # warn about keys whos value is not the default. The
2430
+ # ones that have the default value will not be written
2431
+ # to binary anyway.
2432
+ default = dictObj.defaults.get(name)
2433
+ if default is not None:
2434
+ conv = dictObj.converters[name]
2435
+ default = conv.read(dictObj, default)
2436
+ if getattr(dictObj, name, None) != default:
2437
+ ignoredNames.append(name)
2438
+ if ignoredNames:
2439
+ log.warning(
2440
+ "Some CFF FDArray/FontDict keys were ignored upon compile: "
2441
+ + " ".join(sorted(ignoredNames))
2442
+ )
2443
+
2444
+ def getChildren(self, strings):
2445
+ children = []
2446
+ if hasattr(self.dictObj, "Private"):
2447
+ privComp = self.dictObj.Private.getCompiler(strings, self)
2448
+ children.append(privComp)
2449
+ children.extend(privComp.getChildren(strings))
2450
+ return children
2451
+
2452
+
2453
+ class PrivateDictCompiler(DictCompiler):
2454
+ maxBlendStack = maxStackLimit
2455
+ opcodes = buildOpcodeDict(privateDictOperators)
2456
+
2457
+ def setPos(self, pos, endPos):
2458
+ size = endPos - pos
2459
+ self.parent.rawDict["Private"] = size, pos
2460
+ self.pos = pos
2461
+
2462
+ def getChildren(self, strings):
2463
+ children = []
2464
+ if hasattr(self.dictObj, "Subrs"):
2465
+ children.append(self.dictObj.Subrs.getCompiler(strings, self))
2466
+ return children
2467
+
2468
+
2469
+ class BaseDict(object):
2470
+ def __init__(self, strings=None, file=None, offset=None, isCFF2=None):
2471
+ assert (isCFF2 is None) == (file is None)
2472
+ self.rawDict = {}
2473
+ self.skipNames = []
2474
+ self.strings = strings
2475
+ if file is None:
2476
+ return
2477
+ self._isCFF2 = isCFF2
2478
+ self.file = file
2479
+ if offset is not None:
2480
+ log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset)
2481
+ self.offset = offset
2482
+
2483
+ def decompile(self, data):
2484
+ log.log(DEBUG, " length %s is %d", self.__class__.__name__, len(data))
2485
+ dec = self.decompilerClass(self.strings, self)
2486
+ dec.decompile(data)
2487
+ self.rawDict = dec.getDict()
2488
+ self.postDecompile()
2489
+
2490
+ def postDecompile(self):
2491
+ pass
2492
+
2493
+ def getCompiler(self, strings, parent, isCFF2=None):
2494
+ return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
2495
+
2496
+ def __getattr__(self, name):
2497
+ if name[:2] == name[-2:] == "__":
2498
+ # to make deepcopy() and pickle.load() work, we need to signal with
2499
+ # AttributeError that dunder methods like '__deepcopy__' or '__getstate__'
2500
+ # aren't implemented. For more details, see:
2501
+ # https://github.com/fonttools/fonttools/pull/1488
2502
+ raise AttributeError(name)
2503
+ value = self.rawDict.get(name, None)
2504
+ if value is None:
2505
+ value = self.defaults.get(name)
2506
+ if value is None:
2507
+ raise AttributeError(name)
2508
+ conv = self.converters[name]
2509
+ value = conv.read(self, value)
2510
+ setattr(self, name, value)
2511
+ return value
2512
+
2513
+ def toXML(self, xmlWriter):
2514
+ for name in self.order:
2515
+ if name in self.skipNames:
2516
+ continue
2517
+ value = getattr(self, name, None)
2518
+ # XXX For "charset" we never skip calling xmlWrite even if the
2519
+ # value is None, so we always write the following XML comment:
2520
+ #
2521
+ # <!-- charset is dumped separately as the 'GlyphOrder' element -->
2522
+ #
2523
+ # Charset is None when 'CFF ' table is imported from XML into an
2524
+ # empty TTFont(). By writing this comment all the time, we obtain
2525
+ # the same XML output whether roundtripping XML-to-XML or
2526
+ # dumping binary-to-XML
2527
+ if value is None and name != "charset":
2528
+ continue
2529
+ conv = self.converters[name]
2530
+ conv.xmlWrite(xmlWriter, name, value)
2531
+ ignoredNames = set(self.rawDict) - set(self.order)
2532
+ if ignoredNames:
2533
+ xmlWriter.comment(
2534
+ "some keys were ignored: %s" % " ".join(sorted(ignoredNames))
2535
+ )
2536
+ xmlWriter.newline()
2537
+
2538
+ def fromXML(self, name, attrs, content):
2539
+ conv = self.converters[name]
2540
+ value = conv.xmlRead(name, attrs, content, self)
2541
+ setattr(self, name, value)
2542
+
2543
+
2544
+ class TopDict(BaseDict):
2545
+ """The ``TopDict`` represents the top-level dictionary holding font
2546
+ information. CFF2 tables contain a restricted set of top-level entries
2547
+ as described `here <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>`_,
2548
+ but CFF tables may contain a wider range of information. This information
2549
+ can be accessed through attributes or through the dictionary returned
2550
+ through the ``rawDict`` property:
2551
+
2552
+ .. code:: python
2553
+
2554
+ font = tt["CFF "].cff[0]
2555
+ font.FamilyName
2556
+ # 'Linux Libertine O'
2557
+ font.rawDict["FamilyName"]
2558
+ # 'Linux Libertine O'
2559
+
2560
+ More information is available in the CFF file's private dictionary, accessed
2561
+ via the ``Private`` property:
2562
+
2563
+ .. code:: python
2564
+
2565
+ tt["CFF "].cff[0].Private.BlueValues
2566
+ # [-15, 0, 515, 515, 666, 666]
2567
+
2568
+ """
2569
+
2570
+ defaults = buildDefaults(topDictOperators)
2571
+ converters = buildConverters(topDictOperators)
2572
+ compilerClass = TopDictCompiler
2573
+ order = buildOrder(topDictOperators)
2574
+ decompilerClass = TopDictDecompiler
2575
+
2576
+ def __init__(
2577
+ self,
2578
+ strings=None,
2579
+ file=None,
2580
+ offset=None,
2581
+ GlobalSubrs=None,
2582
+ cff2GetGlyphOrder=None,
2583
+ isCFF2=None,
2584
+ ):
2585
+ super(TopDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2586
+ self.cff2GetGlyphOrder = cff2GetGlyphOrder
2587
+ self.GlobalSubrs = GlobalSubrs
2588
+ if isCFF2:
2589
+ self.defaults = buildDefaults(topDictOperators2)
2590
+ self.charset = cff2GetGlyphOrder()
2591
+ self.order = buildOrder(topDictOperators2)
2592
+ else:
2593
+ self.defaults = buildDefaults(topDictOperators)
2594
+ self.order = buildOrder(topDictOperators)
2595
+
2596
+ def getGlyphOrder(self):
2597
+ """Returns a list of glyph names in the CFF font."""
2598
+ return self.charset
2599
+
2600
+ def postDecompile(self):
2601
+ offset = self.rawDict.get("CharStrings")
2602
+ if offset is None:
2603
+ return
2604
+ # get the number of glyphs beforehand.
2605
+ self.file.seek(offset)
2606
+ if self._isCFF2:
2607
+ self.numGlyphs = readCard32(self.file)
2608
+ else:
2609
+ self.numGlyphs = readCard16(self.file)
2610
+
2611
+ def toXML(self, xmlWriter):
2612
+ if hasattr(self, "CharStrings"):
2613
+ self.decompileAllCharStrings()
2614
+ if hasattr(self, "ROS"):
2615
+ self.skipNames = ["Encoding"]
2616
+ if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
2617
+ # these values have default values, but I only want them to show up
2618
+ # in CID fonts.
2619
+ self.skipNames = [
2620
+ "CIDFontVersion",
2621
+ "CIDFontRevision",
2622
+ "CIDFontType",
2623
+ "CIDCount",
2624
+ ]
2625
+ BaseDict.toXML(self, xmlWriter)
2626
+
2627
+ def decompileAllCharStrings(self):
2628
+ # Make sure that all the Private Dicts have been instantiated.
2629
+ for i, charString in enumerate(self.CharStrings.values()):
2630
+ try:
2631
+ charString.decompile()
2632
+ except:
2633
+ log.error("Error in charstring %s", i)
2634
+ raise
2635
+
2636
+ def recalcFontBBox(self):
2637
+ fontBBox = None
2638
+ for charString in self.CharStrings.values():
2639
+ bounds = charString.calcBounds(self.CharStrings)
2640
+ if bounds is not None:
2641
+ if fontBBox is not None:
2642
+ fontBBox = unionRect(fontBBox, bounds)
2643
+ else:
2644
+ fontBBox = bounds
2645
+
2646
+ if fontBBox is None:
2647
+ self.FontBBox = self.defaults["FontBBox"][:]
2648
+ else:
2649
+ self.FontBBox = list(intRect(fontBBox))
2650
+
2651
+
2652
+ class FontDict(BaseDict):
2653
+ #
2654
+ # Since fonttools used to pass a lot of fields that are not relevant in the FDArray
2655
+ # FontDict, there are 'ttx' files in the wild that contain all these. These got in
2656
+ # the ttx files because fonttools writes explicit values for all the TopDict default
2657
+ # values. These are not actually illegal in the context of an FDArray FontDict - you
2658
+ # can legally, per spec, put any arbitrary key/value pair in a FontDict - but are
2659
+ # useless since current major company CFF interpreters ignore anything but the set
2660
+ # listed in this file. So, we just silently skip them. An exception is Weight: this
2661
+ # is not used by any interpreter, but some foundries have asked that this be
2662
+ # supported in FDArray FontDicts just to preserve information about the design when
2663
+ # the font is being inspected.
2664
+ #
2665
+ # On top of that, there are fonts out there that contain such useless FontDict values.
2666
+ #
2667
+ # By subclassing TopDict, we *allow* all key/values from TopDict, both when reading
2668
+ # from binary or when reading from XML, but by overriding `order` with a limited
2669
+ # list of names, we ensure that only the useful names ever get exported to XML and
2670
+ # ever get compiled into the binary font.
2671
+ #
2672
+ # We override compilerClass so we can warn about "useless" key/value pairs, either
2673
+ # from the original binary font or from TTX input.
2674
+ #
2675
+ # See:
2676
+ # - https://github.com/fonttools/fonttools/issues/740
2677
+ # - https://github.com/fonttools/fonttools/issues/601
2678
+ # - https://github.com/adobe-type-tools/afdko/issues/137
2679
+ #
2680
+ defaults = {}
2681
+ converters = buildConverters(topDictOperators)
2682
+ compilerClass = FontDictCompiler
2683
+ orderCFF = ["FontName", "FontMatrix", "Weight", "Private"]
2684
+ orderCFF2 = ["Private"]
2685
+ decompilerClass = TopDictDecompiler
2686
+
2687
+ def __init__(
2688
+ self,
2689
+ strings=None,
2690
+ file=None,
2691
+ offset=None,
2692
+ GlobalSubrs=None,
2693
+ isCFF2=None,
2694
+ vstore=None,
2695
+ ):
2696
+ super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2697
+ self.vstore = vstore
2698
+ self.setCFF2(isCFF2)
2699
+
2700
+ def setCFF2(self, isCFF2):
2701
+ # isCFF2 may be None.
2702
+ if isCFF2:
2703
+ self.order = self.orderCFF2
2704
+ self._isCFF2 = True
2705
+ else:
2706
+ self.order = self.orderCFF
2707
+ self._isCFF2 = False
2708
+
2709
+
2710
+ class PrivateDict(BaseDict):
2711
+ defaults = buildDefaults(privateDictOperators)
2712
+ converters = buildConverters(privateDictOperators)
2713
+ order = buildOrder(privateDictOperators)
2714
+ decompilerClass = PrivateDictDecompiler
2715
+ compilerClass = PrivateDictCompiler
2716
+
2717
+ def __init__(self, strings=None, file=None, offset=None, isCFF2=None, vstore=None):
2718
+ super(PrivateDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2719
+ self.vstore = vstore
2720
+ if isCFF2:
2721
+ self.defaults = buildDefaults(privateDictOperators2)
2722
+ self.order = buildOrder(privateDictOperators2)
2723
+ # Provide dummy values. This avoids needing to provide
2724
+ # an isCFF2 state in a lot of places.
2725
+ self.nominalWidthX = self.defaultWidthX = None
2726
+ self._isCFF2 = True
2727
+ else:
2728
+ self.defaults = buildDefaults(privateDictOperators)
2729
+ self.order = buildOrder(privateDictOperators)
2730
+ self._isCFF2 = False
2731
+
2732
+ @property
2733
+ def in_cff2(self):
2734
+ return self._isCFF2
2735
+
2736
+ def getNumRegions(self, vi=None): # called from misc/psCharStrings.py
2737
+ # if getNumRegions is being called, we can assume that VarStore exists.
2738
+ if vi is None:
2739
+ if hasattr(self, "vsindex"):
2740
+ vi = self.vsindex
2741
+ else:
2742
+ vi = 0
2743
+ numRegions = self.vstore.getNumRegions(vi)
2744
+ return numRegions
2745
+
2746
+
2747
+ class IndexedStrings(object):
2748
+ """SID -> string mapping."""
2749
+
2750
+ def __init__(self, file=None):
2751
+ if file is None:
2752
+ strings = []
2753
+ else:
2754
+ strings = [tostr(s, encoding="latin1") for s in Index(file, isCFF2=False)]
2755
+ self.strings = strings
2756
+
2757
+ def getCompiler(self):
2758
+ return IndexedStringsCompiler(self, None, self, isCFF2=False)
2759
+
2760
+ def __len__(self):
2761
+ return len(self.strings)
2762
+
2763
+ def __getitem__(self, SID):
2764
+ if SID < cffStandardStringCount:
2765
+ return cffStandardStrings[SID]
2766
+ else:
2767
+ return self.strings[SID - cffStandardStringCount]
2768
+
2769
+ def getSID(self, s):
2770
+ if not hasattr(self, "stringMapping"):
2771
+ self.buildStringMapping()
2772
+ s = tostr(s, encoding="latin1")
2773
+ if s in cffStandardStringMapping:
2774
+ SID = cffStandardStringMapping[s]
2775
+ elif s in self.stringMapping:
2776
+ SID = self.stringMapping[s]
2777
+ else:
2778
+ SID = len(self.strings) + cffStandardStringCount
2779
+ self.strings.append(s)
2780
+ self.stringMapping[s] = SID
2781
+ return SID
2782
+
2783
+ def getStrings(self):
2784
+ return self.strings
2785
+
2786
+ def buildStringMapping(self):
2787
+ self.stringMapping = {}
2788
+ for index in range(len(self.strings)):
2789
+ self.stringMapping[self.strings[index]] = index + cffStandardStringCount
2790
+
2791
+
2792
+ # The 391 Standard Strings as used in the CFF format.
2793
+ # from Adobe Technical None #5176, version 1.0, 18 March 1998
2794
+
2795
+ cffStandardStrings = [
2796
+ ".notdef",
2797
+ "space",
2798
+ "exclam",
2799
+ "quotedbl",
2800
+ "numbersign",
2801
+ "dollar",
2802
+ "percent",
2803
+ "ampersand",
2804
+ "quoteright",
2805
+ "parenleft",
2806
+ "parenright",
2807
+ "asterisk",
2808
+ "plus",
2809
+ "comma",
2810
+ "hyphen",
2811
+ "period",
2812
+ "slash",
2813
+ "zero",
2814
+ "one",
2815
+ "two",
2816
+ "three",
2817
+ "four",
2818
+ "five",
2819
+ "six",
2820
+ "seven",
2821
+ "eight",
2822
+ "nine",
2823
+ "colon",
2824
+ "semicolon",
2825
+ "less",
2826
+ "equal",
2827
+ "greater",
2828
+ "question",
2829
+ "at",
2830
+ "A",
2831
+ "B",
2832
+ "C",
2833
+ "D",
2834
+ "E",
2835
+ "F",
2836
+ "G",
2837
+ "H",
2838
+ "I",
2839
+ "J",
2840
+ "K",
2841
+ "L",
2842
+ "M",
2843
+ "N",
2844
+ "O",
2845
+ "P",
2846
+ "Q",
2847
+ "R",
2848
+ "S",
2849
+ "T",
2850
+ "U",
2851
+ "V",
2852
+ "W",
2853
+ "X",
2854
+ "Y",
2855
+ "Z",
2856
+ "bracketleft",
2857
+ "backslash",
2858
+ "bracketright",
2859
+ "asciicircum",
2860
+ "underscore",
2861
+ "quoteleft",
2862
+ "a",
2863
+ "b",
2864
+ "c",
2865
+ "d",
2866
+ "e",
2867
+ "f",
2868
+ "g",
2869
+ "h",
2870
+ "i",
2871
+ "j",
2872
+ "k",
2873
+ "l",
2874
+ "m",
2875
+ "n",
2876
+ "o",
2877
+ "p",
2878
+ "q",
2879
+ "r",
2880
+ "s",
2881
+ "t",
2882
+ "u",
2883
+ "v",
2884
+ "w",
2885
+ "x",
2886
+ "y",
2887
+ "z",
2888
+ "braceleft",
2889
+ "bar",
2890
+ "braceright",
2891
+ "asciitilde",
2892
+ "exclamdown",
2893
+ "cent",
2894
+ "sterling",
2895
+ "fraction",
2896
+ "yen",
2897
+ "florin",
2898
+ "section",
2899
+ "currency",
2900
+ "quotesingle",
2901
+ "quotedblleft",
2902
+ "guillemotleft",
2903
+ "guilsinglleft",
2904
+ "guilsinglright",
2905
+ "fi",
2906
+ "fl",
2907
+ "endash",
2908
+ "dagger",
2909
+ "daggerdbl",
2910
+ "periodcentered",
2911
+ "paragraph",
2912
+ "bullet",
2913
+ "quotesinglbase",
2914
+ "quotedblbase",
2915
+ "quotedblright",
2916
+ "guillemotright",
2917
+ "ellipsis",
2918
+ "perthousand",
2919
+ "questiondown",
2920
+ "grave",
2921
+ "acute",
2922
+ "circumflex",
2923
+ "tilde",
2924
+ "macron",
2925
+ "breve",
2926
+ "dotaccent",
2927
+ "dieresis",
2928
+ "ring",
2929
+ "cedilla",
2930
+ "hungarumlaut",
2931
+ "ogonek",
2932
+ "caron",
2933
+ "emdash",
2934
+ "AE",
2935
+ "ordfeminine",
2936
+ "Lslash",
2937
+ "Oslash",
2938
+ "OE",
2939
+ "ordmasculine",
2940
+ "ae",
2941
+ "dotlessi",
2942
+ "lslash",
2943
+ "oslash",
2944
+ "oe",
2945
+ "germandbls",
2946
+ "onesuperior",
2947
+ "logicalnot",
2948
+ "mu",
2949
+ "trademark",
2950
+ "Eth",
2951
+ "onehalf",
2952
+ "plusminus",
2953
+ "Thorn",
2954
+ "onequarter",
2955
+ "divide",
2956
+ "brokenbar",
2957
+ "degree",
2958
+ "thorn",
2959
+ "threequarters",
2960
+ "twosuperior",
2961
+ "registered",
2962
+ "minus",
2963
+ "eth",
2964
+ "multiply",
2965
+ "threesuperior",
2966
+ "copyright",
2967
+ "Aacute",
2968
+ "Acircumflex",
2969
+ "Adieresis",
2970
+ "Agrave",
2971
+ "Aring",
2972
+ "Atilde",
2973
+ "Ccedilla",
2974
+ "Eacute",
2975
+ "Ecircumflex",
2976
+ "Edieresis",
2977
+ "Egrave",
2978
+ "Iacute",
2979
+ "Icircumflex",
2980
+ "Idieresis",
2981
+ "Igrave",
2982
+ "Ntilde",
2983
+ "Oacute",
2984
+ "Ocircumflex",
2985
+ "Odieresis",
2986
+ "Ograve",
2987
+ "Otilde",
2988
+ "Scaron",
2989
+ "Uacute",
2990
+ "Ucircumflex",
2991
+ "Udieresis",
2992
+ "Ugrave",
2993
+ "Yacute",
2994
+ "Ydieresis",
2995
+ "Zcaron",
2996
+ "aacute",
2997
+ "acircumflex",
2998
+ "adieresis",
2999
+ "agrave",
3000
+ "aring",
3001
+ "atilde",
3002
+ "ccedilla",
3003
+ "eacute",
3004
+ "ecircumflex",
3005
+ "edieresis",
3006
+ "egrave",
3007
+ "iacute",
3008
+ "icircumflex",
3009
+ "idieresis",
3010
+ "igrave",
3011
+ "ntilde",
3012
+ "oacute",
3013
+ "ocircumflex",
3014
+ "odieresis",
3015
+ "ograve",
3016
+ "otilde",
3017
+ "scaron",
3018
+ "uacute",
3019
+ "ucircumflex",
3020
+ "udieresis",
3021
+ "ugrave",
3022
+ "yacute",
3023
+ "ydieresis",
3024
+ "zcaron",
3025
+ "exclamsmall",
3026
+ "Hungarumlautsmall",
3027
+ "dollaroldstyle",
3028
+ "dollarsuperior",
3029
+ "ampersandsmall",
3030
+ "Acutesmall",
3031
+ "parenleftsuperior",
3032
+ "parenrightsuperior",
3033
+ "twodotenleader",
3034
+ "onedotenleader",
3035
+ "zerooldstyle",
3036
+ "oneoldstyle",
3037
+ "twooldstyle",
3038
+ "threeoldstyle",
3039
+ "fouroldstyle",
3040
+ "fiveoldstyle",
3041
+ "sixoldstyle",
3042
+ "sevenoldstyle",
3043
+ "eightoldstyle",
3044
+ "nineoldstyle",
3045
+ "commasuperior",
3046
+ "threequartersemdash",
3047
+ "periodsuperior",
3048
+ "questionsmall",
3049
+ "asuperior",
3050
+ "bsuperior",
3051
+ "centsuperior",
3052
+ "dsuperior",
3053
+ "esuperior",
3054
+ "isuperior",
3055
+ "lsuperior",
3056
+ "msuperior",
3057
+ "nsuperior",
3058
+ "osuperior",
3059
+ "rsuperior",
3060
+ "ssuperior",
3061
+ "tsuperior",
3062
+ "ff",
3063
+ "ffi",
3064
+ "ffl",
3065
+ "parenleftinferior",
3066
+ "parenrightinferior",
3067
+ "Circumflexsmall",
3068
+ "hyphensuperior",
3069
+ "Gravesmall",
3070
+ "Asmall",
3071
+ "Bsmall",
3072
+ "Csmall",
3073
+ "Dsmall",
3074
+ "Esmall",
3075
+ "Fsmall",
3076
+ "Gsmall",
3077
+ "Hsmall",
3078
+ "Ismall",
3079
+ "Jsmall",
3080
+ "Ksmall",
3081
+ "Lsmall",
3082
+ "Msmall",
3083
+ "Nsmall",
3084
+ "Osmall",
3085
+ "Psmall",
3086
+ "Qsmall",
3087
+ "Rsmall",
3088
+ "Ssmall",
3089
+ "Tsmall",
3090
+ "Usmall",
3091
+ "Vsmall",
3092
+ "Wsmall",
3093
+ "Xsmall",
3094
+ "Ysmall",
3095
+ "Zsmall",
3096
+ "colonmonetary",
3097
+ "onefitted",
3098
+ "rupiah",
3099
+ "Tildesmall",
3100
+ "exclamdownsmall",
3101
+ "centoldstyle",
3102
+ "Lslashsmall",
3103
+ "Scaronsmall",
3104
+ "Zcaronsmall",
3105
+ "Dieresissmall",
3106
+ "Brevesmall",
3107
+ "Caronsmall",
3108
+ "Dotaccentsmall",
3109
+ "Macronsmall",
3110
+ "figuredash",
3111
+ "hypheninferior",
3112
+ "Ogoneksmall",
3113
+ "Ringsmall",
3114
+ "Cedillasmall",
3115
+ "questiondownsmall",
3116
+ "oneeighth",
3117
+ "threeeighths",
3118
+ "fiveeighths",
3119
+ "seveneighths",
3120
+ "onethird",
3121
+ "twothirds",
3122
+ "zerosuperior",
3123
+ "foursuperior",
3124
+ "fivesuperior",
3125
+ "sixsuperior",
3126
+ "sevensuperior",
3127
+ "eightsuperior",
3128
+ "ninesuperior",
3129
+ "zeroinferior",
3130
+ "oneinferior",
3131
+ "twoinferior",
3132
+ "threeinferior",
3133
+ "fourinferior",
3134
+ "fiveinferior",
3135
+ "sixinferior",
3136
+ "seveninferior",
3137
+ "eightinferior",
3138
+ "nineinferior",
3139
+ "centinferior",
3140
+ "dollarinferior",
3141
+ "periodinferior",
3142
+ "commainferior",
3143
+ "Agravesmall",
3144
+ "Aacutesmall",
3145
+ "Acircumflexsmall",
3146
+ "Atildesmall",
3147
+ "Adieresissmall",
3148
+ "Aringsmall",
3149
+ "AEsmall",
3150
+ "Ccedillasmall",
3151
+ "Egravesmall",
3152
+ "Eacutesmall",
3153
+ "Ecircumflexsmall",
3154
+ "Edieresissmall",
3155
+ "Igravesmall",
3156
+ "Iacutesmall",
3157
+ "Icircumflexsmall",
3158
+ "Idieresissmall",
3159
+ "Ethsmall",
3160
+ "Ntildesmall",
3161
+ "Ogravesmall",
3162
+ "Oacutesmall",
3163
+ "Ocircumflexsmall",
3164
+ "Otildesmall",
3165
+ "Odieresissmall",
3166
+ "OEsmall",
3167
+ "Oslashsmall",
3168
+ "Ugravesmall",
3169
+ "Uacutesmall",
3170
+ "Ucircumflexsmall",
3171
+ "Udieresissmall",
3172
+ "Yacutesmall",
3173
+ "Thornsmall",
3174
+ "Ydieresissmall",
3175
+ "001.000",
3176
+ "001.001",
3177
+ "001.002",
3178
+ "001.003",
3179
+ "Black",
3180
+ "Bold",
3181
+ "Book",
3182
+ "Light",
3183
+ "Medium",
3184
+ "Regular",
3185
+ "Roman",
3186
+ "Semibold",
3187
+ ]
3188
+
3189
+ cffStandardStringCount = 391
3190
+ assert len(cffStandardStrings) == cffStandardStringCount
3191
+ # build reverse mapping
3192
+ cffStandardStringMapping = {}
3193
+ for _i in range(cffStandardStringCount):
3194
+ cffStandardStringMapping[cffStandardStrings[_i]] = _i
3195
+
3196
+ cffISOAdobeStrings = [
3197
+ ".notdef",
3198
+ "space",
3199
+ "exclam",
3200
+ "quotedbl",
3201
+ "numbersign",
3202
+ "dollar",
3203
+ "percent",
3204
+ "ampersand",
3205
+ "quoteright",
3206
+ "parenleft",
3207
+ "parenright",
3208
+ "asterisk",
3209
+ "plus",
3210
+ "comma",
3211
+ "hyphen",
3212
+ "period",
3213
+ "slash",
3214
+ "zero",
3215
+ "one",
3216
+ "two",
3217
+ "three",
3218
+ "four",
3219
+ "five",
3220
+ "six",
3221
+ "seven",
3222
+ "eight",
3223
+ "nine",
3224
+ "colon",
3225
+ "semicolon",
3226
+ "less",
3227
+ "equal",
3228
+ "greater",
3229
+ "question",
3230
+ "at",
3231
+ "A",
3232
+ "B",
3233
+ "C",
3234
+ "D",
3235
+ "E",
3236
+ "F",
3237
+ "G",
3238
+ "H",
3239
+ "I",
3240
+ "J",
3241
+ "K",
3242
+ "L",
3243
+ "M",
3244
+ "N",
3245
+ "O",
3246
+ "P",
3247
+ "Q",
3248
+ "R",
3249
+ "S",
3250
+ "T",
3251
+ "U",
3252
+ "V",
3253
+ "W",
3254
+ "X",
3255
+ "Y",
3256
+ "Z",
3257
+ "bracketleft",
3258
+ "backslash",
3259
+ "bracketright",
3260
+ "asciicircum",
3261
+ "underscore",
3262
+ "quoteleft",
3263
+ "a",
3264
+ "b",
3265
+ "c",
3266
+ "d",
3267
+ "e",
3268
+ "f",
3269
+ "g",
3270
+ "h",
3271
+ "i",
3272
+ "j",
3273
+ "k",
3274
+ "l",
3275
+ "m",
3276
+ "n",
3277
+ "o",
3278
+ "p",
3279
+ "q",
3280
+ "r",
3281
+ "s",
3282
+ "t",
3283
+ "u",
3284
+ "v",
3285
+ "w",
3286
+ "x",
3287
+ "y",
3288
+ "z",
3289
+ "braceleft",
3290
+ "bar",
3291
+ "braceright",
3292
+ "asciitilde",
3293
+ "exclamdown",
3294
+ "cent",
3295
+ "sterling",
3296
+ "fraction",
3297
+ "yen",
3298
+ "florin",
3299
+ "section",
3300
+ "currency",
3301
+ "quotesingle",
3302
+ "quotedblleft",
3303
+ "guillemotleft",
3304
+ "guilsinglleft",
3305
+ "guilsinglright",
3306
+ "fi",
3307
+ "fl",
3308
+ "endash",
3309
+ "dagger",
3310
+ "daggerdbl",
3311
+ "periodcentered",
3312
+ "paragraph",
3313
+ "bullet",
3314
+ "quotesinglbase",
3315
+ "quotedblbase",
3316
+ "quotedblright",
3317
+ "guillemotright",
3318
+ "ellipsis",
3319
+ "perthousand",
3320
+ "questiondown",
3321
+ "grave",
3322
+ "acute",
3323
+ "circumflex",
3324
+ "tilde",
3325
+ "macron",
3326
+ "breve",
3327
+ "dotaccent",
3328
+ "dieresis",
3329
+ "ring",
3330
+ "cedilla",
3331
+ "hungarumlaut",
3332
+ "ogonek",
3333
+ "caron",
3334
+ "emdash",
3335
+ "AE",
3336
+ "ordfeminine",
3337
+ "Lslash",
3338
+ "Oslash",
3339
+ "OE",
3340
+ "ordmasculine",
3341
+ "ae",
3342
+ "dotlessi",
3343
+ "lslash",
3344
+ "oslash",
3345
+ "oe",
3346
+ "germandbls",
3347
+ "onesuperior",
3348
+ "logicalnot",
3349
+ "mu",
3350
+ "trademark",
3351
+ "Eth",
3352
+ "onehalf",
3353
+ "plusminus",
3354
+ "Thorn",
3355
+ "onequarter",
3356
+ "divide",
3357
+ "brokenbar",
3358
+ "degree",
3359
+ "thorn",
3360
+ "threequarters",
3361
+ "twosuperior",
3362
+ "registered",
3363
+ "minus",
3364
+ "eth",
3365
+ "multiply",
3366
+ "threesuperior",
3367
+ "copyright",
3368
+ "Aacute",
3369
+ "Acircumflex",
3370
+ "Adieresis",
3371
+ "Agrave",
3372
+ "Aring",
3373
+ "Atilde",
3374
+ "Ccedilla",
3375
+ "Eacute",
3376
+ "Ecircumflex",
3377
+ "Edieresis",
3378
+ "Egrave",
3379
+ "Iacute",
3380
+ "Icircumflex",
3381
+ "Idieresis",
3382
+ "Igrave",
3383
+ "Ntilde",
3384
+ "Oacute",
3385
+ "Ocircumflex",
3386
+ "Odieresis",
3387
+ "Ograve",
3388
+ "Otilde",
3389
+ "Scaron",
3390
+ "Uacute",
3391
+ "Ucircumflex",
3392
+ "Udieresis",
3393
+ "Ugrave",
3394
+ "Yacute",
3395
+ "Ydieresis",
3396
+ "Zcaron",
3397
+ "aacute",
3398
+ "acircumflex",
3399
+ "adieresis",
3400
+ "agrave",
3401
+ "aring",
3402
+ "atilde",
3403
+ "ccedilla",
3404
+ "eacute",
3405
+ "ecircumflex",
3406
+ "edieresis",
3407
+ "egrave",
3408
+ "iacute",
3409
+ "icircumflex",
3410
+ "idieresis",
3411
+ "igrave",
3412
+ "ntilde",
3413
+ "oacute",
3414
+ "ocircumflex",
3415
+ "odieresis",
3416
+ "ograve",
3417
+ "otilde",
3418
+ "scaron",
3419
+ "uacute",
3420
+ "ucircumflex",
3421
+ "udieresis",
3422
+ "ugrave",
3423
+ "yacute",
3424
+ "ydieresis",
3425
+ "zcaron",
3426
+ ]
3427
+
3428
+ cffISOAdobeStringCount = 229
3429
+ assert len(cffISOAdobeStrings) == cffISOAdobeStringCount
3430
+
3431
+ cffIExpertStrings = [
3432
+ ".notdef",
3433
+ "space",
3434
+ "exclamsmall",
3435
+ "Hungarumlautsmall",
3436
+ "dollaroldstyle",
3437
+ "dollarsuperior",
3438
+ "ampersandsmall",
3439
+ "Acutesmall",
3440
+ "parenleftsuperior",
3441
+ "parenrightsuperior",
3442
+ "twodotenleader",
3443
+ "onedotenleader",
3444
+ "comma",
3445
+ "hyphen",
3446
+ "period",
3447
+ "fraction",
3448
+ "zerooldstyle",
3449
+ "oneoldstyle",
3450
+ "twooldstyle",
3451
+ "threeoldstyle",
3452
+ "fouroldstyle",
3453
+ "fiveoldstyle",
3454
+ "sixoldstyle",
3455
+ "sevenoldstyle",
3456
+ "eightoldstyle",
3457
+ "nineoldstyle",
3458
+ "colon",
3459
+ "semicolon",
3460
+ "commasuperior",
3461
+ "threequartersemdash",
3462
+ "periodsuperior",
3463
+ "questionsmall",
3464
+ "asuperior",
3465
+ "bsuperior",
3466
+ "centsuperior",
3467
+ "dsuperior",
3468
+ "esuperior",
3469
+ "isuperior",
3470
+ "lsuperior",
3471
+ "msuperior",
3472
+ "nsuperior",
3473
+ "osuperior",
3474
+ "rsuperior",
3475
+ "ssuperior",
3476
+ "tsuperior",
3477
+ "ff",
3478
+ "fi",
3479
+ "fl",
3480
+ "ffi",
3481
+ "ffl",
3482
+ "parenleftinferior",
3483
+ "parenrightinferior",
3484
+ "Circumflexsmall",
3485
+ "hyphensuperior",
3486
+ "Gravesmall",
3487
+ "Asmall",
3488
+ "Bsmall",
3489
+ "Csmall",
3490
+ "Dsmall",
3491
+ "Esmall",
3492
+ "Fsmall",
3493
+ "Gsmall",
3494
+ "Hsmall",
3495
+ "Ismall",
3496
+ "Jsmall",
3497
+ "Ksmall",
3498
+ "Lsmall",
3499
+ "Msmall",
3500
+ "Nsmall",
3501
+ "Osmall",
3502
+ "Psmall",
3503
+ "Qsmall",
3504
+ "Rsmall",
3505
+ "Ssmall",
3506
+ "Tsmall",
3507
+ "Usmall",
3508
+ "Vsmall",
3509
+ "Wsmall",
3510
+ "Xsmall",
3511
+ "Ysmall",
3512
+ "Zsmall",
3513
+ "colonmonetary",
3514
+ "onefitted",
3515
+ "rupiah",
3516
+ "Tildesmall",
3517
+ "exclamdownsmall",
3518
+ "centoldstyle",
3519
+ "Lslashsmall",
3520
+ "Scaronsmall",
3521
+ "Zcaronsmall",
3522
+ "Dieresissmall",
3523
+ "Brevesmall",
3524
+ "Caronsmall",
3525
+ "Dotaccentsmall",
3526
+ "Macronsmall",
3527
+ "figuredash",
3528
+ "hypheninferior",
3529
+ "Ogoneksmall",
3530
+ "Ringsmall",
3531
+ "Cedillasmall",
3532
+ "onequarter",
3533
+ "onehalf",
3534
+ "threequarters",
3535
+ "questiondownsmall",
3536
+ "oneeighth",
3537
+ "threeeighths",
3538
+ "fiveeighths",
3539
+ "seveneighths",
3540
+ "onethird",
3541
+ "twothirds",
3542
+ "zerosuperior",
3543
+ "onesuperior",
3544
+ "twosuperior",
3545
+ "threesuperior",
3546
+ "foursuperior",
3547
+ "fivesuperior",
3548
+ "sixsuperior",
3549
+ "sevensuperior",
3550
+ "eightsuperior",
3551
+ "ninesuperior",
3552
+ "zeroinferior",
3553
+ "oneinferior",
3554
+ "twoinferior",
3555
+ "threeinferior",
3556
+ "fourinferior",
3557
+ "fiveinferior",
3558
+ "sixinferior",
3559
+ "seveninferior",
3560
+ "eightinferior",
3561
+ "nineinferior",
3562
+ "centinferior",
3563
+ "dollarinferior",
3564
+ "periodinferior",
3565
+ "commainferior",
3566
+ "Agravesmall",
3567
+ "Aacutesmall",
3568
+ "Acircumflexsmall",
3569
+ "Atildesmall",
3570
+ "Adieresissmall",
3571
+ "Aringsmall",
3572
+ "AEsmall",
3573
+ "Ccedillasmall",
3574
+ "Egravesmall",
3575
+ "Eacutesmall",
3576
+ "Ecircumflexsmall",
3577
+ "Edieresissmall",
3578
+ "Igravesmall",
3579
+ "Iacutesmall",
3580
+ "Icircumflexsmall",
3581
+ "Idieresissmall",
3582
+ "Ethsmall",
3583
+ "Ntildesmall",
3584
+ "Ogravesmall",
3585
+ "Oacutesmall",
3586
+ "Ocircumflexsmall",
3587
+ "Otildesmall",
3588
+ "Odieresissmall",
3589
+ "OEsmall",
3590
+ "Oslashsmall",
3591
+ "Ugravesmall",
3592
+ "Uacutesmall",
3593
+ "Ucircumflexsmall",
3594
+ "Udieresissmall",
3595
+ "Yacutesmall",
3596
+ "Thornsmall",
3597
+ "Ydieresissmall",
3598
+ ]
3599
+
3600
+ cffExpertStringCount = 166
3601
+ assert len(cffIExpertStrings) == cffExpertStringCount
3602
+
3603
+ cffExpertSubsetStrings = [
3604
+ ".notdef",
3605
+ "space",
3606
+ "dollaroldstyle",
3607
+ "dollarsuperior",
3608
+ "parenleftsuperior",
3609
+ "parenrightsuperior",
3610
+ "twodotenleader",
3611
+ "onedotenleader",
3612
+ "comma",
3613
+ "hyphen",
3614
+ "period",
3615
+ "fraction",
3616
+ "zerooldstyle",
3617
+ "oneoldstyle",
3618
+ "twooldstyle",
3619
+ "threeoldstyle",
3620
+ "fouroldstyle",
3621
+ "fiveoldstyle",
3622
+ "sixoldstyle",
3623
+ "sevenoldstyle",
3624
+ "eightoldstyle",
3625
+ "nineoldstyle",
3626
+ "colon",
3627
+ "semicolon",
3628
+ "commasuperior",
3629
+ "threequartersemdash",
3630
+ "periodsuperior",
3631
+ "asuperior",
3632
+ "bsuperior",
3633
+ "centsuperior",
3634
+ "dsuperior",
3635
+ "esuperior",
3636
+ "isuperior",
3637
+ "lsuperior",
3638
+ "msuperior",
3639
+ "nsuperior",
3640
+ "osuperior",
3641
+ "rsuperior",
3642
+ "ssuperior",
3643
+ "tsuperior",
3644
+ "ff",
3645
+ "fi",
3646
+ "fl",
3647
+ "ffi",
3648
+ "ffl",
3649
+ "parenleftinferior",
3650
+ "parenrightinferior",
3651
+ "hyphensuperior",
3652
+ "colonmonetary",
3653
+ "onefitted",
3654
+ "rupiah",
3655
+ "centoldstyle",
3656
+ "figuredash",
3657
+ "hypheninferior",
3658
+ "onequarter",
3659
+ "onehalf",
3660
+ "threequarters",
3661
+ "oneeighth",
3662
+ "threeeighths",
3663
+ "fiveeighths",
3664
+ "seveneighths",
3665
+ "onethird",
3666
+ "twothirds",
3667
+ "zerosuperior",
3668
+ "onesuperior",
3669
+ "twosuperior",
3670
+ "threesuperior",
3671
+ "foursuperior",
3672
+ "fivesuperior",
3673
+ "sixsuperior",
3674
+ "sevensuperior",
3675
+ "eightsuperior",
3676
+ "ninesuperior",
3677
+ "zeroinferior",
3678
+ "oneinferior",
3679
+ "twoinferior",
3680
+ "threeinferior",
3681
+ "fourinferior",
3682
+ "fiveinferior",
3683
+ "sixinferior",
3684
+ "seveninferior",
3685
+ "eightinferior",
3686
+ "nineinferior",
3687
+ "centinferior",
3688
+ "dollarinferior",
3689
+ "periodinferior",
3690
+ "commainferior",
3691
+ ]
3692
+
3693
+ cffExpertSubsetStringCount = 87
3694
+ assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount