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,1814 @@
1
+ from fontTools.misc import sstruct
2
+ from fontTools.misc.textTools import Tag, tostr, binary2num, safeEval
3
+ from fontTools.feaLib.error import FeatureLibError
4
+ from fontTools.feaLib.lookupDebugInfo import (
5
+ LookupDebugInfo,
6
+ LOOKUP_DEBUG_INFO_KEY,
7
+ LOOKUP_DEBUG_ENV_VAR,
8
+ )
9
+ from fontTools.feaLib.parser import Parser
10
+ from fontTools.feaLib.ast import FeatureFile
11
+ from fontTools.feaLib.variableScalar import VariableScalar
12
+ from fontTools.otlLib import builder as otl
13
+ from fontTools.otlLib.maxContextCalc import maxCtxFont
14
+ from fontTools.ttLib import newTable, getTableModule
15
+ from fontTools.ttLib.tables import otBase, otTables
16
+ from fontTools.otlLib.builder import (
17
+ AlternateSubstBuilder,
18
+ ChainContextPosBuilder,
19
+ ChainContextSubstBuilder,
20
+ LigatureSubstBuilder,
21
+ MultipleSubstBuilder,
22
+ CursivePosBuilder,
23
+ MarkBasePosBuilder,
24
+ MarkLigPosBuilder,
25
+ MarkMarkPosBuilder,
26
+ ReverseChainSingleSubstBuilder,
27
+ SingleSubstBuilder,
28
+ ClassPairPosSubtableBuilder,
29
+ PairPosBuilder,
30
+ SinglePosBuilder,
31
+ ChainContextualRule,
32
+ AnySubstBuilder,
33
+ )
34
+ from fontTools.otlLib.error import OpenTypeLibError
35
+ from fontTools.varLib.errors import VarLibError
36
+ from fontTools.varLib.varStore import OnlineVarStoreBuilder
37
+ from fontTools.varLib.builder import buildVarDevTable
38
+ from fontTools.varLib.featureVars import addFeatureVariationsRaw
39
+ from fontTools.varLib.models import normalizeValue, piecewiseLinearMap
40
+ from collections import defaultdict
41
+ import copy
42
+ import itertools
43
+ from io import StringIO
44
+ import logging
45
+ import warnings
46
+ import os
47
+
48
+
49
+ log = logging.getLogger(__name__)
50
+
51
+
52
+ def addOpenTypeFeatures(font, featurefile, tables=None, debug=False):
53
+ """Add features from a file to a font. Note that this replaces any features
54
+ currently present.
55
+
56
+ Args:
57
+ font (feaLib.ttLib.TTFont): The font object.
58
+ featurefile: Either a path or file object (in which case we
59
+ parse it into an AST), or a pre-parsed AST instance.
60
+ tables: If passed, restrict the set of affected tables to those in the
61
+ list.
62
+ debug: Whether to add source debugging information to the font in the
63
+ ``Debg`` table
64
+
65
+ """
66
+ builder = Builder(font, featurefile)
67
+ builder.build(tables=tables, debug=debug)
68
+
69
+
70
+ def addOpenTypeFeaturesFromString(
71
+ font, features, filename=None, tables=None, debug=False
72
+ ):
73
+ """Add features from a string to a font. Note that this replaces any
74
+ features currently present.
75
+
76
+ Args:
77
+ font (feaLib.ttLib.TTFont): The font object.
78
+ features: A string containing feature code.
79
+ filename: The directory containing ``filename`` is used as the root of
80
+ relative ``include()`` paths; if ``None`` is provided, the current
81
+ directory is assumed.
82
+ tables: If passed, restrict the set of affected tables to those in the
83
+ list.
84
+ debug: Whether to add source debugging information to the font in the
85
+ ``Debg`` table
86
+
87
+ """
88
+
89
+ featurefile = StringIO(tostr(features))
90
+ if filename:
91
+ featurefile.name = filename
92
+ addOpenTypeFeatures(font, featurefile, tables=tables, debug=debug)
93
+
94
+
95
+ class Builder(object):
96
+ supportedTables = frozenset(
97
+ Tag(tag)
98
+ for tag in [
99
+ "BASE",
100
+ "GDEF",
101
+ "GPOS",
102
+ "GSUB",
103
+ "OS/2",
104
+ "head",
105
+ "hhea",
106
+ "name",
107
+ "vhea",
108
+ "STAT",
109
+ ]
110
+ )
111
+
112
+ def __init__(self, font, featurefile):
113
+ self.font = font
114
+ # 'featurefile' can be either a path or file object (in which case we
115
+ # parse it into an AST), or a pre-parsed AST instance
116
+ if isinstance(featurefile, FeatureFile):
117
+ self.parseTree, self.file = featurefile, None
118
+ else:
119
+ self.parseTree, self.file = None, featurefile
120
+ self.glyphMap = font.getReverseGlyphMap()
121
+ self.varstorebuilder = None
122
+ if "fvar" in font:
123
+ self.axes = font["fvar"].axes
124
+ self.varstorebuilder = OnlineVarStoreBuilder(
125
+ [ax.axisTag for ax in self.axes]
126
+ )
127
+ self.default_language_systems_ = set()
128
+ self.script_ = None
129
+ self.lookupflag_ = 0
130
+ self.lookupflag_markFilterSet_ = None
131
+ self.use_extension_ = False
132
+ self.language_systems = set()
133
+ self.seen_non_DFLT_script_ = False
134
+ self.named_lookups_ = {}
135
+ self.cur_lookup_ = None
136
+ self.cur_lookup_name_ = None
137
+ self.cur_feature_name_ = None
138
+ self.lookups_ = []
139
+ self.lookup_locations = {"GSUB": {}, "GPOS": {}}
140
+ self.features_ = {} # ('latn', 'DEU ', 'smcp') --> [LookupBuilder*]
141
+ self.required_features_ = {} # ('latn', 'DEU ') --> 'scmp'
142
+ self.feature_variations_ = {}
143
+ # for feature 'aalt'
144
+ self.aalt_features_ = [] # [(location, featureName)*], for 'aalt'
145
+ self.aalt_location_ = None
146
+ self.aalt_alternates_ = {}
147
+ self.aalt_use_extension_ = False
148
+ # for 'featureNames'
149
+ self.featureNames_ = set()
150
+ self.featureNames_ids_ = {}
151
+ # for 'cvParameters'
152
+ self.cv_parameters_ = set()
153
+ self.cv_parameters_ids_ = {}
154
+ self.cv_num_named_params_ = {}
155
+ self.cv_characters_ = defaultdict(list)
156
+ # for feature 'size'
157
+ self.size_parameters_ = None
158
+ # for table 'head'
159
+ self.fontRevision_ = None # 2.71
160
+ # for table 'name'
161
+ self.names_ = []
162
+ # for table 'BASE'
163
+ self.base_horiz_axis_ = None
164
+ self.base_vert_axis_ = None
165
+ # for table 'GDEF'
166
+ self.attachPoints_ = {} # "a" --> {3, 7}
167
+ self.ligCaretCoords_ = {} # "f_f_i" --> {300, 600}
168
+ self.ligCaretPoints_ = {} # "f_f_i" --> {3, 7}
169
+ self.glyphClassDefs_ = {} # "fi" --> (2, (file, line, column))
170
+ self.markAttach_ = {} # "acute" --> (4, (file, line, column))
171
+ self.markAttachClassID_ = {} # frozenset({"acute", "grave"}) --> 4
172
+ self.markFilterSets_ = {} # frozenset({"acute", "grave"}) --> 4
173
+ # for table 'OS/2'
174
+ self.os2_ = {}
175
+ # for table 'hhea'
176
+ self.hhea_ = {}
177
+ # for table 'vhea'
178
+ self.vhea_ = {}
179
+ # for table 'STAT'
180
+ self.stat_ = {}
181
+ # for conditionsets
182
+ self.conditionsets_ = {}
183
+ # We will often use exactly the same locations (i.e. the font's masters)
184
+ # for a large number of variable scalars. Instead of creating a model
185
+ # for each, let's share the models.
186
+ self.model_cache = {}
187
+
188
+ def build(self, tables=None, debug=False):
189
+ if self.parseTree is None:
190
+ self.parseTree = Parser(self.file, self.glyphMap).parse()
191
+ self.parseTree.build(self)
192
+ # by default, build all the supported tables
193
+ if tables is None:
194
+ tables = self.supportedTables
195
+ else:
196
+ tables = frozenset(tables)
197
+ unsupported = tables - self.supportedTables
198
+ if unsupported:
199
+ unsupported_string = ", ".join(sorted(unsupported))
200
+ raise NotImplementedError(
201
+ "The following tables were requested but are unsupported: "
202
+ f"{unsupported_string}."
203
+ )
204
+ if "GSUB" in tables:
205
+ self.build_feature_aalt_()
206
+ if "head" in tables:
207
+ self.build_head()
208
+ if "hhea" in tables:
209
+ self.build_hhea()
210
+ if "vhea" in tables:
211
+ self.build_vhea()
212
+ if "name" in tables:
213
+ self.build_name()
214
+ if "OS/2" in tables:
215
+ self.build_OS_2()
216
+ if "STAT" in tables:
217
+ self.build_STAT()
218
+ for tag in ("GPOS", "GSUB"):
219
+ if tag not in tables:
220
+ continue
221
+ table = self.makeTable(tag)
222
+ if self.feature_variations_:
223
+ self.makeFeatureVariations(table, tag)
224
+ if (
225
+ table.ScriptList.ScriptCount > 0
226
+ or table.FeatureList.FeatureCount > 0
227
+ or table.LookupList.LookupCount > 0
228
+ ):
229
+ fontTable = self.font[tag] = newTable(tag)
230
+ fontTable.table = table
231
+ elif tag in self.font:
232
+ del self.font[tag]
233
+ if any(tag in self.font for tag in ("GPOS", "GSUB")) and "OS/2" in self.font:
234
+ self.font["OS/2"].usMaxContext = maxCtxFont(self.font)
235
+ if "GDEF" in tables:
236
+ gdef = self.buildGDEF()
237
+ if gdef:
238
+ self.font["GDEF"] = gdef
239
+ elif "GDEF" in self.font:
240
+ del self.font["GDEF"]
241
+ if "BASE" in tables:
242
+ base = self.buildBASE()
243
+ if base:
244
+ self.font["BASE"] = base
245
+ elif "BASE" in self.font:
246
+ del self.font["BASE"]
247
+ if debug or os.environ.get(LOOKUP_DEBUG_ENV_VAR):
248
+ self.buildDebg()
249
+
250
+ def get_chained_lookup_(self, location, builder_class):
251
+ result = builder_class(self.font, location)
252
+ result.lookupflag = self.lookupflag_
253
+ result.markFilterSet = self.lookupflag_markFilterSet_
254
+ result.extension = self.use_extension_
255
+ self.lookups_.append(result)
256
+ return result
257
+
258
+ def add_lookup_to_feature_(self, lookup, feature_name):
259
+ for script, lang in self.language_systems:
260
+ key = (script, lang, feature_name)
261
+ self.features_.setdefault(key, []).append(lookup)
262
+
263
+ def get_lookup_(self, location, builder_class, mapping=None):
264
+ if (
265
+ self.cur_lookup_
266
+ and type(self.cur_lookup_) == builder_class
267
+ and self.cur_lookup_.lookupflag == self.lookupflag_
268
+ and self.cur_lookup_.markFilterSet == self.lookupflag_markFilterSet_
269
+ and self.cur_lookup_.can_add_mapping(mapping)
270
+ ):
271
+ return self.cur_lookup_
272
+ if self.cur_lookup_name_ and self.cur_lookup_:
273
+ raise FeatureLibError(
274
+ "Within a named lookup block, all rules must be of "
275
+ "the same lookup type and flag",
276
+ location,
277
+ )
278
+ self.cur_lookup_ = builder_class(self.font, location)
279
+ self.cur_lookup_.lookupflag = self.lookupflag_
280
+ self.cur_lookup_.markFilterSet = self.lookupflag_markFilterSet_
281
+ self.cur_lookup_.extension = self.use_extension_
282
+ self.lookups_.append(self.cur_lookup_)
283
+ if self.cur_lookup_name_:
284
+ # We are starting a lookup rule inside a named lookup block.
285
+ self.named_lookups_[self.cur_lookup_name_] = self.cur_lookup_
286
+ if self.cur_feature_name_:
287
+ # We are starting a lookup rule inside a feature. This includes
288
+ # lookup rules inside named lookups inside features.
289
+ self.add_lookup_to_feature_(self.cur_lookup_, self.cur_feature_name_)
290
+ return self.cur_lookup_
291
+
292
+ def build_feature_aalt_(self):
293
+ if not self.aalt_features_ and not self.aalt_alternates_:
294
+ return
295
+ # > alternate glyphs will be sorted in the order that the source features
296
+ # > are named in the aalt definition, not the order of the feature definitions
297
+ # > in the file. Alternates defined explicitly ... will precede all others.
298
+ # https://github.com/fonttools/fonttools/issues/836
299
+ alternates = {g: list(a) for g, a in self.aalt_alternates_.items()}
300
+ for location, name in self.aalt_features_ + [(None, "aalt")]:
301
+ feature = [
302
+ (script, lang, feature, lookups)
303
+ for (script, lang, feature), lookups in self.features_.items()
304
+ if feature == name
305
+ ]
306
+ # "aalt" does not have to specify its own lookups, but it might.
307
+ if not feature and name != "aalt":
308
+ warnings.warn("%s: Feature %s has not been defined" % (location, name))
309
+ continue
310
+ for script, lang, feature, lookups in feature:
311
+ for lookuplist in lookups:
312
+ if not isinstance(lookuplist, list):
313
+ lookuplist = [lookuplist]
314
+ for lookup in lookuplist:
315
+ for glyph, alts in lookup.getAlternateGlyphs().items():
316
+ alts_for_glyph = alternates.setdefault(glyph, [])
317
+ alts_for_glyph.extend(
318
+ g for g in alts if g not in alts_for_glyph
319
+ )
320
+ single = {
321
+ glyph: repl[0] for glyph, repl in alternates.items() if len(repl) == 1
322
+ }
323
+ multi = {glyph: repl for glyph, repl in alternates.items() if len(repl) > 1}
324
+ if not single and not multi:
325
+ return
326
+ self.features_ = {
327
+ (script, lang, feature): lookups
328
+ for (script, lang, feature), lookups in self.features_.items()
329
+ if feature != "aalt"
330
+ }
331
+ old_lookups = self.lookups_
332
+ self.lookups_ = []
333
+ self.start_feature(self.aalt_location_, "aalt", self.aalt_use_extension_)
334
+ if single:
335
+ single_lookup = self.get_lookup_(location, SingleSubstBuilder)
336
+ single_lookup.mapping = single
337
+ if multi:
338
+ multi_lookup = self.get_lookup_(location, AlternateSubstBuilder)
339
+ multi_lookup.alternates = multi
340
+ self.end_feature()
341
+ self.lookups_.extend(old_lookups)
342
+
343
+ def build_head(self):
344
+ if not self.fontRevision_:
345
+ return
346
+ table = self.font.get("head")
347
+ if not table: # this only happens for unit tests
348
+ table = self.font["head"] = newTable("head")
349
+ table.decompile(b"\0" * 54, self.font)
350
+ table.tableVersion = 1.0
351
+ table.magicNumber = 0x5F0F3CF5
352
+ table.created = table.modified = 3406620153 # 2011-12-13 11:22:33
353
+ table.fontRevision = self.fontRevision_
354
+
355
+ def build_hhea(self):
356
+ if not self.hhea_:
357
+ return
358
+ table = self.font.get("hhea")
359
+ if not table: # this only happens for unit tests
360
+ table = self.font["hhea"] = newTable("hhea")
361
+ table.decompile(b"\0" * 36, self.font)
362
+ table.tableVersion = 0x00010000
363
+ if "caretoffset" in self.hhea_:
364
+ table.caretOffset = self.hhea_["caretoffset"]
365
+ if "ascender" in self.hhea_:
366
+ table.ascent = self.hhea_["ascender"]
367
+ if "descender" in self.hhea_:
368
+ table.descent = self.hhea_["descender"]
369
+ if "linegap" in self.hhea_:
370
+ table.lineGap = self.hhea_["linegap"]
371
+
372
+ def build_vhea(self):
373
+ if not self.vhea_:
374
+ return
375
+ table = self.font.get("vhea")
376
+ if not table: # this only happens for unit tests
377
+ table = self.font["vhea"] = newTable("vhea")
378
+ table.decompile(b"\0" * 36, self.font)
379
+ table.tableVersion = 0x00011000
380
+ if "verttypoascender" in self.vhea_:
381
+ table.ascent = self.vhea_["verttypoascender"]
382
+ if "verttypodescender" in self.vhea_:
383
+ table.descent = self.vhea_["verttypodescender"]
384
+ if "verttypolinegap" in self.vhea_:
385
+ table.lineGap = self.vhea_["verttypolinegap"]
386
+
387
+ def get_user_name_id(self, table):
388
+ # Try to find first unused font-specific name id
389
+ nameIDs = [name.nameID for name in table.names]
390
+ for user_name_id in range(256, 32767):
391
+ if user_name_id not in nameIDs:
392
+ return user_name_id
393
+
394
+ def buildFeatureParams(self, tag):
395
+ params = None
396
+ if tag == "size":
397
+ params = otTables.FeatureParamsSize()
398
+ (
399
+ params.DesignSize,
400
+ params.SubfamilyID,
401
+ params.RangeStart,
402
+ params.RangeEnd,
403
+ ) = self.size_parameters_
404
+ if tag in self.featureNames_ids_:
405
+ params.SubfamilyNameID = self.featureNames_ids_[tag]
406
+ else:
407
+ params.SubfamilyNameID = 0
408
+ elif tag in self.featureNames_:
409
+ if not self.featureNames_ids_:
410
+ # name table wasn't selected among the tables to build; skip
411
+ pass
412
+ else:
413
+ assert tag in self.featureNames_ids_
414
+ params = otTables.FeatureParamsStylisticSet()
415
+ params.Version = 0
416
+ params.UINameID = self.featureNames_ids_[tag]
417
+ elif tag in self.cv_parameters_:
418
+ params = otTables.FeatureParamsCharacterVariants()
419
+ params.Format = 0
420
+ params.FeatUILabelNameID = self.cv_parameters_ids_.get(
421
+ (tag, "FeatUILabelNameID"), 0
422
+ )
423
+ params.FeatUITooltipTextNameID = self.cv_parameters_ids_.get(
424
+ (tag, "FeatUITooltipTextNameID"), 0
425
+ )
426
+ params.SampleTextNameID = self.cv_parameters_ids_.get(
427
+ (tag, "SampleTextNameID"), 0
428
+ )
429
+ params.NumNamedParameters = self.cv_num_named_params_.get(tag, 0)
430
+ params.FirstParamUILabelNameID = self.cv_parameters_ids_.get(
431
+ (tag, "ParamUILabelNameID_0"), 0
432
+ )
433
+ params.CharCount = len(self.cv_characters_[tag])
434
+ params.Character = self.cv_characters_[tag]
435
+ return params
436
+
437
+ def build_name(self):
438
+ if not self.names_:
439
+ return
440
+ table = self.font.get("name")
441
+ if not table: # this only happens for unit tests
442
+ table = self.font["name"] = newTable("name")
443
+ table.names = []
444
+ for name in self.names_:
445
+ nameID, platformID, platEncID, langID, string = name
446
+ # For featureNames block, nameID is 'feature tag'
447
+ # For cvParameters blocks, nameID is ('feature tag', 'block name')
448
+ if not isinstance(nameID, int):
449
+ tag = nameID
450
+ if tag in self.featureNames_:
451
+ if tag not in self.featureNames_ids_:
452
+ self.featureNames_ids_[tag] = self.get_user_name_id(table)
453
+ assert self.featureNames_ids_[tag] is not None
454
+ nameID = self.featureNames_ids_[tag]
455
+ elif tag[0] in self.cv_parameters_:
456
+ if tag not in self.cv_parameters_ids_:
457
+ self.cv_parameters_ids_[tag] = self.get_user_name_id(table)
458
+ assert self.cv_parameters_ids_[tag] is not None
459
+ nameID = self.cv_parameters_ids_[tag]
460
+ table.setName(string, nameID, platformID, platEncID, langID)
461
+ table.names.sort()
462
+
463
+ def build_OS_2(self):
464
+ if not self.os2_:
465
+ return
466
+ table = self.font.get("OS/2")
467
+ if not table: # this only happens for unit tests
468
+ table = self.font["OS/2"] = newTable("OS/2")
469
+ data = b"\0" * sstruct.calcsize(getTableModule("OS/2").OS2_format_0)
470
+ table.decompile(data, self.font)
471
+ version = 0
472
+ if "fstype" in self.os2_:
473
+ table.fsType = self.os2_["fstype"]
474
+ if "panose" in self.os2_:
475
+ panose = getTableModule("OS/2").Panose()
476
+ (
477
+ panose.bFamilyType,
478
+ panose.bSerifStyle,
479
+ panose.bWeight,
480
+ panose.bProportion,
481
+ panose.bContrast,
482
+ panose.bStrokeVariation,
483
+ panose.bArmStyle,
484
+ panose.bLetterForm,
485
+ panose.bMidline,
486
+ panose.bXHeight,
487
+ ) = self.os2_["panose"]
488
+ table.panose = panose
489
+ if "typoascender" in self.os2_:
490
+ table.sTypoAscender = self.os2_["typoascender"]
491
+ if "typodescender" in self.os2_:
492
+ table.sTypoDescender = self.os2_["typodescender"]
493
+ if "typolinegap" in self.os2_:
494
+ table.sTypoLineGap = self.os2_["typolinegap"]
495
+ if "winascent" in self.os2_:
496
+ table.usWinAscent = self.os2_["winascent"]
497
+ if "windescent" in self.os2_:
498
+ table.usWinDescent = self.os2_["windescent"]
499
+ if "vendor" in self.os2_:
500
+ table.achVendID = safeEval("'''" + self.os2_["vendor"] + "'''")
501
+ if "weightclass" in self.os2_:
502
+ table.usWeightClass = self.os2_["weightclass"]
503
+ if "widthclass" in self.os2_:
504
+ table.usWidthClass = self.os2_["widthclass"]
505
+ if "unicoderange" in self.os2_:
506
+ table.setUnicodeRanges(self.os2_["unicoderange"])
507
+ if "codepagerange" in self.os2_:
508
+ pages = self.build_codepages_(self.os2_["codepagerange"])
509
+ table.ulCodePageRange1, table.ulCodePageRange2 = pages
510
+ version = 1
511
+ if "xheight" in self.os2_:
512
+ table.sxHeight = self.os2_["xheight"]
513
+ version = 2
514
+ if "capheight" in self.os2_:
515
+ table.sCapHeight = self.os2_["capheight"]
516
+ version = 2
517
+ if "loweropsize" in self.os2_:
518
+ table.usLowerOpticalPointSize = self.os2_["loweropsize"]
519
+ version = 5
520
+ if "upperopsize" in self.os2_:
521
+ table.usUpperOpticalPointSize = self.os2_["upperopsize"]
522
+ version = 5
523
+
524
+ def checkattr(table, attrs):
525
+ for attr in attrs:
526
+ if not hasattr(table, attr):
527
+ setattr(table, attr, 0)
528
+
529
+ table.version = max(version, table.version)
530
+ # this only happens for unit tests
531
+ if version >= 1:
532
+ checkattr(table, ("ulCodePageRange1", "ulCodePageRange2"))
533
+ if version >= 2:
534
+ checkattr(
535
+ table,
536
+ (
537
+ "sxHeight",
538
+ "sCapHeight",
539
+ "usDefaultChar",
540
+ "usBreakChar",
541
+ "usMaxContext",
542
+ ),
543
+ )
544
+ if version >= 5:
545
+ checkattr(table, ("usLowerOpticalPointSize", "usUpperOpticalPointSize"))
546
+
547
+ def setElidedFallbackName(self, value, location):
548
+ # ElidedFallbackName is a convenience method for setting
549
+ # ElidedFallbackNameID so only one can be allowed
550
+ for token in ("ElidedFallbackName", "ElidedFallbackNameID"):
551
+ if token in self.stat_:
552
+ raise FeatureLibError(
553
+ f"{token} is already set.",
554
+ location,
555
+ )
556
+ if isinstance(value, int):
557
+ self.stat_["ElidedFallbackNameID"] = value
558
+ elif isinstance(value, list):
559
+ self.stat_["ElidedFallbackName"] = value
560
+ else:
561
+ raise AssertionError(value)
562
+
563
+ def addDesignAxis(self, designAxis, location):
564
+ if "DesignAxes" not in self.stat_:
565
+ self.stat_["DesignAxes"] = []
566
+ if designAxis.tag in (r.tag for r in self.stat_["DesignAxes"]):
567
+ raise FeatureLibError(
568
+ f'DesignAxis already defined for tag "{designAxis.tag}".',
569
+ location,
570
+ )
571
+ if designAxis.axisOrder in (r.axisOrder for r in self.stat_["DesignAxes"]):
572
+ raise FeatureLibError(
573
+ f"DesignAxis already defined for axis number {designAxis.axisOrder}.",
574
+ location,
575
+ )
576
+ self.stat_["DesignAxes"].append(designAxis)
577
+
578
+ def addAxisValueRecord(self, axisValueRecord, location):
579
+ if "AxisValueRecords" not in self.stat_:
580
+ self.stat_["AxisValueRecords"] = []
581
+ # Check for duplicate AxisValueRecords
582
+ for record_ in self.stat_["AxisValueRecords"]:
583
+ if (
584
+ {n.asFea() for n in record_.names}
585
+ == {n.asFea() for n in axisValueRecord.names}
586
+ and {n.asFea() for n in record_.locations}
587
+ == {n.asFea() for n in axisValueRecord.locations}
588
+ and record_.flags == axisValueRecord.flags
589
+ ):
590
+ raise FeatureLibError(
591
+ "An AxisValueRecord with these values is already defined.",
592
+ location,
593
+ )
594
+ self.stat_["AxisValueRecords"].append(axisValueRecord)
595
+
596
+ def build_STAT(self):
597
+ if not self.stat_:
598
+ return
599
+
600
+ axes = self.stat_.get("DesignAxes")
601
+ if not axes:
602
+ raise FeatureLibError("DesignAxes not defined", None)
603
+ axisValueRecords = self.stat_.get("AxisValueRecords")
604
+ axisValues = {}
605
+ format4_locations = []
606
+ for tag in axes:
607
+ axisValues[tag.tag] = []
608
+ if axisValueRecords is not None:
609
+ for avr in axisValueRecords:
610
+ valuesDict = {}
611
+ if avr.flags > 0:
612
+ valuesDict["flags"] = avr.flags
613
+ if len(avr.locations) == 1:
614
+ location = avr.locations[0]
615
+ values = location.values
616
+ if len(values) == 1: # format1
617
+ valuesDict.update({"value": values[0], "name": avr.names})
618
+ if len(values) == 2: # format3
619
+ valuesDict.update(
620
+ {
621
+ "value": values[0],
622
+ "linkedValue": values[1],
623
+ "name": avr.names,
624
+ }
625
+ )
626
+ if len(values) == 3: # format2
627
+ nominal, minVal, maxVal = values
628
+ valuesDict.update(
629
+ {
630
+ "nominalValue": nominal,
631
+ "rangeMinValue": minVal,
632
+ "rangeMaxValue": maxVal,
633
+ "name": avr.names,
634
+ }
635
+ )
636
+ axisValues[location.tag].append(valuesDict)
637
+ else:
638
+ valuesDict.update(
639
+ {
640
+ "location": {i.tag: i.values[0] for i in avr.locations},
641
+ "name": avr.names,
642
+ }
643
+ )
644
+ format4_locations.append(valuesDict)
645
+
646
+ designAxes = [
647
+ {
648
+ "ordering": a.axisOrder,
649
+ "tag": a.tag,
650
+ "name": a.names,
651
+ "values": axisValues[a.tag],
652
+ }
653
+ for a in axes
654
+ ]
655
+
656
+ nameTable = self.font.get("name")
657
+ if not nameTable: # this only happens for unit tests
658
+ nameTable = self.font["name"] = newTable("name")
659
+ nameTable.names = []
660
+
661
+ if "ElidedFallbackNameID" in self.stat_:
662
+ nameID = self.stat_["ElidedFallbackNameID"]
663
+ name = nameTable.getDebugName(nameID)
664
+ if not name:
665
+ raise FeatureLibError(
666
+ f"ElidedFallbackNameID {nameID} points "
667
+ "to a nameID that does not exist in the "
668
+ '"name" table',
669
+ None,
670
+ )
671
+ elif "ElidedFallbackName" in self.stat_:
672
+ nameID = self.stat_["ElidedFallbackName"]
673
+
674
+ otl.buildStatTable(
675
+ self.font,
676
+ designAxes,
677
+ locations=format4_locations,
678
+ elidedFallbackName=nameID,
679
+ )
680
+
681
+ def build_codepages_(self, pages):
682
+ pages2bits = {
683
+ 1252: 0,
684
+ 1250: 1,
685
+ 1251: 2,
686
+ 1253: 3,
687
+ 1254: 4,
688
+ 1255: 5,
689
+ 1256: 6,
690
+ 1257: 7,
691
+ 1258: 8,
692
+ 874: 16,
693
+ 932: 17,
694
+ 936: 18,
695
+ 949: 19,
696
+ 950: 20,
697
+ 1361: 21,
698
+ 869: 48,
699
+ 866: 49,
700
+ 865: 50,
701
+ 864: 51,
702
+ 863: 52,
703
+ 862: 53,
704
+ 861: 54,
705
+ 860: 55,
706
+ 857: 56,
707
+ 855: 57,
708
+ 852: 58,
709
+ 775: 59,
710
+ 737: 60,
711
+ 708: 61,
712
+ 850: 62,
713
+ 437: 63,
714
+ }
715
+ bits = [pages2bits[p] for p in pages if p in pages2bits]
716
+ pages = []
717
+ for i in range(2):
718
+ pages.append("")
719
+ for j in range(i * 32, (i + 1) * 32):
720
+ if j in bits:
721
+ pages[i] += "1"
722
+ else:
723
+ pages[i] += "0"
724
+ return [binary2num(p[::-1]) for p in pages]
725
+
726
+ def buildBASE(self):
727
+ if not self.base_horiz_axis_ and not self.base_vert_axis_:
728
+ return None
729
+ base = otTables.BASE()
730
+ base.Version = 0x00010000
731
+ base.HorizAxis = self.buildBASEAxis(self.base_horiz_axis_)
732
+ base.VertAxis = self.buildBASEAxis(self.base_vert_axis_)
733
+
734
+ result = newTable("BASE")
735
+ result.table = base
736
+ return result
737
+
738
+ def buildBASECoord(self, c):
739
+ coord = otTables.BaseCoord()
740
+ coord.Format = 1
741
+ coord.Coordinate = c
742
+ return coord
743
+
744
+ def buildBASEAxis(self, axis):
745
+ if not axis:
746
+ return
747
+ bases, scripts, minmax = axis
748
+ axis = otTables.Axis()
749
+ axis.BaseTagList = otTables.BaseTagList()
750
+ axis.BaseTagList.BaselineTag = bases
751
+ axis.BaseTagList.BaseTagCount = len(bases)
752
+ axis.BaseScriptList = otTables.BaseScriptList()
753
+ axis.BaseScriptList.BaseScriptRecord = []
754
+ axis.BaseScriptList.BaseScriptCount = len(scripts)
755
+ for script in sorted(scripts):
756
+ minmax_for_script = [
757
+ record[1:] for record in minmax if record[0] == script[0]
758
+ ]
759
+ record = otTables.BaseScriptRecord()
760
+ record.BaseScriptTag = script[0]
761
+ record.BaseScript = otTables.BaseScript()
762
+ record.BaseScript.BaseValues = otTables.BaseValues()
763
+ record.BaseScript.BaseValues.DefaultIndex = bases.index(script[1])
764
+ record.BaseScript.BaseValues.BaseCoord = []
765
+ record.BaseScript.BaseValues.BaseCoordCount = len(script[2])
766
+ record.BaseScript.BaseLangSysRecord = []
767
+
768
+ for c in script[2]:
769
+ record.BaseScript.BaseValues.BaseCoord.append(self.buildBASECoord(c))
770
+ for language, min_coord, max_coord in sorted(minmax_for_script):
771
+ minmax_record = otTables.MinMax()
772
+ minmax_record.MinCoord = self.buildBASECoord(min_coord)
773
+ minmax_record.MaxCoord = self.buildBASECoord(max_coord)
774
+ minmax_record.FeatMinMaxCount = 0
775
+ if language == "dflt":
776
+ record.BaseScript.DefaultMinMax = minmax_record
777
+ else:
778
+ lang_record = otTables.BaseLangSysRecord()
779
+ lang_record.BaseLangSysTag = language
780
+ lang_record.MinMax = minmax_record
781
+ record.BaseScript.BaseLangSysRecord.append(lang_record)
782
+ record.BaseScript.BaseLangSysCount = len(
783
+ record.BaseScript.BaseLangSysRecord
784
+ )
785
+ axis.BaseScriptList.BaseScriptRecord.append(record)
786
+ return axis
787
+
788
+ def buildGDEF(self):
789
+ gdef = otTables.GDEF()
790
+ gdef.GlyphClassDef = self.buildGDEFGlyphClassDef_()
791
+ gdef.AttachList = otl.buildAttachList(self.attachPoints_, self.glyphMap)
792
+ gdef.LigCaretList = otl.buildLigCaretList(
793
+ self.ligCaretCoords_, self.ligCaretPoints_, self.glyphMap
794
+ )
795
+ gdef.MarkAttachClassDef = self.buildGDEFMarkAttachClassDef_()
796
+ gdef.MarkGlyphSetsDef = self.buildGDEFMarkGlyphSetsDef_()
797
+ gdef.Version = 0x00010002 if gdef.MarkGlyphSetsDef else 0x00010000
798
+ if self.varstorebuilder:
799
+ store = self.varstorebuilder.finish()
800
+ if store:
801
+ gdef.Version = 0x00010003
802
+ gdef.VarStore = store
803
+ varidx_map = store.optimize()
804
+
805
+ gdef.remap_device_varidxes(varidx_map)
806
+ if "GPOS" in self.font:
807
+ self.font["GPOS"].table.remap_device_varidxes(varidx_map)
808
+ self.model_cache.clear()
809
+ if any(
810
+ (
811
+ gdef.GlyphClassDef,
812
+ gdef.AttachList,
813
+ gdef.LigCaretList,
814
+ gdef.MarkAttachClassDef,
815
+ gdef.MarkGlyphSetsDef,
816
+ )
817
+ ) or hasattr(gdef, "VarStore"):
818
+ result = newTable("GDEF")
819
+ result.table = gdef
820
+ return result
821
+ else:
822
+ return None
823
+
824
+ def buildGDEFGlyphClassDef_(self):
825
+ if self.glyphClassDefs_:
826
+ classes = {g: c for (g, (c, _)) in self.glyphClassDefs_.items()}
827
+ else:
828
+ classes = {}
829
+ for lookup in self.lookups_:
830
+ classes.update(lookup.inferGlyphClasses())
831
+ for markClass in self.parseTree.markClasses.values():
832
+ for markClassDef in markClass.definitions:
833
+ for glyph in markClassDef.glyphSet():
834
+ classes[glyph] = 3
835
+ if classes:
836
+ result = otTables.GlyphClassDef()
837
+ result.classDefs = classes
838
+ return result
839
+ else:
840
+ return None
841
+
842
+ def buildGDEFMarkAttachClassDef_(self):
843
+ classDefs = {g: c for g, (c, _) in self.markAttach_.items()}
844
+ if not classDefs:
845
+ return None
846
+ result = otTables.MarkAttachClassDef()
847
+ result.classDefs = classDefs
848
+ return result
849
+
850
+ def buildGDEFMarkGlyphSetsDef_(self):
851
+ sets = []
852
+ for glyphs, id_ in sorted(
853
+ self.markFilterSets_.items(), key=lambda item: item[1]
854
+ ):
855
+ sets.append(glyphs)
856
+ return otl.buildMarkGlyphSetsDef(sets, self.glyphMap)
857
+
858
+ def buildDebg(self):
859
+ if "Debg" not in self.font:
860
+ self.font["Debg"] = newTable("Debg")
861
+ self.font["Debg"].data = {}
862
+ self.font["Debg"].data[LOOKUP_DEBUG_INFO_KEY] = self.lookup_locations
863
+
864
+ def buildLookups_(self, tag):
865
+ assert tag in ("GPOS", "GSUB"), tag
866
+ for lookup in self.lookups_:
867
+ lookup.lookup_index = None
868
+ lookups = []
869
+ for lookup in self.lookups_:
870
+ if lookup.table != tag:
871
+ continue
872
+ name = self.get_lookup_name_(lookup)
873
+ resolved = lookup.promote_lookup_type(is_named_lookup=name is not None)
874
+ if resolved is None:
875
+ raise FeatureLibError(
876
+ "Within a named lookup block, all rules must be of "
877
+ "the same lookup type and flag",
878
+ lookup.location,
879
+ )
880
+ for l in resolved:
881
+ lookup.lookup_index = len(lookups)
882
+ self.lookup_locations[tag][str(lookup.lookup_index)] = LookupDebugInfo(
883
+ location=str(lookup.location),
884
+ name=name,
885
+ feature=None,
886
+ )
887
+ lookups.append(l)
888
+ otLookups = []
889
+ for l in lookups:
890
+ try:
891
+ otLookups.append(l.build())
892
+ except OpenTypeLibError as e:
893
+ raise FeatureLibError(str(e), e.location) from e
894
+ except Exception as e:
895
+ location = self.lookup_locations[tag][str(l.lookup_index)].location
896
+ raise FeatureLibError(str(e), location) from e
897
+ return otLookups
898
+
899
+ def makeTable(self, tag):
900
+ table = getattr(otTables, tag, None)()
901
+ table.Version = 0x00010000
902
+ table.ScriptList = otTables.ScriptList()
903
+ table.ScriptList.ScriptRecord = []
904
+ table.FeatureList = otTables.FeatureList()
905
+ table.FeatureList.FeatureRecord = []
906
+ table.LookupList = otTables.LookupList()
907
+ table.LookupList.Lookup = self.buildLookups_(tag)
908
+
909
+ # Build a table for mapping (tag, lookup_indices) to feature_index.
910
+ # For example, ('liga', (2,3,7)) --> 23.
911
+ feature_indices = {}
912
+ required_feature_indices = {} # ('latn', 'DEU') --> 23
913
+ scripts = {} # 'latn' --> {'DEU': [23, 24]} for feature #23,24
914
+ # Sort the feature table by feature tag:
915
+ # https://github.com/fonttools/fonttools/issues/568
916
+ sortFeatureTag = lambda f: (f[0][2], f[0][1], f[0][0], f[1])
917
+ for key, lookups in sorted(self.features_.items(), key=sortFeatureTag):
918
+ script, lang, feature_tag = key
919
+ # l.lookup_index will be None when a lookup is not needed
920
+ # for the table under construction. For example, substitution
921
+ # rules will have no lookup_index while building GPOS tables.
922
+ # We also deduplicate lookup indices, as they only get applied once
923
+ # within a given feature:
924
+ # https://github.com/fonttools/fonttools/issues/2946
925
+ lookup_indices = tuple(
926
+ dict.fromkeys(
927
+ l.lookup_index for l in lookups if l.lookup_index is not None
928
+ )
929
+ )
930
+ # order doesn't matter, but lookup_indices preserves it.
931
+ # We want to combine identical sets of lookups (order doesn't matter)
932
+ # but also respect the order provided by the user (although there's
933
+ # a reasonable argument to just sort and dedupe, which fontc does)
934
+ lookup_key = frozenset(lookup_indices)
935
+
936
+ size_feature = tag == "GPOS" and feature_tag == "size"
937
+ force_feature = self.any_feature_variations(feature_tag, tag)
938
+ if len(lookup_indices) == 0 and not size_feature and not force_feature:
939
+ continue
940
+
941
+ for ix in lookup_indices:
942
+ try:
943
+ self.lookup_locations[tag][str(ix)] = self.lookup_locations[tag][
944
+ str(ix)
945
+ ]._replace(feature=key)
946
+ except KeyError:
947
+ warnings.warn(
948
+ "feaLib.Builder subclass needs upgrading to "
949
+ "stash debug information. See fonttools#2065."
950
+ )
951
+
952
+ feature_key = (feature_tag, lookup_key)
953
+ feature_index = feature_indices.get(feature_key)
954
+ if feature_index is None:
955
+ feature_index = len(table.FeatureList.FeatureRecord)
956
+ frec = otTables.FeatureRecord()
957
+ frec.FeatureTag = feature_tag
958
+ frec.Feature = otTables.Feature()
959
+ frec.Feature.FeatureParams = self.buildFeatureParams(feature_tag)
960
+ frec.Feature.LookupListIndex = list(lookup_indices)
961
+ frec.Feature.LookupCount = len(lookup_indices)
962
+ table.FeatureList.FeatureRecord.append(frec)
963
+ feature_indices[feature_key] = feature_index
964
+ scripts.setdefault(script, {}).setdefault(lang, []).append(feature_index)
965
+ if self.required_features_.get((script, lang)) == feature_tag:
966
+ required_feature_indices[(script, lang)] = feature_index
967
+
968
+ # Build ScriptList.
969
+ for script, lang_features in sorted(scripts.items()):
970
+ srec = otTables.ScriptRecord()
971
+ srec.ScriptTag = script
972
+ srec.Script = otTables.Script()
973
+ srec.Script.DefaultLangSys = None
974
+ srec.Script.LangSysRecord = []
975
+ for lang, feature_indices in sorted(lang_features.items()):
976
+ langrec = otTables.LangSysRecord()
977
+ langrec.LangSys = otTables.LangSys()
978
+ langrec.LangSys.LookupOrder = None
979
+
980
+ req_feature_index = required_feature_indices.get((script, lang))
981
+ if req_feature_index is None:
982
+ langrec.LangSys.ReqFeatureIndex = 0xFFFF
983
+ else:
984
+ langrec.LangSys.ReqFeatureIndex = req_feature_index
985
+
986
+ langrec.LangSys.FeatureIndex = [
987
+ i for i in feature_indices if i != req_feature_index
988
+ ]
989
+ langrec.LangSys.FeatureCount = len(langrec.LangSys.FeatureIndex)
990
+
991
+ if lang == "dflt":
992
+ srec.Script.DefaultLangSys = langrec.LangSys
993
+ else:
994
+ langrec.LangSysTag = lang
995
+ srec.Script.LangSysRecord.append(langrec)
996
+ srec.Script.LangSysCount = len(srec.Script.LangSysRecord)
997
+ table.ScriptList.ScriptRecord.append(srec)
998
+
999
+ table.ScriptList.ScriptCount = len(table.ScriptList.ScriptRecord)
1000
+ table.FeatureList.FeatureCount = len(table.FeatureList.FeatureRecord)
1001
+ table.LookupList.LookupCount = len(table.LookupList.Lookup)
1002
+ return table
1003
+
1004
+ def makeFeatureVariations(self, table, table_tag):
1005
+ feature_vars = {}
1006
+ has_any_variations = False
1007
+ # Sort out which lookups to build, gather their indices
1008
+ for (_, _, feature_tag), variations in self.feature_variations_.items():
1009
+ feature_vars[feature_tag] = []
1010
+ for conditionset, builders in variations.items():
1011
+ raw_conditionset = self.conditionsets_[conditionset]
1012
+ indices = []
1013
+ for b in builders:
1014
+ if b.table != table_tag:
1015
+ continue
1016
+ assert b.lookup_index is not None
1017
+ indices.append(b.lookup_index)
1018
+ has_any_variations = True
1019
+ feature_vars[feature_tag].append((raw_conditionset, indices))
1020
+
1021
+ if has_any_variations:
1022
+ for feature_tag, conditions_and_lookups in feature_vars.items():
1023
+ addFeatureVariationsRaw(
1024
+ self.font, table, conditions_and_lookups, feature_tag
1025
+ )
1026
+
1027
+ def any_feature_variations(self, feature_tag, table_tag):
1028
+ for (_, _, feature), variations in self.feature_variations_.items():
1029
+ if feature != feature_tag:
1030
+ continue
1031
+ for conditionset, builders in variations.items():
1032
+ if any(b.table == table_tag for b in builders):
1033
+ return True
1034
+ return False
1035
+
1036
+ def get_lookup_name_(self, lookup):
1037
+ rev = {v: k for k, v in self.named_lookups_.items()}
1038
+ if lookup in rev:
1039
+ return rev[lookup]
1040
+ return None
1041
+
1042
+ def add_language_system(self, location, script, language):
1043
+ # OpenType Feature File Specification, section 4.b.i
1044
+ if script == "DFLT" and language == "dflt" and self.default_language_systems_:
1045
+ raise FeatureLibError(
1046
+ 'If "languagesystem DFLT dflt" is present, it must be '
1047
+ "the first of the languagesystem statements",
1048
+ location,
1049
+ )
1050
+ if script == "DFLT":
1051
+ if self.seen_non_DFLT_script_:
1052
+ raise FeatureLibError(
1053
+ 'languagesystems using the "DFLT" script tag must '
1054
+ "precede all other languagesystems",
1055
+ location,
1056
+ )
1057
+ else:
1058
+ self.seen_non_DFLT_script_ = True
1059
+ if (script, language) in self.default_language_systems_:
1060
+ raise FeatureLibError(
1061
+ '"languagesystem %s %s" has already been specified'
1062
+ % (script.strip(), language.strip()),
1063
+ location,
1064
+ )
1065
+ self.default_language_systems_.add((script, language))
1066
+
1067
+ def get_default_language_systems_(self):
1068
+ # OpenType Feature File specification, 4.b.i. languagesystem:
1069
+ # If no "languagesystem" statement is present, then the
1070
+ # implementation must behave exactly as though the following
1071
+ # statement were present at the beginning of the feature file:
1072
+ # languagesystem DFLT dflt;
1073
+ if self.default_language_systems_:
1074
+ return frozenset(self.default_language_systems_)
1075
+ else:
1076
+ return frozenset({("DFLT", "dflt")})
1077
+
1078
+ def start_feature(self, location, name, use_extension=False):
1079
+ if use_extension and name != "aalt":
1080
+ raise FeatureLibError(
1081
+ "'useExtension' keyword for feature blocks is allowed only for 'aalt' feature",
1082
+ location,
1083
+ )
1084
+ self.language_systems = self.get_default_language_systems_()
1085
+ self.script_ = "DFLT"
1086
+ self.cur_lookup_ = None
1087
+ self.cur_feature_name_ = name
1088
+ self.lookupflag_ = 0
1089
+ self.lookupflag_markFilterSet_ = None
1090
+ self.use_extension_ = use_extension
1091
+ if name == "aalt":
1092
+ self.aalt_location_ = location
1093
+ self.aalt_use_extension_ = use_extension
1094
+
1095
+ def end_feature(self):
1096
+ assert self.cur_feature_name_ is not None
1097
+ self.cur_feature_name_ = None
1098
+ self.language_systems = None
1099
+ self.cur_lookup_ = None
1100
+ self.lookupflag_ = 0
1101
+ self.lookupflag_markFilterSet_ = None
1102
+ self.use_extension_ = False
1103
+
1104
+ def start_lookup_block(self, location, name, use_extension=False):
1105
+ if name in self.named_lookups_:
1106
+ raise FeatureLibError(
1107
+ 'Lookup "%s" has already been defined' % name, location
1108
+ )
1109
+ if self.cur_feature_name_ == "aalt":
1110
+ raise FeatureLibError(
1111
+ "Lookup blocks cannot be placed inside 'aalt' features; "
1112
+ "move it out, and then refer to it with a lookup statement",
1113
+ location,
1114
+ )
1115
+ self.cur_lookup_name_ = name
1116
+ self.named_lookups_[name] = None
1117
+ self.cur_lookup_ = None
1118
+ self.use_extension_ = use_extension
1119
+ if self.cur_feature_name_ is None:
1120
+ self.lookupflag_ = 0
1121
+ self.lookupflag_markFilterSet_ = None
1122
+
1123
+ def end_lookup_block(self):
1124
+ assert self.cur_lookup_name_ is not None
1125
+ self.cur_lookup_name_ = None
1126
+ self.cur_lookup_ = None
1127
+ self.use_extension_ = False
1128
+ if self.cur_feature_name_ is None:
1129
+ self.lookupflag_ = 0
1130
+ self.lookupflag_markFilterSet_ = None
1131
+
1132
+ def add_lookup_call(self, lookup_name):
1133
+ assert lookup_name in self.named_lookups_, lookup_name
1134
+ self.cur_lookup_ = None
1135
+ lookup = self.named_lookups_[lookup_name]
1136
+ if lookup is not None: # skip empty named lookup
1137
+ self.add_lookup_to_feature_(lookup, self.cur_feature_name_)
1138
+
1139
+ def set_font_revision(self, location, revision):
1140
+ self.fontRevision_ = revision
1141
+
1142
+ def set_language(self, location, language, include_default, required):
1143
+ assert len(language) == 4
1144
+ if self.cur_feature_name_ in ("aalt", "size"):
1145
+ raise FeatureLibError(
1146
+ "Language statements are not allowed "
1147
+ 'within "feature %s"' % self.cur_feature_name_,
1148
+ location,
1149
+ )
1150
+ if self.cur_feature_name_ is None:
1151
+ raise FeatureLibError(
1152
+ "Language statements are not allowed "
1153
+ "within standalone lookup blocks",
1154
+ location,
1155
+ )
1156
+ self.cur_lookup_ = None
1157
+
1158
+ key = (self.script_, language, self.cur_feature_name_)
1159
+ lookups = self.features_.get((key[0], "dflt", key[2]))
1160
+ if (language == "dflt" or include_default) and lookups:
1161
+ self.features_[key] = lookups[:]
1162
+ else:
1163
+ # if we aren't including default we need to manually remove the
1164
+ # default lookups, which were added to all declared langsystems
1165
+ # as they were encountered (we don't remove all lookups because
1166
+ # we want to allow duplicate script/lang statements;
1167
+ # see https://github.com/fonttools/fonttools/issues/3748
1168
+ cur_lookups = self.features_.get(key, [])
1169
+ self.features_[key] = [x for x in cur_lookups if x not in lookups]
1170
+ self.language_systems = frozenset([(self.script_, language)])
1171
+
1172
+ if required:
1173
+ key = (self.script_, language)
1174
+ if key in self.required_features_:
1175
+ raise FeatureLibError(
1176
+ "Language %s (script %s) has already "
1177
+ "specified feature %s as its required feature"
1178
+ % (
1179
+ language.strip(),
1180
+ self.script_.strip(),
1181
+ self.required_features_[key].strip(),
1182
+ ),
1183
+ location,
1184
+ )
1185
+ self.required_features_[key] = self.cur_feature_name_
1186
+
1187
+ def getMarkAttachClass_(self, location, glyphs):
1188
+ glyphs = frozenset(glyphs)
1189
+ id_ = self.markAttachClassID_.get(glyphs)
1190
+ if id_ is not None:
1191
+ return id_
1192
+ id_ = len(self.markAttachClassID_) + 1
1193
+ self.markAttachClassID_[glyphs] = id_
1194
+ for glyph in glyphs:
1195
+ if glyph in self.markAttach_:
1196
+ _, loc = self.markAttach_[glyph]
1197
+ raise FeatureLibError(
1198
+ "Glyph %s already has been assigned "
1199
+ "a MarkAttachmentType at %s" % (glyph, loc),
1200
+ location,
1201
+ )
1202
+ self.markAttach_[glyph] = (id_, location)
1203
+ return id_
1204
+
1205
+ def getMarkFilterSet_(self, location, glyphs):
1206
+ glyphs = frozenset(glyphs)
1207
+ id_ = self.markFilterSets_.get(glyphs)
1208
+ if id_ is not None:
1209
+ return id_
1210
+ id_ = len(self.markFilterSets_)
1211
+ self.markFilterSets_[glyphs] = id_
1212
+ return id_
1213
+
1214
+ def set_lookup_flag(self, location, value, markAttach, markFilter):
1215
+ value = value & 0xFF
1216
+ if markAttach is not None:
1217
+ markAttachClass = self.getMarkAttachClass_(location, markAttach)
1218
+ value = value | (markAttachClass << 8)
1219
+ if markFilter is not None:
1220
+ markFilterSet = self.getMarkFilterSet_(location, markFilter)
1221
+ value = value | 0x10
1222
+ self.lookupflag_markFilterSet_ = markFilterSet
1223
+ else:
1224
+ self.lookupflag_markFilterSet_ = None
1225
+ self.lookupflag_ = value
1226
+
1227
+ def set_script(self, location, script):
1228
+ if self.cur_feature_name_ in ("aalt", "size"):
1229
+ raise FeatureLibError(
1230
+ "Script statements are not allowed "
1231
+ 'within "feature %s"' % self.cur_feature_name_,
1232
+ location,
1233
+ )
1234
+ if self.cur_feature_name_ is None:
1235
+ raise FeatureLibError(
1236
+ "Script statements are not allowed " "within standalone lookup blocks",
1237
+ location,
1238
+ )
1239
+ if self.language_systems == {(script, "dflt")}:
1240
+ # Nothing to do.
1241
+ return
1242
+ self.cur_lookup_ = None
1243
+ self.script_ = script
1244
+ self.lookupflag_ = 0
1245
+ self.lookupflag_markFilterSet_ = None
1246
+ self.set_language(location, "dflt", include_default=True, required=False)
1247
+
1248
+ def find_lookup_builders_(self, lookups):
1249
+ """Helper for building chain contextual substitutions
1250
+
1251
+ Given a list of lookup names, finds the LookupBuilder for each name.
1252
+ If an input name is None, it gets mapped to a None LookupBuilder.
1253
+ """
1254
+ lookup_builders = []
1255
+ for lookuplist in lookups:
1256
+ if lookuplist is not None:
1257
+ lookup_builders.append(
1258
+ [self.named_lookups_.get(l.name) for l in lookuplist]
1259
+ )
1260
+ else:
1261
+ lookup_builders.append(None)
1262
+ return lookup_builders
1263
+
1264
+ def add_attach_points(self, location, glyphs, contourPoints):
1265
+ for glyph in glyphs:
1266
+ self.attachPoints_.setdefault(glyph, set()).update(contourPoints)
1267
+
1268
+ def add_feature_reference(self, location, featureName):
1269
+ if self.cur_feature_name_ != "aalt":
1270
+ raise FeatureLibError(
1271
+ 'Feature references are only allowed inside "feature aalt"', location
1272
+ )
1273
+ self.aalt_features_.append((location, featureName))
1274
+
1275
+ def add_featureName(self, tag):
1276
+ self.featureNames_.add(tag)
1277
+
1278
+ def add_cv_parameter(self, tag):
1279
+ self.cv_parameters_.add(tag)
1280
+
1281
+ def add_to_cv_num_named_params(self, tag):
1282
+ """Adds new items to ``self.cv_num_named_params_``
1283
+ or increments the count of existing items."""
1284
+ if tag in self.cv_num_named_params_:
1285
+ self.cv_num_named_params_[tag] += 1
1286
+ else:
1287
+ self.cv_num_named_params_[tag] = 1
1288
+
1289
+ def add_cv_character(self, character, tag):
1290
+ self.cv_characters_[tag].append(character)
1291
+
1292
+ def set_base_axis(self, bases, scripts, vertical, minmax=[]):
1293
+ if vertical:
1294
+ self.base_vert_axis_ = (bases, scripts, minmax)
1295
+ else:
1296
+ self.base_horiz_axis_ = (bases, scripts, minmax)
1297
+
1298
+ def set_size_parameters(
1299
+ self, location, DesignSize, SubfamilyID, RangeStart, RangeEnd
1300
+ ):
1301
+ if self.cur_feature_name_ != "size":
1302
+ raise FeatureLibError(
1303
+ "Parameters statements are not allowed "
1304
+ 'within "feature %s"' % self.cur_feature_name_,
1305
+ location,
1306
+ )
1307
+ self.size_parameters_ = [DesignSize, SubfamilyID, RangeStart, RangeEnd]
1308
+ for script, lang in self.language_systems:
1309
+ key = (script, lang, self.cur_feature_name_)
1310
+ self.features_.setdefault(key, [])
1311
+
1312
+ # GSUB rules
1313
+
1314
+ def add_any_subst_(self, location, mapping):
1315
+ lookup = self.get_lookup_(location, AnySubstBuilder, mapping=mapping)
1316
+ for key, value in mapping.items():
1317
+ if key in lookup.mapping:
1318
+ if value == lookup.mapping[key]:
1319
+ log.info(
1320
+ 'Removing duplicate substitution from "%s" to "%s" at %s',
1321
+ ", ".join(key),
1322
+ ", ".join(value),
1323
+ location,
1324
+ )
1325
+ else:
1326
+ raise FeatureLibError(
1327
+ 'Already defined substitution for "%s"' % ", ".join(key),
1328
+ location,
1329
+ )
1330
+ lookup.mapping[key] = value
1331
+
1332
+ # GSUB 1
1333
+ def add_single_subst(self, location, prefix, suffix, mapping, forceChain):
1334
+ if self.cur_feature_name_ == "aalt":
1335
+ for from_glyph, to_glyph in mapping.items():
1336
+ alts = self.aalt_alternates_.setdefault(from_glyph, [])
1337
+ if to_glyph not in alts:
1338
+ alts.append(to_glyph)
1339
+ return
1340
+ if prefix or suffix or forceChain:
1341
+ self.add_single_subst_chained_(location, prefix, suffix, mapping)
1342
+ return
1343
+
1344
+ self.add_any_subst_(
1345
+ location,
1346
+ {(key,): (value,) for key, value in mapping.items()},
1347
+ )
1348
+
1349
+ # GSUB 2
1350
+ def add_multiple_subst(
1351
+ self, location, prefix, glyph, suffix, replacements, forceChain=False
1352
+ ):
1353
+ if prefix or suffix or forceChain:
1354
+ self.add_multi_subst_chained_(location, prefix, glyph, suffix, replacements)
1355
+ return
1356
+ self.add_any_subst_(
1357
+ location,
1358
+ {(glyph,): tuple(replacements)},
1359
+ )
1360
+
1361
+ # GSUB 3
1362
+ def add_alternate_subst(self, location, prefix, glyph, suffix, replacement):
1363
+ if self.cur_feature_name_ == "aalt":
1364
+ alts = self.aalt_alternates_.setdefault(glyph, [])
1365
+ alts.extend(g for g in replacement if g not in alts)
1366
+ return
1367
+ if prefix or suffix:
1368
+ chain = self.get_lookup_(location, ChainContextSubstBuilder)
1369
+ lookup = self.get_chained_lookup_(location, AlternateSubstBuilder)
1370
+ chain.rules.append(ChainContextualRule(prefix, [{glyph}], suffix, [lookup]))
1371
+ else:
1372
+ lookup = self.get_lookup_(location, AlternateSubstBuilder)
1373
+ if glyph in lookup.alternates:
1374
+ raise FeatureLibError(
1375
+ 'Already defined alternates for glyph "%s"' % glyph, location
1376
+ )
1377
+ # We allow empty replacement glyphs here.
1378
+ lookup.alternates[glyph] = replacement
1379
+
1380
+ # GSUB 4
1381
+ def add_ligature_subst(
1382
+ self, location, prefix, glyphs, suffix, replacement, forceChain
1383
+ ):
1384
+ if prefix or suffix or forceChain:
1385
+ self.add_ligature_subst_chained_(
1386
+ location, prefix, glyphs, suffix, replacement
1387
+ )
1388
+ return
1389
+ if not all(glyphs):
1390
+ raise FeatureLibError("Empty glyph class in substitution", location)
1391
+
1392
+ # OpenType feature file syntax, section 5.d, "Ligature substitution":
1393
+ # "Since the OpenType specification does not allow ligature
1394
+ # substitutions to be specified on target sequences that contain
1395
+ # glyph classes, the implementation software will enumerate
1396
+ # all specific glyph sequences if glyph classes are detected"
1397
+ self.add_any_subst_(
1398
+ location,
1399
+ {g: (replacement,) for g in itertools.product(*glyphs)},
1400
+ )
1401
+
1402
+ # GSUB 5/6
1403
+ def add_chain_context_subst(self, location, prefix, glyphs, suffix, lookups):
1404
+ if not all(glyphs) or not all(prefix) or not all(suffix):
1405
+ raise FeatureLibError(
1406
+ "Empty glyph class in contextual substitution", location
1407
+ )
1408
+ lookup = self.get_lookup_(location, ChainContextSubstBuilder)
1409
+ lookup.rules.append(
1410
+ ChainContextualRule(
1411
+ prefix, glyphs, suffix, self.find_lookup_builders_(lookups)
1412
+ )
1413
+ )
1414
+
1415
+ def add_single_subst_chained_(self, location, prefix, suffix, mapping):
1416
+ if not mapping or not all(prefix) or not all(suffix):
1417
+ raise FeatureLibError(
1418
+ "Empty glyph class in contextual substitution", location
1419
+ )
1420
+ # https://github.com/fonttools/fonttools/issues/512
1421
+ # https://github.com/fonttools/fonttools/issues/2150
1422
+ chain = self.get_lookup_(location, ChainContextSubstBuilder)
1423
+ sub = chain.find_chainable_subst(mapping, SingleSubstBuilder)
1424
+ if sub is None:
1425
+ sub = self.get_chained_lookup_(location, SingleSubstBuilder)
1426
+ sub.mapping.update(mapping)
1427
+ chain.rules.append(
1428
+ ChainContextualRule(prefix, [list(mapping.keys())], suffix, [sub])
1429
+ )
1430
+
1431
+ def add_multi_subst_chained_(self, location, prefix, glyph, suffix, replacements):
1432
+ if not all(prefix) or not all(suffix):
1433
+ raise FeatureLibError(
1434
+ "Empty glyph class in contextual substitution", location
1435
+ )
1436
+ # https://github.com/fonttools/fonttools/issues/3551
1437
+ chain = self.get_lookup_(location, ChainContextSubstBuilder)
1438
+ sub = chain.find_chainable_subst({glyph: replacements}, MultipleSubstBuilder)
1439
+ if sub is None:
1440
+ sub = self.get_chained_lookup_(location, MultipleSubstBuilder)
1441
+ sub.mapping[glyph] = replacements
1442
+ chain.rules.append(ChainContextualRule(prefix, [{glyph}], suffix, [sub]))
1443
+
1444
+ def add_ligature_subst_chained_(
1445
+ self, location, prefix, glyphs, suffix, replacement
1446
+ ):
1447
+ # https://github.com/fonttools/fonttools/issues/3701
1448
+ if not all(prefix) or not all(suffix):
1449
+ raise FeatureLibError(
1450
+ "Empty glyph class in contextual substitution", location
1451
+ )
1452
+ chain = self.get_lookup_(location, ChainContextSubstBuilder)
1453
+ sub = chain.find_chainable_ligature_subst(glyphs, replacement)
1454
+ if sub is None:
1455
+ sub = self.get_chained_lookup_(location, LigatureSubstBuilder)
1456
+
1457
+ for g in itertools.product(*glyphs):
1458
+ existing = sub.ligatures.get(g, replacement)
1459
+ if existing != replacement:
1460
+ raise FeatureLibError(
1461
+ f"Conflicting ligature sub rules: '{g}' maps to '{existing}' and '{replacement}'",
1462
+ location,
1463
+ )
1464
+
1465
+ sub.ligatures[g] = replacement
1466
+
1467
+ chain.rules.append(ChainContextualRule(prefix, glyphs, suffix, [sub]))
1468
+
1469
+ # GSUB 8
1470
+ def add_reverse_chain_single_subst(self, location, old_prefix, old_suffix, mapping):
1471
+ if not mapping:
1472
+ raise FeatureLibError("Empty glyph class in substitution", location)
1473
+ lookup = self.get_lookup_(location, ReverseChainSingleSubstBuilder)
1474
+ lookup.rules.append((old_prefix, old_suffix, mapping))
1475
+
1476
+ # GPOS rules
1477
+
1478
+ # GPOS 1
1479
+ def add_single_pos(self, location, prefix, suffix, pos, forceChain):
1480
+ if prefix or suffix or forceChain:
1481
+ self.add_single_pos_chained_(location, prefix, suffix, pos)
1482
+ else:
1483
+ lookup = self.get_lookup_(location, SinglePosBuilder)
1484
+ for glyphs, value in pos:
1485
+ if not glyphs:
1486
+ raise FeatureLibError(
1487
+ "Empty glyph class in positioning rule", location
1488
+ )
1489
+ otValueRecord = self.makeOpenTypeValueRecord(
1490
+ location, value, pairPosContext=False
1491
+ )
1492
+ for glyph in glyphs:
1493
+ try:
1494
+ lookup.add_pos(location, glyph, otValueRecord)
1495
+ except OpenTypeLibError as e:
1496
+ raise FeatureLibError(str(e), e.location) from e
1497
+
1498
+ # GPOS 2
1499
+ def add_class_pair_pos(self, location, glyphclass1, value1, glyphclass2, value2):
1500
+ if not glyphclass1 or not glyphclass2:
1501
+ raise FeatureLibError("Empty glyph class in positioning rule", location)
1502
+ lookup = self.get_lookup_(location, PairPosBuilder)
1503
+ v1 = self.makeOpenTypeValueRecord(location, value1, pairPosContext=True)
1504
+ v2 = self.makeOpenTypeValueRecord(location, value2, pairPosContext=True)
1505
+ cls1 = tuple(sorted(set(glyphclass1)))
1506
+ cls2 = tuple(sorted(set(glyphclass2)))
1507
+ lookup.addClassPair(location, cls1, v1, cls2, v2)
1508
+
1509
+ def add_specific_pair_pos(self, location, glyph1, value1, glyph2, value2):
1510
+ if not glyph1 or not glyph2:
1511
+ raise FeatureLibError("Empty glyph class in positioning rule", location)
1512
+ lookup = self.get_lookup_(location, PairPosBuilder)
1513
+ v1 = self.makeOpenTypeValueRecord(location, value1, pairPosContext=True)
1514
+ v2 = self.makeOpenTypeValueRecord(location, value2, pairPosContext=True)
1515
+ lookup.addGlyphPair(location, glyph1, v1, glyph2, v2)
1516
+
1517
+ # GPOS 3
1518
+ def add_cursive_pos(self, location, glyphclass, entryAnchor, exitAnchor):
1519
+ if not glyphclass:
1520
+ raise FeatureLibError("Empty glyph class in positioning rule", location)
1521
+ lookup = self.get_lookup_(location, CursivePosBuilder)
1522
+ lookup.add_attachment(
1523
+ location,
1524
+ glyphclass,
1525
+ self.makeOpenTypeAnchor(location, entryAnchor),
1526
+ self.makeOpenTypeAnchor(location, exitAnchor),
1527
+ )
1528
+
1529
+ # GPOS 4
1530
+ def add_mark_base_pos(self, location, bases, marks):
1531
+ builder = self.get_lookup_(location, MarkBasePosBuilder)
1532
+ self.add_marks_(location, builder, marks)
1533
+ if not bases:
1534
+ raise FeatureLibError("Empty glyph class in positioning rule", location)
1535
+ for baseAnchor, markClass in marks:
1536
+ otBaseAnchor = self.makeOpenTypeAnchor(location, baseAnchor)
1537
+ for base in bases:
1538
+ builder.bases.setdefault(base, {})[markClass.name] = otBaseAnchor
1539
+
1540
+ # GPOS 5
1541
+ def add_mark_lig_pos(self, location, ligatures, components):
1542
+ builder = self.get_lookup_(location, MarkLigPosBuilder)
1543
+ componentAnchors = []
1544
+ if not ligatures:
1545
+ raise FeatureLibError("Empty glyph class in positioning rule", location)
1546
+ for marks in components:
1547
+ anchors = {}
1548
+ self.add_marks_(location, builder, marks)
1549
+ for ligAnchor, markClass in marks:
1550
+ anchors[markClass.name] = self.makeOpenTypeAnchor(location, ligAnchor)
1551
+ componentAnchors.append(anchors)
1552
+ for glyph in ligatures:
1553
+ builder.ligatures[glyph] = componentAnchors
1554
+
1555
+ # GPOS 6
1556
+ def add_mark_mark_pos(self, location, baseMarks, marks):
1557
+ builder = self.get_lookup_(location, MarkMarkPosBuilder)
1558
+ self.add_marks_(location, builder, marks)
1559
+ if not baseMarks:
1560
+ raise FeatureLibError("Empty glyph class in positioning rule", location)
1561
+ for baseAnchor, markClass in marks:
1562
+ otBaseAnchor = self.makeOpenTypeAnchor(location, baseAnchor)
1563
+ for baseMark in baseMarks:
1564
+ builder.baseMarks.setdefault(baseMark, {})[
1565
+ markClass.name
1566
+ ] = otBaseAnchor
1567
+
1568
+ # GPOS 7/8
1569
+ def add_chain_context_pos(self, location, prefix, glyphs, suffix, lookups):
1570
+ if not all(glyphs) or not all(prefix) or not all(suffix):
1571
+ raise FeatureLibError(
1572
+ "Empty glyph class in contextual positioning rule", location
1573
+ )
1574
+ lookup = self.get_lookup_(location, ChainContextPosBuilder)
1575
+ lookup.rules.append(
1576
+ ChainContextualRule(
1577
+ prefix, glyphs, suffix, self.find_lookup_builders_(lookups)
1578
+ )
1579
+ )
1580
+
1581
+ def add_single_pos_chained_(self, location, prefix, suffix, pos):
1582
+ if not pos or not all(prefix) or not all(suffix):
1583
+ raise FeatureLibError(
1584
+ "Empty glyph class in contextual positioning rule", location
1585
+ )
1586
+ # https://github.com/fonttools/fonttools/issues/514
1587
+ chain = self.get_lookup_(location, ChainContextPosBuilder)
1588
+ targets = []
1589
+ for _, _, _, lookups in chain.rules:
1590
+ targets.extend(lookups)
1591
+ subs = []
1592
+ for glyphs, value in pos:
1593
+ if value is None:
1594
+ subs.append(None)
1595
+ continue
1596
+ otValue = self.makeOpenTypeValueRecord(
1597
+ location, value, pairPosContext=False
1598
+ )
1599
+ sub = chain.find_chainable_single_pos(targets, glyphs, otValue)
1600
+ if sub is None:
1601
+ sub = self.get_chained_lookup_(location, SinglePosBuilder)
1602
+ targets.append(sub)
1603
+ for glyph in glyphs:
1604
+ sub.add_pos(location, glyph, otValue)
1605
+ subs.append(sub)
1606
+ assert len(pos) == len(subs), (pos, subs)
1607
+ chain.rules.append(
1608
+ ChainContextualRule(prefix, [g for g, v in pos], suffix, subs)
1609
+ )
1610
+
1611
+ def add_marks_(self, location, lookupBuilder, marks):
1612
+ """Helper for add_mark_{base,liga,mark}_pos."""
1613
+ for _, markClass in marks:
1614
+ for markClassDef in markClass.definitions:
1615
+ for mark in markClassDef.glyphs.glyphSet():
1616
+ if mark not in lookupBuilder.marks:
1617
+ otMarkAnchor = self.makeOpenTypeAnchor(
1618
+ location, copy.deepcopy(markClassDef.anchor)
1619
+ )
1620
+ lookupBuilder.marks[mark] = (markClass.name, otMarkAnchor)
1621
+ else:
1622
+ existingMarkClass = lookupBuilder.marks[mark][0]
1623
+ if markClass.name != existingMarkClass:
1624
+ raise FeatureLibError(
1625
+ "Glyph %s cannot be in both @%s and @%s"
1626
+ % (mark, existingMarkClass, markClass.name),
1627
+ location,
1628
+ )
1629
+
1630
+ def add_subtable_break(self, location):
1631
+ self.cur_lookup_.add_subtable_break(location)
1632
+
1633
+ def setGlyphClass_(self, location, glyph, glyphClass):
1634
+ oldClass, oldLocation = self.glyphClassDefs_.get(glyph, (None, None))
1635
+ if oldClass and oldClass != glyphClass:
1636
+ raise FeatureLibError(
1637
+ "Glyph %s was assigned to a different class at %s"
1638
+ % (glyph, oldLocation),
1639
+ location,
1640
+ )
1641
+ self.glyphClassDefs_[glyph] = (glyphClass, location)
1642
+
1643
+ def add_glyphClassDef(
1644
+ self, location, baseGlyphs, ligatureGlyphs, markGlyphs, componentGlyphs
1645
+ ):
1646
+ for glyph in baseGlyphs:
1647
+ self.setGlyphClass_(location, glyph, 1)
1648
+ for glyph in ligatureGlyphs:
1649
+ self.setGlyphClass_(location, glyph, 2)
1650
+ for glyph in markGlyphs:
1651
+ self.setGlyphClass_(location, glyph, 3)
1652
+ for glyph in componentGlyphs:
1653
+ self.setGlyphClass_(location, glyph, 4)
1654
+
1655
+ def add_ligatureCaretByIndex_(self, location, glyphs, carets):
1656
+ for glyph in glyphs:
1657
+ if glyph not in self.ligCaretPoints_:
1658
+ self.ligCaretPoints_[glyph] = carets
1659
+
1660
+ def makeLigCaret(self, location, caret):
1661
+ if not isinstance(caret, VariableScalar):
1662
+ return caret
1663
+ default, device = self.makeVariablePos(location, caret)
1664
+ if device is not None:
1665
+ return (default, device)
1666
+ return default
1667
+
1668
+ def add_ligatureCaretByPos_(self, location, glyphs, carets):
1669
+ carets = [self.makeLigCaret(location, caret) for caret in carets]
1670
+ for glyph in glyphs:
1671
+ if glyph not in self.ligCaretCoords_:
1672
+ self.ligCaretCoords_[glyph] = carets
1673
+
1674
+ def add_name_record(self, location, nameID, platformID, platEncID, langID, string):
1675
+ self.names_.append([nameID, platformID, platEncID, langID, string])
1676
+
1677
+ def add_os2_field(self, key, value):
1678
+ self.os2_[key] = value
1679
+
1680
+ def add_hhea_field(self, key, value):
1681
+ self.hhea_[key] = value
1682
+
1683
+ def add_vhea_field(self, key, value):
1684
+ self.vhea_[key] = value
1685
+
1686
+ def add_conditionset(self, location, key, value):
1687
+ if "fvar" not in self.font:
1688
+ raise FeatureLibError(
1689
+ "Cannot add feature variations to a font without an 'fvar' table",
1690
+ location,
1691
+ )
1692
+
1693
+ if key in self.conditionsets_:
1694
+ raise FeatureLibError(
1695
+ f"Condition set '{key}' has the same name as a previous condition set",
1696
+ location,
1697
+ )
1698
+
1699
+ # Normalize
1700
+ axisMap = {
1701
+ axis.axisTag: (axis.minValue, axis.defaultValue, axis.maxValue)
1702
+ for axis in self.axes
1703
+ }
1704
+
1705
+ value = {
1706
+ tag: (
1707
+ normalizeValue(bottom, axisMap[tag]),
1708
+ normalizeValue(top, axisMap[tag]),
1709
+ )
1710
+ for tag, (bottom, top) in value.items()
1711
+ }
1712
+
1713
+ # NOTE: This might result in rounding errors (off-by-ones) compared to
1714
+ # rules in Designspace files, since we're working with what's in the
1715
+ # `avar` table rather than the original values.
1716
+ if "avar" in self.font:
1717
+ mapping = self.font["avar"].segments
1718
+ value = {
1719
+ axis: tuple(
1720
+ piecewiseLinearMap(v, mapping[axis]) if axis in mapping else v
1721
+ for v in condition_range
1722
+ )
1723
+ for axis, condition_range in value.items()
1724
+ }
1725
+
1726
+ self.conditionsets_[key] = value
1727
+
1728
+ def makeVariablePos(self, location, varscalar):
1729
+ if not self.varstorebuilder:
1730
+ raise FeatureLibError(
1731
+ "Can't define a variable scalar in a non-variable font", location
1732
+ )
1733
+
1734
+ varscalar.axes = self.axes
1735
+ if not varscalar.does_vary:
1736
+ return varscalar.default, None
1737
+
1738
+ try:
1739
+ default, index = varscalar.add_to_variation_store(
1740
+ self.varstorebuilder, self.model_cache, self.font.get("avar")
1741
+ )
1742
+ except VarLibError as e:
1743
+ raise FeatureLibError(
1744
+ "Failed to compute deltas for variable scalar", location
1745
+ ) from e
1746
+
1747
+ device = None
1748
+ if index is not None and index != 0xFFFFFFFF:
1749
+ device = buildVarDevTable(index)
1750
+
1751
+ return default, device
1752
+
1753
+ def makeAnchorPos(self, varscalar, deviceTable, location):
1754
+ device = None
1755
+ if not isinstance(varscalar, VariableScalar):
1756
+ if deviceTable is not None:
1757
+ device = otl.buildDevice(dict(deviceTable))
1758
+ return varscalar, device
1759
+ default, device = self.makeVariablePos(location, varscalar)
1760
+ if device is not None and deviceTable is not None:
1761
+ raise FeatureLibError(
1762
+ "Can't define a device coordinate and variable scalar", location
1763
+ )
1764
+ return default, device
1765
+
1766
+ def makeOpenTypeAnchor(self, location, anchor):
1767
+ """ast.Anchor --> otTables.Anchor"""
1768
+ if anchor is None:
1769
+ return None
1770
+ deviceX, deviceY = None, None
1771
+ if anchor.xDeviceTable is not None:
1772
+ deviceX = otl.buildDevice(dict(anchor.xDeviceTable))
1773
+ if anchor.yDeviceTable is not None:
1774
+ deviceY = otl.buildDevice(dict(anchor.yDeviceTable))
1775
+ x, deviceX = self.makeAnchorPos(anchor.x, anchor.xDeviceTable, location)
1776
+ y, deviceY = self.makeAnchorPos(anchor.y, anchor.yDeviceTable, location)
1777
+ otlanchor = otl.buildAnchor(x, y, anchor.contourpoint, deviceX, deviceY)
1778
+ return otlanchor
1779
+
1780
+ _VALUEREC_ATTRS = {
1781
+ name[0].lower() + name[1:]: (name, isDevice)
1782
+ for _, name, isDevice, _ in otBase.valueRecordFormat
1783
+ if not name.startswith("Reserved")
1784
+ }
1785
+
1786
+ def makeOpenTypeValueRecord(self, location, v, pairPosContext):
1787
+ """ast.ValueRecord --> otBase.ValueRecord"""
1788
+ if not v:
1789
+ return None
1790
+
1791
+ vr = {}
1792
+ for astName, (otName, isDevice) in self._VALUEREC_ATTRS.items():
1793
+ val = getattr(v, astName, None)
1794
+ if not val:
1795
+ continue
1796
+ if isDevice:
1797
+ vr[otName] = otl.buildDevice(dict(val))
1798
+ elif isinstance(val, VariableScalar):
1799
+ otDeviceName = otName[0:4] + "Device"
1800
+ feaDeviceName = otDeviceName[0].lower() + otDeviceName[1:]
1801
+ if getattr(v, feaDeviceName):
1802
+ raise FeatureLibError(
1803
+ "Can't define a device coordinate and variable scalar", location
1804
+ )
1805
+ vr[otName], device = self.makeVariablePos(location, val)
1806
+ if device is not None:
1807
+ vr[otDeviceName] = device
1808
+ else:
1809
+ vr[otName] = val
1810
+
1811
+ if pairPosContext and not vr:
1812
+ vr = {"YAdvance": 0} if v.vertical else {"XAdvance": 0}
1813
+ valRec = otl.buildValue(vr)
1814
+ return valRec