fonttools 4.60.2__cp311-cp311-win32.whl

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