fonttools 4.55.6__cp311-cp311-musllinux_1_2_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-311-x86_64-linux-musl.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-311-x86_64-linux-musl.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-311-x86_64-linux-musl.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-311-x86_64-linux-musl.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-311-x86_64-linux-musl.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-311-x86_64-linux-musl.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 +5 -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,2134 @@
1
+ from fontTools.feaLib.error import FeatureLibError
2
+ from fontTools.feaLib.location import FeatureLibLocation
3
+ from fontTools.misc.encodingTools import getEncoding
4
+ from fontTools.misc.textTools import byteord, tobytes
5
+ from collections import OrderedDict
6
+ import itertools
7
+
8
+ SHIFT = " " * 4
9
+
10
+ __all__ = [
11
+ "Element",
12
+ "FeatureFile",
13
+ "Comment",
14
+ "GlyphName",
15
+ "GlyphClass",
16
+ "GlyphClassName",
17
+ "MarkClassName",
18
+ "AnonymousBlock",
19
+ "Block",
20
+ "FeatureBlock",
21
+ "NestedBlock",
22
+ "LookupBlock",
23
+ "GlyphClassDefinition",
24
+ "GlyphClassDefStatement",
25
+ "MarkClass",
26
+ "MarkClassDefinition",
27
+ "AlternateSubstStatement",
28
+ "Anchor",
29
+ "AnchorDefinition",
30
+ "AttachStatement",
31
+ "AxisValueLocationStatement",
32
+ "BaseAxis",
33
+ "CVParametersNameStatement",
34
+ "ChainContextPosStatement",
35
+ "ChainContextSubstStatement",
36
+ "CharacterStatement",
37
+ "ConditionsetStatement",
38
+ "CursivePosStatement",
39
+ "ElidedFallbackName",
40
+ "ElidedFallbackNameID",
41
+ "Expression",
42
+ "FeatureNameStatement",
43
+ "FeatureReferenceStatement",
44
+ "FontRevisionStatement",
45
+ "HheaField",
46
+ "IgnorePosStatement",
47
+ "IgnoreSubstStatement",
48
+ "IncludeStatement",
49
+ "LanguageStatement",
50
+ "LanguageSystemStatement",
51
+ "LigatureCaretByIndexStatement",
52
+ "LigatureCaretByPosStatement",
53
+ "LigatureSubstStatement",
54
+ "LookupFlagStatement",
55
+ "LookupReferenceStatement",
56
+ "MarkBasePosStatement",
57
+ "MarkLigPosStatement",
58
+ "MarkMarkPosStatement",
59
+ "MultipleSubstStatement",
60
+ "NameRecord",
61
+ "OS2Field",
62
+ "PairPosStatement",
63
+ "ReverseChainSingleSubstStatement",
64
+ "ScriptStatement",
65
+ "SinglePosStatement",
66
+ "SingleSubstStatement",
67
+ "SizeParameters",
68
+ "Statement",
69
+ "STATAxisValueStatement",
70
+ "STATDesignAxisStatement",
71
+ "STATNameStatement",
72
+ "SubtableStatement",
73
+ "TableBlock",
74
+ "ValueRecord",
75
+ "ValueRecordDefinition",
76
+ "VheaField",
77
+ ]
78
+
79
+
80
+ def deviceToString(device):
81
+ if device is None:
82
+ return "<device NULL>"
83
+ else:
84
+ return "<device %s>" % ", ".join("%d %d" % t for t in device)
85
+
86
+
87
+ fea_keywords = set(
88
+ [
89
+ "anchor",
90
+ "anchordef",
91
+ "anon",
92
+ "anonymous",
93
+ "by",
94
+ "contour",
95
+ "cursive",
96
+ "device",
97
+ "enum",
98
+ "enumerate",
99
+ "excludedflt",
100
+ "exclude_dflt",
101
+ "feature",
102
+ "from",
103
+ "ignore",
104
+ "ignorebaseglyphs",
105
+ "ignoreligatures",
106
+ "ignoremarks",
107
+ "include",
108
+ "includedflt",
109
+ "include_dflt",
110
+ "language",
111
+ "languagesystem",
112
+ "lookup",
113
+ "lookupflag",
114
+ "mark",
115
+ "markattachmenttype",
116
+ "markclass",
117
+ "nameid",
118
+ "null",
119
+ "parameters",
120
+ "pos",
121
+ "position",
122
+ "required",
123
+ "righttoleft",
124
+ "reversesub",
125
+ "rsub",
126
+ "script",
127
+ "sub",
128
+ "substitute",
129
+ "subtable",
130
+ "table",
131
+ "usemarkfilteringset",
132
+ "useextension",
133
+ "valuerecorddef",
134
+ "base",
135
+ "gdef",
136
+ "head",
137
+ "hhea",
138
+ "name",
139
+ "vhea",
140
+ "vmtx",
141
+ ]
142
+ )
143
+
144
+
145
+ def asFea(g):
146
+ if hasattr(g, "asFea"):
147
+ return g.asFea()
148
+ elif isinstance(g, tuple) and len(g) == 2:
149
+ return asFea(g[0]) + " - " + asFea(g[1]) # a range
150
+ elif g.lower() in fea_keywords:
151
+ return "\\" + g
152
+ else:
153
+ return g
154
+
155
+
156
+ class Element(object):
157
+ """A base class representing "something" in a feature file."""
158
+
159
+ def __init__(self, location=None):
160
+ #: location of this element as a `FeatureLibLocation` object.
161
+ if location and not isinstance(location, FeatureLibLocation):
162
+ location = FeatureLibLocation(*location)
163
+ self.location = location
164
+
165
+ def build(self, builder):
166
+ pass
167
+
168
+ def asFea(self, indent=""):
169
+ """Returns this element as a string of feature code. For block-type
170
+ elements (such as :class:`FeatureBlock`), the `indent` string is
171
+ added to the start of each line in the output."""
172
+ raise NotImplementedError
173
+
174
+ def __str__(self):
175
+ return self.asFea()
176
+
177
+
178
+ class Statement(Element):
179
+ pass
180
+
181
+
182
+ class Expression(Element):
183
+ pass
184
+
185
+
186
+ class Comment(Element):
187
+ """A comment in a feature file."""
188
+
189
+ def __init__(self, text, location=None):
190
+ super(Comment, self).__init__(location)
191
+ #: Text of the comment
192
+ self.text = text
193
+
194
+ def asFea(self, indent=""):
195
+ return self.text
196
+
197
+
198
+ class NullGlyph(Expression):
199
+ """The NULL glyph, used in glyph deletion substitutions."""
200
+
201
+ def __init__(self, location=None):
202
+ Expression.__init__(self, location)
203
+ #: The name itself as a string
204
+
205
+ def glyphSet(self):
206
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
207
+ return ()
208
+
209
+ def asFea(self, indent=""):
210
+ return "NULL"
211
+
212
+
213
+ class GlyphName(Expression):
214
+ """A single glyph name, such as ``cedilla``."""
215
+
216
+ def __init__(self, glyph, location=None):
217
+ Expression.__init__(self, location)
218
+ #: The name itself as a string
219
+ self.glyph = glyph
220
+
221
+ def glyphSet(self):
222
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
223
+ return (self.glyph,)
224
+
225
+ def asFea(self, indent=""):
226
+ return asFea(self.glyph)
227
+
228
+
229
+ class GlyphClass(Expression):
230
+ """A glyph class, such as ``[acute cedilla grave]``."""
231
+
232
+ def __init__(self, glyphs=None, location=None):
233
+ Expression.__init__(self, location)
234
+ #: The list of glyphs in this class, as :class:`GlyphName` objects.
235
+ self.glyphs = glyphs if glyphs is not None else []
236
+ self.original = []
237
+ self.curr = 0
238
+
239
+ def glyphSet(self):
240
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
241
+ return tuple(self.glyphs)
242
+
243
+ def asFea(self, indent=""):
244
+ if len(self.original):
245
+ if self.curr < len(self.glyphs):
246
+ self.original.extend(self.glyphs[self.curr :])
247
+ self.curr = len(self.glyphs)
248
+ return "[" + " ".join(map(asFea, self.original)) + "]"
249
+ else:
250
+ return "[" + " ".join(map(asFea, self.glyphs)) + "]"
251
+
252
+ def extend(self, glyphs):
253
+ """Add a list of :class:`GlyphName` objects to the class."""
254
+ self.glyphs.extend(glyphs)
255
+
256
+ def append(self, glyph):
257
+ """Add a single :class:`GlyphName` object to the class."""
258
+ self.glyphs.append(glyph)
259
+
260
+ def add_range(self, start, end, glyphs):
261
+ """Add a range (e.g. ``A-Z``) to the class. ``start`` and ``end``
262
+ are either :class:`GlyphName` objects or strings representing the
263
+ start and end glyphs in the class, and ``glyphs`` is the full list of
264
+ :class:`GlyphName` objects in the range."""
265
+ if self.curr < len(self.glyphs):
266
+ self.original.extend(self.glyphs[self.curr :])
267
+ self.original.append((start, end))
268
+ self.glyphs.extend(glyphs)
269
+ self.curr = len(self.glyphs)
270
+
271
+ def add_cid_range(self, start, end, glyphs):
272
+ """Add a range to the class by glyph ID. ``start`` and ``end`` are the
273
+ initial and final IDs, and ``glyphs`` is the full list of
274
+ :class:`GlyphName` objects in the range."""
275
+ if self.curr < len(self.glyphs):
276
+ self.original.extend(self.glyphs[self.curr :])
277
+ self.original.append(("\\{}".format(start), "\\{}".format(end)))
278
+ self.glyphs.extend(glyphs)
279
+ self.curr = len(self.glyphs)
280
+
281
+ def add_class(self, gc):
282
+ """Add glyphs from the given :class:`GlyphClassName` object to the
283
+ class."""
284
+ if self.curr < len(self.glyphs):
285
+ self.original.extend(self.glyphs[self.curr :])
286
+ self.original.append(gc)
287
+ self.glyphs.extend(gc.glyphSet())
288
+ self.curr = len(self.glyphs)
289
+
290
+
291
+ class GlyphClassName(Expression):
292
+ """A glyph class name, such as ``@FRENCH_MARKS``. This must be instantiated
293
+ with a :class:`GlyphClassDefinition` object."""
294
+
295
+ def __init__(self, glyphclass, location=None):
296
+ Expression.__init__(self, location)
297
+ assert isinstance(glyphclass, GlyphClassDefinition)
298
+ self.glyphclass = glyphclass
299
+
300
+ def glyphSet(self):
301
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
302
+ return tuple(self.glyphclass.glyphSet())
303
+
304
+ def asFea(self, indent=""):
305
+ return "@" + self.glyphclass.name
306
+
307
+
308
+ class MarkClassName(Expression):
309
+ """A mark class name, such as ``@FRENCH_MARKS`` defined with ``markClass``.
310
+ This must be instantiated with a :class:`MarkClass` object."""
311
+
312
+ def __init__(self, markClass, location=None):
313
+ Expression.__init__(self, location)
314
+ assert isinstance(markClass, MarkClass)
315
+ self.markClass = markClass
316
+
317
+ def glyphSet(self):
318
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
319
+ return self.markClass.glyphSet()
320
+
321
+ def asFea(self, indent=""):
322
+ return "@" + self.markClass.name
323
+
324
+
325
+ class AnonymousBlock(Statement):
326
+ """An anonymous data block."""
327
+
328
+ def __init__(self, tag, content, location=None):
329
+ Statement.__init__(self, location)
330
+ self.tag = tag #: string containing the block's "tag"
331
+ self.content = content #: block data as string
332
+
333
+ def asFea(self, indent=""):
334
+ res = "anon {} {{\n".format(self.tag)
335
+ res += self.content
336
+ res += "}} {};\n\n".format(self.tag)
337
+ return res
338
+
339
+
340
+ class Block(Statement):
341
+ """A block of statements: feature, lookup, etc."""
342
+
343
+ def __init__(self, location=None):
344
+ Statement.__init__(self, location)
345
+ self.statements = [] #: Statements contained in the block
346
+
347
+ def build(self, builder):
348
+ """When handed a 'builder' object of comparable interface to
349
+ :class:`fontTools.feaLib.builder`, walks the statements in this
350
+ block, calling the builder callbacks."""
351
+ for s in self.statements:
352
+ s.build(builder)
353
+
354
+ def asFea(self, indent=""):
355
+ indent += SHIFT
356
+ return (
357
+ indent
358
+ + ("\n" + indent).join([s.asFea(indent=indent) for s in self.statements])
359
+ + "\n"
360
+ )
361
+
362
+
363
+ class FeatureFile(Block):
364
+ """The top-level element of the syntax tree, containing the whole feature
365
+ file in its ``statements`` attribute."""
366
+
367
+ def __init__(self):
368
+ Block.__init__(self, location=None)
369
+ self.markClasses = {} # name --> ast.MarkClass
370
+
371
+ def asFea(self, indent=""):
372
+ return "\n".join(s.asFea(indent=indent) for s in self.statements)
373
+
374
+
375
+ class FeatureBlock(Block):
376
+ """A named feature block."""
377
+
378
+ def __init__(self, name, use_extension=False, location=None):
379
+ Block.__init__(self, location)
380
+ self.name, self.use_extension = name, use_extension
381
+
382
+ def build(self, builder):
383
+ """Call the ``start_feature`` callback on the builder object, visit
384
+ all the statements in this feature, and then call ``end_feature``."""
385
+ # TODO(sascha): Handle use_extension.
386
+ builder.start_feature(self.location, self.name)
387
+ # language exclude_dflt statements modify builder.features_
388
+ # limit them to this block with temporary builder.features_
389
+ features = builder.features_
390
+ builder.features_ = {}
391
+ Block.build(self, builder)
392
+ for key, value in builder.features_.items():
393
+ features.setdefault(key, []).extend(value)
394
+ builder.features_ = features
395
+ builder.end_feature()
396
+
397
+ def asFea(self, indent=""):
398
+ res = indent + "feature %s " % self.name.strip()
399
+ if self.use_extension:
400
+ res += "useExtension "
401
+ res += "{\n"
402
+ res += Block.asFea(self, indent=indent)
403
+ res += indent + "} %s;\n" % self.name.strip()
404
+ return res
405
+
406
+
407
+ class NestedBlock(Block):
408
+ """A block inside another block, for example when found inside a
409
+ ``cvParameters`` block."""
410
+
411
+ def __init__(self, tag, block_name, location=None):
412
+ Block.__init__(self, location)
413
+ self.tag = tag
414
+ self.block_name = block_name
415
+
416
+ def build(self, builder):
417
+ Block.build(self, builder)
418
+ if self.block_name == "ParamUILabelNameID":
419
+ builder.add_to_cv_num_named_params(self.tag)
420
+
421
+ def asFea(self, indent=""):
422
+ res = "{}{} {{\n".format(indent, self.block_name)
423
+ res += Block.asFea(self, indent=indent)
424
+ res += "{}}};\n".format(indent)
425
+ return res
426
+
427
+
428
+ class LookupBlock(Block):
429
+ """A named lookup, containing ``statements``."""
430
+
431
+ def __init__(self, name, use_extension=False, location=None):
432
+ Block.__init__(self, location)
433
+ self.name, self.use_extension = name, use_extension
434
+
435
+ def build(self, builder):
436
+ # TODO(sascha): Handle use_extension.
437
+ builder.start_lookup_block(self.location, self.name)
438
+ Block.build(self, builder)
439
+ builder.end_lookup_block()
440
+
441
+ def asFea(self, indent=""):
442
+ res = "lookup {} ".format(self.name)
443
+ if self.use_extension:
444
+ res += "useExtension "
445
+ res += "{\n"
446
+ res += Block.asFea(self, indent=indent)
447
+ res += "{}}} {};\n".format(indent, self.name)
448
+ return res
449
+
450
+
451
+ class TableBlock(Block):
452
+ """A ``table ... { }`` block."""
453
+
454
+ def __init__(self, name, location=None):
455
+ Block.__init__(self, location)
456
+ self.name = name
457
+
458
+ def asFea(self, indent=""):
459
+ res = "table {} {{\n".format(self.name.strip())
460
+ res += super(TableBlock, self).asFea(indent=indent)
461
+ res += "}} {};\n".format(self.name.strip())
462
+ return res
463
+
464
+
465
+ class GlyphClassDefinition(Statement):
466
+ """Example: ``@UPPERCASE = [A-Z];``."""
467
+
468
+ def __init__(self, name, glyphs, location=None):
469
+ Statement.__init__(self, location)
470
+ self.name = name #: class name as a string, without initial ``@``
471
+ self.glyphs = glyphs #: a :class:`GlyphClass` object
472
+
473
+ def glyphSet(self):
474
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
475
+ return tuple(self.glyphs.glyphSet())
476
+
477
+ def asFea(self, indent=""):
478
+ return "@" + self.name + " = " + self.glyphs.asFea() + ";"
479
+
480
+
481
+ class GlyphClassDefStatement(Statement):
482
+ """Example: ``GlyphClassDef @UPPERCASE, [B], [C], [D];``. The parameters
483
+ must be either :class:`GlyphClass` or :class:`GlyphClassName` objects, or
484
+ ``None``."""
485
+
486
+ def __init__(
487
+ self, baseGlyphs, markGlyphs, ligatureGlyphs, componentGlyphs, location=None
488
+ ):
489
+ Statement.__init__(self, location)
490
+ self.baseGlyphs, self.markGlyphs = (baseGlyphs, markGlyphs)
491
+ self.ligatureGlyphs = ligatureGlyphs
492
+ self.componentGlyphs = componentGlyphs
493
+
494
+ def build(self, builder):
495
+ """Calls the builder's ``add_glyphClassDef`` callback."""
496
+ base = self.baseGlyphs.glyphSet() if self.baseGlyphs else tuple()
497
+ liga = self.ligatureGlyphs.glyphSet() if self.ligatureGlyphs else tuple()
498
+ mark = self.markGlyphs.glyphSet() if self.markGlyphs else tuple()
499
+ comp = self.componentGlyphs.glyphSet() if self.componentGlyphs else tuple()
500
+ builder.add_glyphClassDef(self.location, base, liga, mark, comp)
501
+
502
+ def asFea(self, indent=""):
503
+ return "GlyphClassDef {}, {}, {}, {};".format(
504
+ self.baseGlyphs.asFea() if self.baseGlyphs else "",
505
+ self.ligatureGlyphs.asFea() if self.ligatureGlyphs else "",
506
+ self.markGlyphs.asFea() if self.markGlyphs else "",
507
+ self.componentGlyphs.asFea() if self.componentGlyphs else "",
508
+ )
509
+
510
+
511
+ class MarkClass(object):
512
+ """One `or more` ``markClass`` statements for the same mark class.
513
+
514
+ While glyph classes can be defined only once, the feature file format
515
+ allows expanding mark classes with multiple definitions, each using
516
+ different glyphs and anchors. The following are two ``MarkClassDefinitions``
517
+ for the same ``MarkClass``::
518
+
519
+ markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
520
+ markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;
521
+
522
+ The ``MarkClass`` object is therefore just a container for a list of
523
+ :class:`MarkClassDefinition` statements.
524
+ """
525
+
526
+ def __init__(self, name):
527
+ self.name = name
528
+ self.definitions = []
529
+ self.glyphs = OrderedDict() # glyph --> ast.MarkClassDefinitions
530
+
531
+ def addDefinition(self, definition):
532
+ """Add a :class:`MarkClassDefinition` statement to this mark class."""
533
+ assert isinstance(definition, MarkClassDefinition)
534
+ self.definitions.append(definition)
535
+ for glyph in definition.glyphSet():
536
+ if glyph in self.glyphs:
537
+ otherLoc = self.glyphs[glyph].location
538
+ if otherLoc is None:
539
+ end = ""
540
+ else:
541
+ end = f" at {otherLoc}"
542
+ raise FeatureLibError(
543
+ "Glyph %s already defined%s" % (glyph, end), definition.location
544
+ )
545
+ self.glyphs[glyph] = definition
546
+
547
+ def glyphSet(self):
548
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
549
+ return tuple(self.glyphs.keys())
550
+
551
+ def asFea(self, indent=""):
552
+ res = "\n".join(d.asFea() for d in self.definitions)
553
+ return res
554
+
555
+
556
+ class MarkClassDefinition(Statement):
557
+ """A single ``markClass`` statement. The ``markClass`` should be a
558
+ :class:`MarkClass` object, the ``anchor`` an :class:`Anchor` object,
559
+ and the ``glyphs`` parameter should be a `glyph-containing object`_ .
560
+
561
+ Example:
562
+
563
+ .. code:: python
564
+
565
+ mc = MarkClass("FRENCH_ACCENTS")
566
+ mc.addDefinition( MarkClassDefinition(mc, Anchor(350, 800),
567
+ GlyphClass([ GlyphName("acute"), GlyphName("grave") ])
568
+ ) )
569
+ mc.addDefinition( MarkClassDefinition(mc, Anchor(350, -200),
570
+ GlyphClass([ GlyphName("cedilla") ])
571
+ ) )
572
+
573
+ mc.asFea()
574
+ # markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS;
575
+ # markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS;
576
+
577
+ """
578
+
579
+ def __init__(self, markClass, anchor, glyphs, location=None):
580
+ Statement.__init__(self, location)
581
+ assert isinstance(markClass, MarkClass)
582
+ assert isinstance(anchor, Anchor) and isinstance(glyphs, Expression)
583
+ self.markClass, self.anchor, self.glyphs = markClass, anchor, glyphs
584
+
585
+ def glyphSet(self):
586
+ """The glyphs in this class as a tuple of :class:`GlyphName` objects."""
587
+ return self.glyphs.glyphSet()
588
+
589
+ def asFea(self, indent=""):
590
+ return "markClass {} {} @{};".format(
591
+ self.glyphs.asFea(), self.anchor.asFea(), self.markClass.name
592
+ )
593
+
594
+
595
+ class AlternateSubstStatement(Statement):
596
+ """A ``sub ... from ...`` statement.
597
+
598
+ ``glyph`` and ``replacement`` should be `glyph-containing objects`_.
599
+ ``prefix`` and ``suffix`` should be lists of `glyph-containing objects`_."""
600
+
601
+ def __init__(self, prefix, glyph, suffix, replacement, location=None):
602
+ Statement.__init__(self, location)
603
+ self.prefix, self.glyph, self.suffix = (prefix, glyph, suffix)
604
+ self.replacement = replacement
605
+
606
+ def build(self, builder):
607
+ """Calls the builder's ``add_alternate_subst`` callback."""
608
+ glyph = self.glyph.glyphSet()
609
+ assert len(glyph) == 1, glyph
610
+ glyph = list(glyph)[0]
611
+ prefix = [p.glyphSet() for p in self.prefix]
612
+ suffix = [s.glyphSet() for s in self.suffix]
613
+ replacement = self.replacement.glyphSet()
614
+ builder.add_alternate_subst(self.location, prefix, glyph, suffix, replacement)
615
+
616
+ def asFea(self, indent=""):
617
+ res = "sub "
618
+ if len(self.prefix) or len(self.suffix):
619
+ if len(self.prefix):
620
+ res += " ".join(map(asFea, self.prefix)) + " "
621
+ res += asFea(self.glyph) + "'" # even though we really only use 1
622
+ if len(self.suffix):
623
+ res += " " + " ".join(map(asFea, self.suffix))
624
+ else:
625
+ res += asFea(self.glyph)
626
+ res += " from "
627
+ res += asFea(self.replacement)
628
+ res += ";"
629
+ return res
630
+
631
+
632
+ class Anchor(Expression):
633
+ """An ``Anchor`` element, used inside a ``pos`` rule.
634
+
635
+ If a ``name`` is given, this will be used in preference to the coordinates.
636
+ Other values should be integer.
637
+ """
638
+
639
+ def __init__(
640
+ self,
641
+ x,
642
+ y,
643
+ name=None,
644
+ contourpoint=None,
645
+ xDeviceTable=None,
646
+ yDeviceTable=None,
647
+ location=None,
648
+ ):
649
+ Expression.__init__(self, location)
650
+ self.name = name
651
+ self.x, self.y, self.contourpoint = x, y, contourpoint
652
+ self.xDeviceTable, self.yDeviceTable = xDeviceTable, yDeviceTable
653
+
654
+ def asFea(self, indent=""):
655
+ if self.name is not None:
656
+ return "<anchor {}>".format(self.name)
657
+ res = "<anchor {} {}".format(self.x, self.y)
658
+ if self.contourpoint:
659
+ res += " contourpoint {}".format(self.contourpoint)
660
+ if self.xDeviceTable or self.yDeviceTable:
661
+ res += " "
662
+ res += deviceToString(self.xDeviceTable)
663
+ res += " "
664
+ res += deviceToString(self.yDeviceTable)
665
+ res += ">"
666
+ return res
667
+
668
+
669
+ class AnchorDefinition(Statement):
670
+ """A named anchor definition. (2.e.viii). ``name`` should be a string."""
671
+
672
+ def __init__(self, name, x, y, contourpoint=None, location=None):
673
+ Statement.__init__(self, location)
674
+ self.name, self.x, self.y, self.contourpoint = name, x, y, contourpoint
675
+
676
+ def asFea(self, indent=""):
677
+ res = "anchorDef {} {}".format(self.x, self.y)
678
+ if self.contourpoint:
679
+ res += " contourpoint {}".format(self.contourpoint)
680
+ res += " {};".format(self.name)
681
+ return res
682
+
683
+
684
+ class AttachStatement(Statement):
685
+ """A ``GDEF`` table ``Attach`` statement."""
686
+
687
+ def __init__(self, glyphs, contourPoints, location=None):
688
+ Statement.__init__(self, location)
689
+ self.glyphs = glyphs #: A `glyph-containing object`_
690
+ self.contourPoints = contourPoints #: A list of integer contour points
691
+
692
+ def build(self, builder):
693
+ """Calls the builder's ``add_attach_points`` callback."""
694
+ glyphs = self.glyphs.glyphSet()
695
+ builder.add_attach_points(self.location, glyphs, self.contourPoints)
696
+
697
+ def asFea(self, indent=""):
698
+ return "Attach {} {};".format(
699
+ self.glyphs.asFea(), " ".join(str(c) for c in self.contourPoints)
700
+ )
701
+
702
+
703
+ class ChainContextPosStatement(Statement):
704
+ r"""A chained contextual positioning statement.
705
+
706
+ ``prefix``, ``glyphs``, and ``suffix`` should be lists of
707
+ `glyph-containing objects`_ .
708
+
709
+ ``lookups`` should be a list of elements representing what lookups
710
+ to apply at each glyph position. Each element should be a
711
+ :class:`LookupBlock` to apply a single chaining lookup at the given
712
+ position, a list of :class:`LookupBlock`\ s to apply multiple
713
+ lookups, or ``None`` to apply no lookup. The length of the outer
714
+ list should equal the length of ``glyphs``; the inner lists can be
715
+ of variable length."""
716
+
717
+ def __init__(self, prefix, glyphs, suffix, lookups, location=None):
718
+ Statement.__init__(self, location)
719
+ self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
720
+ self.lookups = list(lookups)
721
+ for i, lookup in enumerate(lookups):
722
+ if lookup:
723
+ try:
724
+ (_ for _ in lookup)
725
+ except TypeError:
726
+ self.lookups[i] = [lookup]
727
+
728
+ def build(self, builder):
729
+ """Calls the builder's ``add_chain_context_pos`` callback."""
730
+ prefix = [p.glyphSet() for p in self.prefix]
731
+ glyphs = [g.glyphSet() for g in self.glyphs]
732
+ suffix = [s.glyphSet() for s in self.suffix]
733
+ builder.add_chain_context_pos(
734
+ self.location, prefix, glyphs, suffix, self.lookups
735
+ )
736
+
737
+ def asFea(self, indent=""):
738
+ res = "pos "
739
+ if (
740
+ len(self.prefix)
741
+ or len(self.suffix)
742
+ or any([x is not None for x in self.lookups])
743
+ ):
744
+ if len(self.prefix):
745
+ res += " ".join(g.asFea() for g in self.prefix) + " "
746
+ for i, g in enumerate(self.glyphs):
747
+ res += g.asFea() + "'"
748
+ if self.lookups[i]:
749
+ for lu in self.lookups[i]:
750
+ res += " lookup " + lu.name
751
+ if i < len(self.glyphs) - 1:
752
+ res += " "
753
+ if len(self.suffix):
754
+ res += " " + " ".join(map(asFea, self.suffix))
755
+ else:
756
+ res += " ".join(map(asFea, self.glyph))
757
+ res += ";"
758
+ return res
759
+
760
+
761
+ class ChainContextSubstStatement(Statement):
762
+ r"""A chained contextual substitution statement.
763
+
764
+ ``prefix``, ``glyphs``, and ``suffix`` should be lists of
765
+ `glyph-containing objects`_ .
766
+
767
+ ``lookups`` should be a list of elements representing what lookups
768
+ to apply at each glyph position. Each element should be a
769
+ :class:`LookupBlock` to apply a single chaining lookup at the given
770
+ position, a list of :class:`LookupBlock`\ s to apply multiple
771
+ lookups, or ``None`` to apply no lookup. The length of the outer
772
+ list should equal the length of ``glyphs``; the inner lists can be
773
+ of variable length."""
774
+
775
+ def __init__(self, prefix, glyphs, suffix, lookups, location=None):
776
+ Statement.__init__(self, location)
777
+ self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
778
+ self.lookups = list(lookups)
779
+ for i, lookup in enumerate(lookups):
780
+ if lookup:
781
+ try:
782
+ (_ for _ in lookup)
783
+ except TypeError:
784
+ self.lookups[i] = [lookup]
785
+
786
+ def build(self, builder):
787
+ """Calls the builder's ``add_chain_context_subst`` callback."""
788
+ prefix = [p.glyphSet() for p in self.prefix]
789
+ glyphs = [g.glyphSet() for g in self.glyphs]
790
+ suffix = [s.glyphSet() for s in self.suffix]
791
+ builder.add_chain_context_subst(
792
+ self.location, prefix, glyphs, suffix, self.lookups
793
+ )
794
+
795
+ def asFea(self, indent=""):
796
+ res = "sub "
797
+ if (
798
+ len(self.prefix)
799
+ or len(self.suffix)
800
+ or any([x is not None for x in self.lookups])
801
+ ):
802
+ if len(self.prefix):
803
+ res += " ".join(g.asFea() for g in self.prefix) + " "
804
+ for i, g in enumerate(self.glyphs):
805
+ res += g.asFea() + "'"
806
+ if self.lookups[i]:
807
+ for lu in self.lookups[i]:
808
+ res += " lookup " + lu.name
809
+ if i < len(self.glyphs) - 1:
810
+ res += " "
811
+ if len(self.suffix):
812
+ res += " " + " ".join(map(asFea, self.suffix))
813
+ else:
814
+ res += " ".join(map(asFea, self.glyph))
815
+ res += ";"
816
+ return res
817
+
818
+
819
+ class CursivePosStatement(Statement):
820
+ """A cursive positioning statement. Entry and exit anchors can either
821
+ be :class:`Anchor` objects or ``None``."""
822
+
823
+ def __init__(self, glyphclass, entryAnchor, exitAnchor, location=None):
824
+ Statement.__init__(self, location)
825
+ self.glyphclass = glyphclass
826
+ self.entryAnchor, self.exitAnchor = entryAnchor, exitAnchor
827
+
828
+ def build(self, builder):
829
+ """Calls the builder object's ``add_cursive_pos`` callback."""
830
+ builder.add_cursive_pos(
831
+ self.location, self.glyphclass.glyphSet(), self.entryAnchor, self.exitAnchor
832
+ )
833
+
834
+ def asFea(self, indent=""):
835
+ entry = self.entryAnchor.asFea() if self.entryAnchor else "<anchor NULL>"
836
+ exit = self.exitAnchor.asFea() if self.exitAnchor else "<anchor NULL>"
837
+ return "pos cursive {} {} {};".format(self.glyphclass.asFea(), entry, exit)
838
+
839
+
840
+ class FeatureReferenceStatement(Statement):
841
+ """Example: ``feature salt;``"""
842
+
843
+ def __init__(self, featureName, location=None):
844
+ Statement.__init__(self, location)
845
+ self.location, self.featureName = (location, featureName)
846
+
847
+ def build(self, builder):
848
+ """Calls the builder object's ``add_feature_reference`` callback."""
849
+ builder.add_feature_reference(self.location, self.featureName)
850
+
851
+ def asFea(self, indent=""):
852
+ return "feature {};".format(self.featureName)
853
+
854
+
855
+ class IgnorePosStatement(Statement):
856
+ """An ``ignore pos`` statement, containing `one or more` contexts to ignore.
857
+
858
+ ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
859
+ with each of ``prefix``, ``glyphs`` and ``suffix`` being
860
+ `glyph-containing objects`_ ."""
861
+
862
+ def __init__(self, chainContexts, location=None):
863
+ Statement.__init__(self, location)
864
+ self.chainContexts = chainContexts
865
+
866
+ def build(self, builder):
867
+ """Calls the builder object's ``add_chain_context_pos`` callback on each
868
+ rule context."""
869
+ for prefix, glyphs, suffix in self.chainContexts:
870
+ prefix = [p.glyphSet() for p in prefix]
871
+ glyphs = [g.glyphSet() for g in glyphs]
872
+ suffix = [s.glyphSet() for s in suffix]
873
+ builder.add_chain_context_pos(self.location, prefix, glyphs, suffix, [])
874
+
875
+ def asFea(self, indent=""):
876
+ contexts = []
877
+ for prefix, glyphs, suffix in self.chainContexts:
878
+ res = ""
879
+ if len(prefix) or len(suffix):
880
+ if len(prefix):
881
+ res += " ".join(map(asFea, prefix)) + " "
882
+ res += " ".join(g.asFea() + "'" for g in glyphs)
883
+ if len(suffix):
884
+ res += " " + " ".join(map(asFea, suffix))
885
+ else:
886
+ res += " ".join(map(asFea, glyphs))
887
+ contexts.append(res)
888
+ return "ignore pos " + ", ".join(contexts) + ";"
889
+
890
+
891
+ class IgnoreSubstStatement(Statement):
892
+ """An ``ignore sub`` statement, containing `one or more` contexts to ignore.
893
+
894
+ ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
895
+ with each of ``prefix``, ``glyphs`` and ``suffix`` being
896
+ `glyph-containing objects`_ ."""
897
+
898
+ def __init__(self, chainContexts, location=None):
899
+ Statement.__init__(self, location)
900
+ self.chainContexts = chainContexts
901
+
902
+ def build(self, builder):
903
+ """Calls the builder object's ``add_chain_context_subst`` callback on
904
+ each rule context."""
905
+ for prefix, glyphs, suffix in self.chainContexts:
906
+ prefix = [p.glyphSet() for p in prefix]
907
+ glyphs = [g.glyphSet() for g in glyphs]
908
+ suffix = [s.glyphSet() for s in suffix]
909
+ builder.add_chain_context_subst(self.location, prefix, glyphs, suffix, [])
910
+
911
+ def asFea(self, indent=""):
912
+ contexts = []
913
+ for prefix, glyphs, suffix in self.chainContexts:
914
+ res = ""
915
+ if len(prefix):
916
+ res += " ".join(map(asFea, prefix)) + " "
917
+ res += " ".join(g.asFea() + "'" for g in glyphs)
918
+ if len(suffix):
919
+ res += " " + " ".join(map(asFea, suffix))
920
+ contexts.append(res)
921
+ return "ignore sub " + ", ".join(contexts) + ";"
922
+
923
+
924
+ class IncludeStatement(Statement):
925
+ """An ``include()`` statement."""
926
+
927
+ def __init__(self, filename, location=None):
928
+ super(IncludeStatement, self).__init__(location)
929
+ self.filename = filename #: String containing name of file to include
930
+
931
+ def build(self):
932
+ # TODO: consider lazy-loading the including parser/lexer?
933
+ raise FeatureLibError(
934
+ "Building an include statement is not implemented yet. "
935
+ "Instead, use Parser(..., followIncludes=True) for building.",
936
+ self.location,
937
+ )
938
+
939
+ def asFea(self, indent=""):
940
+ return indent + "include(%s);" % self.filename
941
+
942
+
943
+ class LanguageStatement(Statement):
944
+ """A ``language`` statement within a feature."""
945
+
946
+ def __init__(self, language, include_default=True, required=False, location=None):
947
+ Statement.__init__(self, location)
948
+ assert len(language) == 4
949
+ self.language = language #: A four-character language tag
950
+ self.include_default = include_default #: If false, "exclude_dflt"
951
+ self.required = required
952
+
953
+ def build(self, builder):
954
+ """Call the builder object's ``set_language`` callback."""
955
+ builder.set_language(
956
+ location=self.location,
957
+ language=self.language,
958
+ include_default=self.include_default,
959
+ required=self.required,
960
+ )
961
+
962
+ def asFea(self, indent=""):
963
+ res = "language {}".format(self.language.strip())
964
+ if not self.include_default:
965
+ res += " exclude_dflt"
966
+ if self.required:
967
+ res += " required"
968
+ res += ";"
969
+ return res
970
+
971
+
972
+ class LanguageSystemStatement(Statement):
973
+ """A top-level ``languagesystem`` statement."""
974
+
975
+ def __init__(self, script, language, location=None):
976
+ Statement.__init__(self, location)
977
+ self.script, self.language = (script, language)
978
+
979
+ def build(self, builder):
980
+ """Calls the builder object's ``add_language_system`` callback."""
981
+ builder.add_language_system(self.location, self.script, self.language)
982
+
983
+ def asFea(self, indent=""):
984
+ return "languagesystem {} {};".format(self.script, self.language.strip())
985
+
986
+
987
+ class FontRevisionStatement(Statement):
988
+ """A ``head`` table ``FontRevision`` statement. ``revision`` should be a
989
+ number, and will be formatted to three significant decimal places."""
990
+
991
+ def __init__(self, revision, location=None):
992
+ Statement.__init__(self, location)
993
+ self.revision = revision
994
+
995
+ def build(self, builder):
996
+ builder.set_font_revision(self.location, self.revision)
997
+
998
+ def asFea(self, indent=""):
999
+ return "FontRevision {:.3f};".format(self.revision)
1000
+
1001
+
1002
+ class LigatureCaretByIndexStatement(Statement):
1003
+ """A ``GDEF`` table ``LigatureCaretByIndex`` statement. ``glyphs`` should be
1004
+ a `glyph-containing object`_, and ``carets`` should be a list of integers."""
1005
+
1006
+ def __init__(self, glyphs, carets, location=None):
1007
+ Statement.__init__(self, location)
1008
+ self.glyphs, self.carets = (glyphs, carets)
1009
+
1010
+ def build(self, builder):
1011
+ """Calls the builder object's ``add_ligatureCaretByIndex_`` callback."""
1012
+ glyphs = self.glyphs.glyphSet()
1013
+ builder.add_ligatureCaretByIndex_(self.location, glyphs, set(self.carets))
1014
+
1015
+ def asFea(self, indent=""):
1016
+ return "LigatureCaretByIndex {} {};".format(
1017
+ self.glyphs.asFea(), " ".join(str(x) for x in self.carets)
1018
+ )
1019
+
1020
+
1021
+ class LigatureCaretByPosStatement(Statement):
1022
+ """A ``GDEF`` table ``LigatureCaretByPos`` statement. ``glyphs`` should be
1023
+ a `glyph-containing object`_, and ``carets`` should be a list of integers."""
1024
+
1025
+ def __init__(self, glyphs, carets, location=None):
1026
+ Statement.__init__(self, location)
1027
+ self.glyphs, self.carets = (glyphs, carets)
1028
+
1029
+ def build(self, builder):
1030
+ """Calls the builder object's ``add_ligatureCaretByPos_`` callback."""
1031
+ glyphs = self.glyphs.glyphSet()
1032
+ builder.add_ligatureCaretByPos_(self.location, glyphs, set(self.carets))
1033
+
1034
+ def asFea(self, indent=""):
1035
+ return "LigatureCaretByPos {} {};".format(
1036
+ self.glyphs.asFea(), " ".join(str(x) for x in self.carets)
1037
+ )
1038
+
1039
+
1040
+ class LigatureSubstStatement(Statement):
1041
+ """A chained contextual substitution statement.
1042
+
1043
+ ``prefix``, ``glyphs``, and ``suffix`` should be lists of
1044
+ `glyph-containing objects`_; ``replacement`` should be a single
1045
+ `glyph-containing object`_.
1046
+
1047
+ If ``forceChain`` is True, this is expressed as a chaining rule
1048
+ (e.g. ``sub f' i' by f_i``) even when no context is given."""
1049
+
1050
+ def __init__(self, prefix, glyphs, suffix, replacement, forceChain, location=None):
1051
+ Statement.__init__(self, location)
1052
+ self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix)
1053
+ self.replacement, self.forceChain = replacement, forceChain
1054
+
1055
+ def build(self, builder):
1056
+ prefix = [p.glyphSet() for p in self.prefix]
1057
+ glyphs = [g.glyphSet() for g in self.glyphs]
1058
+ suffix = [s.glyphSet() for s in self.suffix]
1059
+ builder.add_ligature_subst(
1060
+ self.location, prefix, glyphs, suffix, self.replacement, self.forceChain
1061
+ )
1062
+
1063
+ def asFea(self, indent=""):
1064
+ res = "sub "
1065
+ if len(self.prefix) or len(self.suffix) or self.forceChain:
1066
+ if len(self.prefix):
1067
+ res += " ".join(g.asFea() for g in self.prefix) + " "
1068
+ res += " ".join(g.asFea() + "'" for g in self.glyphs)
1069
+ if len(self.suffix):
1070
+ res += " " + " ".join(g.asFea() for g in self.suffix)
1071
+ else:
1072
+ res += " ".join(g.asFea() for g in self.glyphs)
1073
+ res += " by "
1074
+ res += asFea(self.replacement)
1075
+ res += ";"
1076
+ return res
1077
+
1078
+
1079
+ class LookupFlagStatement(Statement):
1080
+ """A ``lookupflag`` statement. The ``value`` should be an integer value
1081
+ representing the flags in use, but not including the ``markAttachment``
1082
+ class and ``markFilteringSet`` values, which must be specified as
1083
+ glyph-containing objects."""
1084
+
1085
+ def __init__(
1086
+ self, value=0, markAttachment=None, markFilteringSet=None, location=None
1087
+ ):
1088
+ Statement.__init__(self, location)
1089
+ self.value = value
1090
+ self.markAttachment = markAttachment
1091
+ self.markFilteringSet = markFilteringSet
1092
+
1093
+ def build(self, builder):
1094
+ """Calls the builder object's ``set_lookup_flag`` callback."""
1095
+ markAttach = None
1096
+ if self.markAttachment is not None:
1097
+ markAttach = self.markAttachment.glyphSet()
1098
+ markFilter = None
1099
+ if self.markFilteringSet is not None:
1100
+ markFilter = self.markFilteringSet.glyphSet()
1101
+ builder.set_lookup_flag(self.location, self.value, markAttach, markFilter)
1102
+
1103
+ def asFea(self, indent=""):
1104
+ res = []
1105
+ flags = ["RightToLeft", "IgnoreBaseGlyphs", "IgnoreLigatures", "IgnoreMarks"]
1106
+ curr = 1
1107
+ for i in range(len(flags)):
1108
+ if self.value & curr != 0:
1109
+ res.append(flags[i])
1110
+ curr = curr << 1
1111
+ if self.markAttachment is not None:
1112
+ res.append("MarkAttachmentType {}".format(self.markAttachment.asFea()))
1113
+ if self.markFilteringSet is not None:
1114
+ res.append("UseMarkFilteringSet {}".format(self.markFilteringSet.asFea()))
1115
+ if not res:
1116
+ res = ["0"]
1117
+ return "lookupflag {};".format(" ".join(res))
1118
+
1119
+
1120
+ class LookupReferenceStatement(Statement):
1121
+ """Represents a ``lookup ...;`` statement to include a lookup in a feature.
1122
+
1123
+ The ``lookup`` should be a :class:`LookupBlock` object."""
1124
+
1125
+ def __init__(self, lookup, location=None):
1126
+ Statement.__init__(self, location)
1127
+ self.location, self.lookup = (location, lookup)
1128
+
1129
+ def build(self, builder):
1130
+ """Calls the builder object's ``add_lookup_call`` callback."""
1131
+ builder.add_lookup_call(self.lookup.name)
1132
+
1133
+ def asFea(self, indent=""):
1134
+ return "lookup {};".format(self.lookup.name)
1135
+
1136
+
1137
+ class MarkBasePosStatement(Statement):
1138
+ """A mark-to-base positioning rule. The ``base`` should be a
1139
+ `glyph-containing object`_. The ``marks`` should be a list of
1140
+ (:class:`Anchor`, :class:`MarkClass`) tuples."""
1141
+
1142
+ def __init__(self, base, marks, location=None):
1143
+ Statement.__init__(self, location)
1144
+ self.base, self.marks = base, marks
1145
+
1146
+ def build(self, builder):
1147
+ """Calls the builder object's ``add_mark_base_pos`` callback."""
1148
+ builder.add_mark_base_pos(self.location, self.base.glyphSet(), self.marks)
1149
+
1150
+ def asFea(self, indent=""):
1151
+ res = "pos base {}".format(self.base.asFea())
1152
+ for a, m in self.marks:
1153
+ res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name)
1154
+ res += ";"
1155
+ return res
1156
+
1157
+
1158
+ class MarkLigPosStatement(Statement):
1159
+ """A mark-to-ligature positioning rule. The ``ligatures`` must be a
1160
+ `glyph-containing object`_. The ``marks`` should be a list of lists: each
1161
+ element in the top-level list represents a component glyph, and is made
1162
+ up of a list of (:class:`Anchor`, :class:`MarkClass`) tuples representing
1163
+ mark attachment points for that position.
1164
+
1165
+ Example::
1166
+
1167
+ m1 = MarkClass("TOP_MARKS")
1168
+ m2 = MarkClass("BOTTOM_MARKS")
1169
+ # ... add definitions to mark classes...
1170
+
1171
+ glyph = GlyphName("lam_meem_jeem")
1172
+ marks = [
1173
+ [ (Anchor(625,1800), m1) ], # Attachments on 1st component (lam)
1174
+ [ (Anchor(376,-378), m2) ], # Attachments on 2nd component (meem)
1175
+ [ ] # No attachments on the jeem
1176
+ ]
1177
+ mlp = MarkLigPosStatement(glyph, marks)
1178
+
1179
+ mlp.asFea()
1180
+ # pos ligature lam_meem_jeem <anchor 625 1800> mark @TOP_MARKS
1181
+ # ligComponent <anchor 376 -378> mark @BOTTOM_MARKS;
1182
+
1183
+ """
1184
+
1185
+ def __init__(self, ligatures, marks, location=None):
1186
+ Statement.__init__(self, location)
1187
+ self.ligatures, self.marks = ligatures, marks
1188
+
1189
+ def build(self, builder):
1190
+ """Calls the builder object's ``add_mark_lig_pos`` callback."""
1191
+ builder.add_mark_lig_pos(self.location, self.ligatures.glyphSet(), self.marks)
1192
+
1193
+ def asFea(self, indent=""):
1194
+ res = "pos ligature {}".format(self.ligatures.asFea())
1195
+ ligs = []
1196
+ for l in self.marks:
1197
+ temp = ""
1198
+ if l is None or not len(l):
1199
+ temp = "\n" + indent + SHIFT * 2 + "<anchor NULL>"
1200
+ else:
1201
+ for a, m in l:
1202
+ temp += (
1203
+ "\n"
1204
+ + indent
1205
+ + SHIFT * 2
1206
+ + "{} mark @{}".format(a.asFea(), m.name)
1207
+ )
1208
+ ligs.append(temp)
1209
+ res += ("\n" + indent + SHIFT + "ligComponent").join(ligs)
1210
+ res += ";"
1211
+ return res
1212
+
1213
+
1214
+ class MarkMarkPosStatement(Statement):
1215
+ """A mark-to-mark positioning rule. The ``baseMarks`` must be a
1216
+ `glyph-containing object`_. The ``marks`` should be a list of
1217
+ (:class:`Anchor`, :class:`MarkClass`) tuples."""
1218
+
1219
+ def __init__(self, baseMarks, marks, location=None):
1220
+ Statement.__init__(self, location)
1221
+ self.baseMarks, self.marks = baseMarks, marks
1222
+
1223
+ def build(self, builder):
1224
+ """Calls the builder object's ``add_mark_mark_pos`` callback."""
1225
+ builder.add_mark_mark_pos(self.location, self.baseMarks.glyphSet(), self.marks)
1226
+
1227
+ def asFea(self, indent=""):
1228
+ res = "pos mark {}".format(self.baseMarks.asFea())
1229
+ for a, m in self.marks:
1230
+ res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name)
1231
+ res += ";"
1232
+ return res
1233
+
1234
+
1235
+ class MultipleSubstStatement(Statement):
1236
+ """A multiple substitution statement.
1237
+
1238
+ Args:
1239
+ prefix: a list of `glyph-containing objects`_.
1240
+ glyph: a single glyph-containing object.
1241
+ suffix: a list of glyph-containing objects.
1242
+ replacement: a list of glyph-containing objects.
1243
+ forceChain: If true, the statement is expressed as a chaining rule
1244
+ (e.g. ``sub f' i' by f_i``) even when no context is given.
1245
+ """
1246
+
1247
+ def __init__(
1248
+ self, prefix, glyph, suffix, replacement, forceChain=False, location=None
1249
+ ):
1250
+ Statement.__init__(self, location)
1251
+ self.prefix, self.glyph, self.suffix = prefix, glyph, suffix
1252
+ self.replacement = replacement
1253
+ self.forceChain = forceChain
1254
+
1255
+ def build(self, builder):
1256
+ """Calls the builder object's ``add_multiple_subst`` callback."""
1257
+ prefix = [p.glyphSet() for p in self.prefix]
1258
+ suffix = [s.glyphSet() for s in self.suffix]
1259
+ if hasattr(self.glyph, "glyphSet"):
1260
+ originals = self.glyph.glyphSet()
1261
+ else:
1262
+ originals = [self.glyph]
1263
+ count = len(originals)
1264
+ replaces = []
1265
+ for r in self.replacement:
1266
+ if hasattr(r, "glyphSet"):
1267
+ replace = r.glyphSet()
1268
+ else:
1269
+ replace = [r]
1270
+ if len(replace) == 1 and len(replace) != count:
1271
+ replace = replace * count
1272
+ replaces.append(replace)
1273
+ replaces = list(zip(*replaces))
1274
+
1275
+ seen_originals = set()
1276
+ for i, original in enumerate(originals):
1277
+ if original not in seen_originals:
1278
+ seen_originals.add(original)
1279
+ builder.add_multiple_subst(
1280
+ self.location,
1281
+ prefix,
1282
+ original,
1283
+ suffix,
1284
+ replaces and replaces[i] or (),
1285
+ self.forceChain,
1286
+ )
1287
+
1288
+ def asFea(self, indent=""):
1289
+ res = "sub "
1290
+ if len(self.prefix) or len(self.suffix) or self.forceChain:
1291
+ if len(self.prefix):
1292
+ res += " ".join(map(asFea, self.prefix)) + " "
1293
+ res += asFea(self.glyph) + "'"
1294
+ if len(self.suffix):
1295
+ res += " " + " ".join(map(asFea, self.suffix))
1296
+ else:
1297
+ res += asFea(self.glyph)
1298
+ replacement = self.replacement or [NullGlyph()]
1299
+ res += " by "
1300
+ res += " ".join(map(asFea, replacement))
1301
+ res += ";"
1302
+ return res
1303
+
1304
+
1305
+ class PairPosStatement(Statement):
1306
+ """A pair positioning statement.
1307
+
1308
+ ``glyphs1`` and ``glyphs2`` should be `glyph-containing objects`_.
1309
+ ``valuerecord1`` should be a :class:`ValueRecord` object;
1310
+ ``valuerecord2`` should be either a :class:`ValueRecord` object or ``None``.
1311
+ If ``enumerated`` is true, then this is expressed as an
1312
+ `enumerated pair <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#6.b.ii>`_.
1313
+ """
1314
+
1315
+ def __init__(
1316
+ self,
1317
+ glyphs1,
1318
+ valuerecord1,
1319
+ glyphs2,
1320
+ valuerecord2,
1321
+ enumerated=False,
1322
+ location=None,
1323
+ ):
1324
+ Statement.__init__(self, location)
1325
+ self.enumerated = enumerated
1326
+ self.glyphs1, self.valuerecord1 = glyphs1, valuerecord1
1327
+ self.glyphs2, self.valuerecord2 = glyphs2, valuerecord2
1328
+
1329
+ def build(self, builder):
1330
+ """Calls a callback on the builder object:
1331
+
1332
+ * If the rule is enumerated, calls ``add_specific_pair_pos`` on each
1333
+ combination of first and second glyphs.
1334
+ * If the glyphs are both single :class:`GlyphName` objects, calls
1335
+ ``add_specific_pair_pos``.
1336
+ * Else, calls ``add_class_pair_pos``.
1337
+ """
1338
+ if self.enumerated:
1339
+ g = [self.glyphs1.glyphSet(), self.glyphs2.glyphSet()]
1340
+ seen_pair = False
1341
+ for glyph1, glyph2 in itertools.product(*g):
1342
+ seen_pair = True
1343
+ builder.add_specific_pair_pos(
1344
+ self.location, glyph1, self.valuerecord1, glyph2, self.valuerecord2
1345
+ )
1346
+ if not seen_pair:
1347
+ raise FeatureLibError(
1348
+ "Empty glyph class in positioning rule", self.location
1349
+ )
1350
+ return
1351
+
1352
+ is_specific = isinstance(self.glyphs1, GlyphName) and isinstance(
1353
+ self.glyphs2, GlyphName
1354
+ )
1355
+ if is_specific:
1356
+ builder.add_specific_pair_pos(
1357
+ self.location,
1358
+ self.glyphs1.glyph,
1359
+ self.valuerecord1,
1360
+ self.glyphs2.glyph,
1361
+ self.valuerecord2,
1362
+ )
1363
+ else:
1364
+ builder.add_class_pair_pos(
1365
+ self.location,
1366
+ self.glyphs1.glyphSet(),
1367
+ self.valuerecord1,
1368
+ self.glyphs2.glyphSet(),
1369
+ self.valuerecord2,
1370
+ )
1371
+
1372
+ def asFea(self, indent=""):
1373
+ res = "enum " if self.enumerated else ""
1374
+ if self.valuerecord2:
1375
+ res += "pos {} {} {} {};".format(
1376
+ self.glyphs1.asFea(),
1377
+ self.valuerecord1.asFea(),
1378
+ self.glyphs2.asFea(),
1379
+ self.valuerecord2.asFea(),
1380
+ )
1381
+ else:
1382
+ res += "pos {} {} {};".format(
1383
+ self.glyphs1.asFea(), self.glyphs2.asFea(), self.valuerecord1.asFea()
1384
+ )
1385
+ return res
1386
+
1387
+
1388
+ class ReverseChainSingleSubstStatement(Statement):
1389
+ """A reverse chaining substitution statement. You don't see those every day.
1390
+
1391
+ Note the unusual argument order: ``suffix`` comes `before` ``glyphs``.
1392
+ ``old_prefix``, ``old_suffix``, ``glyphs`` and ``replacements`` should be
1393
+ lists of `glyph-containing objects`_. ``glyphs`` and ``replacements`` should
1394
+ be one-item lists.
1395
+ """
1396
+
1397
+ def __init__(self, old_prefix, old_suffix, glyphs, replacements, location=None):
1398
+ Statement.__init__(self, location)
1399
+ self.old_prefix, self.old_suffix = old_prefix, old_suffix
1400
+ self.glyphs = glyphs
1401
+ self.replacements = replacements
1402
+
1403
+ def build(self, builder):
1404
+ prefix = [p.glyphSet() for p in self.old_prefix]
1405
+ suffix = [s.glyphSet() for s in self.old_suffix]
1406
+ originals = self.glyphs[0].glyphSet()
1407
+ replaces = self.replacements[0].glyphSet()
1408
+ if len(replaces) == 1:
1409
+ replaces = replaces * len(originals)
1410
+ builder.add_reverse_chain_single_subst(
1411
+ self.location, prefix, suffix, dict(zip(originals, replaces))
1412
+ )
1413
+
1414
+ def asFea(self, indent=""):
1415
+ res = "rsub "
1416
+ if len(self.old_prefix) or len(self.old_suffix):
1417
+ if len(self.old_prefix):
1418
+ res += " ".join(asFea(g) for g in self.old_prefix) + " "
1419
+ res += " ".join(asFea(g) + "'" for g in self.glyphs)
1420
+ if len(self.old_suffix):
1421
+ res += " " + " ".join(asFea(g) for g in self.old_suffix)
1422
+ else:
1423
+ res += " ".join(map(asFea, self.glyphs))
1424
+ res += " by {};".format(" ".join(asFea(g) for g in self.replacements))
1425
+ return res
1426
+
1427
+
1428
+ class SingleSubstStatement(Statement):
1429
+ """A single substitution statement.
1430
+
1431
+ Note the unusual argument order: ``prefix`` and suffix come `after`
1432
+ the replacement ``glyphs``. ``prefix``, ``suffix``, ``glyphs`` and
1433
+ ``replace`` should be lists of `glyph-containing objects`_. ``glyphs`` and
1434
+ ``replace`` should be one-item lists.
1435
+ """
1436
+
1437
+ def __init__(self, glyphs, replace, prefix, suffix, forceChain, location=None):
1438
+ Statement.__init__(self, location)
1439
+ self.prefix, self.suffix = prefix, suffix
1440
+ self.forceChain = forceChain
1441
+ self.glyphs = glyphs
1442
+ self.replacements = replace
1443
+
1444
+ def build(self, builder):
1445
+ """Calls the builder object's ``add_single_subst`` callback."""
1446
+ prefix = [p.glyphSet() for p in self.prefix]
1447
+ suffix = [s.glyphSet() for s in self.suffix]
1448
+ originals = self.glyphs[0].glyphSet()
1449
+ replaces = self.replacements[0].glyphSet()
1450
+ if len(replaces) == 1:
1451
+ replaces = replaces * len(originals)
1452
+ builder.add_single_subst(
1453
+ self.location,
1454
+ prefix,
1455
+ suffix,
1456
+ OrderedDict(zip(originals, replaces)),
1457
+ self.forceChain,
1458
+ )
1459
+
1460
+ def asFea(self, indent=""):
1461
+ res = "sub "
1462
+ if len(self.prefix) or len(self.suffix) or self.forceChain:
1463
+ if len(self.prefix):
1464
+ res += " ".join(asFea(g) for g in self.prefix) + " "
1465
+ res += " ".join(asFea(g) + "'" for g in self.glyphs)
1466
+ if len(self.suffix):
1467
+ res += " " + " ".join(asFea(g) for g in self.suffix)
1468
+ else:
1469
+ res += " ".join(asFea(g) for g in self.glyphs)
1470
+ res += " by {};".format(" ".join(asFea(g) for g in self.replacements))
1471
+ return res
1472
+
1473
+
1474
+ class ScriptStatement(Statement):
1475
+ """A ``script`` statement."""
1476
+
1477
+ def __init__(self, script, location=None):
1478
+ Statement.__init__(self, location)
1479
+ self.script = script #: the script code
1480
+
1481
+ def build(self, builder):
1482
+ """Calls the builder's ``set_script`` callback."""
1483
+ builder.set_script(self.location, self.script)
1484
+
1485
+ def asFea(self, indent=""):
1486
+ return "script {};".format(self.script.strip())
1487
+
1488
+
1489
+ class SinglePosStatement(Statement):
1490
+ """A single position statement. ``prefix`` and ``suffix`` should be
1491
+ lists of `glyph-containing objects`_.
1492
+
1493
+ ``pos`` should be a one-element list containing a (`glyph-containing object`_,
1494
+ :class:`ValueRecord`) tuple."""
1495
+
1496
+ def __init__(self, pos, prefix, suffix, forceChain, location=None):
1497
+ Statement.__init__(self, location)
1498
+ self.pos, self.prefix, self.suffix = pos, prefix, suffix
1499
+ self.forceChain = forceChain
1500
+
1501
+ def build(self, builder):
1502
+ """Calls the builder object's ``add_single_pos`` callback."""
1503
+ prefix = [p.glyphSet() for p in self.prefix]
1504
+ suffix = [s.glyphSet() for s in self.suffix]
1505
+ pos = [(g.glyphSet(), value) for g, value in self.pos]
1506
+ builder.add_single_pos(self.location, prefix, suffix, pos, self.forceChain)
1507
+
1508
+ def asFea(self, indent=""):
1509
+ res = "pos "
1510
+ if len(self.prefix) or len(self.suffix) or self.forceChain:
1511
+ if len(self.prefix):
1512
+ res += " ".join(map(asFea, self.prefix)) + " "
1513
+ res += " ".join(
1514
+ [
1515
+ asFea(x[0]) + "'" + ((" " + x[1].asFea()) if x[1] else "")
1516
+ for x in self.pos
1517
+ ]
1518
+ )
1519
+ if len(self.suffix):
1520
+ res += " " + " ".join(map(asFea, self.suffix))
1521
+ else:
1522
+ res += " ".join(
1523
+ [asFea(x[0]) + " " + (x[1].asFea() if x[1] else "") for x in self.pos]
1524
+ )
1525
+ res += ";"
1526
+ return res
1527
+
1528
+
1529
+ class SubtableStatement(Statement):
1530
+ """Represents a subtable break."""
1531
+
1532
+ def __init__(self, location=None):
1533
+ Statement.__init__(self, location)
1534
+
1535
+ def build(self, builder):
1536
+ """Calls the builder objects's ``add_subtable_break`` callback."""
1537
+ builder.add_subtable_break(self.location)
1538
+
1539
+ def asFea(self, indent=""):
1540
+ return "subtable;"
1541
+
1542
+
1543
+ class ValueRecord(Expression):
1544
+ """Represents a value record."""
1545
+
1546
+ def __init__(
1547
+ self,
1548
+ xPlacement=None,
1549
+ yPlacement=None,
1550
+ xAdvance=None,
1551
+ yAdvance=None,
1552
+ xPlaDevice=None,
1553
+ yPlaDevice=None,
1554
+ xAdvDevice=None,
1555
+ yAdvDevice=None,
1556
+ vertical=False,
1557
+ location=None,
1558
+ ):
1559
+ Expression.__init__(self, location)
1560
+ self.xPlacement, self.yPlacement = (xPlacement, yPlacement)
1561
+ self.xAdvance, self.yAdvance = (xAdvance, yAdvance)
1562
+ self.xPlaDevice, self.yPlaDevice = (xPlaDevice, yPlaDevice)
1563
+ self.xAdvDevice, self.yAdvDevice = (xAdvDevice, yAdvDevice)
1564
+ self.vertical = vertical
1565
+
1566
+ def __eq__(self, other):
1567
+ return (
1568
+ self.xPlacement == other.xPlacement
1569
+ and self.yPlacement == other.yPlacement
1570
+ and self.xAdvance == other.xAdvance
1571
+ and self.yAdvance == other.yAdvance
1572
+ and self.xPlaDevice == other.xPlaDevice
1573
+ and self.xAdvDevice == other.xAdvDevice
1574
+ )
1575
+
1576
+ def __ne__(self, other):
1577
+ return not self.__eq__(other)
1578
+
1579
+ def __hash__(self):
1580
+ return (
1581
+ hash(self.xPlacement)
1582
+ ^ hash(self.yPlacement)
1583
+ ^ hash(self.xAdvance)
1584
+ ^ hash(self.yAdvance)
1585
+ ^ hash(self.xPlaDevice)
1586
+ ^ hash(self.yPlaDevice)
1587
+ ^ hash(self.xAdvDevice)
1588
+ ^ hash(self.yAdvDevice)
1589
+ )
1590
+
1591
+ def asFea(self, indent=""):
1592
+ if not self:
1593
+ return "<NULL>"
1594
+
1595
+ x, y = self.xPlacement, self.yPlacement
1596
+ xAdvance, yAdvance = self.xAdvance, self.yAdvance
1597
+ xPlaDevice, yPlaDevice = self.xPlaDevice, self.yPlaDevice
1598
+ xAdvDevice, yAdvDevice = self.xAdvDevice, self.yAdvDevice
1599
+ vertical = self.vertical
1600
+
1601
+ # Try format A, if possible.
1602
+ if x is None and y is None:
1603
+ if xAdvance is None and vertical:
1604
+ return str(yAdvance)
1605
+ elif yAdvance is None and not vertical:
1606
+ return str(xAdvance)
1607
+
1608
+ # Make any remaining None value 0 to avoid generating invalid records.
1609
+ x = x or 0
1610
+ y = y or 0
1611
+ xAdvance = xAdvance or 0
1612
+ yAdvance = yAdvance or 0
1613
+
1614
+ # Try format B, if possible.
1615
+ if (
1616
+ xPlaDevice is None
1617
+ and yPlaDevice is None
1618
+ and xAdvDevice is None
1619
+ and yAdvDevice is None
1620
+ ):
1621
+ return "<%s %s %s %s>" % (x, y, xAdvance, yAdvance)
1622
+
1623
+ # Last resort is format C.
1624
+ return "<%s %s %s %s %s %s %s %s>" % (
1625
+ x,
1626
+ y,
1627
+ xAdvance,
1628
+ yAdvance,
1629
+ deviceToString(xPlaDevice),
1630
+ deviceToString(yPlaDevice),
1631
+ deviceToString(xAdvDevice),
1632
+ deviceToString(yAdvDevice),
1633
+ )
1634
+
1635
+ def __bool__(self):
1636
+ return any(
1637
+ getattr(self, v) is not None
1638
+ for v in [
1639
+ "xPlacement",
1640
+ "yPlacement",
1641
+ "xAdvance",
1642
+ "yAdvance",
1643
+ "xPlaDevice",
1644
+ "yPlaDevice",
1645
+ "xAdvDevice",
1646
+ "yAdvDevice",
1647
+ ]
1648
+ )
1649
+
1650
+ __nonzero__ = __bool__
1651
+
1652
+
1653
+ class ValueRecordDefinition(Statement):
1654
+ """Represents a named value record definition."""
1655
+
1656
+ def __init__(self, name, value, location=None):
1657
+ Statement.__init__(self, location)
1658
+ self.name = name #: Value record name as string
1659
+ self.value = value #: :class:`ValueRecord` object
1660
+
1661
+ def asFea(self, indent=""):
1662
+ return "valueRecordDef {} {};".format(self.value.asFea(), self.name)
1663
+
1664
+
1665
+ def simplify_name_attributes(pid, eid, lid):
1666
+ if pid == 3 and eid == 1 and lid == 1033:
1667
+ return ""
1668
+ elif pid == 1 and eid == 0 and lid == 0:
1669
+ return "1"
1670
+ else:
1671
+ return "{} {} {}".format(pid, eid, lid)
1672
+
1673
+
1674
+ class NameRecord(Statement):
1675
+ """Represents a name record. (`Section 9.e. <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#9.e>`_)"""
1676
+
1677
+ def __init__(self, nameID, platformID, platEncID, langID, string, location=None):
1678
+ Statement.__init__(self, location)
1679
+ self.nameID = nameID #: Name ID as integer (e.g. 9 for designer's name)
1680
+ self.platformID = platformID #: Platform ID as integer
1681
+ self.platEncID = platEncID #: Platform encoding ID as integer
1682
+ self.langID = langID #: Language ID as integer
1683
+ self.string = string #: Name record value
1684
+
1685
+ def build(self, builder):
1686
+ """Calls the builder object's ``add_name_record`` callback."""
1687
+ builder.add_name_record(
1688
+ self.location,
1689
+ self.nameID,
1690
+ self.platformID,
1691
+ self.platEncID,
1692
+ self.langID,
1693
+ self.string,
1694
+ )
1695
+
1696
+ def asFea(self, indent=""):
1697
+ def escape(c, escape_pattern):
1698
+ # Also escape U+0022 QUOTATION MARK and U+005C REVERSE SOLIDUS
1699
+ if c >= 0x20 and c <= 0x7E and c not in (0x22, 0x5C):
1700
+ return chr(c)
1701
+ else:
1702
+ return escape_pattern % c
1703
+
1704
+ encoding = getEncoding(self.platformID, self.platEncID, self.langID)
1705
+ if encoding is None:
1706
+ raise FeatureLibError("Unsupported encoding", self.location)
1707
+ s = tobytes(self.string, encoding=encoding)
1708
+ if encoding == "utf_16_be":
1709
+ escaped_string = "".join(
1710
+ [
1711
+ escape(byteord(s[i]) * 256 + byteord(s[i + 1]), r"\%04x")
1712
+ for i in range(0, len(s), 2)
1713
+ ]
1714
+ )
1715
+ else:
1716
+ escaped_string = "".join([escape(byteord(b), r"\%02x") for b in s])
1717
+ plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
1718
+ if plat != "":
1719
+ plat += " "
1720
+ return 'nameid {} {}"{}";'.format(self.nameID, plat, escaped_string)
1721
+
1722
+
1723
+ class FeatureNameStatement(NameRecord):
1724
+ """Represents a ``sizemenuname`` or ``name`` statement."""
1725
+
1726
+ def build(self, builder):
1727
+ """Calls the builder object's ``add_featureName`` callback."""
1728
+ NameRecord.build(self, builder)
1729
+ builder.add_featureName(self.nameID)
1730
+
1731
+ def asFea(self, indent=""):
1732
+ if self.nameID == "size":
1733
+ tag = "sizemenuname"
1734
+ else:
1735
+ tag = "name"
1736
+ plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
1737
+ if plat != "":
1738
+ plat += " "
1739
+ return '{} {}"{}";'.format(tag, plat, self.string)
1740
+
1741
+
1742
+ class STATNameStatement(NameRecord):
1743
+ """Represents a STAT table ``name`` statement."""
1744
+
1745
+ def asFea(self, indent=""):
1746
+ plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
1747
+ if plat != "":
1748
+ plat += " "
1749
+ return 'name {}"{}";'.format(plat, self.string)
1750
+
1751
+
1752
+ class SizeParameters(Statement):
1753
+ """A ``parameters`` statement."""
1754
+
1755
+ def __init__(self, DesignSize, SubfamilyID, RangeStart, RangeEnd, location=None):
1756
+ Statement.__init__(self, location)
1757
+ self.DesignSize = DesignSize
1758
+ self.SubfamilyID = SubfamilyID
1759
+ self.RangeStart = RangeStart
1760
+ self.RangeEnd = RangeEnd
1761
+
1762
+ def build(self, builder):
1763
+ """Calls the builder object's ``set_size_parameters`` callback."""
1764
+ builder.set_size_parameters(
1765
+ self.location,
1766
+ self.DesignSize,
1767
+ self.SubfamilyID,
1768
+ self.RangeStart,
1769
+ self.RangeEnd,
1770
+ )
1771
+
1772
+ def asFea(self, indent=""):
1773
+ res = "parameters {:.1f} {}".format(self.DesignSize, self.SubfamilyID)
1774
+ if self.RangeStart != 0 or self.RangeEnd != 0:
1775
+ res += " {} {}".format(int(self.RangeStart * 10), int(self.RangeEnd * 10))
1776
+ return res + ";"
1777
+
1778
+
1779
+ class CVParametersNameStatement(NameRecord):
1780
+ """Represent a name statement inside a ``cvParameters`` block."""
1781
+
1782
+ def __init__(
1783
+ self, nameID, platformID, platEncID, langID, string, block_name, location=None
1784
+ ):
1785
+ NameRecord.__init__(
1786
+ self, nameID, platformID, platEncID, langID, string, location=location
1787
+ )
1788
+ self.block_name = block_name
1789
+
1790
+ def build(self, builder):
1791
+ """Calls the builder object's ``add_cv_parameter`` callback."""
1792
+ item = ""
1793
+ if self.block_name == "ParamUILabelNameID":
1794
+ item = "_{}".format(builder.cv_num_named_params_.get(self.nameID, 0))
1795
+ builder.add_cv_parameter(self.nameID)
1796
+ self.nameID = (self.nameID, self.block_name + item)
1797
+ NameRecord.build(self, builder)
1798
+
1799
+ def asFea(self, indent=""):
1800
+ plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
1801
+ if plat != "":
1802
+ plat += " "
1803
+ return 'name {}"{}";'.format(plat, self.string)
1804
+
1805
+
1806
+ class CharacterStatement(Statement):
1807
+ """
1808
+ Statement used in cvParameters blocks of Character Variant features (cvXX).
1809
+ The Unicode value may be written with either decimal or hexadecimal
1810
+ notation. The value must be preceded by '0x' if it is a hexadecimal value.
1811
+ The largest Unicode value allowed is 0xFFFFFF.
1812
+ """
1813
+
1814
+ def __init__(self, character, tag, location=None):
1815
+ Statement.__init__(self, location)
1816
+ self.character = character
1817
+ self.tag = tag
1818
+
1819
+ def build(self, builder):
1820
+ """Calls the builder object's ``add_cv_character`` callback."""
1821
+ builder.add_cv_character(self.character, self.tag)
1822
+
1823
+ def asFea(self, indent=""):
1824
+ return "Character {:#x};".format(self.character)
1825
+
1826
+
1827
+ class BaseAxis(Statement):
1828
+ """An axis definition, being either a ``VertAxis.BaseTagList/BaseScriptList``
1829
+ pair or a ``HorizAxis.BaseTagList/BaseScriptList`` pair."""
1830
+
1831
+ def __init__(self, bases, scripts, vertical, location=None):
1832
+ Statement.__init__(self, location)
1833
+ self.bases = bases #: A list of baseline tag names as strings
1834
+ self.scripts = scripts #: A list of script record tuplets (script tag, default baseline tag, base coordinate)
1835
+ self.vertical = vertical #: Boolean; VertAxis if True, HorizAxis if False
1836
+
1837
+ def build(self, builder):
1838
+ """Calls the builder object's ``set_base_axis`` callback."""
1839
+ builder.set_base_axis(self.bases, self.scripts, self.vertical)
1840
+
1841
+ def asFea(self, indent=""):
1842
+ direction = "Vert" if self.vertical else "Horiz"
1843
+ scripts = [
1844
+ "{} {} {}".format(a[0], a[1], " ".join(map(str, a[2])))
1845
+ for a in self.scripts
1846
+ ]
1847
+ return "{}Axis.BaseTagList {};\n{}{}Axis.BaseScriptList {};".format(
1848
+ direction, " ".join(self.bases), indent, direction, ", ".join(scripts)
1849
+ )
1850
+
1851
+
1852
+ class OS2Field(Statement):
1853
+ """An entry in the ``OS/2`` table. Most ``values`` should be numbers or
1854
+ strings, apart from when the key is ``UnicodeRange``, ``CodePageRange``
1855
+ or ``Panose``, in which case it should be an array of integers."""
1856
+
1857
+ def __init__(self, key, value, location=None):
1858
+ Statement.__init__(self, location)
1859
+ self.key = key
1860
+ self.value = value
1861
+
1862
+ def build(self, builder):
1863
+ """Calls the builder object's ``add_os2_field`` callback."""
1864
+ builder.add_os2_field(self.key, self.value)
1865
+
1866
+ def asFea(self, indent=""):
1867
+ def intarr2str(x):
1868
+ return " ".join(map(str, x))
1869
+
1870
+ numbers = (
1871
+ "FSType",
1872
+ "TypoAscender",
1873
+ "TypoDescender",
1874
+ "TypoLineGap",
1875
+ "winAscent",
1876
+ "winDescent",
1877
+ "XHeight",
1878
+ "CapHeight",
1879
+ "WeightClass",
1880
+ "WidthClass",
1881
+ "LowerOpSize",
1882
+ "UpperOpSize",
1883
+ )
1884
+ ranges = ("UnicodeRange", "CodePageRange")
1885
+ keywords = dict([(x.lower(), [x, str]) for x in numbers])
1886
+ keywords.update([(x.lower(), [x, intarr2str]) for x in ranges])
1887
+ keywords["panose"] = ["Panose", intarr2str]
1888
+ keywords["vendor"] = ["Vendor", lambda y: '"{}"'.format(y)]
1889
+ if self.key in keywords:
1890
+ return "{} {};".format(
1891
+ keywords[self.key][0], keywords[self.key][1](self.value)
1892
+ )
1893
+ return "" # should raise exception
1894
+
1895
+
1896
+ class HheaField(Statement):
1897
+ """An entry in the ``hhea`` table."""
1898
+
1899
+ def __init__(self, key, value, location=None):
1900
+ Statement.__init__(self, location)
1901
+ self.key = key
1902
+ self.value = value
1903
+
1904
+ def build(self, builder):
1905
+ """Calls the builder object's ``add_hhea_field`` callback."""
1906
+ builder.add_hhea_field(self.key, self.value)
1907
+
1908
+ def asFea(self, indent=""):
1909
+ fields = ("CaretOffset", "Ascender", "Descender", "LineGap")
1910
+ keywords = dict([(x.lower(), x) for x in fields])
1911
+ return "{} {};".format(keywords[self.key], self.value)
1912
+
1913
+
1914
+ class VheaField(Statement):
1915
+ """An entry in the ``vhea`` table."""
1916
+
1917
+ def __init__(self, key, value, location=None):
1918
+ Statement.__init__(self, location)
1919
+ self.key = key
1920
+ self.value = value
1921
+
1922
+ def build(self, builder):
1923
+ """Calls the builder object's ``add_vhea_field`` callback."""
1924
+ builder.add_vhea_field(self.key, self.value)
1925
+
1926
+ def asFea(self, indent=""):
1927
+ fields = ("VertTypoAscender", "VertTypoDescender", "VertTypoLineGap")
1928
+ keywords = dict([(x.lower(), x) for x in fields])
1929
+ return "{} {};".format(keywords[self.key], self.value)
1930
+
1931
+
1932
+ class STATDesignAxisStatement(Statement):
1933
+ """A STAT table Design Axis
1934
+
1935
+ Args:
1936
+ tag (str): a 4 letter axis tag
1937
+ axisOrder (int): an int
1938
+ names (list): a list of :class:`STATNameStatement` objects
1939
+ """
1940
+
1941
+ def __init__(self, tag, axisOrder, names, location=None):
1942
+ Statement.__init__(self, location)
1943
+ self.tag = tag
1944
+ self.axisOrder = axisOrder
1945
+ self.names = names
1946
+ self.location = location
1947
+
1948
+ def build(self, builder):
1949
+ builder.addDesignAxis(self, self.location)
1950
+
1951
+ def asFea(self, indent=""):
1952
+ indent += SHIFT
1953
+ res = f"DesignAxis {self.tag} {self.axisOrder} {{ \n"
1954
+ res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
1955
+ res += "};"
1956
+ return res
1957
+
1958
+
1959
+ class ElidedFallbackName(Statement):
1960
+ """STAT table ElidedFallbackName
1961
+
1962
+ Args:
1963
+ names: a list of :class:`STATNameStatement` objects
1964
+ """
1965
+
1966
+ def __init__(self, names, location=None):
1967
+ Statement.__init__(self, location)
1968
+ self.names = names
1969
+ self.location = location
1970
+
1971
+ def build(self, builder):
1972
+ builder.setElidedFallbackName(self.names, self.location)
1973
+
1974
+ def asFea(self, indent=""):
1975
+ indent += SHIFT
1976
+ res = "ElidedFallbackName { \n"
1977
+ res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
1978
+ res += "};"
1979
+ return res
1980
+
1981
+
1982
+ class ElidedFallbackNameID(Statement):
1983
+ """STAT table ElidedFallbackNameID
1984
+
1985
+ Args:
1986
+ value: an int pointing to an existing name table name ID
1987
+ """
1988
+
1989
+ def __init__(self, value, location=None):
1990
+ Statement.__init__(self, location)
1991
+ self.value = value
1992
+ self.location = location
1993
+
1994
+ def build(self, builder):
1995
+ builder.setElidedFallbackName(self.value, self.location)
1996
+
1997
+ def asFea(self, indent=""):
1998
+ return f"ElidedFallbackNameID {self.value};"
1999
+
2000
+
2001
+ class STATAxisValueStatement(Statement):
2002
+ """A STAT table Axis Value Record
2003
+
2004
+ Args:
2005
+ names (list): a list of :class:`STATNameStatement` objects
2006
+ locations (list): a list of :class:`AxisValueLocationStatement` objects
2007
+ flags (int): an int
2008
+ """
2009
+
2010
+ def __init__(self, names, locations, flags, location=None):
2011
+ Statement.__init__(self, location)
2012
+ self.names = names
2013
+ self.locations = locations
2014
+ self.flags = flags
2015
+
2016
+ def build(self, builder):
2017
+ builder.addAxisValueRecord(self, self.location)
2018
+
2019
+ def asFea(self, indent=""):
2020
+ res = "AxisValue {\n"
2021
+ for location in self.locations:
2022
+ res += location.asFea()
2023
+
2024
+ for nameRecord in self.names:
2025
+ res += nameRecord.asFea()
2026
+ res += "\n"
2027
+
2028
+ if self.flags:
2029
+ flags = ["OlderSiblingFontAttribute", "ElidableAxisValueName"]
2030
+ flagStrings = []
2031
+ curr = 1
2032
+ for i in range(len(flags)):
2033
+ if self.flags & curr != 0:
2034
+ flagStrings.append(flags[i])
2035
+ curr = curr << 1
2036
+ res += f"flag {' '.join(flagStrings)};\n"
2037
+ res += "};"
2038
+ return res
2039
+
2040
+
2041
+ class AxisValueLocationStatement(Statement):
2042
+ """
2043
+ A STAT table Axis Value Location
2044
+
2045
+ Args:
2046
+ tag (str): a 4 letter axis tag
2047
+ values (list): a list of ints and/or floats
2048
+ """
2049
+
2050
+ def __init__(self, tag, values, location=None):
2051
+ Statement.__init__(self, location)
2052
+ self.tag = tag
2053
+ self.values = values
2054
+
2055
+ def asFea(self, res=""):
2056
+ res += f"location {self.tag} "
2057
+ res += f"{' '.join(str(i) for i in self.values)};\n"
2058
+ return res
2059
+
2060
+
2061
+ class ConditionsetStatement(Statement):
2062
+ """
2063
+ A variable layout conditionset
2064
+
2065
+ Args:
2066
+ name (str): the name of this conditionset
2067
+ conditions (dict): a dictionary mapping axis tags to a
2068
+ tuple of (min,max) userspace coordinates.
2069
+ """
2070
+
2071
+ def __init__(self, name, conditions, location=None):
2072
+ Statement.__init__(self, location)
2073
+ self.name = name
2074
+ self.conditions = conditions
2075
+
2076
+ def build(self, builder):
2077
+ builder.add_conditionset(self.location, self.name, self.conditions)
2078
+
2079
+ def asFea(self, res="", indent=""):
2080
+ res += indent + f"conditionset {self.name} " + "{\n"
2081
+ for tag, (minvalue, maxvalue) in self.conditions.items():
2082
+ res += indent + SHIFT + f"{tag} {minvalue} {maxvalue};\n"
2083
+ res += indent + "}" + f" {self.name};\n"
2084
+ return res
2085
+
2086
+
2087
+ class VariationBlock(Block):
2088
+ """A variation feature block, applicable in a given set of conditions."""
2089
+
2090
+ def __init__(self, name, conditionset, use_extension=False, location=None):
2091
+ Block.__init__(self, location)
2092
+ self.name, self.conditionset, self.use_extension = (
2093
+ name,
2094
+ conditionset,
2095
+ use_extension,
2096
+ )
2097
+
2098
+ def build(self, builder):
2099
+ """Call the ``start_feature`` callback on the builder object, visit
2100
+ all the statements in this feature, and then call ``end_feature``."""
2101
+ builder.start_feature(self.location, self.name)
2102
+ if (
2103
+ self.conditionset != "NULL"
2104
+ and self.conditionset not in builder.conditionsets_
2105
+ ):
2106
+ raise FeatureLibError(
2107
+ f"variation block used undefined conditionset {self.conditionset}",
2108
+ self.location,
2109
+ )
2110
+
2111
+ # language exclude_dflt statements modify builder.features_
2112
+ # limit them to this block with temporary builder.features_
2113
+ features = builder.features_
2114
+ builder.features_ = {}
2115
+ Block.build(self, builder)
2116
+ for key, value in builder.features_.items():
2117
+ items = builder.feature_variations_.setdefault(key, {}).setdefault(
2118
+ self.conditionset, []
2119
+ )
2120
+ items.extend(value)
2121
+ if key not in features:
2122
+ features[key] = [] # Ensure we make a feature record
2123
+ builder.features_ = features
2124
+ builder.end_feature()
2125
+
2126
+ def asFea(self, indent=""):
2127
+ res = indent + "variation %s " % self.name.strip()
2128
+ res += self.conditionset + " "
2129
+ if self.use_extension:
2130
+ res += "useExtension "
2131
+ res += "{\n"
2132
+ res += Block.asFea(self, indent=indent)
2133
+ res += indent + "} %s;\n" % self.name.strip()
2134
+ return res