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,1400 @@
1
+ # FontDame-to-FontTools for OpenType Layout tables
2
+ #
3
+ # Source language spec is available at:
4
+ # http://monotype.github.io/OpenType_Table_Source/otl_source.html
5
+ # https://github.com/Monotype/OpenType_Table_Source/
6
+
7
+ from fontTools import ttLib
8
+ from fontTools.ttLib.tables._c_m_a_p import cmap_classes
9
+ from fontTools.ttLib.tables import otTables as ot
10
+ from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
11
+ from fontTools.otlLib import builder as otl
12
+ from contextlib import contextmanager
13
+ from fontTools.ttLib import newTable
14
+ from fontTools.feaLib.lookupDebugInfo import LOOKUP_DEBUG_ENV_VAR, LOOKUP_DEBUG_INFO_KEY
15
+ from operator import setitem
16
+ import os
17
+ import logging
18
+
19
+
20
+ class MtiLibError(Exception):
21
+ pass
22
+
23
+
24
+ class ReferenceNotFoundError(MtiLibError):
25
+ pass
26
+
27
+
28
+ class FeatureNotFoundError(ReferenceNotFoundError):
29
+ pass
30
+
31
+
32
+ class LookupNotFoundError(ReferenceNotFoundError):
33
+ pass
34
+
35
+
36
+ log = logging.getLogger("fontTools.mtiLib")
37
+
38
+
39
+ def makeGlyph(s):
40
+ if s[:2] in ["U ", "u "]:
41
+ return ttLib.TTFont._makeGlyphName(int(s[2:], 16))
42
+ elif s[:2] == "# ":
43
+ return "glyph%.5d" % int(s[2:])
44
+ assert s.find(" ") < 0, "Space found in glyph name: %s" % s
45
+ assert s, "Glyph name is empty"
46
+ return s
47
+
48
+
49
+ def makeGlyphs(l):
50
+ return [makeGlyph(g) for g in l]
51
+
52
+
53
+ def mapLookup(sym, mapping):
54
+ # Lookups are addressed by name. So resolved them using a map if available.
55
+ # Fallback to parsing as lookup index if a map isn't provided.
56
+ if mapping is not None:
57
+ try:
58
+ idx = mapping[sym]
59
+ except KeyError:
60
+ raise LookupNotFoundError(sym)
61
+ else:
62
+ idx = int(sym)
63
+ return idx
64
+
65
+
66
+ def mapFeature(sym, mapping):
67
+ # Features are referenced by index according the spec. So, if symbol is an
68
+ # integer, use it directly. Otherwise look up in the map if provided.
69
+ try:
70
+ idx = int(sym)
71
+ except ValueError:
72
+ try:
73
+ idx = mapping[sym]
74
+ except KeyError:
75
+ raise FeatureNotFoundError(sym)
76
+ return idx
77
+
78
+
79
+ def setReference(mapper, mapping, sym, setter, collection, key):
80
+ try:
81
+ mapped = mapper(sym, mapping)
82
+ except ReferenceNotFoundError as e:
83
+ try:
84
+ if mapping is not None:
85
+ mapping.addDeferredMapping(
86
+ lambda ref: setter(collection, key, ref), sym, e
87
+ )
88
+ return
89
+ except AttributeError:
90
+ pass
91
+ raise
92
+ setter(collection, key, mapped)
93
+
94
+
95
+ class DeferredMapping(dict):
96
+ def __init__(self):
97
+ self._deferredMappings = []
98
+
99
+ def addDeferredMapping(self, setter, sym, e):
100
+ log.debug("Adding deferred mapping for symbol '%s' %s", sym, type(e).__name__)
101
+ self._deferredMappings.append((setter, sym, e))
102
+
103
+ def applyDeferredMappings(self):
104
+ for setter, sym, e in self._deferredMappings:
105
+ log.debug(
106
+ "Applying deferred mapping for symbol '%s' %s", sym, type(e).__name__
107
+ )
108
+ try:
109
+ mapped = self[sym]
110
+ except KeyError:
111
+ raise e
112
+ setter(mapped)
113
+ log.debug("Set to %s", mapped)
114
+ self._deferredMappings = []
115
+
116
+
117
+ def parseScriptList(lines, featureMap=None):
118
+ self = ot.ScriptList()
119
+ records = []
120
+ with lines.between("script table"):
121
+ for line in lines:
122
+ while len(line) < 4:
123
+ line.append("")
124
+ scriptTag, langSysTag, defaultFeature, features = line
125
+ log.debug("Adding script %s language-system %s", scriptTag, langSysTag)
126
+
127
+ langSys = ot.LangSys()
128
+ langSys.LookupOrder = None
129
+ if defaultFeature:
130
+ setReference(
131
+ mapFeature,
132
+ featureMap,
133
+ defaultFeature,
134
+ setattr,
135
+ langSys,
136
+ "ReqFeatureIndex",
137
+ )
138
+ else:
139
+ langSys.ReqFeatureIndex = 0xFFFF
140
+ syms = stripSplitComma(features)
141
+ langSys.FeatureIndex = theList = [3] * len(syms)
142
+ for i, sym in enumerate(syms):
143
+ setReference(mapFeature, featureMap, sym, setitem, theList, i)
144
+ langSys.FeatureCount = len(langSys.FeatureIndex)
145
+
146
+ script = [s for s in records if s.ScriptTag == scriptTag]
147
+ if script:
148
+ script = script[0].Script
149
+ else:
150
+ scriptRec = ot.ScriptRecord()
151
+ scriptRec.ScriptTag = scriptTag + " " * (4 - len(scriptTag))
152
+ scriptRec.Script = ot.Script()
153
+ records.append(scriptRec)
154
+ script = scriptRec.Script
155
+ script.DefaultLangSys = None
156
+ script.LangSysRecord = []
157
+ script.LangSysCount = 0
158
+
159
+ if langSysTag == "default":
160
+ script.DefaultLangSys = langSys
161
+ else:
162
+ langSysRec = ot.LangSysRecord()
163
+ langSysRec.LangSysTag = langSysTag + " " * (4 - len(langSysTag))
164
+ langSysRec.LangSys = langSys
165
+ script.LangSysRecord.append(langSysRec)
166
+ script.LangSysCount = len(script.LangSysRecord)
167
+
168
+ for script in records:
169
+ script.Script.LangSysRecord = sorted(
170
+ script.Script.LangSysRecord, key=lambda rec: rec.LangSysTag
171
+ )
172
+ self.ScriptRecord = sorted(records, key=lambda rec: rec.ScriptTag)
173
+ self.ScriptCount = len(self.ScriptRecord)
174
+ return self
175
+
176
+
177
+ def parseFeatureList(lines, lookupMap=None, featureMap=None):
178
+ self = ot.FeatureList()
179
+ self.FeatureRecord = []
180
+ with lines.between("feature table"):
181
+ for line in lines:
182
+ name, featureTag, lookups = line
183
+ if featureMap is not None:
184
+ assert name not in featureMap, "Duplicate feature name: %s" % name
185
+ featureMap[name] = len(self.FeatureRecord)
186
+ # If feature name is integer, make sure it matches its index.
187
+ try:
188
+ assert int(name) == len(self.FeatureRecord), "%d %d" % (
189
+ name,
190
+ len(self.FeatureRecord),
191
+ )
192
+ except ValueError:
193
+ pass
194
+ featureRec = ot.FeatureRecord()
195
+ featureRec.FeatureTag = featureTag
196
+ featureRec.Feature = ot.Feature()
197
+ self.FeatureRecord.append(featureRec)
198
+ feature = featureRec.Feature
199
+ feature.FeatureParams = None
200
+ syms = stripSplitComma(lookups)
201
+ feature.LookupListIndex = theList = [None] * len(syms)
202
+ for i, sym in enumerate(syms):
203
+ setReference(mapLookup, lookupMap, sym, setitem, theList, i)
204
+ feature.LookupCount = len(feature.LookupListIndex)
205
+
206
+ self.FeatureCount = len(self.FeatureRecord)
207
+ return self
208
+
209
+
210
+ def parseLookupFlags(lines):
211
+ flags = 0
212
+ filterset = None
213
+ allFlags = [
214
+ "righttoleft",
215
+ "ignorebaseglyphs",
216
+ "ignoreligatures",
217
+ "ignoremarks",
218
+ "markattachmenttype",
219
+ "markfiltertype",
220
+ ]
221
+ while lines.peeks()[0].lower() in allFlags:
222
+ line = next(lines)
223
+ flag = {
224
+ "righttoleft": 0x0001,
225
+ "ignorebaseglyphs": 0x0002,
226
+ "ignoreligatures": 0x0004,
227
+ "ignoremarks": 0x0008,
228
+ }.get(line[0].lower())
229
+ if flag:
230
+ assert line[1].lower() in ["yes", "no"], line[1]
231
+ if line[1].lower() == "yes":
232
+ flags |= flag
233
+ continue
234
+ if line[0].lower() == "markattachmenttype":
235
+ flags |= int(line[1]) << 8
236
+ continue
237
+ if line[0].lower() == "markfiltertype":
238
+ flags |= 0x10
239
+ filterset = int(line[1])
240
+ return flags, filterset
241
+
242
+
243
+ def parseSingleSubst(lines, font, _lookupMap=None):
244
+ mapping = {}
245
+ for line in lines:
246
+ assert len(line) == 2, line
247
+ line = makeGlyphs(line)
248
+ mapping[line[0]] = line[1]
249
+ return otl.buildSingleSubstSubtable(mapping)
250
+
251
+
252
+ def parseMultiple(lines, font, _lookupMap=None):
253
+ mapping = {}
254
+ for line in lines:
255
+ line = makeGlyphs(line)
256
+ mapping[line[0]] = line[1:]
257
+ return otl.buildMultipleSubstSubtable(mapping)
258
+
259
+
260
+ def parseAlternate(lines, font, _lookupMap=None):
261
+ mapping = {}
262
+ for line in lines:
263
+ line = makeGlyphs(line)
264
+ mapping[line[0]] = line[1:]
265
+ return otl.buildAlternateSubstSubtable(mapping)
266
+
267
+
268
+ def parseLigature(lines, font, _lookupMap=None):
269
+ mapping = {}
270
+ for line in lines:
271
+ assert len(line) >= 2, line
272
+ line = makeGlyphs(line)
273
+ mapping[tuple(line[1:])] = line[0]
274
+ return otl.buildLigatureSubstSubtable(mapping)
275
+
276
+
277
+ def parseSinglePos(lines, font, _lookupMap=None):
278
+ values = {}
279
+ for line in lines:
280
+ assert len(line) == 3, line
281
+ w = line[0].title().replace(" ", "")
282
+ assert w in valueRecordFormatDict
283
+ g = makeGlyph(line[1])
284
+ v = int(line[2])
285
+ if g not in values:
286
+ values[g] = ValueRecord()
287
+ assert not hasattr(values[g], w), (g, w)
288
+ setattr(values[g], w, v)
289
+ return otl.buildSinglePosSubtable(values, font.getReverseGlyphMap())
290
+
291
+
292
+ def parsePair(lines, font, _lookupMap=None):
293
+ self = ot.PairPos()
294
+ self.ValueFormat1 = self.ValueFormat2 = 0
295
+ typ = lines.peeks()[0].split()[0].lower()
296
+ if typ in ("left", "right"):
297
+ self.Format = 1
298
+ values = {}
299
+ for line in lines:
300
+ assert len(line) == 4, line
301
+ side = line[0].split()[0].lower()
302
+ assert side in ("left", "right"), side
303
+ what = line[0][len(side) :].title().replace(" ", "")
304
+ mask = valueRecordFormatDict[what][0]
305
+ glyph1, glyph2 = makeGlyphs(line[1:3])
306
+ value = int(line[3])
307
+ if not glyph1 in values:
308
+ values[glyph1] = {}
309
+ if not glyph2 in values[glyph1]:
310
+ values[glyph1][glyph2] = (ValueRecord(), ValueRecord())
311
+ rec2 = values[glyph1][glyph2]
312
+ if side == "left":
313
+ self.ValueFormat1 |= mask
314
+ vr = rec2[0]
315
+ else:
316
+ self.ValueFormat2 |= mask
317
+ vr = rec2[1]
318
+ assert not hasattr(vr, what), (vr, what)
319
+ setattr(vr, what, value)
320
+ self.Coverage = makeCoverage(set(values.keys()), font)
321
+ self.PairSet = []
322
+ for glyph1 in self.Coverage.glyphs:
323
+ values1 = values[glyph1]
324
+ pairset = ot.PairSet()
325
+ records = pairset.PairValueRecord = []
326
+ for glyph2 in sorted(values1.keys(), key=font.getGlyphID):
327
+ values2 = values1[glyph2]
328
+ pair = ot.PairValueRecord()
329
+ pair.SecondGlyph = glyph2
330
+ pair.Value1 = values2[0]
331
+ pair.Value2 = values2[1] if self.ValueFormat2 else None
332
+ records.append(pair)
333
+ pairset.PairValueCount = len(pairset.PairValueRecord)
334
+ self.PairSet.append(pairset)
335
+ self.PairSetCount = len(self.PairSet)
336
+ elif typ.endswith("class"):
337
+ self.Format = 2
338
+ classDefs = [None, None]
339
+ while lines.peeks()[0].endswith("class definition begin"):
340
+ typ = lines.peek()[0][: -len("class definition begin")].lower()
341
+ idx, klass = {
342
+ "first": (0, ot.ClassDef1),
343
+ "second": (1, ot.ClassDef2),
344
+ }[typ]
345
+ assert classDefs[idx] is None
346
+ classDefs[idx] = parseClassDef(lines, font, klass=klass)
347
+ self.ClassDef1, self.ClassDef2 = classDefs
348
+ self.Class1Count, self.Class2Count = (
349
+ 1 + max(c.classDefs.values()) for c in classDefs
350
+ )
351
+ self.Class1Record = [ot.Class1Record() for i in range(self.Class1Count)]
352
+ for rec1 in self.Class1Record:
353
+ rec1.Class2Record = [ot.Class2Record() for j in range(self.Class2Count)]
354
+ for rec2 in rec1.Class2Record:
355
+ rec2.Value1 = ValueRecord()
356
+ rec2.Value2 = ValueRecord()
357
+ for line in lines:
358
+ assert len(line) == 4, line
359
+ side = line[0].split()[0].lower()
360
+ assert side in ("left", "right"), side
361
+ what = line[0][len(side) :].title().replace(" ", "")
362
+ mask = valueRecordFormatDict[what][0]
363
+ class1, class2, value = (int(x) for x in line[1:4])
364
+ rec2 = self.Class1Record[class1].Class2Record[class2]
365
+ if side == "left":
366
+ self.ValueFormat1 |= mask
367
+ vr = rec2.Value1
368
+ else:
369
+ self.ValueFormat2 |= mask
370
+ vr = rec2.Value2
371
+ assert not hasattr(vr, what), (vr, what)
372
+ setattr(vr, what, value)
373
+ for rec1 in self.Class1Record:
374
+ for rec2 in rec1.Class2Record:
375
+ rec2.Value1 = ValueRecord(self.ValueFormat1, rec2.Value1)
376
+ rec2.Value2 = (
377
+ ValueRecord(self.ValueFormat2, rec2.Value2)
378
+ if self.ValueFormat2
379
+ else None
380
+ )
381
+
382
+ self.Coverage = makeCoverage(set(self.ClassDef1.classDefs.keys()), font)
383
+ else:
384
+ assert 0, typ
385
+ return self
386
+
387
+
388
+ def parseKernset(lines, font, _lookupMap=None):
389
+ typ = lines.peeks()[0].split()[0].lower()
390
+ if typ in ("left", "right"):
391
+ with lines.until(
392
+ ("firstclass definition begin", "secondclass definition begin")
393
+ ):
394
+ return parsePair(lines, font)
395
+ return parsePair(lines, font)
396
+
397
+
398
+ def makeAnchor(data, klass=ot.Anchor):
399
+ assert len(data) <= 2
400
+ anchor = klass()
401
+ anchor.Format = 1
402
+ anchor.XCoordinate, anchor.YCoordinate = intSplitComma(data[0])
403
+ if len(data) > 1 and data[1] != "":
404
+ anchor.Format = 2
405
+ anchor.AnchorPoint = int(data[1])
406
+ return anchor
407
+
408
+
409
+ def parseCursive(lines, font, _lookupMap=None):
410
+ records = {}
411
+ for line in lines:
412
+ assert len(line) in [3, 4], line
413
+ idx, klass = {
414
+ "entry": (0, ot.EntryAnchor),
415
+ "exit": (1, ot.ExitAnchor),
416
+ }[line[0]]
417
+ glyph = makeGlyph(line[1])
418
+ if glyph not in records:
419
+ records[glyph] = [None, None]
420
+ assert records[glyph][idx] is None, (glyph, idx)
421
+ records[glyph][idx] = makeAnchor(line[2:], klass)
422
+ return otl.buildCursivePosSubtable(records, font.getReverseGlyphMap())
423
+
424
+
425
+ def makeMarkRecords(data, coverage, c):
426
+ records = []
427
+ for glyph in coverage.glyphs:
428
+ klass, anchor = data[glyph]
429
+ record = c.MarkRecordClass()
430
+ record.Class = klass
431
+ setattr(record, c.MarkAnchor, anchor)
432
+ records.append(record)
433
+ return records
434
+
435
+
436
+ def makeBaseRecords(data, coverage, c, classCount):
437
+ records = []
438
+ idx = {}
439
+ for glyph in coverage.glyphs:
440
+ idx[glyph] = len(records)
441
+ record = c.BaseRecordClass()
442
+ anchors = [None] * classCount
443
+ setattr(record, c.BaseAnchor, anchors)
444
+ records.append(record)
445
+ for (glyph, klass), anchor in data.items():
446
+ record = records[idx[glyph]]
447
+ anchors = getattr(record, c.BaseAnchor)
448
+ assert anchors[klass] is None, (glyph, klass)
449
+ anchors[klass] = anchor
450
+ return records
451
+
452
+
453
+ def makeLigatureRecords(data, coverage, c, classCount):
454
+ records = [None] * len(coverage.glyphs)
455
+ idx = {g: i for i, g in enumerate(coverage.glyphs)}
456
+
457
+ for (glyph, klass, compIdx, compCount), anchor in data.items():
458
+ record = records[idx[glyph]]
459
+ if record is None:
460
+ record = records[idx[glyph]] = ot.LigatureAttach()
461
+ record.ComponentCount = compCount
462
+ record.ComponentRecord = [ot.ComponentRecord() for i in range(compCount)]
463
+ for compRec in record.ComponentRecord:
464
+ compRec.LigatureAnchor = [None] * classCount
465
+ assert record.ComponentCount == compCount, (
466
+ glyph,
467
+ record.ComponentCount,
468
+ compCount,
469
+ )
470
+
471
+ anchors = record.ComponentRecord[compIdx - 1].LigatureAnchor
472
+ assert anchors[klass] is None, (glyph, compIdx, klass)
473
+ anchors[klass] = anchor
474
+ return records
475
+
476
+
477
+ def parseMarkToSomething(lines, font, c):
478
+ self = c.Type()
479
+ self.Format = 1
480
+ markData = {}
481
+ baseData = {}
482
+ Data = {
483
+ "mark": (markData, c.MarkAnchorClass),
484
+ "base": (baseData, c.BaseAnchorClass),
485
+ "ligature": (baseData, c.BaseAnchorClass),
486
+ }
487
+ maxKlass = 0
488
+ for line in lines:
489
+ typ = line[0]
490
+ assert typ in ("mark", "base", "ligature")
491
+ glyph = makeGlyph(line[1])
492
+ data, anchorClass = Data[typ]
493
+ extraItems = 2 if typ == "ligature" else 0
494
+ extras = tuple(int(i) for i in line[2 : 2 + extraItems])
495
+ klass = int(line[2 + extraItems])
496
+ anchor = makeAnchor(line[3 + extraItems :], anchorClass)
497
+ if typ == "mark":
498
+ key, value = glyph, (klass, anchor)
499
+ else:
500
+ key, value = ((glyph, klass) + extras), anchor
501
+ assert key not in data, key
502
+ data[key] = value
503
+ maxKlass = max(maxKlass, klass)
504
+
505
+ # Mark
506
+ markCoverage = makeCoverage(set(markData.keys()), font, c.MarkCoverageClass)
507
+ markArray = c.MarkArrayClass()
508
+ markRecords = makeMarkRecords(markData, markCoverage, c)
509
+ setattr(markArray, c.MarkRecord, markRecords)
510
+ setattr(markArray, c.MarkCount, len(markRecords))
511
+ setattr(self, c.MarkCoverage, markCoverage)
512
+ setattr(self, c.MarkArray, markArray)
513
+ self.ClassCount = maxKlass + 1
514
+
515
+ # Base
516
+ self.classCount = 0 if not baseData else 1 + max(k[1] for k, v in baseData.items())
517
+ baseCoverage = makeCoverage(
518
+ set([k[0] for k in baseData.keys()]), font, c.BaseCoverageClass
519
+ )
520
+ baseArray = c.BaseArrayClass()
521
+ if c.Base == "Ligature":
522
+ baseRecords = makeLigatureRecords(baseData, baseCoverage, c, self.classCount)
523
+ else:
524
+ baseRecords = makeBaseRecords(baseData, baseCoverage, c, self.classCount)
525
+ setattr(baseArray, c.BaseRecord, baseRecords)
526
+ setattr(baseArray, c.BaseCount, len(baseRecords))
527
+ setattr(self, c.BaseCoverage, baseCoverage)
528
+ setattr(self, c.BaseArray, baseArray)
529
+
530
+ return self
531
+
532
+
533
+ class MarkHelper(object):
534
+ def __init__(self):
535
+ for Which in ("Mark", "Base"):
536
+ for What in ("Coverage", "Array", "Count", "Record", "Anchor"):
537
+ key = Which + What
538
+ if Which == "Mark" and What in ("Count", "Record", "Anchor"):
539
+ value = key
540
+ else:
541
+ value = getattr(self, Which) + What
542
+ if value == "LigatureRecord":
543
+ value = "LigatureAttach"
544
+ setattr(self, key, value)
545
+ if What != "Count":
546
+ klass = getattr(ot, value)
547
+ setattr(self, key + "Class", klass)
548
+
549
+
550
+ class MarkToBaseHelper(MarkHelper):
551
+ Mark = "Mark"
552
+ Base = "Base"
553
+ Type = ot.MarkBasePos
554
+
555
+
556
+ class MarkToMarkHelper(MarkHelper):
557
+ Mark = "Mark1"
558
+ Base = "Mark2"
559
+ Type = ot.MarkMarkPos
560
+
561
+
562
+ class MarkToLigatureHelper(MarkHelper):
563
+ Mark = "Mark"
564
+ Base = "Ligature"
565
+ Type = ot.MarkLigPos
566
+
567
+
568
+ def parseMarkToBase(lines, font, _lookupMap=None):
569
+ return parseMarkToSomething(lines, font, MarkToBaseHelper())
570
+
571
+
572
+ def parseMarkToMark(lines, font, _lookupMap=None):
573
+ return parseMarkToSomething(lines, font, MarkToMarkHelper())
574
+
575
+
576
+ def parseMarkToLigature(lines, font, _lookupMap=None):
577
+ return parseMarkToSomething(lines, font, MarkToLigatureHelper())
578
+
579
+
580
+ def stripSplitComma(line):
581
+ return [s.strip() for s in line.split(",")] if line else []
582
+
583
+
584
+ def intSplitComma(line):
585
+ return [int(i) for i in line.split(",")] if line else []
586
+
587
+
588
+ # Copied from fontTools.subset
589
+ class ContextHelper(object):
590
+ def __init__(self, klassName, Format):
591
+ if klassName.endswith("Subst"):
592
+ Typ = "Sub"
593
+ Type = "Subst"
594
+ else:
595
+ Typ = "Pos"
596
+ Type = "Pos"
597
+ if klassName.startswith("Chain"):
598
+ Chain = "Chain"
599
+ InputIdx = 1
600
+ DataLen = 3
601
+ else:
602
+ Chain = ""
603
+ InputIdx = 0
604
+ DataLen = 1
605
+ ChainTyp = Chain + Typ
606
+
607
+ self.Typ = Typ
608
+ self.Type = Type
609
+ self.Chain = Chain
610
+ self.ChainTyp = ChainTyp
611
+ self.InputIdx = InputIdx
612
+ self.DataLen = DataLen
613
+
614
+ self.LookupRecord = Type + "LookupRecord"
615
+
616
+ if Format == 1:
617
+ Coverage = lambda r: r.Coverage
618
+ ChainCoverage = lambda r: r.Coverage
619
+ ContextData = lambda r: (None,)
620
+ ChainContextData = lambda r: (None, None, None)
621
+ SetContextData = None
622
+ SetChainContextData = None
623
+ RuleData = lambda r: (r.Input,)
624
+ ChainRuleData = lambda r: (r.Backtrack, r.Input, r.LookAhead)
625
+
626
+ def SetRuleData(r, d):
627
+ (r.Input,) = d
628
+ (r.GlyphCount,) = (len(x) + 1 for x in d)
629
+
630
+ def ChainSetRuleData(r, d):
631
+ (r.Backtrack, r.Input, r.LookAhead) = d
632
+ (
633
+ r.BacktrackGlyphCount,
634
+ r.InputGlyphCount,
635
+ r.LookAheadGlyphCount,
636
+ ) = (len(d[0]), len(d[1]) + 1, len(d[2]))
637
+
638
+ elif Format == 2:
639
+ Coverage = lambda r: r.Coverage
640
+ ChainCoverage = lambda r: r.Coverage
641
+ ContextData = lambda r: (r.ClassDef,)
642
+ ChainContextData = lambda r: (
643
+ r.BacktrackClassDef,
644
+ r.InputClassDef,
645
+ r.LookAheadClassDef,
646
+ )
647
+
648
+ def SetContextData(r, d):
649
+ (r.ClassDef,) = d
650
+
651
+ def SetChainContextData(r, d):
652
+ (r.BacktrackClassDef, r.InputClassDef, r.LookAheadClassDef) = d
653
+
654
+ RuleData = lambda r: (r.Class,)
655
+ ChainRuleData = lambda r: (r.Backtrack, r.Input, r.LookAhead)
656
+
657
+ def SetRuleData(r, d):
658
+ (r.Class,) = d
659
+ (r.GlyphCount,) = (len(x) + 1 for x in d)
660
+
661
+ def ChainSetRuleData(r, d):
662
+ (r.Backtrack, r.Input, r.LookAhead) = d
663
+ (
664
+ r.BacktrackGlyphCount,
665
+ r.InputGlyphCount,
666
+ r.LookAheadGlyphCount,
667
+ ) = (len(d[0]), len(d[1]) + 1, len(d[2]))
668
+
669
+ elif Format == 3:
670
+ Coverage = lambda r: r.Coverage[0]
671
+ ChainCoverage = lambda r: r.InputCoverage[0]
672
+ ContextData = None
673
+ ChainContextData = None
674
+ SetContextData = None
675
+ SetChainContextData = None
676
+ RuleData = lambda r: r.Coverage
677
+ ChainRuleData = lambda r: (
678
+ r.BacktrackCoverage + r.InputCoverage + r.LookAheadCoverage
679
+ )
680
+
681
+ def SetRuleData(r, d):
682
+ (r.Coverage,) = d
683
+ (r.GlyphCount,) = (len(x) for x in d)
684
+
685
+ def ChainSetRuleData(r, d):
686
+ (r.BacktrackCoverage, r.InputCoverage, r.LookAheadCoverage) = d
687
+ (
688
+ r.BacktrackGlyphCount,
689
+ r.InputGlyphCount,
690
+ r.LookAheadGlyphCount,
691
+ ) = (len(x) for x in d)
692
+
693
+ else:
694
+ assert 0, "unknown format: %s" % Format
695
+
696
+ if Chain:
697
+ self.Coverage = ChainCoverage
698
+ self.ContextData = ChainContextData
699
+ self.SetContextData = SetChainContextData
700
+ self.RuleData = ChainRuleData
701
+ self.SetRuleData = ChainSetRuleData
702
+ else:
703
+ self.Coverage = Coverage
704
+ self.ContextData = ContextData
705
+ self.SetContextData = SetContextData
706
+ self.RuleData = RuleData
707
+ self.SetRuleData = SetRuleData
708
+
709
+ if Format == 1:
710
+ self.Rule = ChainTyp + "Rule"
711
+ self.RuleCount = ChainTyp + "RuleCount"
712
+ self.RuleSet = ChainTyp + "RuleSet"
713
+ self.RuleSetCount = ChainTyp + "RuleSetCount"
714
+ self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
715
+ elif Format == 2:
716
+ self.Rule = ChainTyp + "ClassRule"
717
+ self.RuleCount = ChainTyp + "ClassRuleCount"
718
+ self.RuleSet = ChainTyp + "ClassSet"
719
+ self.RuleSetCount = ChainTyp + "ClassSetCount"
720
+ self.Intersect = lambda glyphs, c, r: (
721
+ c.intersect_class(glyphs, r)
722
+ if c
723
+ else (set(glyphs) if r == 0 else set())
724
+ )
725
+
726
+ self.ClassDef = "InputClassDef" if Chain else "ClassDef"
727
+ self.ClassDefIndex = 1 if Chain else 0
728
+ self.Input = "Input" if Chain else "Class"
729
+
730
+
731
+ def parseLookupRecords(items, klassName, lookupMap=None):
732
+ klass = getattr(ot, klassName)
733
+ lst = []
734
+ for item in items:
735
+ rec = klass()
736
+ item = stripSplitComma(item)
737
+ assert len(item) == 2, item
738
+ idx = int(item[0])
739
+ assert idx > 0, idx
740
+ rec.SequenceIndex = idx - 1
741
+ setReference(mapLookup, lookupMap, item[1], setattr, rec, "LookupListIndex")
742
+ lst.append(rec)
743
+ return lst
744
+
745
+
746
+ def makeClassDef(classDefs, font, klass=ot.Coverage):
747
+ if not classDefs:
748
+ return None
749
+ self = klass()
750
+ self.classDefs = dict(classDefs)
751
+ return self
752
+
753
+
754
+ def parseClassDef(lines, font, klass=ot.ClassDef):
755
+ classDefs = {}
756
+ with lines.between("class definition"):
757
+ for line in lines:
758
+ glyph = makeGlyph(line[0])
759
+ assert glyph not in classDefs, glyph
760
+ classDefs[glyph] = int(line[1])
761
+ return makeClassDef(classDefs, font, klass)
762
+
763
+
764
+ def makeCoverage(glyphs, font, klass=ot.Coverage):
765
+ if not glyphs:
766
+ return None
767
+ if isinstance(glyphs, set):
768
+ glyphs = sorted(glyphs)
769
+ coverage = klass()
770
+ coverage.glyphs = sorted(set(glyphs), key=font.getGlyphID)
771
+ return coverage
772
+
773
+
774
+ def parseCoverage(lines, font, klass=ot.Coverage):
775
+ glyphs = []
776
+ with lines.between("coverage definition"):
777
+ for line in lines:
778
+ glyphs.append(makeGlyph(line[0]))
779
+ return makeCoverage(glyphs, font, klass)
780
+
781
+
782
+ def bucketizeRules(self, c, rules, bucketKeys):
783
+ buckets = {}
784
+ for seq, recs in rules:
785
+ buckets.setdefault(seq[c.InputIdx][0], []).append(
786
+ (tuple(s[1 if i == c.InputIdx else 0 :] for i, s in enumerate(seq)), recs)
787
+ )
788
+
789
+ rulesets = []
790
+ for firstGlyph in bucketKeys:
791
+ if firstGlyph not in buckets:
792
+ rulesets.append(None)
793
+ continue
794
+ thisRules = []
795
+ for seq, recs in buckets[firstGlyph]:
796
+ rule = getattr(ot, c.Rule)()
797
+ c.SetRuleData(rule, seq)
798
+ setattr(rule, c.Type + "Count", len(recs))
799
+ setattr(rule, c.LookupRecord, recs)
800
+ thisRules.append(rule)
801
+
802
+ ruleset = getattr(ot, c.RuleSet)()
803
+ setattr(ruleset, c.Rule, thisRules)
804
+ setattr(ruleset, c.RuleCount, len(thisRules))
805
+ rulesets.append(ruleset)
806
+
807
+ setattr(self, c.RuleSet, rulesets)
808
+ setattr(self, c.RuleSetCount, len(rulesets))
809
+
810
+
811
+ def parseContext(lines, font, Type, lookupMap=None):
812
+ self = getattr(ot, Type)()
813
+ typ = lines.peeks()[0].split()[0].lower()
814
+ if typ == "glyph":
815
+ self.Format = 1
816
+ log.debug("Parsing %s format %s", Type, self.Format)
817
+ c = ContextHelper(Type, self.Format)
818
+ rules = []
819
+ for line in lines:
820
+ assert line[0].lower() == "glyph", line[0]
821
+ while len(line) < 1 + c.DataLen:
822
+ line.append("")
823
+ seq = tuple(makeGlyphs(stripSplitComma(i)) for i in line[1 : 1 + c.DataLen])
824
+ recs = parseLookupRecords(line[1 + c.DataLen :], c.LookupRecord, lookupMap)
825
+ rules.append((seq, recs))
826
+
827
+ firstGlyphs = set(seq[c.InputIdx][0] for seq, recs in rules)
828
+ self.Coverage = makeCoverage(firstGlyphs, font)
829
+ bucketizeRules(self, c, rules, self.Coverage.glyphs)
830
+ elif typ.endswith("class"):
831
+ self.Format = 2
832
+ log.debug("Parsing %s format %s", Type, self.Format)
833
+ c = ContextHelper(Type, self.Format)
834
+ classDefs = [None] * c.DataLen
835
+ while lines.peeks()[0].endswith("class definition begin"):
836
+ typ = lines.peek()[0][: -len("class definition begin")].lower()
837
+ idx, klass = {
838
+ 1: {
839
+ "": (0, ot.ClassDef),
840
+ },
841
+ 3: {
842
+ "backtrack": (0, ot.BacktrackClassDef),
843
+ "": (1, ot.InputClassDef),
844
+ "lookahead": (2, ot.LookAheadClassDef),
845
+ },
846
+ }[c.DataLen][typ]
847
+ assert classDefs[idx] is None, idx
848
+ classDefs[idx] = parseClassDef(lines, font, klass=klass)
849
+ c.SetContextData(self, classDefs)
850
+ rules = []
851
+ for line in lines:
852
+ assert line[0].lower().startswith("class"), line[0]
853
+ while len(line) < 1 + c.DataLen:
854
+ line.append("")
855
+ seq = tuple(intSplitComma(i) for i in line[1 : 1 + c.DataLen])
856
+ recs = parseLookupRecords(line[1 + c.DataLen :], c.LookupRecord, lookupMap)
857
+ rules.append((seq, recs))
858
+ firstClasses = set(seq[c.InputIdx][0] for seq, recs in rules)
859
+ firstGlyphs = set(
860
+ g for g, c in classDefs[c.InputIdx].classDefs.items() if c in firstClasses
861
+ )
862
+ self.Coverage = makeCoverage(firstGlyphs, font)
863
+ bucketizeRules(self, c, rules, range(max(firstClasses) + 1))
864
+ elif typ.endswith("coverage"):
865
+ self.Format = 3
866
+ log.debug("Parsing %s format %s", Type, self.Format)
867
+ c = ContextHelper(Type, self.Format)
868
+ coverages = tuple([] for i in range(c.DataLen))
869
+ while lines.peeks()[0].endswith("coverage definition begin"):
870
+ typ = lines.peek()[0][: -len("coverage definition begin")].lower()
871
+ idx, klass = {
872
+ 1: {
873
+ "": (0, ot.Coverage),
874
+ },
875
+ 3: {
876
+ "backtrack": (0, ot.BacktrackCoverage),
877
+ "input": (1, ot.InputCoverage),
878
+ "lookahead": (2, ot.LookAheadCoverage),
879
+ },
880
+ }[c.DataLen][typ]
881
+ coverages[idx].append(parseCoverage(lines, font, klass=klass))
882
+ c.SetRuleData(self, coverages)
883
+ lines = list(lines)
884
+ assert len(lines) == 1
885
+ line = lines[0]
886
+ assert line[0].lower() == "coverage", line[0]
887
+ recs = parseLookupRecords(line[1:], c.LookupRecord, lookupMap)
888
+ setattr(self, c.Type + "Count", len(recs))
889
+ setattr(self, c.LookupRecord, recs)
890
+ else:
891
+ assert 0, typ
892
+ return self
893
+
894
+
895
+ def parseContextSubst(lines, font, lookupMap=None):
896
+ return parseContext(lines, font, "ContextSubst", lookupMap=lookupMap)
897
+
898
+
899
+ def parseContextPos(lines, font, lookupMap=None):
900
+ return parseContext(lines, font, "ContextPos", lookupMap=lookupMap)
901
+
902
+
903
+ def parseChainedSubst(lines, font, lookupMap=None):
904
+ return parseContext(lines, font, "ChainContextSubst", lookupMap=lookupMap)
905
+
906
+
907
+ def parseChainedPos(lines, font, lookupMap=None):
908
+ return parseContext(lines, font, "ChainContextPos", lookupMap=lookupMap)
909
+
910
+
911
+ def parseReverseChainedSubst(lines, font, _lookupMap=None):
912
+ self = ot.ReverseChainSingleSubst()
913
+ self.Format = 1
914
+ coverages = ([], [])
915
+ while lines.peeks()[0].endswith("coverage definition begin"):
916
+ typ = lines.peek()[0][: -len("coverage definition begin")].lower()
917
+ idx, klass = {
918
+ "backtrack": (0, ot.BacktrackCoverage),
919
+ "lookahead": (1, ot.LookAheadCoverage),
920
+ }[typ]
921
+ coverages[idx].append(parseCoverage(lines, font, klass=klass))
922
+ self.BacktrackCoverage = coverages[0]
923
+ self.BacktrackGlyphCount = len(self.BacktrackCoverage)
924
+ self.LookAheadCoverage = coverages[1]
925
+ self.LookAheadGlyphCount = len(self.LookAheadCoverage)
926
+ mapping = {}
927
+ for line in lines:
928
+ assert len(line) == 2, line
929
+ line = makeGlyphs(line)
930
+ mapping[line[0]] = line[1]
931
+ self.Coverage = makeCoverage(set(mapping.keys()), font)
932
+ self.Substitute = [mapping[k] for k in self.Coverage.glyphs]
933
+ self.GlyphCount = len(self.Substitute)
934
+ return self
935
+
936
+
937
+ def parseLookup(lines, tableTag, font, lookupMap=None):
938
+ line = lines.expect("lookup")
939
+ _, name, typ = line
940
+ log.debug("Parsing lookup type %s %s", typ, name)
941
+ lookup = ot.Lookup()
942
+ lookup.LookupFlag, filterset = parseLookupFlags(lines)
943
+ if filterset is not None:
944
+ lookup.MarkFilteringSet = filterset
945
+ lookup.LookupType, parseLookupSubTable = {
946
+ "GSUB": {
947
+ "single": (1, parseSingleSubst),
948
+ "multiple": (2, parseMultiple),
949
+ "alternate": (3, parseAlternate),
950
+ "ligature": (4, parseLigature),
951
+ "context": (5, parseContextSubst),
952
+ "chained": (6, parseChainedSubst),
953
+ "reversechained": (8, parseReverseChainedSubst),
954
+ },
955
+ "GPOS": {
956
+ "single": (1, parseSinglePos),
957
+ "pair": (2, parsePair),
958
+ "kernset": (2, parseKernset),
959
+ "cursive": (3, parseCursive),
960
+ "mark to base": (4, parseMarkToBase),
961
+ "mark to ligature": (5, parseMarkToLigature),
962
+ "mark to mark": (6, parseMarkToMark),
963
+ "context": (7, parseContextPos),
964
+ "chained": (8, parseChainedPos),
965
+ },
966
+ }[tableTag][typ]
967
+
968
+ with lines.until("lookup end"):
969
+ subtables = []
970
+
971
+ while lines.peek():
972
+ with lines.until(("% subtable", "subtable end")):
973
+ while lines.peek():
974
+ subtable = parseLookupSubTable(lines, font, lookupMap)
975
+ assert lookup.LookupType == subtable.LookupType
976
+ subtables.append(subtable)
977
+ if lines.peeks()[0] in ("% subtable", "subtable end"):
978
+ next(lines)
979
+ lines.expect("lookup end")
980
+
981
+ lookup.SubTable = subtables
982
+ lookup.SubTableCount = len(lookup.SubTable)
983
+ if lookup.SubTableCount == 0:
984
+ # Remove this return when following is fixed:
985
+ # https://github.com/fonttools/fonttools/issues/789
986
+ return None
987
+ return lookup
988
+
989
+
990
+ def parseGSUBGPOS(lines, font, tableTag):
991
+ container = ttLib.getTableClass(tableTag)()
992
+ lookupMap = DeferredMapping()
993
+ featureMap = DeferredMapping()
994
+ assert tableTag in ("GSUB", "GPOS")
995
+ log.debug("Parsing %s", tableTag)
996
+ self = getattr(ot, tableTag)()
997
+ self.Version = 0x00010000
998
+ fields = {
999
+ "script table begin": (
1000
+ "ScriptList",
1001
+ lambda lines: parseScriptList(lines, featureMap),
1002
+ ),
1003
+ "feature table begin": (
1004
+ "FeatureList",
1005
+ lambda lines: parseFeatureList(lines, lookupMap, featureMap),
1006
+ ),
1007
+ "lookup": ("LookupList", None),
1008
+ }
1009
+ for attr, parser in fields.values():
1010
+ setattr(self, attr, None)
1011
+ while lines.peek() is not None:
1012
+ typ = lines.peek()[0].lower()
1013
+ if typ not in fields:
1014
+ log.debug("Skipping %s", lines.peek())
1015
+ next(lines)
1016
+ continue
1017
+ attr, parser = fields[typ]
1018
+ if typ == "lookup":
1019
+ if self.LookupList is None:
1020
+ self.LookupList = ot.LookupList()
1021
+ self.LookupList.Lookup = []
1022
+ _, name, _ = lines.peek()
1023
+ lookup = parseLookup(lines, tableTag, font, lookupMap)
1024
+ if lookupMap is not None:
1025
+ assert name not in lookupMap, "Duplicate lookup name: %s" % name
1026
+ lookupMap[name] = len(self.LookupList.Lookup)
1027
+ else:
1028
+ assert int(name) == len(self.LookupList.Lookup), "%d %d" % (
1029
+ name,
1030
+ len(self.Lookup),
1031
+ )
1032
+ self.LookupList.Lookup.append(lookup)
1033
+ else:
1034
+ assert getattr(self, attr) is None, attr
1035
+ setattr(self, attr, parser(lines))
1036
+ if self.LookupList:
1037
+ self.LookupList.LookupCount = len(self.LookupList.Lookup)
1038
+ if lookupMap is not None:
1039
+ lookupMap.applyDeferredMappings()
1040
+ if os.environ.get(LOOKUP_DEBUG_ENV_VAR):
1041
+ if "Debg" not in font:
1042
+ font["Debg"] = newTable("Debg")
1043
+ font["Debg"].data = {}
1044
+ debug = (
1045
+ font["Debg"]
1046
+ .data.setdefault(LOOKUP_DEBUG_INFO_KEY, {})
1047
+ .setdefault(tableTag, {})
1048
+ )
1049
+ for name, lookup in lookupMap.items():
1050
+ debug[str(lookup)] = ["", name, ""]
1051
+
1052
+ featureMap.applyDeferredMappings()
1053
+ container.table = self
1054
+ return container
1055
+
1056
+
1057
+ def parseGSUB(lines, font):
1058
+ return parseGSUBGPOS(lines, font, "GSUB")
1059
+
1060
+
1061
+ def parseGPOS(lines, font):
1062
+ return parseGSUBGPOS(lines, font, "GPOS")
1063
+
1064
+
1065
+ def parseAttachList(lines, font):
1066
+ points = {}
1067
+ with lines.between("attachment list"):
1068
+ for line in lines:
1069
+ glyph = makeGlyph(line[0])
1070
+ assert glyph not in points, glyph
1071
+ points[glyph] = [int(i) for i in line[1:]]
1072
+ return otl.buildAttachList(points, font.getReverseGlyphMap())
1073
+
1074
+
1075
+ def parseCaretList(lines, font):
1076
+ carets = {}
1077
+ with lines.between("carets"):
1078
+ for line in lines:
1079
+ glyph = makeGlyph(line[0])
1080
+ assert glyph not in carets, glyph
1081
+ num = int(line[1])
1082
+ thisCarets = [int(i) for i in line[2:]]
1083
+ assert num == len(thisCarets), line
1084
+ carets[glyph] = thisCarets
1085
+ return otl.buildLigCaretList(carets, {}, font.getReverseGlyphMap())
1086
+
1087
+
1088
+ def makeMarkFilteringSets(sets, font):
1089
+ self = ot.MarkGlyphSetsDef()
1090
+ self.MarkSetTableFormat = 1
1091
+ self.MarkSetCount = 1 + max(sets.keys())
1092
+ self.Coverage = [None] * self.MarkSetCount
1093
+ for k, v in sorted(sets.items()):
1094
+ self.Coverage[k] = makeCoverage(set(v), font)
1095
+ return self
1096
+
1097
+
1098
+ def parseMarkFilteringSets(lines, font):
1099
+ sets = {}
1100
+ with lines.between("set definition"):
1101
+ for line in lines:
1102
+ assert len(line) == 2, line
1103
+ glyph = makeGlyph(line[0])
1104
+ # TODO accept set names
1105
+ st = int(line[1])
1106
+ if st not in sets:
1107
+ sets[st] = []
1108
+ sets[st].append(glyph)
1109
+ return makeMarkFilteringSets(sets, font)
1110
+
1111
+
1112
+ def parseGDEF(lines, font):
1113
+ container = ttLib.getTableClass("GDEF")()
1114
+ log.debug("Parsing GDEF")
1115
+ self = ot.GDEF()
1116
+ fields = {
1117
+ "class definition begin": (
1118
+ "GlyphClassDef",
1119
+ lambda lines, font: parseClassDef(lines, font, klass=ot.GlyphClassDef),
1120
+ ),
1121
+ "attachment list begin": ("AttachList", parseAttachList),
1122
+ "carets begin": ("LigCaretList", parseCaretList),
1123
+ "mark attachment class definition begin": (
1124
+ "MarkAttachClassDef",
1125
+ lambda lines, font: parseClassDef(lines, font, klass=ot.MarkAttachClassDef),
1126
+ ),
1127
+ "markfilter set definition begin": ("MarkGlyphSetsDef", parseMarkFilteringSets),
1128
+ }
1129
+ for attr, parser in fields.values():
1130
+ setattr(self, attr, None)
1131
+ while lines.peek() is not None:
1132
+ typ = lines.peek()[0].lower()
1133
+ if typ not in fields:
1134
+ log.debug("Skipping %s", typ)
1135
+ next(lines)
1136
+ continue
1137
+ attr, parser = fields[typ]
1138
+ assert getattr(self, attr) is None, attr
1139
+ setattr(self, attr, parser(lines, font))
1140
+ self.Version = 0x00010000 if self.MarkGlyphSetsDef is None else 0x00010002
1141
+ container.table = self
1142
+ return container
1143
+
1144
+
1145
+ def parseCmap(lines, font):
1146
+ container = ttLib.getTableClass("cmap")()
1147
+ log.debug("Parsing cmap")
1148
+ tables = []
1149
+ while lines.peek() is not None:
1150
+ lines.expect("cmap subtable %d" % len(tables))
1151
+ platId, encId, fmt, lang = [
1152
+ parseCmapId(lines, field)
1153
+ for field in ("platformID", "encodingID", "format", "language")
1154
+ ]
1155
+ table = cmap_classes[fmt](fmt)
1156
+ table.platformID = platId
1157
+ table.platEncID = encId
1158
+ table.language = lang
1159
+ table.cmap = {}
1160
+ line = next(lines)
1161
+ while line[0] != "end subtable":
1162
+ table.cmap[int(line[0], 16)] = line[1]
1163
+ line = next(lines)
1164
+ tables.append(table)
1165
+ container.tableVersion = 0
1166
+ container.tables = tables
1167
+ return container
1168
+
1169
+
1170
+ def parseCmapId(lines, field):
1171
+ line = next(lines)
1172
+ assert field == line[0]
1173
+ return int(line[1])
1174
+
1175
+
1176
+ def parseTable(lines, font, tableTag=None):
1177
+ log.debug("Parsing table")
1178
+ line = lines.peeks()
1179
+ tag = None
1180
+ if line[0].split()[0] == "FontDame":
1181
+ tag = line[0].split()[1]
1182
+ elif " ".join(line[0].split()[:3]) == "Font Chef Table":
1183
+ tag = line[0].split()[3]
1184
+ if tag is not None:
1185
+ next(lines)
1186
+ tag = tag.ljust(4)
1187
+ if tableTag is None:
1188
+ tableTag = tag
1189
+ else:
1190
+ assert tableTag == tag, (tableTag, tag)
1191
+
1192
+ assert (
1193
+ tableTag is not None
1194
+ ), "Don't know what table to parse and data doesn't specify"
1195
+
1196
+ return {
1197
+ "GSUB": parseGSUB,
1198
+ "GPOS": parseGPOS,
1199
+ "GDEF": parseGDEF,
1200
+ "cmap": parseCmap,
1201
+ }[tableTag](lines, font)
1202
+
1203
+
1204
+ class Tokenizer(object):
1205
+ def __init__(self, f):
1206
+ # TODO BytesIO / StringIO as needed? also, figure out whether we work on bytes or unicode
1207
+ lines = iter(f)
1208
+ try:
1209
+ self.filename = f.name
1210
+ except:
1211
+ self.filename = None
1212
+ self.lines = iter(lines)
1213
+ self.line = ""
1214
+ self.lineno = 0
1215
+ self.stoppers = []
1216
+ self.buffer = None
1217
+
1218
+ def __iter__(self):
1219
+ return self
1220
+
1221
+ def _next_line(self):
1222
+ self.lineno += 1
1223
+ line = self.line = next(self.lines)
1224
+ line = [s.strip() for s in line.split("\t")]
1225
+ if len(line) == 1 and not line[0]:
1226
+ del line[0]
1227
+ if line and not line[-1]:
1228
+ log.warning("trailing tab found on line %d: %s" % (self.lineno, self.line))
1229
+ while line and not line[-1]:
1230
+ del line[-1]
1231
+ return line
1232
+
1233
+ def _next_nonempty(self):
1234
+ while True:
1235
+ line = self._next_line()
1236
+ # Skip comments and empty lines
1237
+ if line and line[0] and (line[0][0] != "%" or line[0] == "% subtable"):
1238
+ return line
1239
+
1240
+ def _next_buffered(self):
1241
+ if self.buffer:
1242
+ ret = self.buffer
1243
+ self.buffer = None
1244
+ return ret
1245
+ else:
1246
+ return self._next_nonempty()
1247
+
1248
+ def __next__(self):
1249
+ line = self._next_buffered()
1250
+ if line[0].lower() in self.stoppers:
1251
+ self.buffer = line
1252
+ raise StopIteration
1253
+ return line
1254
+
1255
+ def next(self):
1256
+ return self.__next__()
1257
+
1258
+ def peek(self):
1259
+ if not self.buffer:
1260
+ try:
1261
+ self.buffer = self._next_nonempty()
1262
+ except StopIteration:
1263
+ return None
1264
+ if self.buffer[0].lower() in self.stoppers:
1265
+ return None
1266
+ return self.buffer
1267
+
1268
+ def peeks(self):
1269
+ ret = self.peek()
1270
+ return ret if ret is not None else ("",)
1271
+
1272
+ @contextmanager
1273
+ def between(self, tag):
1274
+ start = tag + " begin"
1275
+ end = tag + " end"
1276
+ self.expectendswith(start)
1277
+ self.stoppers.append(end)
1278
+ yield
1279
+ del self.stoppers[-1]
1280
+ self.expect(tag + " end")
1281
+
1282
+ @contextmanager
1283
+ def until(self, tags):
1284
+ if type(tags) is not tuple:
1285
+ tags = (tags,)
1286
+ self.stoppers.extend(tags)
1287
+ yield
1288
+ del self.stoppers[-len(tags) :]
1289
+
1290
+ def expect(self, s):
1291
+ line = next(self)
1292
+ tag = line[0].lower()
1293
+ assert tag == s, "Expected '%s', got '%s'" % (s, tag)
1294
+ return line
1295
+
1296
+ def expectendswith(self, s):
1297
+ line = next(self)
1298
+ tag = line[0].lower()
1299
+ assert tag.endswith(s), "Expected '*%s', got '%s'" % (s, tag)
1300
+ return line
1301
+
1302
+
1303
+ def build(f, font, tableTag=None):
1304
+ """Convert a Monotype font layout file to an OpenType layout object
1305
+
1306
+ A font object must be passed, but this may be a "dummy" font; it is only
1307
+ used for sorting glyph sets when making coverage tables and to hold the
1308
+ OpenType layout table while it is being built.
1309
+
1310
+ Args:
1311
+ f: A file object.
1312
+ font (TTFont): A font object.
1313
+ tableTag (string): If provided, asserts that the file contains data for the
1314
+ given OpenType table.
1315
+
1316
+ Returns:
1317
+ An object representing the table. (e.g. ``table_G_S_U_B_``)
1318
+ """
1319
+ lines = Tokenizer(f)
1320
+ return parseTable(lines, font, tableTag=tableTag)
1321
+
1322
+
1323
+ def main(args=None, font=None):
1324
+ """Convert a FontDame OTL file to TTX XML
1325
+
1326
+ Writes XML output to stdout.
1327
+
1328
+ Args:
1329
+ args: Command line arguments (``--font``, ``--table``, input files).
1330
+ """
1331
+ import sys
1332
+ from fontTools import configLogger
1333
+ from fontTools.misc.testTools import MockFont
1334
+
1335
+ if args is None:
1336
+ args = sys.argv[1:]
1337
+
1338
+ # configure the library logger (for >= WARNING)
1339
+ configLogger()
1340
+ # comment this out to enable debug messages from mtiLib's logger
1341
+ # log.setLevel(logging.DEBUG)
1342
+
1343
+ import argparse
1344
+
1345
+ parser = argparse.ArgumentParser(
1346
+ "fonttools mtiLib",
1347
+ description=main.__doc__,
1348
+ )
1349
+
1350
+ parser.add_argument(
1351
+ "--font",
1352
+ "-f",
1353
+ metavar="FILE",
1354
+ dest="font",
1355
+ help="Input TTF files (used for glyph classes and sorting coverage tables)",
1356
+ )
1357
+ parser.add_argument(
1358
+ "--table",
1359
+ "-t",
1360
+ metavar="TABLE",
1361
+ dest="tableTag",
1362
+ help="Table to fill (sniffed from input file if not provided)",
1363
+ )
1364
+ parser.add_argument(
1365
+ "inputs", metavar="FILE", type=str, nargs="+", help="Input FontDame .txt files"
1366
+ )
1367
+
1368
+ args = parser.parse_args(args)
1369
+
1370
+ if font is None:
1371
+ if args.font:
1372
+ font = ttLib.TTFont(args.font)
1373
+ else:
1374
+ font = MockFont()
1375
+
1376
+ for f in args.inputs:
1377
+ log.debug("Processing %s", f)
1378
+ with open(f, "rt", encoding="utf-8-sig") as f:
1379
+ table = build(f, font, tableTag=args.tableTag)
1380
+ blob = table.compile(font) # Make sure it compiles
1381
+ decompiled = table.__class__()
1382
+ decompiled.decompile(blob, font) # Make sure it decompiles!
1383
+
1384
+ # continue
1385
+ from fontTools.misc import xmlWriter
1386
+
1387
+ tag = table.tableTag
1388
+ writer = xmlWriter.XMLWriter(sys.stdout)
1389
+ writer.begintag(tag)
1390
+ writer.newline()
1391
+ # table.toXML(writer, font)
1392
+ decompiled.toXML(writer, font)
1393
+ writer.endtag(tag)
1394
+ writer.newline()
1395
+
1396
+
1397
+ if __name__ == "__main__":
1398
+ import sys
1399
+
1400
+ sys.exit(main())