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