fonttools 4.55.6__cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

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

Potentially problematic release.


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

Files changed (329) hide show
  1. fontTools/__init__.py +8 -0
  2. fontTools/__main__.py +35 -0
  3. fontTools/afmLib.py +439 -0
  4. fontTools/agl.py +5233 -0
  5. fontTools/cffLib/CFF2ToCFF.py +203 -0
  6. fontTools/cffLib/CFFToCFF2.py +305 -0
  7. fontTools/cffLib/__init__.py +3659 -0
  8. fontTools/cffLib/specializer.py +924 -0
  9. fontTools/cffLib/transforms.py +490 -0
  10. fontTools/cffLib/width.py +210 -0
  11. fontTools/colorLib/__init__.py +0 -0
  12. fontTools/colorLib/builder.py +664 -0
  13. fontTools/colorLib/errors.py +2 -0
  14. fontTools/colorLib/geometry.py +143 -0
  15. fontTools/colorLib/table_builder.py +223 -0
  16. fontTools/colorLib/unbuilder.py +81 -0
  17. fontTools/config/__init__.py +75 -0
  18. fontTools/cu2qu/__init__.py +15 -0
  19. fontTools/cu2qu/__main__.py +6 -0
  20. fontTools/cu2qu/benchmark.py +54 -0
  21. fontTools/cu2qu/cli.py +198 -0
  22. fontTools/cu2qu/cu2qu.c +14829 -0
  23. fontTools/cu2qu/cu2qu.cpython-39-x86_64-linux-gnu.so +0 -0
  24. fontTools/cu2qu/cu2qu.py +531 -0
  25. fontTools/cu2qu/errors.py +77 -0
  26. fontTools/cu2qu/ufo.py +349 -0
  27. fontTools/designspaceLib/__init__.py +3338 -0
  28. fontTools/designspaceLib/__main__.py +6 -0
  29. fontTools/designspaceLib/split.py +475 -0
  30. fontTools/designspaceLib/statNames.py +253 -0
  31. fontTools/designspaceLib/types.py +147 -0
  32. fontTools/encodings/MacRoman.py +258 -0
  33. fontTools/encodings/StandardEncoding.py +258 -0
  34. fontTools/encodings/__init__.py +1 -0
  35. fontTools/encodings/codecs.py +135 -0
  36. fontTools/feaLib/__init__.py +4 -0
  37. fontTools/feaLib/__main__.py +78 -0
  38. fontTools/feaLib/ast.py +2134 -0
  39. fontTools/feaLib/builder.py +1747 -0
  40. fontTools/feaLib/error.py +22 -0
  41. fontTools/feaLib/lexer.c +17986 -0
  42. fontTools/feaLib/lexer.cpython-39-x86_64-linux-gnu.so +0 -0
  43. fontTools/feaLib/lexer.py +287 -0
  44. fontTools/feaLib/location.py +12 -0
  45. fontTools/feaLib/lookupDebugInfo.py +12 -0
  46. fontTools/feaLib/parser.py +2359 -0
  47. fontTools/feaLib/variableScalar.py +113 -0
  48. fontTools/fontBuilder.py +1008 -0
  49. fontTools/help.py +36 -0
  50. fontTools/merge/__init__.py +248 -0
  51. fontTools/merge/__main__.py +6 -0
  52. fontTools/merge/base.py +81 -0
  53. fontTools/merge/cmap.py +141 -0
  54. fontTools/merge/layout.py +526 -0
  55. fontTools/merge/options.py +85 -0
  56. fontTools/merge/tables.py +341 -0
  57. fontTools/merge/unicode.py +78 -0
  58. fontTools/merge/util.py +143 -0
  59. fontTools/misc/__init__.py +1 -0
  60. fontTools/misc/arrayTools.py +424 -0
  61. fontTools/misc/bezierTools.c +41831 -0
  62. fontTools/misc/bezierTools.cpython-39-x86_64-linux-gnu.so +0 -0
  63. fontTools/misc/bezierTools.py +1497 -0
  64. fontTools/misc/classifyTools.py +170 -0
  65. fontTools/misc/cliTools.py +53 -0
  66. fontTools/misc/configTools.py +349 -0
  67. fontTools/misc/cython.py +27 -0
  68. fontTools/misc/dictTools.py +83 -0
  69. fontTools/misc/eexec.py +119 -0
  70. fontTools/misc/encodingTools.py +72 -0
  71. fontTools/misc/etree.py +479 -0
  72. fontTools/misc/filenames.py +245 -0
  73. fontTools/misc/fixedTools.py +253 -0
  74. fontTools/misc/intTools.py +25 -0
  75. fontTools/misc/iterTools.py +12 -0
  76. fontTools/misc/lazyTools.py +42 -0
  77. fontTools/misc/loggingTools.py +543 -0
  78. fontTools/misc/macCreatorType.py +56 -0
  79. fontTools/misc/macRes.py +261 -0
  80. fontTools/misc/plistlib/__init__.py +681 -0
  81. fontTools/misc/plistlib/py.typed +0 -0
  82. fontTools/misc/psCharStrings.py +1496 -0
  83. fontTools/misc/psLib.py +398 -0
  84. fontTools/misc/psOperators.py +572 -0
  85. fontTools/misc/py23.py +96 -0
  86. fontTools/misc/roundTools.py +110 -0
  87. fontTools/misc/sstruct.py +231 -0
  88. fontTools/misc/symfont.py +244 -0
  89. fontTools/misc/testTools.py +229 -0
  90. fontTools/misc/textTools.py +154 -0
  91. fontTools/misc/timeTools.py +88 -0
  92. fontTools/misc/transform.py +516 -0
  93. fontTools/misc/treeTools.py +45 -0
  94. fontTools/misc/vector.py +147 -0
  95. fontTools/misc/visitor.py +142 -0
  96. fontTools/misc/xmlReader.py +188 -0
  97. fontTools/misc/xmlWriter.py +204 -0
  98. fontTools/mtiLib/__init__.py +1402 -0
  99. fontTools/mtiLib/__main__.py +5 -0
  100. fontTools/otlLib/__init__.py +1 -0
  101. fontTools/otlLib/builder.py +3221 -0
  102. fontTools/otlLib/error.py +11 -0
  103. fontTools/otlLib/maxContextCalc.py +96 -0
  104. fontTools/otlLib/optimize/__init__.py +53 -0
  105. fontTools/otlLib/optimize/__main__.py +6 -0
  106. fontTools/otlLib/optimize/gpos.py +453 -0
  107. fontTools/pens/__init__.py +1 -0
  108. fontTools/pens/areaPen.py +52 -0
  109. fontTools/pens/basePen.py +475 -0
  110. fontTools/pens/boundsPen.py +98 -0
  111. fontTools/pens/cairoPen.py +26 -0
  112. fontTools/pens/cocoaPen.py +26 -0
  113. fontTools/pens/cu2quPen.py +325 -0
  114. fontTools/pens/explicitClosingLinePen.py +101 -0
  115. fontTools/pens/filterPen.py +241 -0
  116. fontTools/pens/freetypePen.py +462 -0
  117. fontTools/pens/hashPointPen.py +89 -0
  118. fontTools/pens/momentsPen.c +13448 -0
  119. fontTools/pens/momentsPen.cpython-39-x86_64-linux-gnu.so +0 -0
  120. fontTools/pens/momentsPen.py +879 -0
  121. fontTools/pens/perimeterPen.py +69 -0
  122. fontTools/pens/pointInsidePen.py +192 -0
  123. fontTools/pens/pointPen.py +600 -0
  124. fontTools/pens/qtPen.py +29 -0
  125. fontTools/pens/qu2cuPen.py +105 -0
  126. fontTools/pens/quartzPen.py +43 -0
  127. fontTools/pens/recordingPen.py +335 -0
  128. fontTools/pens/reportLabPen.py +79 -0
  129. fontTools/pens/reverseContourPen.py +96 -0
  130. fontTools/pens/roundingPen.py +130 -0
  131. fontTools/pens/statisticsPen.py +312 -0
  132. fontTools/pens/svgPathPen.py +310 -0
  133. fontTools/pens/t2CharStringPen.py +68 -0
  134. fontTools/pens/teePen.py +55 -0
  135. fontTools/pens/transformPen.py +115 -0
  136. fontTools/pens/ttGlyphPen.py +335 -0
  137. fontTools/pens/wxPen.py +29 -0
  138. fontTools/qu2cu/__init__.py +15 -0
  139. fontTools/qu2cu/__main__.py +7 -0
  140. fontTools/qu2cu/benchmark.py +56 -0
  141. fontTools/qu2cu/cli.py +125 -0
  142. fontTools/qu2cu/qu2cu.c +16269 -0
  143. fontTools/qu2cu/qu2cu.cpython-39-x86_64-linux-gnu.so +0 -0
  144. fontTools/qu2cu/qu2cu.py +405 -0
  145. fontTools/subset/__init__.py +3838 -0
  146. fontTools/subset/__main__.py +6 -0
  147. fontTools/subset/cff.py +184 -0
  148. fontTools/subset/svg.py +253 -0
  149. fontTools/subset/util.py +25 -0
  150. fontTools/svgLib/__init__.py +3 -0
  151. fontTools/svgLib/path/__init__.py +65 -0
  152. fontTools/svgLib/path/arc.py +154 -0
  153. fontTools/svgLib/path/parser.py +322 -0
  154. fontTools/svgLib/path/shapes.py +183 -0
  155. fontTools/t1Lib/__init__.py +648 -0
  156. fontTools/tfmLib.py +460 -0
  157. fontTools/ttLib/__init__.py +26 -0
  158. fontTools/ttLib/__main__.py +109 -0
  159. fontTools/ttLib/macUtils.py +54 -0
  160. fontTools/ttLib/removeOverlaps.py +393 -0
  161. fontTools/ttLib/reorderGlyphs.py +284 -0
  162. fontTools/ttLib/scaleUpem.py +436 -0
  163. fontTools/ttLib/sfnt.py +662 -0
  164. fontTools/ttLib/standardGlyphOrder.py +271 -0
  165. fontTools/ttLib/tables/B_A_S_E_.py +14 -0
  166. fontTools/ttLib/tables/BitmapGlyphMetrics.py +64 -0
  167. fontTools/ttLib/tables/C_B_D_T_.py +113 -0
  168. fontTools/ttLib/tables/C_B_L_C_.py +19 -0
  169. fontTools/ttLib/tables/C_F_F_.py +61 -0
  170. fontTools/ttLib/tables/C_F_F__2.py +26 -0
  171. fontTools/ttLib/tables/C_O_L_R_.py +165 -0
  172. fontTools/ttLib/tables/C_P_A_L_.py +305 -0
  173. fontTools/ttLib/tables/D_S_I_G_.py +158 -0
  174. fontTools/ttLib/tables/D__e_b_g.py +17 -0
  175. fontTools/ttLib/tables/DefaultTable.py +49 -0
  176. fontTools/ttLib/tables/E_B_D_T_.py +835 -0
  177. fontTools/ttLib/tables/E_B_L_C_.py +718 -0
  178. fontTools/ttLib/tables/F_F_T_M_.py +52 -0
  179. fontTools/ttLib/tables/F__e_a_t.py +149 -0
  180. fontTools/ttLib/tables/G_D_E_F_.py +13 -0
  181. fontTools/ttLib/tables/G_M_A_P_.py +148 -0
  182. fontTools/ttLib/tables/G_P_K_G_.py +133 -0
  183. fontTools/ttLib/tables/G_P_O_S_.py +14 -0
  184. fontTools/ttLib/tables/G_S_U_B_.py +13 -0
  185. fontTools/ttLib/tables/G__l_a_t.py +235 -0
  186. fontTools/ttLib/tables/G__l_o_c.py +85 -0
  187. fontTools/ttLib/tables/H_V_A_R_.py +13 -0
  188. fontTools/ttLib/tables/J_S_T_F_.py +13 -0
  189. fontTools/ttLib/tables/L_T_S_H_.py +58 -0
  190. fontTools/ttLib/tables/M_A_T_H_.py +13 -0
  191. fontTools/ttLib/tables/M_E_T_A_.py +352 -0
  192. fontTools/ttLib/tables/M_V_A_R_.py +13 -0
  193. fontTools/ttLib/tables/O_S_2f_2.py +752 -0
  194. fontTools/ttLib/tables/S_I_N_G_.py +99 -0
  195. fontTools/ttLib/tables/S_T_A_T_.py +15 -0
  196. fontTools/ttLib/tables/S_V_G_.py +223 -0
  197. fontTools/ttLib/tables/S__i_l_f.py +1040 -0
  198. fontTools/ttLib/tables/S__i_l_l.py +92 -0
  199. fontTools/ttLib/tables/T_S_I_B_.py +13 -0
  200. fontTools/ttLib/tables/T_S_I_C_.py +14 -0
  201. fontTools/ttLib/tables/T_S_I_D_.py +13 -0
  202. fontTools/ttLib/tables/T_S_I_J_.py +13 -0
  203. fontTools/ttLib/tables/T_S_I_P_.py +13 -0
  204. fontTools/ttLib/tables/T_S_I_S_.py +13 -0
  205. fontTools/ttLib/tables/T_S_I_V_.py +26 -0
  206. fontTools/ttLib/tables/T_S_I__0.py +59 -0
  207. fontTools/ttLib/tables/T_S_I__1.py +166 -0
  208. fontTools/ttLib/tables/T_S_I__2.py +17 -0
  209. fontTools/ttLib/tables/T_S_I__3.py +22 -0
  210. fontTools/ttLib/tables/T_S_I__5.py +49 -0
  211. fontTools/ttLib/tables/T_T_F_A_.py +14 -0
  212. fontTools/ttLib/tables/TupleVariation.py +884 -0
  213. fontTools/ttLib/tables/V_A_R_C_.py +12 -0
  214. fontTools/ttLib/tables/V_D_M_X_.py +249 -0
  215. fontTools/ttLib/tables/V_O_R_G_.py +165 -0
  216. fontTools/ttLib/tables/V_V_A_R_.py +13 -0
  217. fontTools/ttLib/tables/__init__.py +97 -0
  218. fontTools/ttLib/tables/_a_n_k_r.py +15 -0
  219. fontTools/ttLib/tables/_a_v_a_r.py +191 -0
  220. fontTools/ttLib/tables/_b_s_l_n.py +15 -0
  221. fontTools/ttLib/tables/_c_i_d_g.py +24 -0
  222. fontTools/ttLib/tables/_c_m_a_p.py +1578 -0
  223. fontTools/ttLib/tables/_c_v_a_r.py +94 -0
  224. fontTools/ttLib/tables/_c_v_t.py +55 -0
  225. fontTools/ttLib/tables/_f_e_a_t.py +15 -0
  226. fontTools/ttLib/tables/_f_p_g_m.py +60 -0
  227. fontTools/ttLib/tables/_f_v_a_r.py +261 -0
  228. fontTools/ttLib/tables/_g_a_s_p.py +63 -0
  229. fontTools/ttLib/tables/_g_c_i_d.py +13 -0
  230. fontTools/ttLib/tables/_g_l_y_f.py +2311 -0
  231. fontTools/ttLib/tables/_g_v_a_r.py +292 -0
  232. fontTools/ttLib/tables/_h_d_m_x.py +127 -0
  233. fontTools/ttLib/tables/_h_e_a_d.py +130 -0
  234. fontTools/ttLib/tables/_h_h_e_a.py +147 -0
  235. fontTools/ttLib/tables/_h_m_t_x.py +160 -0
  236. fontTools/ttLib/tables/_k_e_r_n.py +289 -0
  237. fontTools/ttLib/tables/_l_c_a_r.py +13 -0
  238. fontTools/ttLib/tables/_l_o_c_a.py +70 -0
  239. fontTools/ttLib/tables/_l_t_a_g.py +72 -0
  240. fontTools/ttLib/tables/_m_a_x_p.py +147 -0
  241. fontTools/ttLib/tables/_m_e_t_a.py +112 -0
  242. fontTools/ttLib/tables/_m_o_r_t.py +14 -0
  243. fontTools/ttLib/tables/_m_o_r_x.py +15 -0
  244. fontTools/ttLib/tables/_n_a_m_e.py +1237 -0
  245. fontTools/ttLib/tables/_o_p_b_d.py +14 -0
  246. fontTools/ttLib/tables/_p_o_s_t.py +317 -0
  247. fontTools/ttLib/tables/_p_r_e_p.py +16 -0
  248. fontTools/ttLib/tables/_p_r_o_p.py +12 -0
  249. fontTools/ttLib/tables/_s_b_i_x.py +129 -0
  250. fontTools/ttLib/tables/_t_r_a_k.py +332 -0
  251. fontTools/ttLib/tables/_v_h_e_a.py +139 -0
  252. fontTools/ttLib/tables/_v_m_t_x.py +19 -0
  253. fontTools/ttLib/tables/asciiTable.py +20 -0
  254. fontTools/ttLib/tables/grUtils.py +92 -0
  255. fontTools/ttLib/tables/otBase.py +1465 -0
  256. fontTools/ttLib/tables/otConverters.py +2065 -0
  257. fontTools/ttLib/tables/otData.py +6400 -0
  258. fontTools/ttLib/tables/otTables.py +2700 -0
  259. fontTools/ttLib/tables/otTraverse.py +162 -0
  260. fontTools/ttLib/tables/sbixGlyph.py +149 -0
  261. fontTools/ttLib/tables/sbixStrike.py +177 -0
  262. fontTools/ttLib/tables/table_API_readme.txt +91 -0
  263. fontTools/ttLib/tables/ttProgram.py +594 -0
  264. fontTools/ttLib/ttCollection.py +125 -0
  265. fontTools/ttLib/ttFont.py +1155 -0
  266. fontTools/ttLib/ttGlyphSet.py +500 -0
  267. fontTools/ttLib/ttVisitor.py +32 -0
  268. fontTools/ttLib/woff2.py +1683 -0
  269. fontTools/ttx.py +467 -0
  270. fontTools/ufoLib/__init__.py +2477 -0
  271. fontTools/ufoLib/converters.py +334 -0
  272. fontTools/ufoLib/errors.py +22 -0
  273. fontTools/ufoLib/etree.py +6 -0
  274. fontTools/ufoLib/filenames.py +291 -0
  275. fontTools/ufoLib/glifLib.py +2022 -0
  276. fontTools/ufoLib/kerning.py +91 -0
  277. fontTools/ufoLib/plistlib.py +47 -0
  278. fontTools/ufoLib/pointPen.py +6 -0
  279. fontTools/ufoLib/utils.py +76 -0
  280. fontTools/ufoLib/validators.py +1186 -0
  281. fontTools/unicode.py +50 -0
  282. fontTools/unicodedata/Blocks.py +802 -0
  283. fontTools/unicodedata/OTTags.py +50 -0
  284. fontTools/unicodedata/ScriptExtensions.py +806 -0
  285. fontTools/unicodedata/Scripts.py +3618 -0
  286. fontTools/unicodedata/__init__.py +298 -0
  287. fontTools/varLib/__init__.py +1511 -0
  288. fontTools/varLib/__main__.py +6 -0
  289. fontTools/varLib/avar.py +260 -0
  290. fontTools/varLib/avarPlanner.py +1004 -0
  291. fontTools/varLib/builder.py +215 -0
  292. fontTools/varLib/cff.py +631 -0
  293. fontTools/varLib/errors.py +219 -0
  294. fontTools/varLib/featureVars.py +689 -0
  295. fontTools/varLib/instancer/__init__.py +1937 -0
  296. fontTools/varLib/instancer/__main__.py +5 -0
  297. fontTools/varLib/instancer/featureVars.py +190 -0
  298. fontTools/varLib/instancer/names.py +388 -0
  299. fontTools/varLib/instancer/solver.py +309 -0
  300. fontTools/varLib/interpolatable.py +1209 -0
  301. fontTools/varLib/interpolatableHelpers.py +396 -0
  302. fontTools/varLib/interpolatablePlot.py +1269 -0
  303. fontTools/varLib/interpolatableTestContourOrder.py +82 -0
  304. fontTools/varLib/interpolatableTestStartingPoint.py +107 -0
  305. fontTools/varLib/interpolate_layout.py +124 -0
  306. fontTools/varLib/iup.c +19154 -0
  307. fontTools/varLib/iup.cpython-39-x86_64-linux-gnu.so +0 -0
  308. fontTools/varLib/iup.py +490 -0
  309. fontTools/varLib/merger.py +1717 -0
  310. fontTools/varLib/models.py +642 -0
  311. fontTools/varLib/multiVarStore.py +253 -0
  312. fontTools/varLib/mutator.py +518 -0
  313. fontTools/varLib/mvar.py +40 -0
  314. fontTools/varLib/plot.py +238 -0
  315. fontTools/varLib/stat.py +149 -0
  316. fontTools/varLib/varStore.py +767 -0
  317. fontTools/voltLib/__init__.py +5 -0
  318. fontTools/voltLib/ast.py +448 -0
  319. fontTools/voltLib/error.py +12 -0
  320. fontTools/voltLib/lexer.py +99 -0
  321. fontTools/voltLib/parser.py +656 -0
  322. fontTools/voltLib/voltToFea.py +730 -0
  323. fonttools-4.55.6.data/data/share/man/man1/ttx.1 +225 -0
  324. fonttools-4.55.6.dist-info/LICENSE +21 -0
  325. fonttools-4.55.6.dist-info/METADATA +3413 -0
  326. fonttools-4.55.6.dist-info/RECORD +329 -0
  327. fonttools-4.55.6.dist-info/WHEEL +6 -0
  328. fonttools-4.55.6.dist-info/entry_points.txt +5 -0
  329. fonttools-4.55.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3659 @@
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
+ while (glyphName + "#" + str(n)) in allNames:
1468
+ n += 1
1469
+ allNames[glyphName] = n + 1
1470
+ glyphName = glyphName + "#" + str(n)
1471
+ allNames[glyphName] = 1
1472
+ newCharset.append(glyphName)
1473
+ charset = newCharset
1474
+ else: # offset == 0 -> no charset data.
1475
+ if isCID or "CharStrings" not in parent.rawDict:
1476
+ # We get here only when processing fontDicts from the FDArray of
1477
+ # CFF-CID fonts. Only the real topDict references the charset.
1478
+ assert value == 0
1479
+ charset = None
1480
+ elif value == 0:
1481
+ charset = cffISOAdobeStrings
1482
+ elif value == 1:
1483
+ charset = cffIExpertStrings
1484
+ elif value == 2:
1485
+ charset = cffExpertSubsetStrings
1486
+ if charset and (len(charset) != parent.numGlyphs):
1487
+ charset = charset[: parent.numGlyphs]
1488
+ return charset
1489
+
1490
+ def write(self, parent, value):
1491
+ return 0 # dummy value
1492
+
1493
+ def xmlWrite(self, xmlWriter, name, value):
1494
+ # XXX only write charset when not in OT/TTX context, where we
1495
+ # dump charset as a separate "GlyphOrder" table.
1496
+ # # xmlWriter.simpletag("charset")
1497
+ xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
1498
+ xmlWriter.newline()
1499
+
1500
+ def xmlRead(self, name, attrs, content, parent):
1501
+ pass
1502
+
1503
+
1504
+ class CharsetCompiler(object):
1505
+ def __init__(self, strings, charset, parent):
1506
+ assert charset[0] == ".notdef"
1507
+ isCID = hasattr(parent.dictObj, "ROS")
1508
+ data0 = packCharset0(charset, isCID, strings)
1509
+ data = packCharset(charset, isCID, strings)
1510
+ if len(data) < len(data0):
1511
+ self.data = data
1512
+ else:
1513
+ self.data = data0
1514
+ self.parent = parent
1515
+
1516
+ def setPos(self, pos, endPos):
1517
+ self.parent.rawDict["charset"] = pos
1518
+
1519
+ def getDataLength(self):
1520
+ return len(self.data)
1521
+
1522
+ def toFile(self, file):
1523
+ file.write(self.data)
1524
+
1525
+
1526
+ def getStdCharSet(charset):
1527
+ # check to see if we can use a predefined charset value.
1528
+ predefinedCharSetVal = None
1529
+ predefinedCharSets = [
1530
+ (cffISOAdobeStringCount, cffISOAdobeStrings, 0),
1531
+ (cffExpertStringCount, cffIExpertStrings, 1),
1532
+ (cffExpertSubsetStringCount, cffExpertSubsetStrings, 2),
1533
+ ]
1534
+ lcs = len(charset)
1535
+ for cnt, pcs, csv in predefinedCharSets:
1536
+ if predefinedCharSetVal is not None:
1537
+ break
1538
+ if lcs > cnt:
1539
+ continue
1540
+ predefinedCharSetVal = csv
1541
+ for i in range(lcs):
1542
+ if charset[i] != pcs[i]:
1543
+ predefinedCharSetVal = None
1544
+ break
1545
+ return predefinedCharSetVal
1546
+
1547
+
1548
+ def getCIDfromName(name, strings):
1549
+ return int(name[3:])
1550
+
1551
+
1552
+ def getSIDfromName(name, strings):
1553
+ return strings.getSID(name)
1554
+
1555
+
1556
+ def packCharset0(charset, isCID, strings):
1557
+ fmt = 0
1558
+ data = [packCard8(fmt)]
1559
+ if isCID:
1560
+ getNameID = getCIDfromName
1561
+ else:
1562
+ getNameID = getSIDfromName
1563
+
1564
+ for name in charset[1:]:
1565
+ data.append(packCard16(getNameID(name, strings)))
1566
+ return bytesjoin(data)
1567
+
1568
+
1569
+ def packCharset(charset, isCID, strings):
1570
+ fmt = 1
1571
+ ranges = []
1572
+ first = None
1573
+ end = 0
1574
+ if isCID:
1575
+ getNameID = getCIDfromName
1576
+ else:
1577
+ getNameID = getSIDfromName
1578
+
1579
+ for name in charset[1:]:
1580
+ SID = getNameID(name, strings)
1581
+ if first is None:
1582
+ first = SID
1583
+ elif end + 1 != SID:
1584
+ nLeft = end - first
1585
+ if nLeft > 255:
1586
+ fmt = 2
1587
+ ranges.append((first, nLeft))
1588
+ first = SID
1589
+ end = SID
1590
+ if end:
1591
+ nLeft = end - first
1592
+ if nLeft > 255:
1593
+ fmt = 2
1594
+ ranges.append((first, nLeft))
1595
+
1596
+ data = [packCard8(fmt)]
1597
+ if fmt == 1:
1598
+ nLeftFunc = packCard8
1599
+ else:
1600
+ nLeftFunc = packCard16
1601
+ for first, nLeft in ranges:
1602
+ data.append(packCard16(first) + nLeftFunc(nLeft))
1603
+ return bytesjoin(data)
1604
+
1605
+
1606
+ def parseCharset0(numGlyphs, file, strings, isCID):
1607
+ charset = [".notdef"]
1608
+ if isCID:
1609
+ for i in range(numGlyphs - 1):
1610
+ CID = readCard16(file)
1611
+ charset.append("cid" + str(CID).zfill(5))
1612
+ else:
1613
+ for i in range(numGlyphs - 1):
1614
+ SID = readCard16(file)
1615
+ charset.append(strings[SID])
1616
+ return charset
1617
+
1618
+
1619
+ def parseCharset(numGlyphs, file, strings, isCID, fmt):
1620
+ charset = [".notdef"]
1621
+ count = 1
1622
+ if fmt == 1:
1623
+ nLeftFunc = readCard8
1624
+ else:
1625
+ nLeftFunc = readCard16
1626
+ while count < numGlyphs:
1627
+ first = readCard16(file)
1628
+ nLeft = nLeftFunc(file)
1629
+ if isCID:
1630
+ for CID in range(first, first + nLeft + 1):
1631
+ charset.append("cid" + str(CID).zfill(5))
1632
+ else:
1633
+ for SID in range(first, first + nLeft + 1):
1634
+ charset.append(strings[SID])
1635
+ count = count + nLeft + 1
1636
+ return charset
1637
+
1638
+
1639
+ class EncodingCompiler(object):
1640
+ def __init__(self, strings, encoding, parent):
1641
+ assert not isinstance(encoding, str)
1642
+ data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
1643
+ data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
1644
+ if len(data0) < len(data1):
1645
+ self.data = data0
1646
+ else:
1647
+ self.data = data1
1648
+ self.parent = parent
1649
+
1650
+ def setPos(self, pos, endPos):
1651
+ self.parent.rawDict["Encoding"] = pos
1652
+
1653
+ def getDataLength(self):
1654
+ return len(self.data)
1655
+
1656
+ def toFile(self, file):
1657
+ file.write(self.data)
1658
+
1659
+
1660
+ class EncodingConverter(SimpleConverter):
1661
+ def _read(self, parent, value):
1662
+ if value == 0:
1663
+ return "StandardEncoding"
1664
+ elif value == 1:
1665
+ return "ExpertEncoding"
1666
+ else:
1667
+ assert value > 1
1668
+ file = parent.file
1669
+ file.seek(value)
1670
+ log.log(DEBUG, "loading Encoding at %s", value)
1671
+ fmt = readCard8(file)
1672
+ haveSupplement = fmt & 0x80
1673
+ if haveSupplement:
1674
+ raise NotImplementedError("Encoding supplements are not yet supported")
1675
+ fmt = fmt & 0x7F
1676
+ if fmt == 0:
1677
+ encoding = parseEncoding0(
1678
+ parent.charset, file, haveSupplement, parent.strings
1679
+ )
1680
+ elif fmt == 1:
1681
+ encoding = parseEncoding1(
1682
+ parent.charset, file, haveSupplement, parent.strings
1683
+ )
1684
+ return encoding
1685
+
1686
+ def write(self, parent, value):
1687
+ if value == "StandardEncoding":
1688
+ return 0
1689
+ elif value == "ExpertEncoding":
1690
+ return 1
1691
+ return 0 # dummy value
1692
+
1693
+ def xmlWrite(self, xmlWriter, name, value):
1694
+ if value in ("StandardEncoding", "ExpertEncoding"):
1695
+ xmlWriter.simpletag(name, name=value)
1696
+ xmlWriter.newline()
1697
+ return
1698
+ xmlWriter.begintag(name)
1699
+ xmlWriter.newline()
1700
+ for code in range(len(value)):
1701
+ glyphName = value[code]
1702
+ if glyphName != ".notdef":
1703
+ xmlWriter.simpletag("map", code=hex(code), name=glyphName)
1704
+ xmlWriter.newline()
1705
+ xmlWriter.endtag(name)
1706
+ xmlWriter.newline()
1707
+
1708
+ def xmlRead(self, name, attrs, content, parent):
1709
+ if "name" in attrs:
1710
+ return attrs["name"]
1711
+ encoding = [".notdef"] * 256
1712
+ for element in content:
1713
+ if isinstance(element, str):
1714
+ continue
1715
+ name, attrs, content = element
1716
+ code = safeEval(attrs["code"])
1717
+ glyphName = attrs["name"]
1718
+ encoding[code] = glyphName
1719
+ return encoding
1720
+
1721
+
1722
+ def parseEncoding0(charset, file, haveSupplement, strings):
1723
+ nCodes = readCard8(file)
1724
+ encoding = [".notdef"] * 256
1725
+ for glyphID in range(1, nCodes + 1):
1726
+ code = readCard8(file)
1727
+ if code != 0:
1728
+ encoding[code] = charset[glyphID]
1729
+ return encoding
1730
+
1731
+
1732
+ def parseEncoding1(charset, file, haveSupplement, strings):
1733
+ nRanges = readCard8(file)
1734
+ encoding = [".notdef"] * 256
1735
+ glyphID = 1
1736
+ for i in range(nRanges):
1737
+ code = readCard8(file)
1738
+ nLeft = readCard8(file)
1739
+ for glyphID in range(glyphID, glyphID + nLeft + 1):
1740
+ encoding[code] = charset[glyphID]
1741
+ code = code + 1
1742
+ glyphID = glyphID + 1
1743
+ return encoding
1744
+
1745
+
1746
+ def packEncoding0(charset, encoding, strings):
1747
+ fmt = 0
1748
+ m = {}
1749
+ for code in range(len(encoding)):
1750
+ name = encoding[code]
1751
+ if name != ".notdef":
1752
+ m[name] = code
1753
+ codes = []
1754
+ for name in charset[1:]:
1755
+ code = m.get(name)
1756
+ codes.append(code)
1757
+
1758
+ while codes and codes[-1] is None:
1759
+ codes.pop()
1760
+
1761
+ data = [packCard8(fmt), packCard8(len(codes))]
1762
+ for code in codes:
1763
+ if code is None:
1764
+ code = 0
1765
+ data.append(packCard8(code))
1766
+ return bytesjoin(data)
1767
+
1768
+
1769
+ def packEncoding1(charset, encoding, strings):
1770
+ fmt = 1
1771
+ m = {}
1772
+ for code in range(len(encoding)):
1773
+ name = encoding[code]
1774
+ if name != ".notdef":
1775
+ m[name] = code
1776
+ ranges = []
1777
+ first = None
1778
+ end = 0
1779
+ for name in charset[1:]:
1780
+ code = m.get(name, -1)
1781
+ if first is None:
1782
+ first = code
1783
+ elif end + 1 != code:
1784
+ nLeft = end - first
1785
+ ranges.append((first, nLeft))
1786
+ first = code
1787
+ end = code
1788
+ nLeft = end - first
1789
+ ranges.append((first, nLeft))
1790
+
1791
+ # remove unencoded glyphs at the end.
1792
+ while ranges and ranges[-1][0] == -1:
1793
+ ranges.pop()
1794
+
1795
+ data = [packCard8(fmt), packCard8(len(ranges))]
1796
+ for first, nLeft in ranges:
1797
+ if first == -1: # unencoded
1798
+ first = 0
1799
+ data.append(packCard8(first) + packCard8(nLeft))
1800
+ return bytesjoin(data)
1801
+
1802
+
1803
+ class FDArrayConverter(TableConverter):
1804
+ def _read(self, parent, value):
1805
+ try:
1806
+ vstore = parent.VarStore
1807
+ except AttributeError:
1808
+ vstore = None
1809
+ file = parent.file
1810
+ isCFF2 = parent._isCFF2
1811
+ file.seek(value)
1812
+ fdArray = FDArrayIndex(file, isCFF2=isCFF2)
1813
+ fdArray.vstore = vstore
1814
+ fdArray.strings = parent.strings
1815
+ fdArray.GlobalSubrs = parent.GlobalSubrs
1816
+ return fdArray
1817
+
1818
+ def write(self, parent, value):
1819
+ return 0 # dummy value
1820
+
1821
+ def xmlRead(self, name, attrs, content, parent):
1822
+ fdArray = FDArrayIndex()
1823
+ for element in content:
1824
+ if isinstance(element, str):
1825
+ continue
1826
+ name, attrs, content = element
1827
+ fdArray.fromXML(name, attrs, content)
1828
+ return fdArray
1829
+
1830
+
1831
+ class FDSelectConverter(SimpleConverter):
1832
+ def _read(self, parent, value):
1833
+ file = parent.file
1834
+ file.seek(value)
1835
+ fdSelect = FDSelect(file, parent.numGlyphs)
1836
+ return fdSelect
1837
+
1838
+ def write(self, parent, value):
1839
+ return 0 # dummy value
1840
+
1841
+ # The FDSelect glyph data is written out to XML in the charstring keys,
1842
+ # so we write out only the format selector
1843
+ def xmlWrite(self, xmlWriter, name, value):
1844
+ xmlWriter.simpletag(name, [("format", value.format)])
1845
+ xmlWriter.newline()
1846
+
1847
+ def xmlRead(self, name, attrs, content, parent):
1848
+ fmt = safeEval(attrs["format"])
1849
+ file = None
1850
+ numGlyphs = None
1851
+ fdSelect = FDSelect(file, numGlyphs, fmt)
1852
+ return fdSelect
1853
+
1854
+
1855
+ class VarStoreConverter(SimpleConverter):
1856
+ def _read(self, parent, value):
1857
+ file = parent.file
1858
+ file.seek(value)
1859
+ varStore = VarStoreData(file)
1860
+ varStore.decompile()
1861
+ return varStore
1862
+
1863
+ def write(self, parent, value):
1864
+ return 0 # dummy value
1865
+
1866
+ def xmlWrite(self, xmlWriter, name, value):
1867
+ value.writeXML(xmlWriter, name)
1868
+
1869
+ def xmlRead(self, name, attrs, content, parent):
1870
+ varStore = VarStoreData()
1871
+ varStore.xmlRead(name, attrs, content, parent)
1872
+ return varStore
1873
+
1874
+
1875
+ def packFDSelect0(fdSelectArray):
1876
+ fmt = 0
1877
+ data = [packCard8(fmt)]
1878
+ for index in fdSelectArray:
1879
+ data.append(packCard8(index))
1880
+ return bytesjoin(data)
1881
+
1882
+
1883
+ def packFDSelect3(fdSelectArray):
1884
+ fmt = 3
1885
+ fdRanges = []
1886
+ lenArray = len(fdSelectArray)
1887
+ lastFDIndex = -1
1888
+ for i in range(lenArray):
1889
+ fdIndex = fdSelectArray[i]
1890
+ if lastFDIndex != fdIndex:
1891
+ fdRanges.append([i, fdIndex])
1892
+ lastFDIndex = fdIndex
1893
+ sentinelGID = i + 1
1894
+
1895
+ data = [packCard8(fmt)]
1896
+ data.append(packCard16(len(fdRanges)))
1897
+ for fdRange in fdRanges:
1898
+ data.append(packCard16(fdRange[0]))
1899
+ data.append(packCard8(fdRange[1]))
1900
+ data.append(packCard16(sentinelGID))
1901
+ return bytesjoin(data)
1902
+
1903
+
1904
+ def packFDSelect4(fdSelectArray):
1905
+ fmt = 4
1906
+ fdRanges = []
1907
+ lenArray = len(fdSelectArray)
1908
+ lastFDIndex = -1
1909
+ for i in range(lenArray):
1910
+ fdIndex = fdSelectArray[i]
1911
+ if lastFDIndex != fdIndex:
1912
+ fdRanges.append([i, fdIndex])
1913
+ lastFDIndex = fdIndex
1914
+ sentinelGID = i + 1
1915
+
1916
+ data = [packCard8(fmt)]
1917
+ data.append(packCard32(len(fdRanges)))
1918
+ for fdRange in fdRanges:
1919
+ data.append(packCard32(fdRange[0]))
1920
+ data.append(packCard16(fdRange[1]))
1921
+ data.append(packCard32(sentinelGID))
1922
+ return bytesjoin(data)
1923
+
1924
+
1925
+ class FDSelectCompiler(object):
1926
+ def __init__(self, fdSelect, parent):
1927
+ fmt = fdSelect.format
1928
+ fdSelectArray = fdSelect.gidArray
1929
+ if fmt == 0:
1930
+ self.data = packFDSelect0(fdSelectArray)
1931
+ elif fmt == 3:
1932
+ self.data = packFDSelect3(fdSelectArray)
1933
+ elif fmt == 4:
1934
+ self.data = packFDSelect4(fdSelectArray)
1935
+ else:
1936
+ # choose smaller of the two formats
1937
+ data0 = packFDSelect0(fdSelectArray)
1938
+ data3 = packFDSelect3(fdSelectArray)
1939
+ if len(data0) < len(data3):
1940
+ self.data = data0
1941
+ fdSelect.format = 0
1942
+ else:
1943
+ self.data = data3
1944
+ fdSelect.format = 3
1945
+
1946
+ self.parent = parent
1947
+
1948
+ def setPos(self, pos, endPos):
1949
+ self.parent.rawDict["FDSelect"] = pos
1950
+
1951
+ def getDataLength(self):
1952
+ return len(self.data)
1953
+
1954
+ def toFile(self, file):
1955
+ file.write(self.data)
1956
+
1957
+
1958
+ class VarStoreCompiler(object):
1959
+ def __init__(self, varStoreData, parent):
1960
+ self.parent = parent
1961
+ if not varStoreData.data:
1962
+ varStoreData.compile()
1963
+ varStoreDataLen = min(0xFFFF, len(varStoreData.data))
1964
+ data = [packCard16(varStoreDataLen), varStoreData.data]
1965
+ self.data = bytesjoin(data)
1966
+
1967
+ def setPos(self, pos, endPos):
1968
+ self.parent.rawDict["VarStore"] = pos
1969
+
1970
+ def getDataLength(self):
1971
+ return len(self.data)
1972
+
1973
+ def toFile(self, file):
1974
+ file.write(self.data)
1975
+
1976
+
1977
+ class ROSConverter(SimpleConverter):
1978
+ def xmlWrite(self, xmlWriter, name, value):
1979
+ registry, order, supplement = value
1980
+ xmlWriter.simpletag(
1981
+ name,
1982
+ [
1983
+ ("Registry", tostr(registry)),
1984
+ ("Order", tostr(order)),
1985
+ ("Supplement", supplement),
1986
+ ],
1987
+ )
1988
+ xmlWriter.newline()
1989
+
1990
+ def xmlRead(self, name, attrs, content, parent):
1991
+ return (attrs["Registry"], attrs["Order"], safeEval(attrs["Supplement"]))
1992
+
1993
+
1994
+ topDictOperators = [
1995
+ # opcode name argument type default converter
1996
+ (25, "maxstack", "number", None, None),
1997
+ ((12, 30), "ROS", ("SID", "SID", "number"), None, ROSConverter()),
1998
+ ((12, 20), "SyntheticBase", "number", None, None),
1999
+ (0, "version", "SID", None, None),
2000
+ (1, "Notice", "SID", None, Latin1Converter()),
2001
+ ((12, 0), "Copyright", "SID", None, Latin1Converter()),
2002
+ (2, "FullName", "SID", None, Latin1Converter()),
2003
+ ((12, 38), "FontName", "SID", None, Latin1Converter()),
2004
+ (3, "FamilyName", "SID", None, Latin1Converter()),
2005
+ (4, "Weight", "SID", None, None),
2006
+ ((12, 1), "isFixedPitch", "number", 0, None),
2007
+ ((12, 2), "ItalicAngle", "number", 0, None),
2008
+ ((12, 3), "UnderlinePosition", "number", -100, None),
2009
+ ((12, 4), "UnderlineThickness", "number", 50, None),
2010
+ ((12, 5), "PaintType", "number", 0, None),
2011
+ ((12, 6), "CharstringType", "number", 2, None),
2012
+ ((12, 7), "FontMatrix", "array", [0.001, 0, 0, 0.001, 0, 0], None),
2013
+ (13, "UniqueID", "number", None, None),
2014
+ (5, "FontBBox", "array", [0, 0, 0, 0], None),
2015
+ ((12, 8), "StrokeWidth", "number", 0, None),
2016
+ (14, "XUID", "array", None, None),
2017
+ ((12, 21), "PostScript", "SID", None, None),
2018
+ ((12, 22), "BaseFontName", "SID", None, None),
2019
+ ((12, 23), "BaseFontBlend", "delta", None, None),
2020
+ ((12, 31), "CIDFontVersion", "number", 0, None),
2021
+ ((12, 32), "CIDFontRevision", "number", 0, None),
2022
+ ((12, 33), "CIDFontType", "number", 0, None),
2023
+ ((12, 34), "CIDCount", "number", 8720, None),
2024
+ (15, "charset", "number", None, CharsetConverter()),
2025
+ ((12, 35), "UIDBase", "number", None, None),
2026
+ (16, "Encoding", "number", 0, EncodingConverter()),
2027
+ (18, "Private", ("number", "number"), None, PrivateDictConverter()),
2028
+ ((12, 37), "FDSelect", "number", None, FDSelectConverter()),
2029
+ ((12, 36), "FDArray", "number", None, FDArrayConverter()),
2030
+ (17, "CharStrings", "number", None, CharStringsConverter()),
2031
+ (24, "VarStore", "number", None, VarStoreConverter()),
2032
+ ]
2033
+
2034
+ topDictOperators2 = [
2035
+ # opcode name argument type default converter
2036
+ (25, "maxstack", "number", None, None),
2037
+ ((12, 7), "FontMatrix", "array", [0.001, 0, 0, 0.001, 0, 0], None),
2038
+ ((12, 37), "FDSelect", "number", None, FDSelectConverter()),
2039
+ ((12, 36), "FDArray", "number", None, FDArrayConverter()),
2040
+ (17, "CharStrings", "number", None, CharStringsConverter()),
2041
+ (24, "VarStore", "number", None, VarStoreConverter()),
2042
+ ]
2043
+
2044
+ # Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
2045
+ # in order for the font to compile back from xml.
2046
+
2047
+ kBlendDictOpName = "blend"
2048
+ blendOp = 23
2049
+
2050
+ privateDictOperators = [
2051
+ # opcode name argument type default converter
2052
+ (22, "vsindex", "number", None, None),
2053
+ (
2054
+ blendOp,
2055
+ kBlendDictOpName,
2056
+ "blendList",
2057
+ None,
2058
+ None,
2059
+ ), # This is for reading to/from XML: it not written to CFF.
2060
+ (6, "BlueValues", "delta", None, None),
2061
+ (7, "OtherBlues", "delta", None, None),
2062
+ (8, "FamilyBlues", "delta", None, None),
2063
+ (9, "FamilyOtherBlues", "delta", None, None),
2064
+ ((12, 9), "BlueScale", "number", 0.039625, None),
2065
+ ((12, 10), "BlueShift", "number", 7, None),
2066
+ ((12, 11), "BlueFuzz", "number", 1, None),
2067
+ (10, "StdHW", "number", None, None),
2068
+ (11, "StdVW", "number", None, None),
2069
+ ((12, 12), "StemSnapH", "delta", None, None),
2070
+ ((12, 13), "StemSnapV", "delta", None, None),
2071
+ ((12, 14), "ForceBold", "number", 0, None),
2072
+ ((12, 15), "ForceBoldThreshold", "number", None, None), # deprecated
2073
+ ((12, 16), "lenIV", "number", None, None), # deprecated
2074
+ ((12, 17), "LanguageGroup", "number", 0, None),
2075
+ ((12, 18), "ExpansionFactor", "number", 0.06, None),
2076
+ ((12, 19), "initialRandomSeed", "number", 0, None),
2077
+ (20, "defaultWidthX", "number", 0, None),
2078
+ (21, "nominalWidthX", "number", 0, None),
2079
+ (19, "Subrs", "number", None, SubrsConverter()),
2080
+ ]
2081
+
2082
+ privateDictOperators2 = [
2083
+ # opcode name argument type default converter
2084
+ (22, "vsindex", "number", None, None),
2085
+ (
2086
+ blendOp,
2087
+ kBlendDictOpName,
2088
+ "blendList",
2089
+ None,
2090
+ None,
2091
+ ), # This is for reading to/from XML: it not written to CFF.
2092
+ (6, "BlueValues", "delta", None, None),
2093
+ (7, "OtherBlues", "delta", None, None),
2094
+ (8, "FamilyBlues", "delta", None, None),
2095
+ (9, "FamilyOtherBlues", "delta", None, None),
2096
+ ((12, 9), "BlueScale", "number", 0.039625, None),
2097
+ ((12, 10), "BlueShift", "number", 7, None),
2098
+ ((12, 11), "BlueFuzz", "number", 1, None),
2099
+ (10, "StdHW", "number", None, None),
2100
+ (11, "StdVW", "number", None, None),
2101
+ ((12, 12), "StemSnapH", "delta", None, None),
2102
+ ((12, 13), "StemSnapV", "delta", None, None),
2103
+ ((12, 17), "LanguageGroup", "number", 0, None),
2104
+ ((12, 18), "ExpansionFactor", "number", 0.06, None),
2105
+ (19, "Subrs", "number", None, SubrsConverter()),
2106
+ ]
2107
+
2108
+
2109
+ def addConverters(table):
2110
+ for i in range(len(table)):
2111
+ op, name, arg, default, conv = table[i]
2112
+ if conv is not None:
2113
+ continue
2114
+ if arg in ("delta", "array"):
2115
+ conv = ArrayConverter()
2116
+ elif arg == "number":
2117
+ conv = NumberConverter()
2118
+ elif arg == "SID":
2119
+ conv = ASCIIConverter()
2120
+ elif arg == "blendList":
2121
+ conv = None
2122
+ else:
2123
+ assert False
2124
+ table[i] = op, name, arg, default, conv
2125
+
2126
+
2127
+ addConverters(privateDictOperators)
2128
+ addConverters(topDictOperators)
2129
+
2130
+
2131
+ class TopDictDecompiler(psCharStrings.DictDecompiler):
2132
+ operators = buildOperatorDict(topDictOperators)
2133
+
2134
+
2135
+ class PrivateDictDecompiler(psCharStrings.DictDecompiler):
2136
+ operators = buildOperatorDict(privateDictOperators)
2137
+
2138
+
2139
+ class DictCompiler(object):
2140
+ maxBlendStack = 0
2141
+
2142
+ def __init__(self, dictObj, strings, parent, isCFF2=None):
2143
+ if strings:
2144
+ assert isinstance(strings, IndexedStrings)
2145
+ if isCFF2 is None and hasattr(parent, "isCFF2"):
2146
+ isCFF2 = parent.isCFF2
2147
+ assert isCFF2 is not None
2148
+ self.isCFF2 = isCFF2
2149
+ self.dictObj = dictObj
2150
+ self.strings = strings
2151
+ self.parent = parent
2152
+ rawDict = {}
2153
+ for name in dictObj.order:
2154
+ value = getattr(dictObj, name, None)
2155
+ if value is None:
2156
+ continue
2157
+ conv = dictObj.converters[name]
2158
+ value = conv.write(dictObj, value)
2159
+ if value == dictObj.defaults.get(name):
2160
+ continue
2161
+ rawDict[name] = value
2162
+ self.rawDict = rawDict
2163
+
2164
+ def setPos(self, pos, endPos):
2165
+ pass
2166
+
2167
+ def getDataLength(self):
2168
+ return len(self.compile("getDataLength"))
2169
+
2170
+ def compile(self, reason):
2171
+ log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason)
2172
+ rawDict = self.rawDict
2173
+ data = []
2174
+ for name in self.dictObj.order:
2175
+ value = rawDict.get(name)
2176
+ if value is None:
2177
+ continue
2178
+ op, argType = self.opcodes[name]
2179
+ if isinstance(argType, tuple):
2180
+ l = len(argType)
2181
+ assert len(value) == l, "value doesn't match arg type"
2182
+ for i in range(l):
2183
+ arg = argType[i]
2184
+ v = value[i]
2185
+ arghandler = getattr(self, "arg_" + arg)
2186
+ data.append(arghandler(v))
2187
+ else:
2188
+ arghandler = getattr(self, "arg_" + argType)
2189
+ data.append(arghandler(value))
2190
+ data.append(op)
2191
+ data = bytesjoin(data)
2192
+ return data
2193
+
2194
+ def toFile(self, file):
2195
+ data = self.compile("toFile")
2196
+ file.write(data)
2197
+
2198
+ def arg_number(self, num):
2199
+ if isinstance(num, list):
2200
+ data = [encodeNumber(val) for val in num]
2201
+ data.append(encodeNumber(1))
2202
+ data.append(bytechr(blendOp))
2203
+ datum = bytesjoin(data)
2204
+ else:
2205
+ datum = encodeNumber(num)
2206
+ return datum
2207
+
2208
+ def arg_SID(self, s):
2209
+ return psCharStrings.encodeIntCFF(self.strings.getSID(s))
2210
+
2211
+ def arg_array(self, value):
2212
+ data = []
2213
+ for num in value:
2214
+ data.append(self.arg_number(num))
2215
+ return bytesjoin(data)
2216
+
2217
+ def arg_delta(self, value):
2218
+ if not value:
2219
+ return b""
2220
+ val0 = value[0]
2221
+ if isinstance(val0, list):
2222
+ data = self.arg_delta_blend(value)
2223
+ else:
2224
+ out = []
2225
+ last = 0
2226
+ for v in value:
2227
+ out.append(v - last)
2228
+ last = v
2229
+ data = []
2230
+ for num in out:
2231
+ data.append(encodeNumber(num))
2232
+ return bytesjoin(data)
2233
+
2234
+ def arg_delta_blend(self, value):
2235
+ """A delta list with blend lists has to be *all* blend lists.
2236
+
2237
+ The value is a list is arranged as follows::
2238
+
2239
+ [
2240
+ [V0, d0..dn]
2241
+ [V1, d0..dn]
2242
+ ...
2243
+ [Vm, d0..dn]
2244
+ ]
2245
+
2246
+ ``V`` is the absolute coordinate value from the default font, and ``d0-dn``
2247
+ are the delta values from the *n* regions. Each ``V`` is an absolute
2248
+ coordinate from the default font.
2249
+
2250
+ We want to return a list::
2251
+
2252
+ [
2253
+ [v0, v1..vm]
2254
+ [d0..dn]
2255
+ ...
2256
+ [d0..dn]
2257
+ numBlends
2258
+ blendOp
2259
+ ]
2260
+
2261
+ where each ``v`` is relative to the previous default font value.
2262
+ """
2263
+ numMasters = len(value[0])
2264
+ numBlends = len(value)
2265
+ numStack = (numBlends * numMasters) + 1
2266
+ if numStack > self.maxBlendStack:
2267
+ # Figure out the max number of value we can blend
2268
+ # and divide this list up into chunks of that size.
2269
+
2270
+ numBlendValues = int((self.maxBlendStack - 1) / numMasters)
2271
+ out = []
2272
+ while True:
2273
+ numVal = min(len(value), numBlendValues)
2274
+ if numVal == 0:
2275
+ break
2276
+ valList = value[0:numVal]
2277
+ out1 = self.arg_delta_blend(valList)
2278
+ out.extend(out1)
2279
+ value = value[numVal:]
2280
+ else:
2281
+ firstList = [0] * numBlends
2282
+ deltaList = [None] * numBlends
2283
+ i = 0
2284
+ prevVal = 0
2285
+ while i < numBlends:
2286
+ # For PrivateDict BlueValues, the default font
2287
+ # values are absolute, not relative.
2288
+ # Must convert these back to relative coordinates
2289
+ # before writing to CFF2.
2290
+ defaultValue = value[i][0]
2291
+ firstList[i] = defaultValue - prevVal
2292
+ prevVal = defaultValue
2293
+ deltaList[i] = value[i][1:]
2294
+ i += 1
2295
+
2296
+ relValueList = firstList
2297
+ for blendList in deltaList:
2298
+ relValueList.extend(blendList)
2299
+ out = [encodeNumber(val) for val in relValueList]
2300
+ out.append(encodeNumber(numBlends))
2301
+ out.append(bytechr(blendOp))
2302
+ return out
2303
+
2304
+
2305
+ def encodeNumber(num):
2306
+ if isinstance(num, float):
2307
+ return psCharStrings.encodeFloat(num)
2308
+ else:
2309
+ return psCharStrings.encodeIntCFF(num)
2310
+
2311
+
2312
+ class TopDictCompiler(DictCompiler):
2313
+ opcodes = buildOpcodeDict(topDictOperators)
2314
+
2315
+ def getChildren(self, strings):
2316
+ isCFF2 = self.isCFF2
2317
+ children = []
2318
+ if self.dictObj.cff2GetGlyphOrder is None:
2319
+ if hasattr(self.dictObj, "charset") and self.dictObj.charset:
2320
+ if hasattr(self.dictObj, "ROS"): # aka isCID
2321
+ charsetCode = None
2322
+ else:
2323
+ charsetCode = getStdCharSet(self.dictObj.charset)
2324
+ if charsetCode is None:
2325
+ children.append(
2326
+ CharsetCompiler(strings, self.dictObj.charset, self)
2327
+ )
2328
+ else:
2329
+ self.rawDict["charset"] = charsetCode
2330
+ if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding:
2331
+ encoding = self.dictObj.Encoding
2332
+ if not isinstance(encoding, str):
2333
+ children.append(EncodingCompiler(strings, encoding, self))
2334
+ else:
2335
+ if hasattr(self.dictObj, "VarStore"):
2336
+ varStoreData = self.dictObj.VarStore
2337
+ varStoreComp = VarStoreCompiler(varStoreData, self)
2338
+ children.append(varStoreComp)
2339
+ if hasattr(self.dictObj, "FDSelect"):
2340
+ # I have not yet supported merging a ttx CFF-CID font, as there are
2341
+ # interesting issues about merging the FDArrays. Here I assume that
2342
+ # either the font was read from XML, and the FDSelect indices are all
2343
+ # in the charstring data, or the FDSelect array is already fully defined.
2344
+ fdSelect = self.dictObj.FDSelect
2345
+ # probably read in from XML; assume fdIndex in CharString data
2346
+ if len(fdSelect) == 0:
2347
+ charStrings = self.dictObj.CharStrings
2348
+ for name in self.dictObj.charset:
2349
+ fdSelect.append(charStrings[name].fdSelectIndex)
2350
+ fdSelectComp = FDSelectCompiler(fdSelect, self)
2351
+ children.append(fdSelectComp)
2352
+ if hasattr(self.dictObj, "CharStrings"):
2353
+ items = []
2354
+ charStrings = self.dictObj.CharStrings
2355
+ for name in self.dictObj.charset:
2356
+ items.append(charStrings[name])
2357
+ charStringsComp = CharStringsCompiler(items, strings, self, isCFF2=isCFF2)
2358
+ children.append(charStringsComp)
2359
+ if hasattr(self.dictObj, "FDArray"):
2360
+ # I have not yet supported merging a ttx CFF-CID font, as there are
2361
+ # interesting issues about merging the FDArrays. Here I assume that the
2362
+ # FDArray info is correct and complete.
2363
+ fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
2364
+ children.append(fdArrayIndexComp)
2365
+ children.extend(fdArrayIndexComp.getChildren(strings))
2366
+ if hasattr(self.dictObj, "Private"):
2367
+ privComp = self.dictObj.Private.getCompiler(strings, self)
2368
+ children.append(privComp)
2369
+ children.extend(privComp.getChildren(strings))
2370
+ return children
2371
+
2372
+
2373
+ class FontDictCompiler(DictCompiler):
2374
+ opcodes = buildOpcodeDict(topDictOperators)
2375
+
2376
+ def __init__(self, dictObj, strings, parent, isCFF2=None):
2377
+ super(FontDictCompiler, self).__init__(dictObj, strings, parent, isCFF2=isCFF2)
2378
+ #
2379
+ # We now take some effort to detect if there were any key/value pairs
2380
+ # supplied that were ignored in the FontDict context, and issue a warning
2381
+ # for those cases.
2382
+ #
2383
+ ignoredNames = []
2384
+ dictObj = self.dictObj
2385
+ for name in sorted(set(dictObj.converters) - set(dictObj.order)):
2386
+ if name in dictObj.rawDict:
2387
+ # The font was directly read from binary. In this
2388
+ # case, we want to report *all* "useless" key/value
2389
+ # pairs that are in the font, not just the ones that
2390
+ # are different from the default.
2391
+ ignoredNames.append(name)
2392
+ else:
2393
+ # The font was probably read from a TTX file. We only
2394
+ # warn about keys whos value is not the default. The
2395
+ # ones that have the default value will not be written
2396
+ # to binary anyway.
2397
+ default = dictObj.defaults.get(name)
2398
+ if default is not None:
2399
+ conv = dictObj.converters[name]
2400
+ default = conv.read(dictObj, default)
2401
+ if getattr(dictObj, name, None) != default:
2402
+ ignoredNames.append(name)
2403
+ if ignoredNames:
2404
+ log.warning(
2405
+ "Some CFF FDArray/FontDict keys were ignored upon compile: "
2406
+ + " ".join(sorted(ignoredNames))
2407
+ )
2408
+
2409
+ def getChildren(self, strings):
2410
+ children = []
2411
+ if hasattr(self.dictObj, "Private"):
2412
+ privComp = self.dictObj.Private.getCompiler(strings, self)
2413
+ children.append(privComp)
2414
+ children.extend(privComp.getChildren(strings))
2415
+ return children
2416
+
2417
+
2418
+ class PrivateDictCompiler(DictCompiler):
2419
+ maxBlendStack = maxStackLimit
2420
+ opcodes = buildOpcodeDict(privateDictOperators)
2421
+
2422
+ def setPos(self, pos, endPos):
2423
+ size = endPos - pos
2424
+ self.parent.rawDict["Private"] = size, pos
2425
+ self.pos = pos
2426
+
2427
+ def getChildren(self, strings):
2428
+ children = []
2429
+ if hasattr(self.dictObj, "Subrs"):
2430
+ children.append(self.dictObj.Subrs.getCompiler(strings, self))
2431
+ return children
2432
+
2433
+
2434
+ class BaseDict(object):
2435
+ def __init__(self, strings=None, file=None, offset=None, isCFF2=None):
2436
+ assert (isCFF2 is None) == (file is None)
2437
+ self.rawDict = {}
2438
+ self.skipNames = []
2439
+ self.strings = strings
2440
+ if file is None:
2441
+ return
2442
+ self._isCFF2 = isCFF2
2443
+ self.file = file
2444
+ if offset is not None:
2445
+ log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset)
2446
+ self.offset = offset
2447
+
2448
+ def decompile(self, data):
2449
+ log.log(DEBUG, " length %s is %d", self.__class__.__name__, len(data))
2450
+ dec = self.decompilerClass(self.strings, self)
2451
+ dec.decompile(data)
2452
+ self.rawDict = dec.getDict()
2453
+ self.postDecompile()
2454
+
2455
+ def postDecompile(self):
2456
+ pass
2457
+
2458
+ def getCompiler(self, strings, parent, isCFF2=None):
2459
+ return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
2460
+
2461
+ def __getattr__(self, name):
2462
+ if name[:2] == name[-2:] == "__":
2463
+ # to make deepcopy() and pickle.load() work, we need to signal with
2464
+ # AttributeError that dunder methods like '__deepcopy__' or '__getstate__'
2465
+ # aren't implemented. For more details, see:
2466
+ # https://github.com/fonttools/fonttools/pull/1488
2467
+ raise AttributeError(name)
2468
+ value = self.rawDict.get(name, None)
2469
+ if value is None:
2470
+ value = self.defaults.get(name)
2471
+ if value is None:
2472
+ raise AttributeError(name)
2473
+ conv = self.converters[name]
2474
+ value = conv.read(self, value)
2475
+ setattr(self, name, value)
2476
+ return value
2477
+
2478
+ def toXML(self, xmlWriter):
2479
+ for name in self.order:
2480
+ if name in self.skipNames:
2481
+ continue
2482
+ value = getattr(self, name, None)
2483
+ # XXX For "charset" we never skip calling xmlWrite even if the
2484
+ # value is None, so we always write the following XML comment:
2485
+ #
2486
+ # <!-- charset is dumped separately as the 'GlyphOrder' element -->
2487
+ #
2488
+ # Charset is None when 'CFF ' table is imported from XML into an
2489
+ # empty TTFont(). By writing this comment all the time, we obtain
2490
+ # the same XML output whether roundtripping XML-to-XML or
2491
+ # dumping binary-to-XML
2492
+ if value is None and name != "charset":
2493
+ continue
2494
+ conv = self.converters[name]
2495
+ conv.xmlWrite(xmlWriter, name, value)
2496
+ ignoredNames = set(self.rawDict) - set(self.order)
2497
+ if ignoredNames:
2498
+ xmlWriter.comment(
2499
+ "some keys were ignored: %s" % " ".join(sorted(ignoredNames))
2500
+ )
2501
+ xmlWriter.newline()
2502
+
2503
+ def fromXML(self, name, attrs, content):
2504
+ conv = self.converters[name]
2505
+ value = conv.xmlRead(name, attrs, content, self)
2506
+ setattr(self, name, value)
2507
+
2508
+
2509
+ class TopDict(BaseDict):
2510
+ """The ``TopDict`` represents the top-level dictionary holding font
2511
+ information. CFF2 tables contain a restricted set of top-level entries
2512
+ as described `here <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>`_,
2513
+ but CFF tables may contain a wider range of information. This information
2514
+ can be accessed through attributes or through the dictionary returned
2515
+ through the ``rawDict`` property:
2516
+
2517
+ .. code:: python
2518
+
2519
+ font = tt["CFF "].cff[0]
2520
+ font.FamilyName
2521
+ # 'Linux Libertine O'
2522
+ font.rawDict["FamilyName"]
2523
+ # 'Linux Libertine O'
2524
+
2525
+ More information is available in the CFF file's private dictionary, accessed
2526
+ via the ``Private`` property:
2527
+
2528
+ .. code:: python
2529
+
2530
+ tt["CFF "].cff[0].Private.BlueValues
2531
+ # [-15, 0, 515, 515, 666, 666]
2532
+
2533
+ """
2534
+
2535
+ defaults = buildDefaults(topDictOperators)
2536
+ converters = buildConverters(topDictOperators)
2537
+ compilerClass = TopDictCompiler
2538
+ order = buildOrder(topDictOperators)
2539
+ decompilerClass = TopDictDecompiler
2540
+
2541
+ def __init__(
2542
+ self,
2543
+ strings=None,
2544
+ file=None,
2545
+ offset=None,
2546
+ GlobalSubrs=None,
2547
+ cff2GetGlyphOrder=None,
2548
+ isCFF2=None,
2549
+ ):
2550
+ super(TopDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2551
+ self.cff2GetGlyphOrder = cff2GetGlyphOrder
2552
+ self.GlobalSubrs = GlobalSubrs
2553
+ if isCFF2:
2554
+ self.defaults = buildDefaults(topDictOperators2)
2555
+ self.charset = cff2GetGlyphOrder()
2556
+ self.order = buildOrder(topDictOperators2)
2557
+ else:
2558
+ self.defaults = buildDefaults(topDictOperators)
2559
+ self.order = buildOrder(topDictOperators)
2560
+
2561
+ def getGlyphOrder(self):
2562
+ """Returns a list of glyph names in the CFF font."""
2563
+ return self.charset
2564
+
2565
+ def postDecompile(self):
2566
+ offset = self.rawDict.get("CharStrings")
2567
+ if offset is None:
2568
+ return
2569
+ # get the number of glyphs beforehand.
2570
+ self.file.seek(offset)
2571
+ if self._isCFF2:
2572
+ self.numGlyphs = readCard32(self.file)
2573
+ else:
2574
+ self.numGlyphs = readCard16(self.file)
2575
+
2576
+ def toXML(self, xmlWriter):
2577
+ if hasattr(self, "CharStrings"):
2578
+ self.decompileAllCharStrings()
2579
+ if hasattr(self, "ROS"):
2580
+ self.skipNames = ["Encoding"]
2581
+ if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
2582
+ # these values have default values, but I only want them to show up
2583
+ # in CID fonts.
2584
+ self.skipNames = [
2585
+ "CIDFontVersion",
2586
+ "CIDFontRevision",
2587
+ "CIDFontType",
2588
+ "CIDCount",
2589
+ ]
2590
+ BaseDict.toXML(self, xmlWriter)
2591
+
2592
+ def decompileAllCharStrings(self):
2593
+ # Make sure that all the Private Dicts have been instantiated.
2594
+ for i, charString in enumerate(self.CharStrings.values()):
2595
+ try:
2596
+ charString.decompile()
2597
+ except:
2598
+ log.error("Error in charstring %s", i)
2599
+ raise
2600
+
2601
+ def recalcFontBBox(self):
2602
+ fontBBox = None
2603
+ for charString in self.CharStrings.values():
2604
+ bounds = charString.calcBounds(self.CharStrings)
2605
+ if bounds is not None:
2606
+ if fontBBox is not None:
2607
+ fontBBox = unionRect(fontBBox, bounds)
2608
+ else:
2609
+ fontBBox = bounds
2610
+
2611
+ if fontBBox is None:
2612
+ self.FontBBox = self.defaults["FontBBox"][:]
2613
+ else:
2614
+ self.FontBBox = list(intRect(fontBBox))
2615
+
2616
+
2617
+ class FontDict(BaseDict):
2618
+ #
2619
+ # Since fonttools used to pass a lot of fields that are not relevant in the FDArray
2620
+ # FontDict, there are 'ttx' files in the wild that contain all these. These got in
2621
+ # the ttx files because fonttools writes explicit values for all the TopDict default
2622
+ # values. These are not actually illegal in the context of an FDArray FontDict - you
2623
+ # can legally, per spec, put any arbitrary key/value pair in a FontDict - but are
2624
+ # useless since current major company CFF interpreters ignore anything but the set
2625
+ # listed in this file. So, we just silently skip them. An exception is Weight: this
2626
+ # is not used by any interpreter, but some foundries have asked that this be
2627
+ # supported in FDArray FontDicts just to preserve information about the design when
2628
+ # the font is being inspected.
2629
+ #
2630
+ # On top of that, there are fonts out there that contain such useless FontDict values.
2631
+ #
2632
+ # By subclassing TopDict, we *allow* all key/values from TopDict, both when reading
2633
+ # from binary or when reading from XML, but by overriding `order` with a limited
2634
+ # list of names, we ensure that only the useful names ever get exported to XML and
2635
+ # ever get compiled into the binary font.
2636
+ #
2637
+ # We override compilerClass so we can warn about "useless" key/value pairs, either
2638
+ # from the original binary font or from TTX input.
2639
+ #
2640
+ # See:
2641
+ # - https://github.com/fonttools/fonttools/issues/740
2642
+ # - https://github.com/fonttools/fonttools/issues/601
2643
+ # - https://github.com/adobe-type-tools/afdko/issues/137
2644
+ #
2645
+ defaults = {}
2646
+ converters = buildConverters(topDictOperators)
2647
+ compilerClass = FontDictCompiler
2648
+ orderCFF = ["FontName", "FontMatrix", "Weight", "Private"]
2649
+ orderCFF2 = ["Private"]
2650
+ decompilerClass = TopDictDecompiler
2651
+
2652
+ def __init__(
2653
+ self,
2654
+ strings=None,
2655
+ file=None,
2656
+ offset=None,
2657
+ GlobalSubrs=None,
2658
+ isCFF2=None,
2659
+ vstore=None,
2660
+ ):
2661
+ super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2662
+ self.vstore = vstore
2663
+ self.setCFF2(isCFF2)
2664
+
2665
+ def setCFF2(self, isCFF2):
2666
+ # isCFF2 may be None.
2667
+ if isCFF2:
2668
+ self.order = self.orderCFF2
2669
+ self._isCFF2 = True
2670
+ else:
2671
+ self.order = self.orderCFF
2672
+ self._isCFF2 = False
2673
+
2674
+
2675
+ class PrivateDict(BaseDict):
2676
+ defaults = buildDefaults(privateDictOperators)
2677
+ converters = buildConverters(privateDictOperators)
2678
+ order = buildOrder(privateDictOperators)
2679
+ decompilerClass = PrivateDictDecompiler
2680
+ compilerClass = PrivateDictCompiler
2681
+
2682
+ def __init__(self, strings=None, file=None, offset=None, isCFF2=None, vstore=None):
2683
+ super(PrivateDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2684
+ self.vstore = vstore
2685
+ if isCFF2:
2686
+ self.defaults = buildDefaults(privateDictOperators2)
2687
+ self.order = buildOrder(privateDictOperators2)
2688
+ # Provide dummy values. This avoids needing to provide
2689
+ # an isCFF2 state in a lot of places.
2690
+ self.nominalWidthX = self.defaultWidthX = None
2691
+ self._isCFF2 = True
2692
+ else:
2693
+ self.defaults = buildDefaults(privateDictOperators)
2694
+ self.order = buildOrder(privateDictOperators)
2695
+ self._isCFF2 = False
2696
+
2697
+ @property
2698
+ def in_cff2(self):
2699
+ return self._isCFF2
2700
+
2701
+ def getNumRegions(self, vi=None): # called from misc/psCharStrings.py
2702
+ # if getNumRegions is being called, we can assume that VarStore exists.
2703
+ if vi is None:
2704
+ if hasattr(self, "vsindex"):
2705
+ vi = self.vsindex
2706
+ else:
2707
+ vi = 0
2708
+ numRegions = self.vstore.getNumRegions(vi)
2709
+ return numRegions
2710
+
2711
+
2712
+ class IndexedStrings(object):
2713
+ """SID -> string mapping."""
2714
+
2715
+ def __init__(self, file=None):
2716
+ if file is None:
2717
+ strings = []
2718
+ else:
2719
+ strings = [tostr(s, encoding="latin1") for s in Index(file, isCFF2=False)]
2720
+ self.strings = strings
2721
+
2722
+ def getCompiler(self):
2723
+ return IndexedStringsCompiler(self, None, self, isCFF2=False)
2724
+
2725
+ def __len__(self):
2726
+ return len(self.strings)
2727
+
2728
+ def __getitem__(self, SID):
2729
+ if SID < cffStandardStringCount:
2730
+ return cffStandardStrings[SID]
2731
+ else:
2732
+ return self.strings[SID - cffStandardStringCount]
2733
+
2734
+ def getSID(self, s):
2735
+ if not hasattr(self, "stringMapping"):
2736
+ self.buildStringMapping()
2737
+ s = tostr(s, encoding="latin1")
2738
+ if s in cffStandardStringMapping:
2739
+ SID = cffStandardStringMapping[s]
2740
+ elif s in self.stringMapping:
2741
+ SID = self.stringMapping[s]
2742
+ else:
2743
+ SID = len(self.strings) + cffStandardStringCount
2744
+ self.strings.append(s)
2745
+ self.stringMapping[s] = SID
2746
+ return SID
2747
+
2748
+ def getStrings(self):
2749
+ return self.strings
2750
+
2751
+ def buildStringMapping(self):
2752
+ self.stringMapping = {}
2753
+ for index in range(len(self.strings)):
2754
+ self.stringMapping[self.strings[index]] = index + cffStandardStringCount
2755
+
2756
+
2757
+ # The 391 Standard Strings as used in the CFF format.
2758
+ # from Adobe Technical None #5176, version 1.0, 18 March 1998
2759
+
2760
+ cffStandardStrings = [
2761
+ ".notdef",
2762
+ "space",
2763
+ "exclam",
2764
+ "quotedbl",
2765
+ "numbersign",
2766
+ "dollar",
2767
+ "percent",
2768
+ "ampersand",
2769
+ "quoteright",
2770
+ "parenleft",
2771
+ "parenright",
2772
+ "asterisk",
2773
+ "plus",
2774
+ "comma",
2775
+ "hyphen",
2776
+ "period",
2777
+ "slash",
2778
+ "zero",
2779
+ "one",
2780
+ "two",
2781
+ "three",
2782
+ "four",
2783
+ "five",
2784
+ "six",
2785
+ "seven",
2786
+ "eight",
2787
+ "nine",
2788
+ "colon",
2789
+ "semicolon",
2790
+ "less",
2791
+ "equal",
2792
+ "greater",
2793
+ "question",
2794
+ "at",
2795
+ "A",
2796
+ "B",
2797
+ "C",
2798
+ "D",
2799
+ "E",
2800
+ "F",
2801
+ "G",
2802
+ "H",
2803
+ "I",
2804
+ "J",
2805
+ "K",
2806
+ "L",
2807
+ "M",
2808
+ "N",
2809
+ "O",
2810
+ "P",
2811
+ "Q",
2812
+ "R",
2813
+ "S",
2814
+ "T",
2815
+ "U",
2816
+ "V",
2817
+ "W",
2818
+ "X",
2819
+ "Y",
2820
+ "Z",
2821
+ "bracketleft",
2822
+ "backslash",
2823
+ "bracketright",
2824
+ "asciicircum",
2825
+ "underscore",
2826
+ "quoteleft",
2827
+ "a",
2828
+ "b",
2829
+ "c",
2830
+ "d",
2831
+ "e",
2832
+ "f",
2833
+ "g",
2834
+ "h",
2835
+ "i",
2836
+ "j",
2837
+ "k",
2838
+ "l",
2839
+ "m",
2840
+ "n",
2841
+ "o",
2842
+ "p",
2843
+ "q",
2844
+ "r",
2845
+ "s",
2846
+ "t",
2847
+ "u",
2848
+ "v",
2849
+ "w",
2850
+ "x",
2851
+ "y",
2852
+ "z",
2853
+ "braceleft",
2854
+ "bar",
2855
+ "braceright",
2856
+ "asciitilde",
2857
+ "exclamdown",
2858
+ "cent",
2859
+ "sterling",
2860
+ "fraction",
2861
+ "yen",
2862
+ "florin",
2863
+ "section",
2864
+ "currency",
2865
+ "quotesingle",
2866
+ "quotedblleft",
2867
+ "guillemotleft",
2868
+ "guilsinglleft",
2869
+ "guilsinglright",
2870
+ "fi",
2871
+ "fl",
2872
+ "endash",
2873
+ "dagger",
2874
+ "daggerdbl",
2875
+ "periodcentered",
2876
+ "paragraph",
2877
+ "bullet",
2878
+ "quotesinglbase",
2879
+ "quotedblbase",
2880
+ "quotedblright",
2881
+ "guillemotright",
2882
+ "ellipsis",
2883
+ "perthousand",
2884
+ "questiondown",
2885
+ "grave",
2886
+ "acute",
2887
+ "circumflex",
2888
+ "tilde",
2889
+ "macron",
2890
+ "breve",
2891
+ "dotaccent",
2892
+ "dieresis",
2893
+ "ring",
2894
+ "cedilla",
2895
+ "hungarumlaut",
2896
+ "ogonek",
2897
+ "caron",
2898
+ "emdash",
2899
+ "AE",
2900
+ "ordfeminine",
2901
+ "Lslash",
2902
+ "Oslash",
2903
+ "OE",
2904
+ "ordmasculine",
2905
+ "ae",
2906
+ "dotlessi",
2907
+ "lslash",
2908
+ "oslash",
2909
+ "oe",
2910
+ "germandbls",
2911
+ "onesuperior",
2912
+ "logicalnot",
2913
+ "mu",
2914
+ "trademark",
2915
+ "Eth",
2916
+ "onehalf",
2917
+ "plusminus",
2918
+ "Thorn",
2919
+ "onequarter",
2920
+ "divide",
2921
+ "brokenbar",
2922
+ "degree",
2923
+ "thorn",
2924
+ "threequarters",
2925
+ "twosuperior",
2926
+ "registered",
2927
+ "minus",
2928
+ "eth",
2929
+ "multiply",
2930
+ "threesuperior",
2931
+ "copyright",
2932
+ "Aacute",
2933
+ "Acircumflex",
2934
+ "Adieresis",
2935
+ "Agrave",
2936
+ "Aring",
2937
+ "Atilde",
2938
+ "Ccedilla",
2939
+ "Eacute",
2940
+ "Ecircumflex",
2941
+ "Edieresis",
2942
+ "Egrave",
2943
+ "Iacute",
2944
+ "Icircumflex",
2945
+ "Idieresis",
2946
+ "Igrave",
2947
+ "Ntilde",
2948
+ "Oacute",
2949
+ "Ocircumflex",
2950
+ "Odieresis",
2951
+ "Ograve",
2952
+ "Otilde",
2953
+ "Scaron",
2954
+ "Uacute",
2955
+ "Ucircumflex",
2956
+ "Udieresis",
2957
+ "Ugrave",
2958
+ "Yacute",
2959
+ "Ydieresis",
2960
+ "Zcaron",
2961
+ "aacute",
2962
+ "acircumflex",
2963
+ "adieresis",
2964
+ "agrave",
2965
+ "aring",
2966
+ "atilde",
2967
+ "ccedilla",
2968
+ "eacute",
2969
+ "ecircumflex",
2970
+ "edieresis",
2971
+ "egrave",
2972
+ "iacute",
2973
+ "icircumflex",
2974
+ "idieresis",
2975
+ "igrave",
2976
+ "ntilde",
2977
+ "oacute",
2978
+ "ocircumflex",
2979
+ "odieresis",
2980
+ "ograve",
2981
+ "otilde",
2982
+ "scaron",
2983
+ "uacute",
2984
+ "ucircumflex",
2985
+ "udieresis",
2986
+ "ugrave",
2987
+ "yacute",
2988
+ "ydieresis",
2989
+ "zcaron",
2990
+ "exclamsmall",
2991
+ "Hungarumlautsmall",
2992
+ "dollaroldstyle",
2993
+ "dollarsuperior",
2994
+ "ampersandsmall",
2995
+ "Acutesmall",
2996
+ "parenleftsuperior",
2997
+ "parenrightsuperior",
2998
+ "twodotenleader",
2999
+ "onedotenleader",
3000
+ "zerooldstyle",
3001
+ "oneoldstyle",
3002
+ "twooldstyle",
3003
+ "threeoldstyle",
3004
+ "fouroldstyle",
3005
+ "fiveoldstyle",
3006
+ "sixoldstyle",
3007
+ "sevenoldstyle",
3008
+ "eightoldstyle",
3009
+ "nineoldstyle",
3010
+ "commasuperior",
3011
+ "threequartersemdash",
3012
+ "periodsuperior",
3013
+ "questionsmall",
3014
+ "asuperior",
3015
+ "bsuperior",
3016
+ "centsuperior",
3017
+ "dsuperior",
3018
+ "esuperior",
3019
+ "isuperior",
3020
+ "lsuperior",
3021
+ "msuperior",
3022
+ "nsuperior",
3023
+ "osuperior",
3024
+ "rsuperior",
3025
+ "ssuperior",
3026
+ "tsuperior",
3027
+ "ff",
3028
+ "ffi",
3029
+ "ffl",
3030
+ "parenleftinferior",
3031
+ "parenrightinferior",
3032
+ "Circumflexsmall",
3033
+ "hyphensuperior",
3034
+ "Gravesmall",
3035
+ "Asmall",
3036
+ "Bsmall",
3037
+ "Csmall",
3038
+ "Dsmall",
3039
+ "Esmall",
3040
+ "Fsmall",
3041
+ "Gsmall",
3042
+ "Hsmall",
3043
+ "Ismall",
3044
+ "Jsmall",
3045
+ "Ksmall",
3046
+ "Lsmall",
3047
+ "Msmall",
3048
+ "Nsmall",
3049
+ "Osmall",
3050
+ "Psmall",
3051
+ "Qsmall",
3052
+ "Rsmall",
3053
+ "Ssmall",
3054
+ "Tsmall",
3055
+ "Usmall",
3056
+ "Vsmall",
3057
+ "Wsmall",
3058
+ "Xsmall",
3059
+ "Ysmall",
3060
+ "Zsmall",
3061
+ "colonmonetary",
3062
+ "onefitted",
3063
+ "rupiah",
3064
+ "Tildesmall",
3065
+ "exclamdownsmall",
3066
+ "centoldstyle",
3067
+ "Lslashsmall",
3068
+ "Scaronsmall",
3069
+ "Zcaronsmall",
3070
+ "Dieresissmall",
3071
+ "Brevesmall",
3072
+ "Caronsmall",
3073
+ "Dotaccentsmall",
3074
+ "Macronsmall",
3075
+ "figuredash",
3076
+ "hypheninferior",
3077
+ "Ogoneksmall",
3078
+ "Ringsmall",
3079
+ "Cedillasmall",
3080
+ "questiondownsmall",
3081
+ "oneeighth",
3082
+ "threeeighths",
3083
+ "fiveeighths",
3084
+ "seveneighths",
3085
+ "onethird",
3086
+ "twothirds",
3087
+ "zerosuperior",
3088
+ "foursuperior",
3089
+ "fivesuperior",
3090
+ "sixsuperior",
3091
+ "sevensuperior",
3092
+ "eightsuperior",
3093
+ "ninesuperior",
3094
+ "zeroinferior",
3095
+ "oneinferior",
3096
+ "twoinferior",
3097
+ "threeinferior",
3098
+ "fourinferior",
3099
+ "fiveinferior",
3100
+ "sixinferior",
3101
+ "seveninferior",
3102
+ "eightinferior",
3103
+ "nineinferior",
3104
+ "centinferior",
3105
+ "dollarinferior",
3106
+ "periodinferior",
3107
+ "commainferior",
3108
+ "Agravesmall",
3109
+ "Aacutesmall",
3110
+ "Acircumflexsmall",
3111
+ "Atildesmall",
3112
+ "Adieresissmall",
3113
+ "Aringsmall",
3114
+ "AEsmall",
3115
+ "Ccedillasmall",
3116
+ "Egravesmall",
3117
+ "Eacutesmall",
3118
+ "Ecircumflexsmall",
3119
+ "Edieresissmall",
3120
+ "Igravesmall",
3121
+ "Iacutesmall",
3122
+ "Icircumflexsmall",
3123
+ "Idieresissmall",
3124
+ "Ethsmall",
3125
+ "Ntildesmall",
3126
+ "Ogravesmall",
3127
+ "Oacutesmall",
3128
+ "Ocircumflexsmall",
3129
+ "Otildesmall",
3130
+ "Odieresissmall",
3131
+ "OEsmall",
3132
+ "Oslashsmall",
3133
+ "Ugravesmall",
3134
+ "Uacutesmall",
3135
+ "Ucircumflexsmall",
3136
+ "Udieresissmall",
3137
+ "Yacutesmall",
3138
+ "Thornsmall",
3139
+ "Ydieresissmall",
3140
+ "001.000",
3141
+ "001.001",
3142
+ "001.002",
3143
+ "001.003",
3144
+ "Black",
3145
+ "Bold",
3146
+ "Book",
3147
+ "Light",
3148
+ "Medium",
3149
+ "Regular",
3150
+ "Roman",
3151
+ "Semibold",
3152
+ ]
3153
+
3154
+ cffStandardStringCount = 391
3155
+ assert len(cffStandardStrings) == cffStandardStringCount
3156
+ # build reverse mapping
3157
+ cffStandardStringMapping = {}
3158
+ for _i in range(cffStandardStringCount):
3159
+ cffStandardStringMapping[cffStandardStrings[_i]] = _i
3160
+
3161
+ cffISOAdobeStrings = [
3162
+ ".notdef",
3163
+ "space",
3164
+ "exclam",
3165
+ "quotedbl",
3166
+ "numbersign",
3167
+ "dollar",
3168
+ "percent",
3169
+ "ampersand",
3170
+ "quoteright",
3171
+ "parenleft",
3172
+ "parenright",
3173
+ "asterisk",
3174
+ "plus",
3175
+ "comma",
3176
+ "hyphen",
3177
+ "period",
3178
+ "slash",
3179
+ "zero",
3180
+ "one",
3181
+ "two",
3182
+ "three",
3183
+ "four",
3184
+ "five",
3185
+ "six",
3186
+ "seven",
3187
+ "eight",
3188
+ "nine",
3189
+ "colon",
3190
+ "semicolon",
3191
+ "less",
3192
+ "equal",
3193
+ "greater",
3194
+ "question",
3195
+ "at",
3196
+ "A",
3197
+ "B",
3198
+ "C",
3199
+ "D",
3200
+ "E",
3201
+ "F",
3202
+ "G",
3203
+ "H",
3204
+ "I",
3205
+ "J",
3206
+ "K",
3207
+ "L",
3208
+ "M",
3209
+ "N",
3210
+ "O",
3211
+ "P",
3212
+ "Q",
3213
+ "R",
3214
+ "S",
3215
+ "T",
3216
+ "U",
3217
+ "V",
3218
+ "W",
3219
+ "X",
3220
+ "Y",
3221
+ "Z",
3222
+ "bracketleft",
3223
+ "backslash",
3224
+ "bracketright",
3225
+ "asciicircum",
3226
+ "underscore",
3227
+ "quoteleft",
3228
+ "a",
3229
+ "b",
3230
+ "c",
3231
+ "d",
3232
+ "e",
3233
+ "f",
3234
+ "g",
3235
+ "h",
3236
+ "i",
3237
+ "j",
3238
+ "k",
3239
+ "l",
3240
+ "m",
3241
+ "n",
3242
+ "o",
3243
+ "p",
3244
+ "q",
3245
+ "r",
3246
+ "s",
3247
+ "t",
3248
+ "u",
3249
+ "v",
3250
+ "w",
3251
+ "x",
3252
+ "y",
3253
+ "z",
3254
+ "braceleft",
3255
+ "bar",
3256
+ "braceright",
3257
+ "asciitilde",
3258
+ "exclamdown",
3259
+ "cent",
3260
+ "sterling",
3261
+ "fraction",
3262
+ "yen",
3263
+ "florin",
3264
+ "section",
3265
+ "currency",
3266
+ "quotesingle",
3267
+ "quotedblleft",
3268
+ "guillemotleft",
3269
+ "guilsinglleft",
3270
+ "guilsinglright",
3271
+ "fi",
3272
+ "fl",
3273
+ "endash",
3274
+ "dagger",
3275
+ "daggerdbl",
3276
+ "periodcentered",
3277
+ "paragraph",
3278
+ "bullet",
3279
+ "quotesinglbase",
3280
+ "quotedblbase",
3281
+ "quotedblright",
3282
+ "guillemotright",
3283
+ "ellipsis",
3284
+ "perthousand",
3285
+ "questiondown",
3286
+ "grave",
3287
+ "acute",
3288
+ "circumflex",
3289
+ "tilde",
3290
+ "macron",
3291
+ "breve",
3292
+ "dotaccent",
3293
+ "dieresis",
3294
+ "ring",
3295
+ "cedilla",
3296
+ "hungarumlaut",
3297
+ "ogonek",
3298
+ "caron",
3299
+ "emdash",
3300
+ "AE",
3301
+ "ordfeminine",
3302
+ "Lslash",
3303
+ "Oslash",
3304
+ "OE",
3305
+ "ordmasculine",
3306
+ "ae",
3307
+ "dotlessi",
3308
+ "lslash",
3309
+ "oslash",
3310
+ "oe",
3311
+ "germandbls",
3312
+ "onesuperior",
3313
+ "logicalnot",
3314
+ "mu",
3315
+ "trademark",
3316
+ "Eth",
3317
+ "onehalf",
3318
+ "plusminus",
3319
+ "Thorn",
3320
+ "onequarter",
3321
+ "divide",
3322
+ "brokenbar",
3323
+ "degree",
3324
+ "thorn",
3325
+ "threequarters",
3326
+ "twosuperior",
3327
+ "registered",
3328
+ "minus",
3329
+ "eth",
3330
+ "multiply",
3331
+ "threesuperior",
3332
+ "copyright",
3333
+ "Aacute",
3334
+ "Acircumflex",
3335
+ "Adieresis",
3336
+ "Agrave",
3337
+ "Aring",
3338
+ "Atilde",
3339
+ "Ccedilla",
3340
+ "Eacute",
3341
+ "Ecircumflex",
3342
+ "Edieresis",
3343
+ "Egrave",
3344
+ "Iacute",
3345
+ "Icircumflex",
3346
+ "Idieresis",
3347
+ "Igrave",
3348
+ "Ntilde",
3349
+ "Oacute",
3350
+ "Ocircumflex",
3351
+ "Odieresis",
3352
+ "Ograve",
3353
+ "Otilde",
3354
+ "Scaron",
3355
+ "Uacute",
3356
+ "Ucircumflex",
3357
+ "Udieresis",
3358
+ "Ugrave",
3359
+ "Yacute",
3360
+ "Ydieresis",
3361
+ "Zcaron",
3362
+ "aacute",
3363
+ "acircumflex",
3364
+ "adieresis",
3365
+ "agrave",
3366
+ "aring",
3367
+ "atilde",
3368
+ "ccedilla",
3369
+ "eacute",
3370
+ "ecircumflex",
3371
+ "edieresis",
3372
+ "egrave",
3373
+ "iacute",
3374
+ "icircumflex",
3375
+ "idieresis",
3376
+ "igrave",
3377
+ "ntilde",
3378
+ "oacute",
3379
+ "ocircumflex",
3380
+ "odieresis",
3381
+ "ograve",
3382
+ "otilde",
3383
+ "scaron",
3384
+ "uacute",
3385
+ "ucircumflex",
3386
+ "udieresis",
3387
+ "ugrave",
3388
+ "yacute",
3389
+ "ydieresis",
3390
+ "zcaron",
3391
+ ]
3392
+
3393
+ cffISOAdobeStringCount = 229
3394
+ assert len(cffISOAdobeStrings) == cffISOAdobeStringCount
3395
+
3396
+ cffIExpertStrings = [
3397
+ ".notdef",
3398
+ "space",
3399
+ "exclamsmall",
3400
+ "Hungarumlautsmall",
3401
+ "dollaroldstyle",
3402
+ "dollarsuperior",
3403
+ "ampersandsmall",
3404
+ "Acutesmall",
3405
+ "parenleftsuperior",
3406
+ "parenrightsuperior",
3407
+ "twodotenleader",
3408
+ "onedotenleader",
3409
+ "comma",
3410
+ "hyphen",
3411
+ "period",
3412
+ "fraction",
3413
+ "zerooldstyle",
3414
+ "oneoldstyle",
3415
+ "twooldstyle",
3416
+ "threeoldstyle",
3417
+ "fouroldstyle",
3418
+ "fiveoldstyle",
3419
+ "sixoldstyle",
3420
+ "sevenoldstyle",
3421
+ "eightoldstyle",
3422
+ "nineoldstyle",
3423
+ "colon",
3424
+ "semicolon",
3425
+ "commasuperior",
3426
+ "threequartersemdash",
3427
+ "periodsuperior",
3428
+ "questionsmall",
3429
+ "asuperior",
3430
+ "bsuperior",
3431
+ "centsuperior",
3432
+ "dsuperior",
3433
+ "esuperior",
3434
+ "isuperior",
3435
+ "lsuperior",
3436
+ "msuperior",
3437
+ "nsuperior",
3438
+ "osuperior",
3439
+ "rsuperior",
3440
+ "ssuperior",
3441
+ "tsuperior",
3442
+ "ff",
3443
+ "fi",
3444
+ "fl",
3445
+ "ffi",
3446
+ "ffl",
3447
+ "parenleftinferior",
3448
+ "parenrightinferior",
3449
+ "Circumflexsmall",
3450
+ "hyphensuperior",
3451
+ "Gravesmall",
3452
+ "Asmall",
3453
+ "Bsmall",
3454
+ "Csmall",
3455
+ "Dsmall",
3456
+ "Esmall",
3457
+ "Fsmall",
3458
+ "Gsmall",
3459
+ "Hsmall",
3460
+ "Ismall",
3461
+ "Jsmall",
3462
+ "Ksmall",
3463
+ "Lsmall",
3464
+ "Msmall",
3465
+ "Nsmall",
3466
+ "Osmall",
3467
+ "Psmall",
3468
+ "Qsmall",
3469
+ "Rsmall",
3470
+ "Ssmall",
3471
+ "Tsmall",
3472
+ "Usmall",
3473
+ "Vsmall",
3474
+ "Wsmall",
3475
+ "Xsmall",
3476
+ "Ysmall",
3477
+ "Zsmall",
3478
+ "colonmonetary",
3479
+ "onefitted",
3480
+ "rupiah",
3481
+ "Tildesmall",
3482
+ "exclamdownsmall",
3483
+ "centoldstyle",
3484
+ "Lslashsmall",
3485
+ "Scaronsmall",
3486
+ "Zcaronsmall",
3487
+ "Dieresissmall",
3488
+ "Brevesmall",
3489
+ "Caronsmall",
3490
+ "Dotaccentsmall",
3491
+ "Macronsmall",
3492
+ "figuredash",
3493
+ "hypheninferior",
3494
+ "Ogoneksmall",
3495
+ "Ringsmall",
3496
+ "Cedillasmall",
3497
+ "onequarter",
3498
+ "onehalf",
3499
+ "threequarters",
3500
+ "questiondownsmall",
3501
+ "oneeighth",
3502
+ "threeeighths",
3503
+ "fiveeighths",
3504
+ "seveneighths",
3505
+ "onethird",
3506
+ "twothirds",
3507
+ "zerosuperior",
3508
+ "onesuperior",
3509
+ "twosuperior",
3510
+ "threesuperior",
3511
+ "foursuperior",
3512
+ "fivesuperior",
3513
+ "sixsuperior",
3514
+ "sevensuperior",
3515
+ "eightsuperior",
3516
+ "ninesuperior",
3517
+ "zeroinferior",
3518
+ "oneinferior",
3519
+ "twoinferior",
3520
+ "threeinferior",
3521
+ "fourinferior",
3522
+ "fiveinferior",
3523
+ "sixinferior",
3524
+ "seveninferior",
3525
+ "eightinferior",
3526
+ "nineinferior",
3527
+ "centinferior",
3528
+ "dollarinferior",
3529
+ "periodinferior",
3530
+ "commainferior",
3531
+ "Agravesmall",
3532
+ "Aacutesmall",
3533
+ "Acircumflexsmall",
3534
+ "Atildesmall",
3535
+ "Adieresissmall",
3536
+ "Aringsmall",
3537
+ "AEsmall",
3538
+ "Ccedillasmall",
3539
+ "Egravesmall",
3540
+ "Eacutesmall",
3541
+ "Ecircumflexsmall",
3542
+ "Edieresissmall",
3543
+ "Igravesmall",
3544
+ "Iacutesmall",
3545
+ "Icircumflexsmall",
3546
+ "Idieresissmall",
3547
+ "Ethsmall",
3548
+ "Ntildesmall",
3549
+ "Ogravesmall",
3550
+ "Oacutesmall",
3551
+ "Ocircumflexsmall",
3552
+ "Otildesmall",
3553
+ "Odieresissmall",
3554
+ "OEsmall",
3555
+ "Oslashsmall",
3556
+ "Ugravesmall",
3557
+ "Uacutesmall",
3558
+ "Ucircumflexsmall",
3559
+ "Udieresissmall",
3560
+ "Yacutesmall",
3561
+ "Thornsmall",
3562
+ "Ydieresissmall",
3563
+ ]
3564
+
3565
+ cffExpertStringCount = 166
3566
+ assert len(cffIExpertStrings) == cffExpertStringCount
3567
+
3568
+ cffExpertSubsetStrings = [
3569
+ ".notdef",
3570
+ "space",
3571
+ "dollaroldstyle",
3572
+ "dollarsuperior",
3573
+ "parenleftsuperior",
3574
+ "parenrightsuperior",
3575
+ "twodotenleader",
3576
+ "onedotenleader",
3577
+ "comma",
3578
+ "hyphen",
3579
+ "period",
3580
+ "fraction",
3581
+ "zerooldstyle",
3582
+ "oneoldstyle",
3583
+ "twooldstyle",
3584
+ "threeoldstyle",
3585
+ "fouroldstyle",
3586
+ "fiveoldstyle",
3587
+ "sixoldstyle",
3588
+ "sevenoldstyle",
3589
+ "eightoldstyle",
3590
+ "nineoldstyle",
3591
+ "colon",
3592
+ "semicolon",
3593
+ "commasuperior",
3594
+ "threequartersemdash",
3595
+ "periodsuperior",
3596
+ "asuperior",
3597
+ "bsuperior",
3598
+ "centsuperior",
3599
+ "dsuperior",
3600
+ "esuperior",
3601
+ "isuperior",
3602
+ "lsuperior",
3603
+ "msuperior",
3604
+ "nsuperior",
3605
+ "osuperior",
3606
+ "rsuperior",
3607
+ "ssuperior",
3608
+ "tsuperior",
3609
+ "ff",
3610
+ "fi",
3611
+ "fl",
3612
+ "ffi",
3613
+ "ffl",
3614
+ "parenleftinferior",
3615
+ "parenrightinferior",
3616
+ "hyphensuperior",
3617
+ "colonmonetary",
3618
+ "onefitted",
3619
+ "rupiah",
3620
+ "centoldstyle",
3621
+ "figuredash",
3622
+ "hypheninferior",
3623
+ "onequarter",
3624
+ "onehalf",
3625
+ "threequarters",
3626
+ "oneeighth",
3627
+ "threeeighths",
3628
+ "fiveeighths",
3629
+ "seveneighths",
3630
+ "onethird",
3631
+ "twothirds",
3632
+ "zerosuperior",
3633
+ "onesuperior",
3634
+ "twosuperior",
3635
+ "threesuperior",
3636
+ "foursuperior",
3637
+ "fivesuperior",
3638
+ "sixsuperior",
3639
+ "sevensuperior",
3640
+ "eightsuperior",
3641
+ "ninesuperior",
3642
+ "zeroinferior",
3643
+ "oneinferior",
3644
+ "twoinferior",
3645
+ "threeinferior",
3646
+ "fourinferior",
3647
+ "fiveinferior",
3648
+ "sixinferior",
3649
+ "seveninferior",
3650
+ "eightinferior",
3651
+ "nineinferior",
3652
+ "centinferior",
3653
+ "dollarinferior",
3654
+ "periodinferior",
3655
+ "commainferior",
3656
+ ]
3657
+
3658
+ cffExpertSubsetStringCount = 87
3659
+ assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount