fonttools 4.60.2__cp311-cp311-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. fontTools/__init__.py +8 -0
  2. fontTools/__main__.py +35 -0
  3. fontTools/afmLib.py +439 -0
  4. fontTools/agl.py +5233 -0
  5. fontTools/annotations.py +30 -0
  6. fontTools/cffLib/CFF2ToCFF.py +258 -0
  7. fontTools/cffLib/CFFToCFF2.py +305 -0
  8. fontTools/cffLib/__init__.py +3694 -0
  9. fontTools/cffLib/specializer.py +927 -0
  10. fontTools/cffLib/transforms.py +495 -0
  11. fontTools/cffLib/width.py +210 -0
  12. fontTools/colorLib/__init__.py +0 -0
  13. fontTools/colorLib/builder.py +664 -0
  14. fontTools/colorLib/errors.py +2 -0
  15. fontTools/colorLib/geometry.py +143 -0
  16. fontTools/colorLib/table_builder.py +223 -0
  17. fontTools/colorLib/unbuilder.py +81 -0
  18. fontTools/config/__init__.py +90 -0
  19. fontTools/cu2qu/__init__.py +15 -0
  20. fontTools/cu2qu/__main__.py +6 -0
  21. fontTools/cu2qu/benchmark.py +54 -0
  22. fontTools/cu2qu/cli.py +198 -0
  23. fontTools/cu2qu/cu2qu.c +15817 -0
  24. fontTools/cu2qu/cu2qu.cp311-win32.pyd +0 -0
  25. fontTools/cu2qu/cu2qu.py +563 -0
  26. fontTools/cu2qu/errors.py +77 -0
  27. fontTools/cu2qu/ufo.py +363 -0
  28. fontTools/designspaceLib/__init__.py +3343 -0
  29. fontTools/designspaceLib/__main__.py +6 -0
  30. fontTools/designspaceLib/split.py +475 -0
  31. fontTools/designspaceLib/statNames.py +260 -0
  32. fontTools/designspaceLib/types.py +147 -0
  33. fontTools/encodings/MacRoman.py +258 -0
  34. fontTools/encodings/StandardEncoding.py +258 -0
  35. fontTools/encodings/__init__.py +1 -0
  36. fontTools/encodings/codecs.py +135 -0
  37. fontTools/feaLib/__init__.py +4 -0
  38. fontTools/feaLib/__main__.py +78 -0
  39. fontTools/feaLib/ast.py +2143 -0
  40. fontTools/feaLib/builder.py +1814 -0
  41. fontTools/feaLib/error.py +22 -0
  42. fontTools/feaLib/lexer.c +17029 -0
  43. fontTools/feaLib/lexer.cp311-win32.pyd +0 -0
  44. fontTools/feaLib/lexer.py +287 -0
  45. fontTools/feaLib/location.py +12 -0
  46. fontTools/feaLib/lookupDebugInfo.py +12 -0
  47. fontTools/feaLib/parser.py +2394 -0
  48. fontTools/feaLib/variableScalar.py +118 -0
  49. fontTools/fontBuilder.py +1014 -0
  50. fontTools/help.py +36 -0
  51. fontTools/merge/__init__.py +248 -0
  52. fontTools/merge/__main__.py +6 -0
  53. fontTools/merge/base.py +81 -0
  54. fontTools/merge/cmap.py +173 -0
  55. fontTools/merge/layout.py +526 -0
  56. fontTools/merge/options.py +85 -0
  57. fontTools/merge/tables.py +352 -0
  58. fontTools/merge/unicode.py +78 -0
  59. fontTools/merge/util.py +143 -0
  60. fontTools/misc/__init__.py +1 -0
  61. fontTools/misc/arrayTools.py +424 -0
  62. fontTools/misc/bezierTools.c +39731 -0
  63. fontTools/misc/bezierTools.cp311-win32.pyd +0 -0
  64. fontTools/misc/bezierTools.py +1500 -0
  65. fontTools/misc/classifyTools.py +170 -0
  66. fontTools/misc/cliTools.py +53 -0
  67. fontTools/misc/configTools.py +349 -0
  68. fontTools/misc/cython.py +27 -0
  69. fontTools/misc/dictTools.py +83 -0
  70. fontTools/misc/eexec.py +119 -0
  71. fontTools/misc/encodingTools.py +72 -0
  72. fontTools/misc/enumTools.py +23 -0
  73. fontTools/misc/etree.py +456 -0
  74. fontTools/misc/filenames.py +245 -0
  75. fontTools/misc/filesystem/__init__.py +68 -0
  76. fontTools/misc/filesystem/_base.py +134 -0
  77. fontTools/misc/filesystem/_copy.py +45 -0
  78. fontTools/misc/filesystem/_errors.py +54 -0
  79. fontTools/misc/filesystem/_info.py +75 -0
  80. fontTools/misc/filesystem/_osfs.py +164 -0
  81. fontTools/misc/filesystem/_path.py +67 -0
  82. fontTools/misc/filesystem/_subfs.py +92 -0
  83. fontTools/misc/filesystem/_tempfs.py +34 -0
  84. fontTools/misc/filesystem/_tools.py +34 -0
  85. fontTools/misc/filesystem/_walk.py +55 -0
  86. fontTools/misc/filesystem/_zipfs.py +204 -0
  87. fontTools/misc/fixedTools.py +253 -0
  88. fontTools/misc/intTools.py +25 -0
  89. fontTools/misc/iterTools.py +12 -0
  90. fontTools/misc/lazyTools.py +42 -0
  91. fontTools/misc/loggingTools.py +543 -0
  92. fontTools/misc/macCreatorType.py +56 -0
  93. fontTools/misc/macRes.py +261 -0
  94. fontTools/misc/plistlib/__init__.py +681 -0
  95. fontTools/misc/plistlib/py.typed +0 -0
  96. fontTools/misc/psCharStrings.py +1511 -0
  97. fontTools/misc/psLib.py +398 -0
  98. fontTools/misc/psOperators.py +572 -0
  99. fontTools/misc/py23.py +96 -0
  100. fontTools/misc/roundTools.py +110 -0
  101. fontTools/misc/sstruct.py +227 -0
  102. fontTools/misc/symfont.py +242 -0
  103. fontTools/misc/testTools.py +233 -0
  104. fontTools/misc/textTools.py +156 -0
  105. fontTools/misc/timeTools.py +88 -0
  106. fontTools/misc/transform.py +516 -0
  107. fontTools/misc/treeTools.py +45 -0
  108. fontTools/misc/vector.py +147 -0
  109. fontTools/misc/visitor.py +158 -0
  110. fontTools/misc/xmlReader.py +188 -0
  111. fontTools/misc/xmlWriter.py +231 -0
  112. fontTools/mtiLib/__init__.py +1400 -0
  113. fontTools/mtiLib/__main__.py +5 -0
  114. fontTools/otlLib/__init__.py +1 -0
  115. fontTools/otlLib/builder.py +3465 -0
  116. fontTools/otlLib/error.py +11 -0
  117. fontTools/otlLib/maxContextCalc.py +96 -0
  118. fontTools/otlLib/optimize/__init__.py +53 -0
  119. fontTools/otlLib/optimize/__main__.py +6 -0
  120. fontTools/otlLib/optimize/gpos.py +439 -0
  121. fontTools/pens/__init__.py +1 -0
  122. fontTools/pens/areaPen.py +52 -0
  123. fontTools/pens/basePen.py +475 -0
  124. fontTools/pens/boundsPen.py +98 -0
  125. fontTools/pens/cairoPen.py +26 -0
  126. fontTools/pens/cocoaPen.py +26 -0
  127. fontTools/pens/cu2quPen.py +325 -0
  128. fontTools/pens/explicitClosingLinePen.py +101 -0
  129. fontTools/pens/filterPen.py +433 -0
  130. fontTools/pens/freetypePen.py +462 -0
  131. fontTools/pens/hashPointPen.py +89 -0
  132. fontTools/pens/momentsPen.c +13378 -0
  133. fontTools/pens/momentsPen.cp311-win32.pyd +0 -0
  134. fontTools/pens/momentsPen.py +879 -0
  135. fontTools/pens/perimeterPen.py +69 -0
  136. fontTools/pens/pointInsidePen.py +192 -0
  137. fontTools/pens/pointPen.py +643 -0
  138. fontTools/pens/qtPen.py +29 -0
  139. fontTools/pens/qu2cuPen.py +105 -0
  140. fontTools/pens/quartzPen.py +43 -0
  141. fontTools/pens/recordingPen.py +335 -0
  142. fontTools/pens/reportLabPen.py +79 -0
  143. fontTools/pens/reverseContourPen.py +96 -0
  144. fontTools/pens/roundingPen.py +130 -0
  145. fontTools/pens/statisticsPen.py +312 -0
  146. fontTools/pens/svgPathPen.py +310 -0
  147. fontTools/pens/t2CharStringPen.py +88 -0
  148. fontTools/pens/teePen.py +55 -0
  149. fontTools/pens/transformPen.py +115 -0
  150. fontTools/pens/ttGlyphPen.py +335 -0
  151. fontTools/pens/wxPen.py +29 -0
  152. fontTools/qu2cu/__init__.py +15 -0
  153. fontTools/qu2cu/__main__.py +7 -0
  154. fontTools/qu2cu/benchmark.py +56 -0
  155. fontTools/qu2cu/cli.py +125 -0
  156. fontTools/qu2cu/qu2cu.c +16682 -0
  157. fontTools/qu2cu/qu2cu.cp311-win32.pyd +0 -0
  158. fontTools/qu2cu/qu2cu.py +405 -0
  159. fontTools/subset/__init__.py +4096 -0
  160. fontTools/subset/__main__.py +6 -0
  161. fontTools/subset/cff.py +184 -0
  162. fontTools/subset/svg.py +253 -0
  163. fontTools/subset/util.py +25 -0
  164. fontTools/svgLib/__init__.py +3 -0
  165. fontTools/svgLib/path/__init__.py +65 -0
  166. fontTools/svgLib/path/arc.py +154 -0
  167. fontTools/svgLib/path/parser.py +322 -0
  168. fontTools/svgLib/path/shapes.py +183 -0
  169. fontTools/t1Lib/__init__.py +648 -0
  170. fontTools/tfmLib.py +460 -0
  171. fontTools/ttLib/__init__.py +30 -0
  172. fontTools/ttLib/__main__.py +148 -0
  173. fontTools/ttLib/macUtils.py +54 -0
  174. fontTools/ttLib/removeOverlaps.py +395 -0
  175. fontTools/ttLib/reorderGlyphs.py +285 -0
  176. fontTools/ttLib/scaleUpem.py +436 -0
  177. fontTools/ttLib/sfnt.py +661 -0
  178. fontTools/ttLib/standardGlyphOrder.py +271 -0
  179. fontTools/ttLib/tables/B_A_S_E_.py +14 -0
  180. fontTools/ttLib/tables/BitmapGlyphMetrics.py +64 -0
  181. fontTools/ttLib/tables/C_B_D_T_.py +113 -0
  182. fontTools/ttLib/tables/C_B_L_C_.py +19 -0
  183. fontTools/ttLib/tables/C_F_F_.py +61 -0
  184. fontTools/ttLib/tables/C_F_F__2.py +26 -0
  185. fontTools/ttLib/tables/C_O_L_R_.py +165 -0
  186. fontTools/ttLib/tables/C_P_A_L_.py +305 -0
  187. fontTools/ttLib/tables/D_S_I_G_.py +158 -0
  188. fontTools/ttLib/tables/D__e_b_g.py +35 -0
  189. fontTools/ttLib/tables/DefaultTable.py +49 -0
  190. fontTools/ttLib/tables/E_B_D_T_.py +835 -0
  191. fontTools/ttLib/tables/E_B_L_C_.py +718 -0
  192. fontTools/ttLib/tables/F_F_T_M_.py +52 -0
  193. fontTools/ttLib/tables/F__e_a_t.py +149 -0
  194. fontTools/ttLib/tables/G_D_E_F_.py +13 -0
  195. fontTools/ttLib/tables/G_M_A_P_.py +148 -0
  196. fontTools/ttLib/tables/G_P_K_G_.py +133 -0
  197. fontTools/ttLib/tables/G_P_O_S_.py +14 -0
  198. fontTools/ttLib/tables/G_S_U_B_.py +13 -0
  199. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  200. fontTools/ttLib/tables/G__l_a_t.py +235 -0
  201. fontTools/ttLib/tables/G__l_o_c.py +85 -0
  202. fontTools/ttLib/tables/H_V_A_R_.py +13 -0
  203. fontTools/ttLib/tables/J_S_T_F_.py +13 -0
  204. fontTools/ttLib/tables/L_T_S_H_.py +58 -0
  205. fontTools/ttLib/tables/M_A_T_H_.py +13 -0
  206. fontTools/ttLib/tables/M_E_T_A_.py +352 -0
  207. fontTools/ttLib/tables/M_V_A_R_.py +13 -0
  208. fontTools/ttLib/tables/O_S_2f_2.py +752 -0
  209. fontTools/ttLib/tables/S_I_N_G_.py +99 -0
  210. fontTools/ttLib/tables/S_T_A_T_.py +15 -0
  211. fontTools/ttLib/tables/S_V_G_.py +223 -0
  212. fontTools/ttLib/tables/S__i_l_f.py +1040 -0
  213. fontTools/ttLib/tables/S__i_l_l.py +92 -0
  214. fontTools/ttLib/tables/T_S_I_B_.py +13 -0
  215. fontTools/ttLib/tables/T_S_I_C_.py +14 -0
  216. fontTools/ttLib/tables/T_S_I_D_.py +13 -0
  217. fontTools/ttLib/tables/T_S_I_J_.py +13 -0
  218. fontTools/ttLib/tables/T_S_I_P_.py +13 -0
  219. fontTools/ttLib/tables/T_S_I_S_.py +13 -0
  220. fontTools/ttLib/tables/T_S_I_V_.py +26 -0
  221. fontTools/ttLib/tables/T_S_I__0.py +70 -0
  222. fontTools/ttLib/tables/T_S_I__1.py +163 -0
  223. fontTools/ttLib/tables/T_S_I__2.py +17 -0
  224. fontTools/ttLib/tables/T_S_I__3.py +22 -0
  225. fontTools/ttLib/tables/T_S_I__5.py +60 -0
  226. fontTools/ttLib/tables/T_T_F_A_.py +14 -0
  227. fontTools/ttLib/tables/TupleVariation.py +884 -0
  228. fontTools/ttLib/tables/V_A_R_C_.py +12 -0
  229. fontTools/ttLib/tables/V_D_M_X_.py +249 -0
  230. fontTools/ttLib/tables/V_O_R_G_.py +165 -0
  231. fontTools/ttLib/tables/V_V_A_R_.py +13 -0
  232. fontTools/ttLib/tables/__init__.py +98 -0
  233. fontTools/ttLib/tables/_a_n_k_r.py +15 -0
  234. fontTools/ttLib/tables/_a_v_a_r.py +193 -0
  235. fontTools/ttLib/tables/_b_s_l_n.py +15 -0
  236. fontTools/ttLib/tables/_c_i_d_g.py +24 -0
  237. fontTools/ttLib/tables/_c_m_a_p.py +1591 -0
  238. fontTools/ttLib/tables/_c_v_a_r.py +94 -0
  239. fontTools/ttLib/tables/_c_v_t.py +56 -0
  240. fontTools/ttLib/tables/_f_e_a_t.py +15 -0
  241. fontTools/ttLib/tables/_f_p_g_m.py +62 -0
  242. fontTools/ttLib/tables/_f_v_a_r.py +261 -0
  243. fontTools/ttLib/tables/_g_a_s_p.py +63 -0
  244. fontTools/ttLib/tables/_g_c_i_d.py +13 -0
  245. fontTools/ttLib/tables/_g_l_y_f.py +2311 -0
  246. fontTools/ttLib/tables/_g_v_a_r.py +340 -0
  247. fontTools/ttLib/tables/_h_d_m_x.py +127 -0
  248. fontTools/ttLib/tables/_h_e_a_d.py +130 -0
  249. fontTools/ttLib/tables/_h_h_e_a.py +147 -0
  250. fontTools/ttLib/tables/_h_m_t_x.py +164 -0
  251. fontTools/ttLib/tables/_k_e_r_n.py +289 -0
  252. fontTools/ttLib/tables/_l_c_a_r.py +13 -0
  253. fontTools/ttLib/tables/_l_o_c_a.py +70 -0
  254. fontTools/ttLib/tables/_l_t_a_g.py +72 -0
  255. fontTools/ttLib/tables/_m_a_x_p.py +147 -0
  256. fontTools/ttLib/tables/_m_e_t_a.py +112 -0
  257. fontTools/ttLib/tables/_m_o_r_t.py +14 -0
  258. fontTools/ttLib/tables/_m_o_r_x.py +15 -0
  259. fontTools/ttLib/tables/_n_a_m_e.py +1242 -0
  260. fontTools/ttLib/tables/_o_p_b_d.py +14 -0
  261. fontTools/ttLib/tables/_p_o_s_t.py +319 -0
  262. fontTools/ttLib/tables/_p_r_e_p.py +16 -0
  263. fontTools/ttLib/tables/_p_r_o_p.py +12 -0
  264. fontTools/ttLib/tables/_s_b_i_x.py +129 -0
  265. fontTools/ttLib/tables/_t_r_a_k.py +332 -0
  266. fontTools/ttLib/tables/_v_h_e_a.py +139 -0
  267. fontTools/ttLib/tables/_v_m_t_x.py +19 -0
  268. fontTools/ttLib/tables/asciiTable.py +20 -0
  269. fontTools/ttLib/tables/grUtils.py +92 -0
  270. fontTools/ttLib/tables/otBase.py +1458 -0
  271. fontTools/ttLib/tables/otConverters.py +2068 -0
  272. fontTools/ttLib/tables/otData.py +6400 -0
  273. fontTools/ttLib/tables/otTables.py +2703 -0
  274. fontTools/ttLib/tables/otTraverse.py +163 -0
  275. fontTools/ttLib/tables/sbixGlyph.py +149 -0
  276. fontTools/ttLib/tables/sbixStrike.py +177 -0
  277. fontTools/ttLib/tables/table_API_readme.txt +91 -0
  278. fontTools/ttLib/tables/ttProgram.py +594 -0
  279. fontTools/ttLib/ttCollection.py +125 -0
  280. fontTools/ttLib/ttFont.py +1148 -0
  281. fontTools/ttLib/ttGlyphSet.py +490 -0
  282. fontTools/ttLib/ttVisitor.py +32 -0
  283. fontTools/ttLib/woff2.py +1680 -0
  284. fontTools/ttx.py +479 -0
  285. fontTools/ufoLib/__init__.py +2575 -0
  286. fontTools/ufoLib/converters.py +407 -0
  287. fontTools/ufoLib/errors.py +30 -0
  288. fontTools/ufoLib/etree.py +6 -0
  289. fontTools/ufoLib/filenames.py +356 -0
  290. fontTools/ufoLib/glifLib.py +2120 -0
  291. fontTools/ufoLib/kerning.py +141 -0
  292. fontTools/ufoLib/plistlib.py +47 -0
  293. fontTools/ufoLib/pointPen.py +6 -0
  294. fontTools/ufoLib/utils.py +107 -0
  295. fontTools/ufoLib/validators.py +1208 -0
  296. fontTools/unicode.py +50 -0
  297. fontTools/unicodedata/Blocks.py +817 -0
  298. fontTools/unicodedata/Mirrored.py +446 -0
  299. fontTools/unicodedata/OTTags.py +50 -0
  300. fontTools/unicodedata/ScriptExtensions.py +832 -0
  301. fontTools/unicodedata/Scripts.py +3639 -0
  302. fontTools/unicodedata/__init__.py +306 -0
  303. fontTools/varLib/__init__.py +1600 -0
  304. fontTools/varLib/__main__.py +6 -0
  305. fontTools/varLib/avar/__init__.py +0 -0
  306. fontTools/varLib/avar/__main__.py +72 -0
  307. fontTools/varLib/avar/build.py +79 -0
  308. fontTools/varLib/avar/map.py +108 -0
  309. fontTools/varLib/avar/plan.py +1004 -0
  310. fontTools/varLib/avar/unbuild.py +271 -0
  311. fontTools/varLib/avarPlanner.py +8 -0
  312. fontTools/varLib/builder.py +215 -0
  313. fontTools/varLib/cff.py +631 -0
  314. fontTools/varLib/errors.py +219 -0
  315. fontTools/varLib/featureVars.py +703 -0
  316. fontTools/varLib/hvar.py +113 -0
  317. fontTools/varLib/instancer/__init__.py +2052 -0
  318. fontTools/varLib/instancer/__main__.py +5 -0
  319. fontTools/varLib/instancer/featureVars.py +190 -0
  320. fontTools/varLib/instancer/names.py +388 -0
  321. fontTools/varLib/instancer/solver.py +309 -0
  322. fontTools/varLib/interpolatable.py +1209 -0
  323. fontTools/varLib/interpolatableHelpers.py +399 -0
  324. fontTools/varLib/interpolatablePlot.py +1269 -0
  325. fontTools/varLib/interpolatableTestContourOrder.py +82 -0
  326. fontTools/varLib/interpolatableTestStartingPoint.py +107 -0
  327. fontTools/varLib/interpolate_layout.py +124 -0
  328. fontTools/varLib/iup.c +19815 -0
  329. fontTools/varLib/iup.cp311-win32.pyd +0 -0
  330. fontTools/varLib/iup.py +490 -0
  331. fontTools/varLib/merger.py +1717 -0
  332. fontTools/varLib/models.py +642 -0
  333. fontTools/varLib/multiVarStore.py +253 -0
  334. fontTools/varLib/mutator.py +529 -0
  335. fontTools/varLib/mvar.py +40 -0
  336. fontTools/varLib/plot.py +238 -0
  337. fontTools/varLib/stat.py +149 -0
  338. fontTools/varLib/varStore.py +739 -0
  339. fontTools/voltLib/__init__.py +5 -0
  340. fontTools/voltLib/__main__.py +206 -0
  341. fontTools/voltLib/ast.py +452 -0
  342. fontTools/voltLib/error.py +12 -0
  343. fontTools/voltLib/lexer.py +99 -0
  344. fontTools/voltLib/parser.py +664 -0
  345. fontTools/voltLib/voltToFea.py +911 -0
  346. fonttools-4.60.2.data/data/share/man/man1/ttx.1 +225 -0
  347. fonttools-4.60.2.dist-info/METADATA +2250 -0
  348. fonttools-4.60.2.dist-info/RECORD +353 -0
  349. fonttools-4.60.2.dist-info/WHEEL +5 -0
  350. fonttools-4.60.2.dist-info/entry_points.txt +5 -0
  351. fonttools-4.60.2.dist-info/licenses/LICENSE +21 -0
  352. fonttools-4.60.2.dist-info/licenses/LICENSE.external +388 -0
  353. fonttools-4.60.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2068 @@
1
+ from fontTools.misc.fixedTools import (
2
+ fixedToFloat as fi2fl,
3
+ floatToFixed as fl2fi,
4
+ floatToFixedToStr as fl2str,
5
+ strToFixedToFloat as str2fl,
6
+ ensureVersionIsLong as fi2ve,
7
+ versionToFixed as ve2fi,
8
+ )
9
+ from fontTools.ttLib.tables.TupleVariation import TupleVariation
10
+ from fontTools.misc.roundTools import nearestMultipleShortestRepr, otRound
11
+ from fontTools.misc.textTools import bytesjoin, tobytes, tostr, pad, safeEval
12
+ from fontTools.misc.lazyTools import LazyList
13
+ from fontTools.ttLib import OPTIMIZE_FONT_SPEED, getSearchRange
14
+ from .otBase import (
15
+ CountReference,
16
+ FormatSwitchingBaseTable,
17
+ OTTableReader,
18
+ OTTableWriter,
19
+ ValueRecordFactory,
20
+ )
21
+ from .otTables import (
22
+ lookupTypes,
23
+ VarCompositeGlyph,
24
+ AATStateTable,
25
+ AATState,
26
+ AATAction,
27
+ ContextualMorphAction,
28
+ LigatureMorphAction,
29
+ InsertionMorphAction,
30
+ MorxSubtable,
31
+ ExtendMode as _ExtendMode,
32
+ CompositeMode as _CompositeMode,
33
+ NO_VARIATION_INDEX,
34
+ )
35
+ from itertools import zip_longest, accumulate
36
+ from functools import partial
37
+ from types import SimpleNamespace
38
+ import re
39
+ import struct
40
+ from typing import Optional
41
+ import logging
42
+
43
+
44
+ log = logging.getLogger(__name__)
45
+ istuple = lambda t: isinstance(t, tuple)
46
+
47
+
48
+ def buildConverters(tableSpec, tableNamespace):
49
+ """Given a table spec from otData.py, build a converter object for each
50
+ field of the table. This is called for each table in otData.py, and
51
+ the results are assigned to the corresponding class in otTables.py."""
52
+ converters = []
53
+ convertersByName = {}
54
+ for tp, name, repeat, aux, descr in tableSpec:
55
+ tableName = name
56
+ if name.startswith("ValueFormat"):
57
+ assert tp == "uint16"
58
+ converterClass = ValueFormat
59
+ elif name.endswith("Count") or name in ("StructLength", "MorphType"):
60
+ converterClass = {
61
+ "uint8": ComputedUInt8,
62
+ "uint16": ComputedUShort,
63
+ "uint32": ComputedULong,
64
+ }[tp]
65
+ elif name == "SubTable":
66
+ converterClass = SubTable
67
+ elif name == "ExtSubTable":
68
+ converterClass = ExtSubTable
69
+ elif name == "SubStruct":
70
+ converterClass = SubStruct
71
+ elif name == "FeatureParams":
72
+ converterClass = FeatureParams
73
+ elif name in ("CIDGlyphMapping", "GlyphCIDMapping"):
74
+ converterClass = StructWithLength
75
+ else:
76
+ if not tp in converterMapping and "(" not in tp:
77
+ tableName = tp
78
+ converterClass = Struct
79
+ else:
80
+ converterClass = eval(tp, tableNamespace, converterMapping)
81
+
82
+ conv = converterClass(name, repeat, aux, description=descr)
83
+
84
+ if conv.tableClass:
85
+ # A "template" such as OffsetTo(AType) knows the table class already
86
+ tableClass = conv.tableClass
87
+ elif tp in ("MortChain", "MortSubtable", "MorxChain"):
88
+ tableClass = tableNamespace.get(tp)
89
+ else:
90
+ tableClass = tableNamespace.get(tableName)
91
+
92
+ if not conv.tableClass:
93
+ conv.tableClass = tableClass
94
+
95
+ if name in ["SubTable", "ExtSubTable", "SubStruct"]:
96
+ conv.lookupTypes = tableNamespace["lookupTypes"]
97
+ # also create reverse mapping
98
+ for t in conv.lookupTypes.values():
99
+ for cls in t.values():
100
+ convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
101
+ if name == "FeatureParams":
102
+ conv.featureParamTypes = tableNamespace["featureParamTypes"]
103
+ conv.defaultFeatureParams = tableNamespace["FeatureParams"]
104
+ for cls in conv.featureParamTypes.values():
105
+ convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
106
+ converters.append(conv)
107
+ assert name not in convertersByName, name
108
+ convertersByName[name] = conv
109
+ return converters, convertersByName
110
+
111
+
112
+ class BaseConverter(object):
113
+ """Base class for converter objects. Apart from the constructor, this
114
+ is an abstract class."""
115
+
116
+ def __init__(self, name, repeat, aux, tableClass=None, *, description=""):
117
+ self.name = name
118
+ self.repeat = repeat
119
+ self.aux = aux
120
+ if self.aux and not self.repeat:
121
+ self.aux = compile(self.aux, "<string>", "eval")
122
+ self.tableClass = tableClass
123
+ self.isCount = name.endswith("Count") or name in [
124
+ "DesignAxisRecordSize",
125
+ "ValueRecordSize",
126
+ ]
127
+ self.isLookupType = name.endswith("LookupType") or name == "MorphType"
128
+ self.isPropagated = name in [
129
+ "ClassCount",
130
+ "Class2Count",
131
+ "FeatureTag",
132
+ "SettingsCount",
133
+ "VarRegionCount",
134
+ "MappingCount",
135
+ "RegionAxisCount",
136
+ "DesignAxisCount",
137
+ "DesignAxisRecordSize",
138
+ "AxisValueCount",
139
+ "ValueRecordSize",
140
+ "AxisCount",
141
+ "BaseGlyphRecordCount",
142
+ "LayerRecordCount",
143
+ "AxisIndicesList",
144
+ ]
145
+ self.description = description
146
+
147
+ def readArray(self, reader, font, tableDict, count):
148
+ """Read an array of values from the reader."""
149
+ lazy = font.lazy and count > 8
150
+ if lazy:
151
+ recordSize = self.getRecordSize(reader)
152
+ if recordSize is NotImplemented:
153
+ lazy = False
154
+ if not lazy:
155
+ l = []
156
+ for i in range(count):
157
+ l.append(self.read(reader, font, tableDict))
158
+ return l
159
+ else:
160
+
161
+ def get_read_item():
162
+ reader_copy = reader.copy()
163
+ pos = reader.pos
164
+
165
+ def read_item(i):
166
+ reader_copy.seek(pos + i * recordSize)
167
+ return self.read(reader_copy, font, {})
168
+
169
+ return read_item
170
+
171
+ read_item = get_read_item()
172
+ l = LazyList(read_item for i in range(count))
173
+ reader.advance(count * recordSize)
174
+
175
+ return l
176
+
177
+ def getRecordSize(self, reader):
178
+ if hasattr(self, "staticSize"):
179
+ return self.staticSize
180
+ return NotImplemented
181
+
182
+ def read(self, reader, font, tableDict):
183
+ """Read a value from the reader."""
184
+ raise NotImplementedError(self)
185
+
186
+ def writeArray(self, writer, font, tableDict, values):
187
+ try:
188
+ for i, value in enumerate(values):
189
+ self.write(writer, font, tableDict, value, i)
190
+ except Exception as e:
191
+ e.args = e.args + (i,)
192
+ raise
193
+
194
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
195
+ """Write a value to the writer."""
196
+ raise NotImplementedError(self)
197
+
198
+ def xmlRead(self, attrs, content, font):
199
+ """Read a value from XML."""
200
+ raise NotImplementedError(self)
201
+
202
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
203
+ """Write a value to XML."""
204
+ raise NotImplementedError(self)
205
+
206
+ varIndexBasePlusOffsetRE = re.compile(r"VarIndexBase\s*\+\s*(\d+)")
207
+
208
+ def getVarIndexOffset(self) -> Optional[int]:
209
+ """If description has `VarIndexBase + {offset}`, return the offset else None."""
210
+ m = self.varIndexBasePlusOffsetRE.search(self.description)
211
+ if not m:
212
+ return None
213
+ return int(m.group(1))
214
+
215
+
216
+ class SimpleValue(BaseConverter):
217
+ @staticmethod
218
+ def toString(value):
219
+ return value
220
+
221
+ @staticmethod
222
+ def fromString(value):
223
+ return value
224
+
225
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
226
+ xmlWriter.simpletag(name, attrs + [("value", self.toString(value))])
227
+ xmlWriter.newline()
228
+
229
+ def xmlRead(self, attrs, content, font):
230
+ return self.fromString(attrs["value"])
231
+
232
+
233
+ class OptionalValue(SimpleValue):
234
+ DEFAULT = None
235
+
236
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
237
+ if value != self.DEFAULT:
238
+ attrs.append(("value", self.toString(value)))
239
+ xmlWriter.simpletag(name, attrs)
240
+ xmlWriter.newline()
241
+
242
+ def xmlRead(self, attrs, content, font):
243
+ if "value" in attrs:
244
+ return self.fromString(attrs["value"])
245
+ return self.DEFAULT
246
+
247
+
248
+ class IntValue(SimpleValue):
249
+ @staticmethod
250
+ def fromString(value):
251
+ return int(value, 0)
252
+
253
+
254
+ class Long(IntValue):
255
+ staticSize = 4
256
+
257
+ def read(self, reader, font, tableDict):
258
+ return reader.readLong()
259
+
260
+ def readArray(self, reader, font, tableDict, count):
261
+ return reader.readLongArray(count)
262
+
263
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
264
+ writer.writeLong(value)
265
+
266
+ def writeArray(self, writer, font, tableDict, values):
267
+ writer.writeLongArray(values)
268
+
269
+
270
+ class ULong(IntValue):
271
+ staticSize = 4
272
+
273
+ def read(self, reader, font, tableDict):
274
+ return reader.readULong()
275
+
276
+ def readArray(self, reader, font, tableDict, count):
277
+ return reader.readULongArray(count)
278
+
279
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
280
+ writer.writeULong(value)
281
+
282
+ def writeArray(self, writer, font, tableDict, values):
283
+ writer.writeULongArray(values)
284
+
285
+
286
+ class Flags32(ULong):
287
+ @staticmethod
288
+ def toString(value):
289
+ return "0x%08X" % value
290
+
291
+
292
+ class VarIndex(OptionalValue, ULong):
293
+ DEFAULT = NO_VARIATION_INDEX
294
+
295
+
296
+ class Short(IntValue):
297
+ staticSize = 2
298
+
299
+ def read(self, reader, font, tableDict):
300
+ return reader.readShort()
301
+
302
+ def readArray(self, reader, font, tableDict, count):
303
+ return reader.readShortArray(count)
304
+
305
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
306
+ writer.writeShort(value)
307
+
308
+ def writeArray(self, writer, font, tableDict, values):
309
+ writer.writeShortArray(values)
310
+
311
+
312
+ class UShort(IntValue):
313
+ staticSize = 2
314
+
315
+ def read(self, reader, font, tableDict):
316
+ return reader.readUShort()
317
+
318
+ def readArray(self, reader, font, tableDict, count):
319
+ return reader.readUShortArray(count)
320
+
321
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
322
+ writer.writeUShort(value)
323
+
324
+ def writeArray(self, writer, font, tableDict, values):
325
+ writer.writeUShortArray(values)
326
+
327
+
328
+ class Int8(IntValue):
329
+ staticSize = 1
330
+
331
+ def read(self, reader, font, tableDict):
332
+ return reader.readInt8()
333
+
334
+ def readArray(self, reader, font, tableDict, count):
335
+ return reader.readInt8Array(count)
336
+
337
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
338
+ writer.writeInt8(value)
339
+
340
+ def writeArray(self, writer, font, tableDict, values):
341
+ writer.writeInt8Array(values)
342
+
343
+
344
+ class UInt8(IntValue):
345
+ staticSize = 1
346
+
347
+ def read(self, reader, font, tableDict):
348
+ return reader.readUInt8()
349
+
350
+ def readArray(self, reader, font, tableDict, count):
351
+ return reader.readUInt8Array(count)
352
+
353
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
354
+ writer.writeUInt8(value)
355
+
356
+ def writeArray(self, writer, font, tableDict, values):
357
+ writer.writeUInt8Array(values)
358
+
359
+
360
+ class UInt24(IntValue):
361
+ staticSize = 3
362
+
363
+ def read(self, reader, font, tableDict):
364
+ return reader.readUInt24()
365
+
366
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
367
+ writer.writeUInt24(value)
368
+
369
+
370
+ class ComputedInt(IntValue):
371
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
372
+ if value is not None:
373
+ xmlWriter.comment("%s=%s" % (name, value))
374
+ xmlWriter.newline()
375
+
376
+
377
+ class ComputedUInt8(ComputedInt, UInt8):
378
+ pass
379
+
380
+
381
+ class ComputedUShort(ComputedInt, UShort):
382
+ pass
383
+
384
+
385
+ class ComputedULong(ComputedInt, ULong):
386
+ pass
387
+
388
+
389
+ class Tag(SimpleValue):
390
+ staticSize = 4
391
+
392
+ def read(self, reader, font, tableDict):
393
+ return reader.readTag()
394
+
395
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
396
+ writer.writeTag(value)
397
+
398
+
399
+ class GlyphID(SimpleValue):
400
+ staticSize = 2
401
+ typecode = "H"
402
+
403
+ def readArray(self, reader, font, tableDict, count):
404
+ return font.getGlyphNameMany(
405
+ reader.readArray(self.typecode, self.staticSize, count)
406
+ )
407
+
408
+ def read(self, reader, font, tableDict):
409
+ return font.getGlyphName(reader.readValue(self.typecode, self.staticSize))
410
+
411
+ def writeArray(self, writer, font, tableDict, values):
412
+ writer.writeArray(self.typecode, font.getGlyphIDMany(values))
413
+
414
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
415
+ writer.writeValue(self.typecode, font.getGlyphID(value))
416
+
417
+
418
+ class GlyphID32(GlyphID):
419
+ staticSize = 4
420
+ typecode = "L"
421
+
422
+
423
+ class NameID(UShort):
424
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
425
+ xmlWriter.simpletag(name, attrs + [("value", value)])
426
+ if font and value:
427
+ nameTable = font.get("name")
428
+ if nameTable:
429
+ name = nameTable.getDebugName(value)
430
+ xmlWriter.write(" ")
431
+ if name:
432
+ xmlWriter.comment(name)
433
+ else:
434
+ xmlWriter.comment("missing from name table")
435
+ log.warning("name id %d missing from name table" % value)
436
+ xmlWriter.newline()
437
+
438
+
439
+ class STATFlags(UShort):
440
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
441
+ xmlWriter.simpletag(name, attrs + [("value", value)])
442
+ flags = []
443
+ if value & 0x01:
444
+ flags.append("OlderSiblingFontAttribute")
445
+ if value & 0x02:
446
+ flags.append("ElidableAxisValueName")
447
+ if flags:
448
+ xmlWriter.write(" ")
449
+ xmlWriter.comment(" ".join(flags))
450
+ xmlWriter.newline()
451
+
452
+
453
+ class FloatValue(SimpleValue):
454
+ @staticmethod
455
+ def fromString(value):
456
+ return float(value)
457
+
458
+
459
+ class DeciPoints(FloatValue):
460
+ staticSize = 2
461
+
462
+ def read(self, reader, font, tableDict):
463
+ return reader.readUShort() / 10
464
+
465
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
466
+ writer.writeUShort(round(value * 10))
467
+
468
+
469
+ class BaseFixedValue(FloatValue):
470
+ staticSize = NotImplemented
471
+ precisionBits = NotImplemented
472
+ readerMethod = NotImplemented
473
+ writerMethod = NotImplemented
474
+
475
+ def read(self, reader, font, tableDict):
476
+ return self.fromInt(getattr(reader, self.readerMethod)())
477
+
478
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
479
+ getattr(writer, self.writerMethod)(self.toInt(value))
480
+
481
+ @classmethod
482
+ def fromInt(cls, value):
483
+ return fi2fl(value, cls.precisionBits)
484
+
485
+ @classmethod
486
+ def toInt(cls, value):
487
+ return fl2fi(value, cls.precisionBits)
488
+
489
+ @classmethod
490
+ def fromString(cls, value):
491
+ return str2fl(value, cls.precisionBits)
492
+
493
+ @classmethod
494
+ def toString(cls, value):
495
+ return fl2str(value, cls.precisionBits)
496
+
497
+
498
+ class Fixed(BaseFixedValue):
499
+ staticSize = 4
500
+ precisionBits = 16
501
+ readerMethod = "readLong"
502
+ writerMethod = "writeLong"
503
+
504
+
505
+ class F2Dot14(BaseFixedValue):
506
+ staticSize = 2
507
+ precisionBits = 14
508
+ readerMethod = "readShort"
509
+ writerMethod = "writeShort"
510
+
511
+
512
+ class Angle(F2Dot14):
513
+ # angles are specified in degrees, and encoded as F2Dot14 fractions of half
514
+ # circle: e.g. 1.0 => 180, -0.5 => -90, -2.0 => -360, etc.
515
+ bias = 0.0
516
+ factor = 1.0 / (1 << 14) * 180 # 0.010986328125
517
+
518
+ @classmethod
519
+ def fromInt(cls, value):
520
+ return (super().fromInt(value) + cls.bias) * 180
521
+
522
+ @classmethod
523
+ def toInt(cls, value):
524
+ return super().toInt((value / 180) - cls.bias)
525
+
526
+ @classmethod
527
+ def fromString(cls, value):
528
+ # quantize to nearest multiples of minimum fixed-precision angle
529
+ return otRound(float(value) / cls.factor) * cls.factor
530
+
531
+ @classmethod
532
+ def toString(cls, value):
533
+ return nearestMultipleShortestRepr(value, cls.factor)
534
+
535
+
536
+ class BiasedAngle(Angle):
537
+ # A bias of 1.0 is used in the representation of start and end angles
538
+ # of COLRv1 PaintSweepGradients to allow for encoding +360deg
539
+ bias = 1.0
540
+
541
+
542
+ class Version(SimpleValue):
543
+ staticSize = 4
544
+
545
+ def read(self, reader, font, tableDict):
546
+ value = reader.readLong()
547
+ return value
548
+
549
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
550
+ value = fi2ve(value)
551
+ writer.writeLong(value)
552
+
553
+ @staticmethod
554
+ def fromString(value):
555
+ return ve2fi(value)
556
+
557
+ @staticmethod
558
+ def toString(value):
559
+ return "0x%08x" % value
560
+
561
+ @staticmethod
562
+ def fromFloat(v):
563
+ return fl2fi(v, 16)
564
+
565
+
566
+ class Char64(SimpleValue):
567
+ """An ASCII string with up to 64 characters.
568
+
569
+ Unused character positions are filled with 0x00 bytes.
570
+ Used in Apple AAT fonts in the `gcid` table.
571
+ """
572
+
573
+ staticSize = 64
574
+
575
+ def read(self, reader, font, tableDict):
576
+ data = reader.readData(self.staticSize)
577
+ zeroPos = data.find(b"\0")
578
+ if zeroPos >= 0:
579
+ data = data[:zeroPos]
580
+ s = tostr(data, encoding="ascii", errors="replace")
581
+ if s != tostr(data, encoding="ascii", errors="ignore"):
582
+ log.warning('replaced non-ASCII characters in "%s"' % s)
583
+ return s
584
+
585
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
586
+ data = tobytes(value, encoding="ascii", errors="replace")
587
+ if data != tobytes(value, encoding="ascii", errors="ignore"):
588
+ log.warning('replacing non-ASCII characters in "%s"' % value)
589
+ if len(data) > self.staticSize:
590
+ log.warning(
591
+ 'truncating overlong "%s" to %d bytes' % (value, self.staticSize)
592
+ )
593
+ data = (data + b"\0" * self.staticSize)[: self.staticSize]
594
+ writer.writeData(data)
595
+
596
+
597
+ class Struct(BaseConverter):
598
+ def getRecordSize(self, reader):
599
+ return self.tableClass and self.tableClass.getRecordSize(reader)
600
+
601
+ def read(self, reader, font, tableDict):
602
+ table = self.tableClass()
603
+ table.decompile(reader, font)
604
+ return table
605
+
606
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
607
+ value.compile(writer, font)
608
+
609
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
610
+ if value is None:
611
+ if attrs:
612
+ # If there are attributes (probably index), then
613
+ # don't drop this even if it's NULL. It will mess
614
+ # up the array indices of the containing element.
615
+ xmlWriter.simpletag(name, attrs + [("empty", 1)])
616
+ xmlWriter.newline()
617
+ else:
618
+ pass # NULL table, ignore
619
+ else:
620
+ value.toXML(xmlWriter, font, attrs, name=name)
621
+
622
+ def xmlRead(self, attrs, content, font):
623
+ if "empty" in attrs and safeEval(attrs["empty"]):
624
+ return None
625
+ table = self.tableClass()
626
+ Format = attrs.get("Format")
627
+ if Format is not None:
628
+ table.Format = int(Format)
629
+
630
+ noPostRead = not hasattr(table, "postRead")
631
+ if noPostRead:
632
+ # TODO Cache table.hasPropagated.
633
+ cleanPropagation = False
634
+ for conv in table.getConverters():
635
+ if conv.isPropagated:
636
+ cleanPropagation = True
637
+ if not hasattr(font, "_propagator"):
638
+ font._propagator = {}
639
+ propagator = font._propagator
640
+ assert conv.name not in propagator, (conv.name, propagator)
641
+ setattr(table, conv.name, None)
642
+ propagator[conv.name] = CountReference(table.__dict__, conv.name)
643
+
644
+ for element in content:
645
+ if isinstance(element, tuple):
646
+ name, attrs, content = element
647
+ table.fromXML(name, attrs, content, font)
648
+ else:
649
+ pass
650
+
651
+ table.populateDefaults(propagator=getattr(font, "_propagator", None))
652
+
653
+ if noPostRead:
654
+ if cleanPropagation:
655
+ for conv in table.getConverters():
656
+ if conv.isPropagated:
657
+ propagator = font._propagator
658
+ del propagator[conv.name]
659
+ if not propagator:
660
+ del font._propagator
661
+
662
+ return table
663
+
664
+ def __repr__(self):
665
+ return "Struct of " + repr(self.tableClass)
666
+
667
+
668
+ class StructWithLength(Struct):
669
+ def read(self, reader, font, tableDict):
670
+ pos = reader.pos
671
+ table = self.tableClass()
672
+ table.decompile(reader, font)
673
+ reader.seek(pos + table.StructLength)
674
+ return table
675
+
676
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
677
+ for convIndex, conv in enumerate(value.getConverters()):
678
+ if conv.name == "StructLength":
679
+ break
680
+ lengthIndex = len(writer.items) + convIndex
681
+ if isinstance(value, FormatSwitchingBaseTable):
682
+ lengthIndex += 1 # implicit Format field
683
+ deadbeef = {1: 0xDE, 2: 0xDEAD, 4: 0xDEADBEEF}[conv.staticSize]
684
+
685
+ before = writer.getDataLength()
686
+ value.StructLength = deadbeef
687
+ value.compile(writer, font)
688
+ length = writer.getDataLength() - before
689
+ lengthWriter = writer.getSubWriter()
690
+ conv.write(lengthWriter, font, tableDict, length)
691
+ assert writer.items[lengthIndex] == b"\xde\xad\xbe\xef"[: conv.staticSize]
692
+ writer.items[lengthIndex] = lengthWriter.getAllData()
693
+
694
+
695
+ class Table(Struct):
696
+ staticSize = 2
697
+
698
+ def readOffset(self, reader):
699
+ return reader.readUShort()
700
+
701
+ def writeNullOffset(self, writer):
702
+ writer.writeUShort(0)
703
+
704
+ def read(self, reader, font, tableDict):
705
+ offset = self.readOffset(reader)
706
+ if offset == 0:
707
+ return None
708
+ table = self.tableClass()
709
+ reader = reader.getSubReader(offset)
710
+ if font.lazy:
711
+ table.reader = reader
712
+ table.font = font
713
+ else:
714
+ table.decompile(reader, font)
715
+ return table
716
+
717
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
718
+ if value is None:
719
+ self.writeNullOffset(writer)
720
+ else:
721
+ subWriter = writer.getSubWriter()
722
+ subWriter.name = self.name
723
+ if repeatIndex is not None:
724
+ subWriter.repeatIndex = repeatIndex
725
+ writer.writeSubTable(subWriter, offsetSize=self.staticSize)
726
+ value.compile(subWriter, font)
727
+
728
+
729
+ class LTable(Table):
730
+ staticSize = 4
731
+
732
+ def readOffset(self, reader):
733
+ return reader.readULong()
734
+
735
+ def writeNullOffset(self, writer):
736
+ writer.writeULong(0)
737
+
738
+
739
+ # Table pointed to by a 24-bit, 3-byte long offset
740
+ class Table24(Table):
741
+ staticSize = 3
742
+
743
+ def readOffset(self, reader):
744
+ return reader.readUInt24()
745
+
746
+ def writeNullOffset(self, writer):
747
+ writer.writeUInt24(0)
748
+
749
+
750
+ # TODO Clean / merge the SubTable and SubStruct
751
+
752
+
753
+ class SubStruct(Struct):
754
+ def getConverter(self, tableType, lookupType):
755
+ tableClass = self.lookupTypes[tableType][lookupType]
756
+ return self.__class__(self.name, self.repeat, self.aux, tableClass)
757
+
758
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
759
+ super(SubStruct, self).xmlWrite(xmlWriter, font, value, None, attrs)
760
+
761
+
762
+ class SubTable(Table):
763
+ def getConverter(self, tableType, lookupType):
764
+ tableClass = self.lookupTypes[tableType][lookupType]
765
+ return self.__class__(self.name, self.repeat, self.aux, tableClass)
766
+
767
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
768
+ super(SubTable, self).xmlWrite(xmlWriter, font, value, None, attrs)
769
+
770
+
771
+ class ExtSubTable(LTable, SubTable):
772
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
773
+ writer.Extension = True # actually, mere presence of the field flags it as an Ext Subtable writer.
774
+ Table.write(self, writer, font, tableDict, value, repeatIndex)
775
+
776
+
777
+ class FeatureParams(Table):
778
+ def getConverter(self, featureTag):
779
+ tableClass = self.featureParamTypes.get(featureTag, self.defaultFeatureParams)
780
+ return self.__class__(self.name, self.repeat, self.aux, tableClass)
781
+
782
+
783
+ class ValueFormat(IntValue):
784
+ staticSize = 2
785
+
786
+ def __init__(self, name, repeat, aux, tableClass=None, *, description=""):
787
+ BaseConverter.__init__(
788
+ self, name, repeat, aux, tableClass, description=description
789
+ )
790
+ self.which = "ValueFormat" + ("2" if name[-1] == "2" else "1")
791
+
792
+ def read(self, reader, font, tableDict):
793
+ format = reader.readUShort()
794
+ reader[self.which] = ValueRecordFactory(format)
795
+ return format
796
+
797
+ def write(self, writer, font, tableDict, format, repeatIndex=None):
798
+ writer.writeUShort(format)
799
+ writer[self.which] = ValueRecordFactory(format)
800
+
801
+
802
+ class ValueRecord(ValueFormat):
803
+ def getRecordSize(self, reader):
804
+ return 2 * len(reader[self.which])
805
+
806
+ def read(self, reader, font, tableDict):
807
+ return reader[self.which].readValueRecord(reader, font)
808
+
809
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
810
+ writer[self.which].writeValueRecord(writer, font, value)
811
+
812
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
813
+ if value is None:
814
+ pass # NULL table, ignore
815
+ else:
816
+ value.toXML(xmlWriter, font, self.name, attrs)
817
+
818
+ def xmlRead(self, attrs, content, font):
819
+ from .otBase import ValueRecord
820
+
821
+ value = ValueRecord()
822
+ value.fromXML(None, attrs, content, font)
823
+ return value
824
+
825
+
826
+ class AATLookup(BaseConverter):
827
+ BIN_SEARCH_HEADER_SIZE = 10
828
+
829
+ def __init__(self, name, repeat, aux, tableClass, *, description=""):
830
+ BaseConverter.__init__(
831
+ self, name, repeat, aux, tableClass, description=description
832
+ )
833
+ if issubclass(self.tableClass, SimpleValue):
834
+ self.converter = self.tableClass(name="Value", repeat=None, aux=None)
835
+ else:
836
+ self.converter = Table(
837
+ name="Value", repeat=None, aux=None, tableClass=self.tableClass
838
+ )
839
+
840
+ def read(self, reader, font, tableDict):
841
+ format = reader.readUShort()
842
+ if format == 0:
843
+ return self.readFormat0(reader, font)
844
+ elif format == 2:
845
+ return self.readFormat2(reader, font)
846
+ elif format == 4:
847
+ return self.readFormat4(reader, font)
848
+ elif format == 6:
849
+ return self.readFormat6(reader, font)
850
+ elif format == 8:
851
+ return self.readFormat8(reader, font)
852
+ else:
853
+ assert False, "unsupported lookup format: %d" % format
854
+
855
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
856
+ values = list(
857
+ sorted([(font.getGlyphID(glyph), val) for glyph, val in value.items()])
858
+ )
859
+ # TODO: Also implement format 4.
860
+ formats = list(
861
+ sorted(
862
+ filter(
863
+ None,
864
+ [
865
+ self.buildFormat0(writer, font, values),
866
+ self.buildFormat2(writer, font, values),
867
+ self.buildFormat6(writer, font, values),
868
+ self.buildFormat8(writer, font, values),
869
+ ],
870
+ )
871
+ )
872
+ )
873
+ # We use the format ID as secondary sort key to make the output
874
+ # deterministic when multiple formats have same encoded size.
875
+ dataSize, lookupFormat, writeMethod = formats[0]
876
+ pos = writer.getDataLength()
877
+ writeMethod()
878
+ actualSize = writer.getDataLength() - pos
879
+ assert (
880
+ actualSize == dataSize
881
+ ), "AATLookup format %d claimed to write %d bytes, but wrote %d" % (
882
+ lookupFormat,
883
+ dataSize,
884
+ actualSize,
885
+ )
886
+
887
+ @staticmethod
888
+ def writeBinSearchHeader(writer, numUnits, unitSize):
889
+ writer.writeUShort(unitSize)
890
+ writer.writeUShort(numUnits)
891
+ searchRange, entrySelector, rangeShift = getSearchRange(
892
+ n=numUnits, itemSize=unitSize
893
+ )
894
+ writer.writeUShort(searchRange)
895
+ writer.writeUShort(entrySelector)
896
+ writer.writeUShort(rangeShift)
897
+
898
+ def buildFormat0(self, writer, font, values):
899
+ numGlyphs = len(font.getGlyphOrder())
900
+ if len(values) != numGlyphs:
901
+ return None
902
+ valueSize = self.converter.staticSize
903
+ return (
904
+ 2 + numGlyphs * valueSize,
905
+ 0,
906
+ lambda: self.writeFormat0(writer, font, values),
907
+ )
908
+
909
+ def writeFormat0(self, writer, font, values):
910
+ writer.writeUShort(0)
911
+ for glyphID_, value in values:
912
+ self.converter.write(
913
+ writer, font, tableDict=None, value=value, repeatIndex=None
914
+ )
915
+
916
+ def buildFormat2(self, writer, font, values):
917
+ segStart, segValue = values[0]
918
+ segEnd = segStart
919
+ segments = []
920
+ for glyphID, curValue in values[1:]:
921
+ if glyphID != segEnd + 1 or curValue != segValue:
922
+ segments.append((segStart, segEnd, segValue))
923
+ segStart = segEnd = glyphID
924
+ segValue = curValue
925
+ else:
926
+ segEnd = glyphID
927
+ segments.append((segStart, segEnd, segValue))
928
+ valueSize = self.converter.staticSize
929
+ numUnits, unitSize = len(segments) + 1, valueSize + 4
930
+ return (
931
+ 2 + self.BIN_SEARCH_HEADER_SIZE + numUnits * unitSize,
932
+ 2,
933
+ lambda: self.writeFormat2(writer, font, segments),
934
+ )
935
+
936
+ def writeFormat2(self, writer, font, segments):
937
+ writer.writeUShort(2)
938
+ valueSize = self.converter.staticSize
939
+ numUnits, unitSize = len(segments), valueSize + 4
940
+ self.writeBinSearchHeader(writer, numUnits, unitSize)
941
+ for firstGlyph, lastGlyph, value in segments:
942
+ writer.writeUShort(lastGlyph)
943
+ writer.writeUShort(firstGlyph)
944
+ self.converter.write(
945
+ writer, font, tableDict=None, value=value, repeatIndex=None
946
+ )
947
+ writer.writeUShort(0xFFFF)
948
+ writer.writeUShort(0xFFFF)
949
+ writer.writeData(b"\x00" * valueSize)
950
+
951
+ def buildFormat6(self, writer, font, values):
952
+ valueSize = self.converter.staticSize
953
+ numUnits, unitSize = len(values), valueSize + 2
954
+ return (
955
+ 2 + self.BIN_SEARCH_HEADER_SIZE + (numUnits + 1) * unitSize,
956
+ 6,
957
+ lambda: self.writeFormat6(writer, font, values),
958
+ )
959
+
960
+ def writeFormat6(self, writer, font, values):
961
+ writer.writeUShort(6)
962
+ valueSize = self.converter.staticSize
963
+ numUnits, unitSize = len(values), valueSize + 2
964
+ self.writeBinSearchHeader(writer, numUnits, unitSize)
965
+ for glyphID, value in values:
966
+ writer.writeUShort(glyphID)
967
+ self.converter.write(
968
+ writer, font, tableDict=None, value=value, repeatIndex=None
969
+ )
970
+ writer.writeUShort(0xFFFF)
971
+ writer.writeData(b"\x00" * valueSize)
972
+
973
+ def buildFormat8(self, writer, font, values):
974
+ minGlyphID, maxGlyphID = values[0][0], values[-1][0]
975
+ if len(values) != maxGlyphID - minGlyphID + 1:
976
+ return None
977
+ valueSize = self.converter.staticSize
978
+ return (
979
+ 6 + len(values) * valueSize,
980
+ 8,
981
+ lambda: self.writeFormat8(writer, font, values),
982
+ )
983
+
984
+ def writeFormat8(self, writer, font, values):
985
+ firstGlyphID = values[0][0]
986
+ writer.writeUShort(8)
987
+ writer.writeUShort(firstGlyphID)
988
+ writer.writeUShort(len(values))
989
+ for _, value in values:
990
+ self.converter.write(
991
+ writer, font, tableDict=None, value=value, repeatIndex=None
992
+ )
993
+
994
+ def readFormat0(self, reader, font):
995
+ numGlyphs = len(font.getGlyphOrder())
996
+ data = self.converter.readArray(reader, font, tableDict=None, count=numGlyphs)
997
+ return {font.getGlyphName(k): value for k, value in enumerate(data)}
998
+
999
+ def readFormat2(self, reader, font):
1000
+ mapping = {}
1001
+ pos = reader.pos - 2 # start of table is at UShort for format
1002
+ unitSize, numUnits = reader.readUShort(), reader.readUShort()
1003
+ assert unitSize >= 4 + self.converter.staticSize, unitSize
1004
+ for i in range(numUnits):
1005
+ reader.seek(pos + i * unitSize + 12)
1006
+ last = reader.readUShort()
1007
+ first = reader.readUShort()
1008
+ value = self.converter.read(reader, font, tableDict=None)
1009
+ if last != 0xFFFF:
1010
+ for k in range(first, last + 1):
1011
+ mapping[font.getGlyphName(k)] = value
1012
+ return mapping
1013
+
1014
+ def readFormat4(self, reader, font):
1015
+ mapping = {}
1016
+ pos = reader.pos - 2 # start of table is at UShort for format
1017
+ unitSize = reader.readUShort()
1018
+ assert unitSize >= 6, unitSize
1019
+ for i in range(reader.readUShort()):
1020
+ reader.seek(pos + i * unitSize + 12)
1021
+ last = reader.readUShort()
1022
+ first = reader.readUShort()
1023
+ offset = reader.readUShort()
1024
+ if last != 0xFFFF:
1025
+ dataReader = reader.getSubReader(0) # relative to current position
1026
+ dataReader.seek(pos + offset) # relative to start of table
1027
+ data = self.converter.readArray(
1028
+ dataReader, font, tableDict=None, count=last - first + 1
1029
+ )
1030
+ for k, v in enumerate(data):
1031
+ mapping[font.getGlyphName(first + k)] = v
1032
+ return mapping
1033
+
1034
+ def readFormat6(self, reader, font):
1035
+ mapping = {}
1036
+ pos = reader.pos - 2 # start of table is at UShort for format
1037
+ unitSize = reader.readUShort()
1038
+ assert unitSize >= 2 + self.converter.staticSize, unitSize
1039
+ for i in range(reader.readUShort()):
1040
+ reader.seek(pos + i * unitSize + 12)
1041
+ glyphID = reader.readUShort()
1042
+ value = self.converter.read(reader, font, tableDict=None)
1043
+ if glyphID != 0xFFFF:
1044
+ mapping[font.getGlyphName(glyphID)] = value
1045
+ return mapping
1046
+
1047
+ def readFormat8(self, reader, font):
1048
+ first = reader.readUShort()
1049
+ count = reader.readUShort()
1050
+ data = self.converter.readArray(reader, font, tableDict=None, count=count)
1051
+ return {font.getGlyphName(first + k): value for (k, value) in enumerate(data)}
1052
+
1053
+ def xmlRead(self, attrs, content, font):
1054
+ value = {}
1055
+ for element in content:
1056
+ if isinstance(element, tuple):
1057
+ name, a, eltContent = element
1058
+ if name == "Lookup":
1059
+ value[a["glyph"]] = self.converter.xmlRead(a, eltContent, font)
1060
+ return value
1061
+
1062
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
1063
+ xmlWriter.begintag(name, attrs)
1064
+ xmlWriter.newline()
1065
+ for glyph, value in sorted(value.items()):
1066
+ self.converter.xmlWrite(
1067
+ xmlWriter, font, value=value, name="Lookup", attrs=[("glyph", glyph)]
1068
+ )
1069
+ xmlWriter.endtag(name)
1070
+ xmlWriter.newline()
1071
+
1072
+
1073
+ # The AAT 'ankr' table has an unusual structure: An offset to an AATLookup
1074
+ # followed by an offset to a glyph data table. Other than usual, the
1075
+ # offsets in the AATLookup are not relative to the beginning of
1076
+ # the beginning of the 'ankr' table, but relative to the glyph data table.
1077
+ # So, to find the anchor data for a glyph, one needs to add the offset
1078
+ # to the data table to the offset found in the AATLookup, and then use
1079
+ # the sum of these two offsets to find the actual data.
1080
+ class AATLookupWithDataOffset(BaseConverter):
1081
+ def read(self, reader, font, tableDict):
1082
+ lookupOffset = reader.readULong()
1083
+ dataOffset = reader.readULong()
1084
+ lookupReader = reader.getSubReader(lookupOffset)
1085
+ lookup = AATLookup("DataOffsets", None, None, UShort)
1086
+ offsets = lookup.read(lookupReader, font, tableDict)
1087
+ result = {}
1088
+ for glyph, offset in offsets.items():
1089
+ dataReader = reader.getSubReader(offset + dataOffset)
1090
+ item = self.tableClass()
1091
+ item.decompile(dataReader, font)
1092
+ result[glyph] = item
1093
+ return result
1094
+
1095
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
1096
+ # We do not work with OTTableWriter sub-writers because
1097
+ # the offsets in our AATLookup are relative to our data
1098
+ # table, for which we need to provide an offset value itself.
1099
+ # It might have been possible to somehow make a kludge for
1100
+ # performing this indirect offset computation directly inside
1101
+ # OTTableWriter. But this would have made the internal logic
1102
+ # of OTTableWriter even more complex than it already is,
1103
+ # so we decided to roll our own offset computation for the
1104
+ # contents of the AATLookup and associated data table.
1105
+ offsetByGlyph, offsetByData, dataLen = {}, {}, 0
1106
+ compiledData = []
1107
+ for glyph in sorted(value, key=font.getGlyphID):
1108
+ subWriter = OTTableWriter()
1109
+ value[glyph].compile(subWriter, font)
1110
+ data = subWriter.getAllData()
1111
+ offset = offsetByData.get(data, None)
1112
+ if offset == None:
1113
+ offset = dataLen
1114
+ dataLen = dataLen + len(data)
1115
+ offsetByData[data] = offset
1116
+ compiledData.append(data)
1117
+ offsetByGlyph[glyph] = offset
1118
+ # For calculating the offsets to our AATLookup and data table,
1119
+ # we can use the regular OTTableWriter infrastructure.
1120
+ lookupWriter = writer.getSubWriter()
1121
+ lookup = AATLookup("DataOffsets", None, None, UShort)
1122
+ lookup.write(lookupWriter, font, tableDict, offsetByGlyph, None)
1123
+
1124
+ dataWriter = writer.getSubWriter()
1125
+ writer.writeSubTable(lookupWriter, offsetSize=4)
1126
+ writer.writeSubTable(dataWriter, offsetSize=4)
1127
+ for d in compiledData:
1128
+ dataWriter.writeData(d)
1129
+
1130
+ def xmlRead(self, attrs, content, font):
1131
+ lookup = AATLookup("DataOffsets", None, None, self.tableClass)
1132
+ return lookup.xmlRead(attrs, content, font)
1133
+
1134
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
1135
+ lookup = AATLookup("DataOffsets", None, None, self.tableClass)
1136
+ lookup.xmlWrite(xmlWriter, font, value, name, attrs)
1137
+
1138
+
1139
+ class MorxSubtableConverter(BaseConverter):
1140
+ _PROCESSING_ORDERS = {
1141
+ # bits 30 and 28 of morx.CoverageFlags; see morx spec
1142
+ (False, False): "LayoutOrder",
1143
+ (True, False): "ReversedLayoutOrder",
1144
+ (False, True): "LogicalOrder",
1145
+ (True, True): "ReversedLogicalOrder",
1146
+ }
1147
+
1148
+ _PROCESSING_ORDERS_REVERSED = {val: key for key, val in _PROCESSING_ORDERS.items()}
1149
+
1150
+ def __init__(self, name, repeat, aux, tableClass=None, *, description=""):
1151
+ BaseConverter.__init__(
1152
+ self, name, repeat, aux, tableClass, description=description
1153
+ )
1154
+
1155
+ def _setTextDirectionFromCoverageFlags(self, flags, subtable):
1156
+ if (flags & 0x20) != 0:
1157
+ subtable.TextDirection = "Any"
1158
+ elif (flags & 0x80) != 0:
1159
+ subtable.TextDirection = "Vertical"
1160
+ else:
1161
+ subtable.TextDirection = "Horizontal"
1162
+
1163
+ def read(self, reader, font, tableDict):
1164
+ pos = reader.pos
1165
+ m = MorxSubtable()
1166
+ m.StructLength = reader.readULong()
1167
+ flags = reader.readUInt8()
1168
+ orderKey = ((flags & 0x40) != 0, (flags & 0x10) != 0)
1169
+ m.ProcessingOrder = self._PROCESSING_ORDERS[orderKey]
1170
+ self._setTextDirectionFromCoverageFlags(flags, m)
1171
+ m.Reserved = reader.readUShort()
1172
+ m.Reserved |= (flags & 0xF) << 16
1173
+ m.MorphType = reader.readUInt8()
1174
+ m.SubFeatureFlags = reader.readULong()
1175
+ tableClass = lookupTypes["morx"].get(m.MorphType)
1176
+ if tableClass is None:
1177
+ assert False, "unsupported 'morx' lookup type %s" % m.MorphType
1178
+ # To decode AAT ligatures, we need to know the subtable size.
1179
+ # The easiest way to pass this along is to create a new reader
1180
+ # that works on just the subtable as its data.
1181
+ headerLength = reader.pos - pos
1182
+ data = reader.data[reader.pos : reader.pos + m.StructLength - headerLength]
1183
+ assert len(data) == m.StructLength - headerLength
1184
+ subReader = OTTableReader(data=data, tableTag=reader.tableTag)
1185
+ m.SubStruct = tableClass()
1186
+ m.SubStruct.decompile(subReader, font)
1187
+ reader.seek(pos + m.StructLength)
1188
+ return m
1189
+
1190
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
1191
+ xmlWriter.begintag(name, attrs)
1192
+ xmlWriter.newline()
1193
+ xmlWriter.comment("StructLength=%d" % value.StructLength)
1194
+ xmlWriter.newline()
1195
+ xmlWriter.simpletag("TextDirection", value=value.TextDirection)
1196
+ xmlWriter.newline()
1197
+ xmlWriter.simpletag("ProcessingOrder", value=value.ProcessingOrder)
1198
+ xmlWriter.newline()
1199
+ if value.Reserved != 0:
1200
+ xmlWriter.simpletag("Reserved", value="0x%04x" % value.Reserved)
1201
+ xmlWriter.newline()
1202
+ xmlWriter.comment("MorphType=%d" % value.MorphType)
1203
+ xmlWriter.newline()
1204
+ xmlWriter.simpletag("SubFeatureFlags", value="0x%08x" % value.SubFeatureFlags)
1205
+ xmlWriter.newline()
1206
+ value.SubStruct.toXML(xmlWriter, font)
1207
+ xmlWriter.endtag(name)
1208
+ xmlWriter.newline()
1209
+
1210
+ def xmlRead(self, attrs, content, font):
1211
+ m = MorxSubtable()
1212
+ covFlags = 0
1213
+ m.Reserved = 0
1214
+ for eltName, eltAttrs, eltContent in filter(istuple, content):
1215
+ if eltName == "CoverageFlags":
1216
+ # Only in XML from old versions of fonttools.
1217
+ covFlags = safeEval(eltAttrs["value"])
1218
+ orderKey = ((covFlags & 0x40) != 0, (covFlags & 0x10) != 0)
1219
+ m.ProcessingOrder = self._PROCESSING_ORDERS[orderKey]
1220
+ self._setTextDirectionFromCoverageFlags(covFlags, m)
1221
+ elif eltName == "ProcessingOrder":
1222
+ m.ProcessingOrder = eltAttrs["value"]
1223
+ assert m.ProcessingOrder in self._PROCESSING_ORDERS_REVERSED, (
1224
+ "unknown ProcessingOrder: %s" % m.ProcessingOrder
1225
+ )
1226
+ elif eltName == "TextDirection":
1227
+ m.TextDirection = eltAttrs["value"]
1228
+ assert m.TextDirection in {"Horizontal", "Vertical", "Any"}, (
1229
+ "unknown TextDirection %s" % m.TextDirection
1230
+ )
1231
+ elif eltName == "Reserved":
1232
+ m.Reserved = safeEval(eltAttrs["value"])
1233
+ elif eltName == "SubFeatureFlags":
1234
+ m.SubFeatureFlags = safeEval(eltAttrs["value"])
1235
+ elif eltName.endswith("Morph"):
1236
+ m.fromXML(eltName, eltAttrs, eltContent, font)
1237
+ else:
1238
+ assert False, eltName
1239
+ m.Reserved = (covFlags & 0xF) << 16 | m.Reserved
1240
+ return m
1241
+
1242
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
1243
+ covFlags = (value.Reserved & 0x000F0000) >> 16
1244
+ reverseOrder, logicalOrder = self._PROCESSING_ORDERS_REVERSED[
1245
+ value.ProcessingOrder
1246
+ ]
1247
+ covFlags |= 0x80 if value.TextDirection == "Vertical" else 0
1248
+ covFlags |= 0x40 if reverseOrder else 0
1249
+ covFlags |= 0x20 if value.TextDirection == "Any" else 0
1250
+ covFlags |= 0x10 if logicalOrder else 0
1251
+ value.CoverageFlags = covFlags
1252
+ lengthIndex = len(writer.items)
1253
+ before = writer.getDataLength()
1254
+ value.StructLength = 0xDEADBEEF
1255
+ # The high nibble of value.Reserved is actuallly encoded
1256
+ # into coverageFlags, so we need to clear it here.
1257
+ origReserved = value.Reserved # including high nibble
1258
+ value.Reserved = value.Reserved & 0xFFFF # without high nibble
1259
+ value.compile(writer, font)
1260
+ value.Reserved = origReserved # restore original value
1261
+ assert writer.items[lengthIndex] == b"\xde\xad\xbe\xef"
1262
+ length = writer.getDataLength() - before
1263
+ writer.items[lengthIndex] = struct.pack(">L", length)
1264
+
1265
+
1266
+ # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#ExtendedStateHeader
1267
+ # TODO: Untangle the implementation of the various lookup-specific formats.
1268
+ class STXHeader(BaseConverter):
1269
+ def __init__(self, name, repeat, aux, tableClass, *, description=""):
1270
+ BaseConverter.__init__(
1271
+ self, name, repeat, aux, tableClass, description=description
1272
+ )
1273
+ assert issubclass(self.tableClass, AATAction)
1274
+ self.classLookup = AATLookup("GlyphClasses", None, None, UShort)
1275
+ if issubclass(self.tableClass, ContextualMorphAction):
1276
+ self.perGlyphLookup = AATLookup("PerGlyphLookup", None, None, GlyphID)
1277
+ else:
1278
+ self.perGlyphLookup = None
1279
+
1280
+ def read(self, reader, font, tableDict):
1281
+ table = AATStateTable()
1282
+ pos = reader.pos
1283
+ classTableReader = reader.getSubReader(0)
1284
+ stateArrayReader = reader.getSubReader(0)
1285
+ entryTableReader = reader.getSubReader(0)
1286
+ actionReader = None
1287
+ ligaturesReader = None
1288
+ table.GlyphClassCount = reader.readULong()
1289
+ classTableReader.seek(pos + reader.readULong())
1290
+ stateArrayReader.seek(pos + reader.readULong())
1291
+ entryTableReader.seek(pos + reader.readULong())
1292
+ if self.perGlyphLookup is not None:
1293
+ perGlyphTableReader = reader.getSubReader(0)
1294
+ perGlyphTableReader.seek(pos + reader.readULong())
1295
+ if issubclass(self.tableClass, LigatureMorphAction):
1296
+ actionReader = reader.getSubReader(0)
1297
+ actionReader.seek(pos + reader.readULong())
1298
+ ligComponentReader = reader.getSubReader(0)
1299
+ ligComponentReader.seek(pos + reader.readULong())
1300
+ ligaturesReader = reader.getSubReader(0)
1301
+ ligaturesReader.seek(pos + reader.readULong())
1302
+ numLigComponents = (ligaturesReader.pos - ligComponentReader.pos) // 2
1303
+ assert numLigComponents >= 0
1304
+ table.LigComponents = ligComponentReader.readUShortArray(numLigComponents)
1305
+ table.Ligatures = self._readLigatures(ligaturesReader, font)
1306
+ elif issubclass(self.tableClass, InsertionMorphAction):
1307
+ actionReader = reader.getSubReader(0)
1308
+ actionReader.seek(pos + reader.readULong())
1309
+ table.GlyphClasses = self.classLookup.read(classTableReader, font, tableDict)
1310
+ numStates = int(
1311
+ (entryTableReader.pos - stateArrayReader.pos) / (table.GlyphClassCount * 2)
1312
+ )
1313
+ for stateIndex in range(numStates):
1314
+ state = AATState()
1315
+ table.States.append(state)
1316
+ for glyphClass in range(table.GlyphClassCount):
1317
+ entryIndex = stateArrayReader.readUShort()
1318
+ state.Transitions[glyphClass] = self._readTransition(
1319
+ entryTableReader, entryIndex, font, actionReader
1320
+ )
1321
+ if self.perGlyphLookup is not None:
1322
+ table.PerGlyphLookups = self._readPerGlyphLookups(
1323
+ table, perGlyphTableReader, font
1324
+ )
1325
+ return table
1326
+
1327
+ def _readTransition(self, reader, entryIndex, font, actionReader):
1328
+ transition = self.tableClass()
1329
+ entryReader = reader.getSubReader(
1330
+ reader.pos + entryIndex * transition.staticSize
1331
+ )
1332
+ transition.decompile(entryReader, font, actionReader)
1333
+ return transition
1334
+
1335
+ def _readLigatures(self, reader, font):
1336
+ limit = len(reader.data)
1337
+ numLigatureGlyphs = (limit - reader.pos) // 2
1338
+ return font.getGlyphNameMany(reader.readUShortArray(numLigatureGlyphs))
1339
+
1340
+ def _countPerGlyphLookups(self, table):
1341
+ # Somewhat annoyingly, the morx table does not encode
1342
+ # the size of the per-glyph table. So we need to find
1343
+ # the maximum value that MorphActions use as index
1344
+ # into this table.
1345
+ numLookups = 0
1346
+ for state in table.States:
1347
+ for t in state.Transitions.values():
1348
+ if isinstance(t, ContextualMorphAction):
1349
+ if t.MarkIndex != 0xFFFF:
1350
+ numLookups = max(numLookups, t.MarkIndex + 1)
1351
+ if t.CurrentIndex != 0xFFFF:
1352
+ numLookups = max(numLookups, t.CurrentIndex + 1)
1353
+ return numLookups
1354
+
1355
+ def _readPerGlyphLookups(self, table, reader, font):
1356
+ pos = reader.pos
1357
+ lookups = []
1358
+ for _ in range(self._countPerGlyphLookups(table)):
1359
+ lookupReader = reader.getSubReader(0)
1360
+ lookupReader.seek(pos + reader.readULong())
1361
+ lookups.append(self.perGlyphLookup.read(lookupReader, font, {}))
1362
+ return lookups
1363
+
1364
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
1365
+ glyphClassWriter = OTTableWriter()
1366
+ self.classLookup.write(
1367
+ glyphClassWriter, font, tableDict, value.GlyphClasses, repeatIndex=None
1368
+ )
1369
+ glyphClassData = pad(glyphClassWriter.getAllData(), 2)
1370
+ glyphClassCount = max(value.GlyphClasses.values()) + 1
1371
+ glyphClassTableOffset = 16 # size of STXHeader
1372
+ if self.perGlyphLookup is not None:
1373
+ glyphClassTableOffset += 4
1374
+
1375
+ glyphClassTableOffset += self.tableClass.actionHeaderSize
1376
+ actionData, actionIndex = self.tableClass.compileActions(font, value.States)
1377
+ stateArrayData, entryTableData = self._compileStates(
1378
+ font, value.States, glyphClassCount, actionIndex
1379
+ )
1380
+ stateArrayOffset = glyphClassTableOffset + len(glyphClassData)
1381
+ entryTableOffset = stateArrayOffset + len(stateArrayData)
1382
+ perGlyphOffset = entryTableOffset + len(entryTableData)
1383
+ perGlyphData = pad(self._compilePerGlyphLookups(value, font), 4)
1384
+ if actionData is not None:
1385
+ actionOffset = entryTableOffset + len(entryTableData)
1386
+ else:
1387
+ actionOffset = None
1388
+
1389
+ ligaturesOffset, ligComponentsOffset = None, None
1390
+ ligComponentsData = self._compileLigComponents(value, font)
1391
+ ligaturesData = self._compileLigatures(value, font)
1392
+ if ligComponentsData is not None:
1393
+ assert len(perGlyphData) == 0
1394
+ ligComponentsOffset = actionOffset + len(actionData)
1395
+ ligaturesOffset = ligComponentsOffset + len(ligComponentsData)
1396
+
1397
+ writer.writeULong(glyphClassCount)
1398
+ writer.writeULong(glyphClassTableOffset)
1399
+ writer.writeULong(stateArrayOffset)
1400
+ writer.writeULong(entryTableOffset)
1401
+ if self.perGlyphLookup is not None:
1402
+ writer.writeULong(perGlyphOffset)
1403
+ if actionOffset is not None:
1404
+ writer.writeULong(actionOffset)
1405
+ if ligComponentsOffset is not None:
1406
+ writer.writeULong(ligComponentsOffset)
1407
+ writer.writeULong(ligaturesOffset)
1408
+ writer.writeData(glyphClassData)
1409
+ writer.writeData(stateArrayData)
1410
+ writer.writeData(entryTableData)
1411
+ writer.writeData(perGlyphData)
1412
+ if actionData is not None:
1413
+ writer.writeData(actionData)
1414
+ if ligComponentsData is not None:
1415
+ writer.writeData(ligComponentsData)
1416
+ if ligaturesData is not None:
1417
+ writer.writeData(ligaturesData)
1418
+
1419
+ def _compileStates(self, font, states, glyphClassCount, actionIndex):
1420
+ stateArrayWriter = OTTableWriter()
1421
+ entries, entryIDs = [], {}
1422
+ for state in states:
1423
+ for glyphClass in range(glyphClassCount):
1424
+ transition = state.Transitions[glyphClass]
1425
+ entryWriter = OTTableWriter()
1426
+ transition.compile(entryWriter, font, actionIndex)
1427
+ entryData = entryWriter.getAllData()
1428
+ assert (
1429
+ len(entryData) == transition.staticSize
1430
+ ), "%s has staticSize %d, " "but actually wrote %d bytes" % (
1431
+ repr(transition),
1432
+ transition.staticSize,
1433
+ len(entryData),
1434
+ )
1435
+ entryIndex = entryIDs.get(entryData)
1436
+ if entryIndex is None:
1437
+ entryIndex = len(entries)
1438
+ entryIDs[entryData] = entryIndex
1439
+ entries.append(entryData)
1440
+ stateArrayWriter.writeUShort(entryIndex)
1441
+ stateArrayData = pad(stateArrayWriter.getAllData(), 4)
1442
+ entryTableData = pad(bytesjoin(entries), 4)
1443
+ return stateArrayData, entryTableData
1444
+
1445
+ def _compilePerGlyphLookups(self, table, font):
1446
+ if self.perGlyphLookup is None:
1447
+ return b""
1448
+ numLookups = self._countPerGlyphLookups(table)
1449
+ assert len(table.PerGlyphLookups) == numLookups, (
1450
+ "len(AATStateTable.PerGlyphLookups) is %d, "
1451
+ "but the actions inside the table refer to %d"
1452
+ % (len(table.PerGlyphLookups), numLookups)
1453
+ )
1454
+ writer = OTTableWriter()
1455
+ for lookup in table.PerGlyphLookups:
1456
+ lookupWriter = writer.getSubWriter()
1457
+ self.perGlyphLookup.write(lookupWriter, font, {}, lookup, None)
1458
+ writer.writeSubTable(lookupWriter, offsetSize=4)
1459
+ return writer.getAllData()
1460
+
1461
+ def _compileLigComponents(self, table, font):
1462
+ if not hasattr(table, "LigComponents"):
1463
+ return None
1464
+ writer = OTTableWriter()
1465
+ for component in table.LigComponents:
1466
+ writer.writeUShort(component)
1467
+ return writer.getAllData()
1468
+
1469
+ def _compileLigatures(self, table, font):
1470
+ if not hasattr(table, "Ligatures"):
1471
+ return None
1472
+ writer = OTTableWriter()
1473
+ for glyphName in table.Ligatures:
1474
+ writer.writeUShort(font.getGlyphID(glyphName))
1475
+ return writer.getAllData()
1476
+
1477
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
1478
+ xmlWriter.begintag(name, attrs)
1479
+ xmlWriter.newline()
1480
+ xmlWriter.comment("GlyphClassCount=%s" % value.GlyphClassCount)
1481
+ xmlWriter.newline()
1482
+ for g, klass in sorted(value.GlyphClasses.items()):
1483
+ xmlWriter.simpletag("GlyphClass", glyph=g, value=klass)
1484
+ xmlWriter.newline()
1485
+ for stateIndex, state in enumerate(value.States):
1486
+ xmlWriter.begintag("State", index=stateIndex)
1487
+ xmlWriter.newline()
1488
+ for glyphClass, trans in sorted(state.Transitions.items()):
1489
+ trans.toXML(
1490
+ xmlWriter,
1491
+ font=font,
1492
+ attrs={"onGlyphClass": glyphClass},
1493
+ name="Transition",
1494
+ )
1495
+ xmlWriter.endtag("State")
1496
+ xmlWriter.newline()
1497
+ for i, lookup in enumerate(value.PerGlyphLookups):
1498
+ xmlWriter.begintag("PerGlyphLookup", index=i)
1499
+ xmlWriter.newline()
1500
+ for glyph, val in sorted(lookup.items()):
1501
+ xmlWriter.simpletag("Lookup", glyph=glyph, value=val)
1502
+ xmlWriter.newline()
1503
+ xmlWriter.endtag("PerGlyphLookup")
1504
+ xmlWriter.newline()
1505
+ if hasattr(value, "LigComponents"):
1506
+ xmlWriter.begintag("LigComponents")
1507
+ xmlWriter.newline()
1508
+ for i, val in enumerate(getattr(value, "LigComponents")):
1509
+ xmlWriter.simpletag("LigComponent", index=i, value=val)
1510
+ xmlWriter.newline()
1511
+ xmlWriter.endtag("LigComponents")
1512
+ xmlWriter.newline()
1513
+ self._xmlWriteLigatures(xmlWriter, font, value, name, attrs)
1514
+ xmlWriter.endtag(name)
1515
+ xmlWriter.newline()
1516
+
1517
+ def _xmlWriteLigatures(self, xmlWriter, font, value, name, attrs):
1518
+ if not hasattr(value, "Ligatures"):
1519
+ return
1520
+ xmlWriter.begintag("Ligatures")
1521
+ xmlWriter.newline()
1522
+ for i, g in enumerate(getattr(value, "Ligatures")):
1523
+ xmlWriter.simpletag("Ligature", index=i, glyph=g)
1524
+ xmlWriter.newline()
1525
+ xmlWriter.endtag("Ligatures")
1526
+ xmlWriter.newline()
1527
+
1528
+ def xmlRead(self, attrs, content, font):
1529
+ table = AATStateTable()
1530
+ for eltName, eltAttrs, eltContent in filter(istuple, content):
1531
+ if eltName == "GlyphClass":
1532
+ glyph = eltAttrs["glyph"]
1533
+ value = eltAttrs["value"]
1534
+ table.GlyphClasses[glyph] = safeEval(value)
1535
+ elif eltName == "State":
1536
+ state = self._xmlReadState(eltAttrs, eltContent, font)
1537
+ table.States.append(state)
1538
+ elif eltName == "PerGlyphLookup":
1539
+ lookup = self.perGlyphLookup.xmlRead(eltAttrs, eltContent, font)
1540
+ table.PerGlyphLookups.append(lookup)
1541
+ elif eltName == "LigComponents":
1542
+ table.LigComponents = self._xmlReadLigComponents(
1543
+ eltAttrs, eltContent, font
1544
+ )
1545
+ elif eltName == "Ligatures":
1546
+ table.Ligatures = self._xmlReadLigatures(eltAttrs, eltContent, font)
1547
+ table.GlyphClassCount = max(table.GlyphClasses.values()) + 1
1548
+ return table
1549
+
1550
+ def _xmlReadState(self, attrs, content, font):
1551
+ state = AATState()
1552
+ for eltName, eltAttrs, eltContent in filter(istuple, content):
1553
+ if eltName == "Transition":
1554
+ glyphClass = safeEval(eltAttrs["onGlyphClass"])
1555
+ transition = self.tableClass()
1556
+ transition.fromXML(eltName, eltAttrs, eltContent, font)
1557
+ state.Transitions[glyphClass] = transition
1558
+ return state
1559
+
1560
+ def _xmlReadLigComponents(self, attrs, content, font):
1561
+ ligComponents = []
1562
+ for eltName, eltAttrs, _eltContent in filter(istuple, content):
1563
+ if eltName == "LigComponent":
1564
+ ligComponents.append(safeEval(eltAttrs["value"]))
1565
+ return ligComponents
1566
+
1567
+ def _xmlReadLigatures(self, attrs, content, font):
1568
+ ligs = []
1569
+ for eltName, eltAttrs, _eltContent in filter(istuple, content):
1570
+ if eltName == "Ligature":
1571
+ ligs.append(eltAttrs["glyph"])
1572
+ return ligs
1573
+
1574
+
1575
+ class CIDGlyphMap(BaseConverter):
1576
+ def read(self, reader, font, tableDict):
1577
+ numCIDs = reader.readUShort()
1578
+ result = {}
1579
+ for cid, glyphID in enumerate(reader.readUShortArray(numCIDs)):
1580
+ if glyphID != 0xFFFF:
1581
+ result[cid] = font.getGlyphName(glyphID)
1582
+ return result
1583
+
1584
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
1585
+ items = {cid: font.getGlyphID(glyph) for cid, glyph in value.items()}
1586
+ count = max(items) + 1 if items else 0
1587
+ writer.writeUShort(count)
1588
+ for cid in range(count):
1589
+ writer.writeUShort(items.get(cid, 0xFFFF))
1590
+
1591
+ def xmlRead(self, attrs, content, font):
1592
+ result = {}
1593
+ for eName, eAttrs, _eContent in filter(istuple, content):
1594
+ if eName == "CID":
1595
+ result[safeEval(eAttrs["cid"])] = eAttrs["glyph"].strip()
1596
+ return result
1597
+
1598
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
1599
+ xmlWriter.begintag(name, attrs)
1600
+ xmlWriter.newline()
1601
+ for cid, glyph in sorted(value.items()):
1602
+ if glyph is not None and glyph != 0xFFFF:
1603
+ xmlWriter.simpletag("CID", cid=cid, glyph=glyph)
1604
+ xmlWriter.newline()
1605
+ xmlWriter.endtag(name)
1606
+ xmlWriter.newline()
1607
+
1608
+
1609
+ class GlyphCIDMap(BaseConverter):
1610
+ def read(self, reader, font, tableDict):
1611
+ glyphOrder = font.getGlyphOrder()
1612
+ count = reader.readUShort()
1613
+ cids = reader.readUShortArray(count)
1614
+ if count > len(glyphOrder):
1615
+ log.warning(
1616
+ "GlyphCIDMap has %d elements, "
1617
+ "but the font has only %d glyphs; "
1618
+ "ignoring the rest" % (count, len(glyphOrder))
1619
+ )
1620
+ result = {}
1621
+ for glyphID in range(min(len(cids), len(glyphOrder))):
1622
+ cid = cids[glyphID]
1623
+ if cid != 0xFFFF:
1624
+ result[glyphOrder[glyphID]] = cid
1625
+ return result
1626
+
1627
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
1628
+ items = {
1629
+ font.getGlyphID(g): cid
1630
+ for g, cid in value.items()
1631
+ if cid is not None and cid != 0xFFFF
1632
+ }
1633
+ count = max(items) + 1 if items else 0
1634
+ writer.writeUShort(count)
1635
+ for glyphID in range(count):
1636
+ writer.writeUShort(items.get(glyphID, 0xFFFF))
1637
+
1638
+ def xmlRead(self, attrs, content, font):
1639
+ result = {}
1640
+ for eName, eAttrs, _eContent in filter(istuple, content):
1641
+ if eName == "CID":
1642
+ result[eAttrs["glyph"]] = safeEval(eAttrs["value"])
1643
+ return result
1644
+
1645
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
1646
+ xmlWriter.begintag(name, attrs)
1647
+ xmlWriter.newline()
1648
+ for glyph, cid in sorted(value.items()):
1649
+ if cid is not None and cid != 0xFFFF:
1650
+ xmlWriter.simpletag("CID", glyph=glyph, value=cid)
1651
+ xmlWriter.newline()
1652
+ xmlWriter.endtag(name)
1653
+ xmlWriter.newline()
1654
+
1655
+
1656
+ class DeltaValue(BaseConverter):
1657
+ def read(self, reader, font, tableDict):
1658
+ StartSize = tableDict["StartSize"]
1659
+ EndSize = tableDict["EndSize"]
1660
+ DeltaFormat = tableDict["DeltaFormat"]
1661
+ assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
1662
+ nItems = EndSize - StartSize + 1
1663
+ nBits = 1 << DeltaFormat
1664
+ minusOffset = 1 << nBits
1665
+ mask = (1 << nBits) - 1
1666
+ signMask = 1 << (nBits - 1)
1667
+
1668
+ DeltaValue = []
1669
+ tmp, shift = 0, 0
1670
+ for i in range(nItems):
1671
+ if shift == 0:
1672
+ tmp, shift = reader.readUShort(), 16
1673
+ shift = shift - nBits
1674
+ value = (tmp >> shift) & mask
1675
+ if value & signMask:
1676
+ value = value - minusOffset
1677
+ DeltaValue.append(value)
1678
+ return DeltaValue
1679
+
1680
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
1681
+ StartSize = tableDict["StartSize"]
1682
+ EndSize = tableDict["EndSize"]
1683
+ DeltaFormat = tableDict["DeltaFormat"]
1684
+ DeltaValue = value
1685
+ assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
1686
+ nItems = EndSize - StartSize + 1
1687
+ nBits = 1 << DeltaFormat
1688
+ assert len(DeltaValue) == nItems
1689
+ mask = (1 << nBits) - 1
1690
+
1691
+ tmp, shift = 0, 16
1692
+ for value in DeltaValue:
1693
+ shift = shift - nBits
1694
+ tmp = tmp | ((value & mask) << shift)
1695
+ if shift == 0:
1696
+ writer.writeUShort(tmp)
1697
+ tmp, shift = 0, 16
1698
+ if shift != 16:
1699
+ writer.writeUShort(tmp)
1700
+
1701
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
1702
+ xmlWriter.simpletag(name, attrs + [("value", value)])
1703
+ xmlWriter.newline()
1704
+
1705
+ def xmlRead(self, attrs, content, font):
1706
+ return safeEval(attrs["value"])
1707
+
1708
+
1709
+ class VarIdxMapValue(BaseConverter):
1710
+ def read(self, reader, font, tableDict):
1711
+ fmt = tableDict["EntryFormat"]
1712
+ nItems = tableDict["MappingCount"]
1713
+
1714
+ innerBits = 1 + (fmt & 0x000F)
1715
+ innerMask = (1 << innerBits) - 1
1716
+ outerMask = 0xFFFFFFFF - innerMask
1717
+ outerShift = 16 - innerBits
1718
+
1719
+ entrySize = 1 + ((fmt & 0x0030) >> 4)
1720
+ readArray = {
1721
+ 1: reader.readUInt8Array,
1722
+ 2: reader.readUShortArray,
1723
+ 3: reader.readUInt24Array,
1724
+ 4: reader.readULongArray,
1725
+ }[entrySize]
1726
+
1727
+ return [
1728
+ (((raw & outerMask) << outerShift) | (raw & innerMask))
1729
+ for raw in readArray(nItems)
1730
+ ]
1731
+
1732
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
1733
+ fmt = tableDict["EntryFormat"]
1734
+ mapping = value
1735
+ writer["MappingCount"].setValue(len(mapping))
1736
+
1737
+ innerBits = 1 + (fmt & 0x000F)
1738
+ innerMask = (1 << innerBits) - 1
1739
+ outerShift = 16 - innerBits
1740
+
1741
+ entrySize = 1 + ((fmt & 0x0030) >> 4)
1742
+ writeArray = {
1743
+ 1: writer.writeUInt8Array,
1744
+ 2: writer.writeUShortArray,
1745
+ 3: writer.writeUInt24Array,
1746
+ 4: writer.writeULongArray,
1747
+ }[entrySize]
1748
+
1749
+ writeArray(
1750
+ [
1751
+ (((idx & 0xFFFF0000) >> outerShift) | (idx & innerMask))
1752
+ for idx in mapping
1753
+ ]
1754
+ )
1755
+
1756
+
1757
+ class VarDataValue(BaseConverter):
1758
+ def read(self, reader, font, tableDict):
1759
+ values = []
1760
+
1761
+ regionCount = tableDict["VarRegionCount"]
1762
+ wordCount = tableDict["NumShorts"]
1763
+
1764
+ # https://github.com/fonttools/fonttools/issues/2279
1765
+ longWords = bool(wordCount & 0x8000)
1766
+ wordCount = wordCount & 0x7FFF
1767
+
1768
+ if longWords:
1769
+ readBigArray, readSmallArray = reader.readLongArray, reader.readShortArray
1770
+ else:
1771
+ readBigArray, readSmallArray = reader.readShortArray, reader.readInt8Array
1772
+
1773
+ n1, n2 = min(regionCount, wordCount), max(regionCount, wordCount)
1774
+ values.extend(readBigArray(n1))
1775
+ values.extend(readSmallArray(n2 - n1))
1776
+ if n2 > regionCount: # Padding
1777
+ del values[regionCount:]
1778
+
1779
+ return values
1780
+
1781
+ def write(self, writer, font, tableDict, values, repeatIndex=None):
1782
+ regionCount = tableDict["VarRegionCount"]
1783
+ wordCount = tableDict["NumShorts"]
1784
+
1785
+ # https://github.com/fonttools/fonttools/issues/2279
1786
+ longWords = bool(wordCount & 0x8000)
1787
+ wordCount = wordCount & 0x7FFF
1788
+
1789
+ (writeBigArray, writeSmallArray) = {
1790
+ False: (writer.writeShortArray, writer.writeInt8Array),
1791
+ True: (writer.writeLongArray, writer.writeShortArray),
1792
+ }[longWords]
1793
+
1794
+ n1, n2 = min(regionCount, wordCount), max(regionCount, wordCount)
1795
+ writeBigArray(values[:n1])
1796
+ writeSmallArray(values[n1:regionCount])
1797
+ if n2 > regionCount: # Padding
1798
+ writer.writeSmallArray([0] * (n2 - regionCount))
1799
+
1800
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
1801
+ xmlWriter.simpletag(name, attrs + [("value", value)])
1802
+ xmlWriter.newline()
1803
+
1804
+ def xmlRead(self, attrs, content, font):
1805
+ return safeEval(attrs["value"])
1806
+
1807
+
1808
+ class TupleValues:
1809
+ def read(self, data, font):
1810
+ return TupleVariation.decompileDeltas_(None, data)[0]
1811
+
1812
+ def write(self, writer, font, tableDict, values, repeatIndex=None):
1813
+ optimizeSpeed = font.cfg[OPTIMIZE_FONT_SPEED]
1814
+ return bytes(
1815
+ TupleVariation.compileDeltaValues_(values, optimizeSize=not optimizeSpeed)
1816
+ )
1817
+
1818
+ def xmlRead(self, attrs, content, font):
1819
+ return safeEval(attrs["value"])
1820
+
1821
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
1822
+ xmlWriter.simpletag(name, attrs + [("value", value)])
1823
+ xmlWriter.newline()
1824
+
1825
+
1826
+ class CFF2Index(BaseConverter):
1827
+ def __init__(
1828
+ self,
1829
+ name,
1830
+ repeat,
1831
+ aux,
1832
+ tableClass=None,
1833
+ *,
1834
+ itemClass=None,
1835
+ itemConverterClass=None,
1836
+ description="",
1837
+ ):
1838
+ BaseConverter.__init__(
1839
+ self, name, repeat, aux, tableClass, description=description
1840
+ )
1841
+ self._itemClass = itemClass
1842
+ self._converter = (
1843
+ itemConverterClass() if itemConverterClass is not None else None
1844
+ )
1845
+
1846
+ def read(self, reader, font, tableDict):
1847
+ count = reader.readULong()
1848
+ if count == 0:
1849
+ return []
1850
+ offSize = reader.readUInt8()
1851
+
1852
+ def getReadArray(reader, offSize):
1853
+ return {
1854
+ 1: reader.readUInt8Array,
1855
+ 2: reader.readUShortArray,
1856
+ 3: reader.readUInt24Array,
1857
+ 4: reader.readULongArray,
1858
+ }[offSize]
1859
+
1860
+ readArray = getReadArray(reader, offSize)
1861
+
1862
+ lazy = font.lazy is not False and count > 8
1863
+ if not lazy:
1864
+ offsets = readArray(count + 1)
1865
+ items = []
1866
+ lastOffset = offsets.pop(0)
1867
+ reader.readData(lastOffset - 1) # In case first offset is not 1
1868
+
1869
+ for offset in offsets:
1870
+ assert lastOffset <= offset
1871
+ item = reader.readData(offset - lastOffset)
1872
+
1873
+ if self._itemClass is not None:
1874
+ obj = self._itemClass()
1875
+ obj.decompile(item, font, reader.localState)
1876
+ item = obj
1877
+ elif self._converter is not None:
1878
+ item = self._converter.read(item, font)
1879
+
1880
+ items.append(item)
1881
+ lastOffset = offset
1882
+ return items
1883
+ else:
1884
+
1885
+ def get_read_item():
1886
+ reader_copy = reader.copy()
1887
+ offset_pos = reader.pos
1888
+ data_pos = offset_pos + (count + 1) * offSize - 1
1889
+ readArray = getReadArray(reader_copy, offSize)
1890
+
1891
+ def read_item(i):
1892
+ reader_copy.seek(offset_pos + i * offSize)
1893
+ offsets = readArray(2)
1894
+ reader_copy.seek(data_pos + offsets[0])
1895
+ item = reader_copy.readData(offsets[1] - offsets[0])
1896
+
1897
+ if self._itemClass is not None:
1898
+ obj = self._itemClass()
1899
+ obj.decompile(item, font, reader_copy.localState)
1900
+ item = obj
1901
+ elif self._converter is not None:
1902
+ item = self._converter.read(item, font)
1903
+ return item
1904
+
1905
+ return read_item
1906
+
1907
+ read_item = get_read_item()
1908
+ l = LazyList([read_item] * count)
1909
+
1910
+ # TODO: Advance reader
1911
+
1912
+ return l
1913
+
1914
+ def write(self, writer, font, tableDict, values, repeatIndex=None):
1915
+ items = values
1916
+
1917
+ writer.writeULong(len(items))
1918
+ if not len(items):
1919
+ return
1920
+
1921
+ if self._itemClass is not None:
1922
+ items = [item.compile(font) for item in items]
1923
+ elif self._converter is not None:
1924
+ items = [
1925
+ self._converter.write(writer, font, tableDict, item, i)
1926
+ for i, item in enumerate(items)
1927
+ ]
1928
+
1929
+ offsets = [len(item) for item in items]
1930
+ offsets = list(accumulate(offsets, initial=1))
1931
+
1932
+ lastOffset = offsets[-1]
1933
+ offSize = (
1934
+ 1
1935
+ if lastOffset < 0x100
1936
+ else 2 if lastOffset < 0x10000 else 3 if lastOffset < 0x1000000 else 4
1937
+ )
1938
+ writer.writeUInt8(offSize)
1939
+
1940
+ writeArray = {
1941
+ 1: writer.writeUInt8Array,
1942
+ 2: writer.writeUShortArray,
1943
+ 3: writer.writeUInt24Array,
1944
+ 4: writer.writeULongArray,
1945
+ }[offSize]
1946
+
1947
+ writeArray(offsets)
1948
+ for item in items:
1949
+ writer.writeData(item)
1950
+
1951
+ def xmlRead(self, attrs, content, font):
1952
+ if self._itemClass is not None:
1953
+ obj = self._itemClass()
1954
+ obj.fromXML(None, attrs, content, font)
1955
+ return obj
1956
+ elif self._converter is not None:
1957
+ return self._converter.xmlRead(attrs, content, font)
1958
+ else:
1959
+ raise NotImplementedError()
1960
+
1961
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
1962
+ if self._itemClass is not None:
1963
+ for i, item in enumerate(value):
1964
+ item.toXML(xmlWriter, font, [("index", i)], name)
1965
+ elif self._converter is not None:
1966
+ for i, item in enumerate(value):
1967
+ self._converter.xmlWrite(
1968
+ xmlWriter, font, item, name, attrs + [("index", i)]
1969
+ )
1970
+ else:
1971
+ raise NotImplementedError()
1972
+
1973
+
1974
+ class LookupFlag(UShort):
1975
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
1976
+ xmlWriter.simpletag(name, attrs + [("value", value)])
1977
+ flags = []
1978
+ if value & 0x01:
1979
+ flags.append("rightToLeft")
1980
+ if value & 0x02:
1981
+ flags.append("ignoreBaseGlyphs")
1982
+ if value & 0x04:
1983
+ flags.append("ignoreLigatures")
1984
+ if value & 0x08:
1985
+ flags.append("ignoreMarks")
1986
+ if value & 0x10:
1987
+ flags.append("useMarkFilteringSet")
1988
+ if value & 0xFF00:
1989
+ flags.append("markAttachmentType[%i]" % (value >> 8))
1990
+ if flags:
1991
+ xmlWriter.comment(" ".join(flags))
1992
+ xmlWriter.newline()
1993
+
1994
+
1995
+ class _UInt8Enum(UInt8):
1996
+ enumClass = NotImplemented
1997
+
1998
+ def read(self, reader, font, tableDict):
1999
+ return self.enumClass(super().read(reader, font, tableDict))
2000
+
2001
+ @classmethod
2002
+ def fromString(cls, value):
2003
+ return getattr(cls.enumClass, value.upper())
2004
+
2005
+ @classmethod
2006
+ def toString(cls, value):
2007
+ return cls.enumClass(value).name.lower()
2008
+
2009
+
2010
+ class ExtendMode(_UInt8Enum):
2011
+ enumClass = _ExtendMode
2012
+
2013
+
2014
+ class CompositeMode(_UInt8Enum):
2015
+ enumClass = _CompositeMode
2016
+
2017
+
2018
+ converterMapping = {
2019
+ # type class
2020
+ "int8": Int8,
2021
+ "int16": Short,
2022
+ "int32": Long,
2023
+ "uint8": UInt8,
2024
+ "uint16": UShort,
2025
+ "uint24": UInt24,
2026
+ "uint32": ULong,
2027
+ "char64": Char64,
2028
+ "Flags32": Flags32,
2029
+ "VarIndex": VarIndex,
2030
+ "Version": Version,
2031
+ "Tag": Tag,
2032
+ "GlyphID": GlyphID,
2033
+ "GlyphID32": GlyphID32,
2034
+ "NameID": NameID,
2035
+ "DeciPoints": DeciPoints,
2036
+ "Fixed": Fixed,
2037
+ "F2Dot14": F2Dot14,
2038
+ "Angle": Angle,
2039
+ "BiasedAngle": BiasedAngle,
2040
+ "struct": Struct,
2041
+ "Offset": Table,
2042
+ "LOffset": LTable,
2043
+ "Offset24": Table24,
2044
+ "ValueRecord": ValueRecord,
2045
+ "DeltaValue": DeltaValue,
2046
+ "VarIdxMapValue": VarIdxMapValue,
2047
+ "VarDataValue": VarDataValue,
2048
+ "LookupFlag": LookupFlag,
2049
+ "ExtendMode": ExtendMode,
2050
+ "CompositeMode": CompositeMode,
2051
+ "STATFlags": STATFlags,
2052
+ "TupleList": partial(CFF2Index, itemConverterClass=TupleValues),
2053
+ "VarCompositeGlyphList": partial(CFF2Index, itemClass=VarCompositeGlyph),
2054
+ # AAT
2055
+ "CIDGlyphMap": CIDGlyphMap,
2056
+ "GlyphCIDMap": GlyphCIDMap,
2057
+ "MortChain": StructWithLength,
2058
+ "MortSubtable": StructWithLength,
2059
+ "MorxChain": StructWithLength,
2060
+ "MorxSubtable": MorxSubtableConverter,
2061
+ # "Template" types
2062
+ "AATLookup": lambda C: partial(AATLookup, tableClass=C),
2063
+ "AATLookupWithDataOffset": lambda C: partial(AATLookupWithDataOffset, tableClass=C),
2064
+ "STXHeader": lambda C: partial(STXHeader, tableClass=C),
2065
+ "OffsetTo": lambda C: partial(Table, tableClass=C),
2066
+ "LOffsetTo": lambda C: partial(LTable, tableClass=C),
2067
+ "LOffset24To": lambda C: partial(Table24, tableClass=C),
2068
+ }