fonttools 4.55.6__cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

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

Potentially problematic release.


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

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