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,6 @@
1
+ import sys
2
+ from fontTools.designspaceLib import main
3
+
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())
@@ -0,0 +1,475 @@
1
+ """Allows building all the variable fonts of a DesignSpace version 5 by
2
+ splitting the document into interpolable sub-space, then into each VF.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import itertools
8
+ import logging
9
+ import math
10
+ from typing import Any, Callable, Dict, Iterator, List, Tuple, cast
11
+
12
+ from fontTools.designspaceLib import (
13
+ AxisDescriptor,
14
+ AxisMappingDescriptor,
15
+ DesignSpaceDocument,
16
+ DiscreteAxisDescriptor,
17
+ InstanceDescriptor,
18
+ RuleDescriptor,
19
+ SimpleLocationDict,
20
+ SourceDescriptor,
21
+ VariableFontDescriptor,
22
+ )
23
+ from fontTools.designspaceLib.statNames import StatNames, getStatNames
24
+ from fontTools.designspaceLib.types import (
25
+ ConditionSet,
26
+ Range,
27
+ Region,
28
+ getVFUserRegion,
29
+ locationInRegion,
30
+ regionInRegion,
31
+ userRegionToDesignRegion,
32
+ )
33
+
34
+ LOGGER = logging.getLogger(__name__)
35
+
36
+ MakeInstanceFilenameCallable = Callable[
37
+ [DesignSpaceDocument, InstanceDescriptor, StatNames], str
38
+ ]
39
+
40
+
41
+ def defaultMakeInstanceFilename(
42
+ doc: DesignSpaceDocument, instance: InstanceDescriptor, statNames: StatNames
43
+ ) -> str:
44
+ """Default callable to synthesize an instance filename
45
+ when makeNames=True, for instances that don't specify an instance name
46
+ in the designspace. This part of the name generation can be overriden
47
+ because it's not specified by the STAT table.
48
+ """
49
+ familyName = instance.familyName or statNames.familyNames.get("en")
50
+ styleName = instance.styleName or statNames.styleNames.get("en")
51
+ return f"{familyName}-{styleName}.ttf"
52
+
53
+
54
+ def splitInterpolable(
55
+ doc: DesignSpaceDocument,
56
+ makeNames: bool = True,
57
+ expandLocations: bool = True,
58
+ makeInstanceFilename: MakeInstanceFilenameCallable = defaultMakeInstanceFilename,
59
+ ) -> Iterator[Tuple[SimpleLocationDict, DesignSpaceDocument]]:
60
+ """Split the given DS5 into several interpolable sub-designspaces.
61
+ There are as many interpolable sub-spaces as there are combinations of
62
+ discrete axis values.
63
+
64
+ E.g. with axes:
65
+ - italic (discrete) Upright or Italic
66
+ - style (discrete) Sans or Serif
67
+ - weight (continuous) 100 to 900
68
+
69
+ There are 4 sub-spaces in which the Weight axis should interpolate:
70
+ (Upright, Sans), (Upright, Serif), (Italic, Sans) and (Italic, Serif).
71
+
72
+ The sub-designspaces still include the full axis definitions and STAT data,
73
+ but the rules, sources, variable fonts, instances are trimmed down to only
74
+ keep what falls within the interpolable sub-space.
75
+
76
+ Args:
77
+ - ``makeNames``: Whether to compute the instance family and style
78
+ names using the STAT data.
79
+ - ``expandLocations``: Whether to turn all locations into "full"
80
+ locations, including implicit default axis values where missing.
81
+ - ``makeInstanceFilename``: Callable to synthesize an instance filename
82
+ when makeNames=True, for instances that don't specify an instance name
83
+ in the designspace. This part of the name generation can be overridden
84
+ because it's not specified by the STAT table.
85
+
86
+ .. versionadded:: 5.0
87
+ """
88
+ discreteAxes = []
89
+ interpolableUserRegion: Region = {}
90
+ for axis in doc.axes:
91
+ if hasattr(axis, "values"):
92
+ # Mypy doesn't support narrowing union types via hasattr()
93
+ # TODO(Python 3.10): use TypeGuard
94
+ # https://mypy.readthedocs.io/en/stable/type_narrowing.html
95
+ axis = cast(DiscreteAxisDescriptor, axis)
96
+ discreteAxes.append(axis)
97
+ else:
98
+ axis = cast(AxisDescriptor, axis)
99
+ interpolableUserRegion[axis.name] = Range(
100
+ axis.minimum,
101
+ axis.maximum,
102
+ axis.default,
103
+ )
104
+ valueCombinations = itertools.product(*[axis.values for axis in discreteAxes])
105
+ for values in valueCombinations:
106
+ discreteUserLocation = {
107
+ discreteAxis.name: value
108
+ for discreteAxis, value in zip(discreteAxes, values)
109
+ }
110
+ subDoc = _extractSubSpace(
111
+ doc,
112
+ {**interpolableUserRegion, **discreteUserLocation},
113
+ keepVFs=True,
114
+ makeNames=makeNames,
115
+ expandLocations=expandLocations,
116
+ makeInstanceFilename=makeInstanceFilename,
117
+ )
118
+ yield discreteUserLocation, subDoc
119
+
120
+
121
+ def splitVariableFonts(
122
+ doc: DesignSpaceDocument,
123
+ makeNames: bool = False,
124
+ expandLocations: bool = False,
125
+ makeInstanceFilename: MakeInstanceFilenameCallable = defaultMakeInstanceFilename,
126
+ ) -> Iterator[Tuple[str, DesignSpaceDocument]]:
127
+ """Convert each variable font listed in this document into a standalone
128
+ designspace. This can be used to compile all the variable fonts from a
129
+ format 5 designspace using tools that can only deal with 1 VF at a time.
130
+
131
+ Args:
132
+ - ``makeNames``: Whether to compute the instance family and style
133
+ names using the STAT data.
134
+ - ``expandLocations``: Whether to turn all locations into "full"
135
+ locations, including implicit default axis values where missing.
136
+ - ``makeInstanceFilename``: Callable to synthesize an instance filename
137
+ when makeNames=True, for instances that don't specify an instance name
138
+ in the designspace. This part of the name generation can be overridden
139
+ because it's not specified by the STAT table.
140
+
141
+ .. versionadded:: 5.0
142
+ """
143
+ # Make one DesignspaceDoc v5 for each variable font
144
+ for vf in doc.getVariableFonts():
145
+ vfUserRegion = getVFUserRegion(doc, vf)
146
+ vfDoc = _extractSubSpace(
147
+ doc,
148
+ vfUserRegion,
149
+ keepVFs=False,
150
+ makeNames=makeNames,
151
+ expandLocations=expandLocations,
152
+ makeInstanceFilename=makeInstanceFilename,
153
+ )
154
+ vfDoc.lib = {**vfDoc.lib, **vf.lib}
155
+ yield vf.name, vfDoc
156
+
157
+
158
+ def convert5to4(
159
+ doc: DesignSpaceDocument,
160
+ ) -> Dict[str, DesignSpaceDocument]:
161
+ """Convert each variable font listed in this document into a standalone
162
+ format 4 designspace. This can be used to compile all the variable fonts
163
+ from a format 5 designspace using tools that only know about format 4.
164
+
165
+ .. versionadded:: 5.0
166
+ """
167
+ vfs = {}
168
+ for _location, subDoc in splitInterpolable(doc):
169
+ for vfName, vfDoc in splitVariableFonts(subDoc):
170
+ vfDoc.formatVersion = "4.1"
171
+ vfs[vfName] = vfDoc
172
+ return vfs
173
+
174
+
175
+ def _extractSubSpace(
176
+ doc: DesignSpaceDocument,
177
+ userRegion: Region,
178
+ *,
179
+ keepVFs: bool,
180
+ makeNames: bool,
181
+ expandLocations: bool,
182
+ makeInstanceFilename: MakeInstanceFilenameCallable,
183
+ ) -> DesignSpaceDocument:
184
+ subDoc = DesignSpaceDocument()
185
+ # Don't include STAT info
186
+ # FIXME: (Jany) let's think about it. Not include = OK because the point of
187
+ # the splitting is to build VFs and we'll use the STAT data of the full
188
+ # document to generate the STAT of the VFs, so "no need" to have STAT data
189
+ # in sub-docs. Counterpoint: what if someone wants to split this DS for
190
+ # other purposes? Maybe for that it would be useful to also subset the STAT
191
+ # data?
192
+ # subDoc.elidedFallbackName = doc.elidedFallbackName
193
+
194
+ def maybeExpandDesignLocation(object):
195
+ if expandLocations:
196
+ return object.getFullDesignLocation(doc)
197
+ else:
198
+ return object.designLocation
199
+
200
+ for axis in doc.axes:
201
+ range = userRegion[axis.name]
202
+ if isinstance(range, Range) and hasattr(axis, "minimum"):
203
+ # Mypy doesn't support narrowing union types via hasattr()
204
+ # TODO(Python 3.10): use TypeGuard
205
+ # https://mypy.readthedocs.io/en/stable/type_narrowing.html
206
+ axis = cast(AxisDescriptor, axis)
207
+ subDoc.addAxis(
208
+ AxisDescriptor(
209
+ # Same info
210
+ tag=axis.tag,
211
+ name=axis.name,
212
+ labelNames=axis.labelNames,
213
+ hidden=axis.hidden,
214
+ # Subset range
215
+ minimum=max(range.minimum, axis.minimum),
216
+ default=range.default or axis.default,
217
+ maximum=min(range.maximum, axis.maximum),
218
+ map=[
219
+ (user, design)
220
+ for user, design in axis.map
221
+ if range.minimum <= user <= range.maximum
222
+ ],
223
+ # Don't include STAT info
224
+ axisOrdering=None,
225
+ axisLabels=None,
226
+ )
227
+ )
228
+
229
+ subDoc.axisMappings = mappings = []
230
+ subDocAxes = {axis.name for axis in subDoc.axes}
231
+ for mapping in doc.axisMappings:
232
+ if not all(axis in subDocAxes for axis in mapping.inputLocation.keys()):
233
+ continue
234
+ if not all(axis in subDocAxes for axis in mapping.outputLocation.keys()):
235
+ LOGGER.error(
236
+ "In axis mapping from input %s, some output axes are not in the variable-font: %s",
237
+ mapping.inputLocation,
238
+ mapping.outputLocation,
239
+ )
240
+ continue
241
+
242
+ mappingAxes = set()
243
+ mappingAxes.update(mapping.inputLocation.keys())
244
+ mappingAxes.update(mapping.outputLocation.keys())
245
+ for axis in doc.axes:
246
+ if axis.name not in mappingAxes:
247
+ continue
248
+ range = userRegion[axis.name]
249
+ if (
250
+ range.minimum != axis.minimum
251
+ or (range.default is not None and range.default != axis.default)
252
+ or range.maximum != axis.maximum
253
+ ):
254
+ LOGGER.error(
255
+ "Limiting axis ranges used in <mapping> elements not supported: %s",
256
+ axis.name,
257
+ )
258
+ continue
259
+
260
+ mappings.append(
261
+ AxisMappingDescriptor(
262
+ inputLocation=mapping.inputLocation,
263
+ outputLocation=mapping.outputLocation,
264
+ )
265
+ )
266
+
267
+ # Don't include STAT info
268
+ # subDoc.locationLabels = doc.locationLabels
269
+
270
+ # Rules: subset them based on conditions
271
+ designRegion = userRegionToDesignRegion(doc, userRegion)
272
+ subDoc.rules = _subsetRulesBasedOnConditions(doc.rules, designRegion)
273
+ subDoc.rulesProcessingLast = doc.rulesProcessingLast
274
+
275
+ # Sources: keep only the ones that fall within the kept axis ranges
276
+ for source in doc.sources:
277
+ if not locationInRegion(doc.map_backward(source.designLocation), userRegion):
278
+ continue
279
+
280
+ subDoc.addSource(
281
+ SourceDescriptor(
282
+ filename=source.filename,
283
+ path=source.path,
284
+ font=source.font,
285
+ name=source.name,
286
+ designLocation=_filterLocation(
287
+ userRegion, maybeExpandDesignLocation(source)
288
+ ),
289
+ layerName=source.layerName,
290
+ familyName=source.familyName,
291
+ styleName=source.styleName,
292
+ muteKerning=source.muteKerning,
293
+ muteInfo=source.muteInfo,
294
+ mutedGlyphNames=source.mutedGlyphNames,
295
+ )
296
+ )
297
+
298
+ # Copy family name translations from the old default source to the new default
299
+ vfDefault = subDoc.findDefault()
300
+ oldDefault = doc.findDefault()
301
+ if vfDefault is not None and oldDefault is not None:
302
+ vfDefault.localisedFamilyName = oldDefault.localisedFamilyName
303
+
304
+ # Variable fonts: keep only the ones that fall within the kept axis ranges
305
+ if keepVFs:
306
+ # Note: call getVariableFont() to make the implicit VFs explicit
307
+ for vf in doc.getVariableFonts():
308
+ vfUserRegion = getVFUserRegion(doc, vf)
309
+ if regionInRegion(vfUserRegion, userRegion):
310
+ subDoc.addVariableFont(
311
+ VariableFontDescriptor(
312
+ name=vf.name,
313
+ filename=vf.filename,
314
+ axisSubsets=[
315
+ axisSubset
316
+ for axisSubset in vf.axisSubsets
317
+ if isinstance(userRegion[axisSubset.name], Range)
318
+ ],
319
+ lib=vf.lib,
320
+ )
321
+ )
322
+
323
+ # Instances: same as Sources + compute missing names
324
+ for instance in doc.instances:
325
+ if not locationInRegion(instance.getFullUserLocation(doc), userRegion):
326
+ continue
327
+
328
+ if makeNames:
329
+ statNames = getStatNames(doc, instance.getFullUserLocation(doc))
330
+ familyName = instance.familyName or statNames.familyNames.get("en")
331
+ styleName = instance.styleName or statNames.styleNames.get("en")
332
+ subDoc.addInstance(
333
+ InstanceDescriptor(
334
+ filename=instance.filename
335
+ or makeInstanceFilename(doc, instance, statNames),
336
+ path=instance.path,
337
+ font=instance.font,
338
+ name=instance.name or f"{familyName} {styleName}",
339
+ userLocation={} if expandLocations else instance.userLocation,
340
+ designLocation=_filterLocation(
341
+ userRegion, maybeExpandDesignLocation(instance)
342
+ ),
343
+ familyName=familyName,
344
+ styleName=styleName,
345
+ postScriptFontName=instance.postScriptFontName
346
+ or statNames.postScriptFontName,
347
+ styleMapFamilyName=instance.styleMapFamilyName
348
+ or statNames.styleMapFamilyNames.get("en"),
349
+ styleMapStyleName=instance.styleMapStyleName
350
+ or statNames.styleMapStyleName,
351
+ localisedFamilyName=instance.localisedFamilyName
352
+ or statNames.familyNames,
353
+ localisedStyleName=instance.localisedStyleName
354
+ or statNames.styleNames,
355
+ localisedStyleMapFamilyName=instance.localisedStyleMapFamilyName
356
+ or statNames.styleMapFamilyNames,
357
+ localisedStyleMapStyleName=instance.localisedStyleMapStyleName
358
+ or {},
359
+ lib=instance.lib,
360
+ )
361
+ )
362
+ else:
363
+ subDoc.addInstance(
364
+ InstanceDescriptor(
365
+ filename=instance.filename,
366
+ path=instance.path,
367
+ font=instance.font,
368
+ name=instance.name,
369
+ userLocation={} if expandLocations else instance.userLocation,
370
+ designLocation=_filterLocation(
371
+ userRegion, maybeExpandDesignLocation(instance)
372
+ ),
373
+ familyName=instance.familyName,
374
+ styleName=instance.styleName,
375
+ postScriptFontName=instance.postScriptFontName,
376
+ styleMapFamilyName=instance.styleMapFamilyName,
377
+ styleMapStyleName=instance.styleMapStyleName,
378
+ localisedFamilyName=instance.localisedFamilyName,
379
+ localisedStyleName=instance.localisedStyleName,
380
+ localisedStyleMapFamilyName=instance.localisedStyleMapFamilyName,
381
+ localisedStyleMapStyleName=instance.localisedStyleMapStyleName,
382
+ lib=instance.lib,
383
+ )
384
+ )
385
+
386
+ subDoc.lib = doc.lib
387
+
388
+ return subDoc
389
+
390
+
391
+ def _conditionSetFrom(conditionSet: List[Dict[str, Any]]) -> ConditionSet:
392
+ c: Dict[str, Range] = {}
393
+ for condition in conditionSet:
394
+ minimum, maximum = condition.get("minimum"), condition.get("maximum")
395
+ c[condition["name"]] = Range(
396
+ minimum if minimum is not None else -math.inf,
397
+ maximum if maximum is not None else math.inf,
398
+ )
399
+ return c
400
+
401
+
402
+ def _subsetRulesBasedOnConditions(
403
+ rules: List[RuleDescriptor], designRegion: Region
404
+ ) -> List[RuleDescriptor]:
405
+ # What rules to keep:
406
+ # - Keep the rule if any conditionset is relevant.
407
+ # - A conditionset is relevant if all conditions are relevant or it is empty.
408
+ # - A condition is relevant if
409
+ # - axis is point (C-AP),
410
+ # - and point in condition's range (C-AP-in)
411
+ # (in this case remove the condition because it's always true)
412
+ # - else (C-AP-out) whole conditionset can be discarded (condition false
413
+ # => conditionset false)
414
+ # - axis is range (C-AR),
415
+ # - (C-AR-all) and axis range fully contained in condition range: we can
416
+ # scrap the condition because it's always true
417
+ # - (C-AR-inter) and intersection(axis range, condition range) not empty:
418
+ # keep the condition with the smaller range (= intersection)
419
+ # - (C-AR-none) else, whole conditionset can be discarded
420
+ newRules: List[RuleDescriptor] = []
421
+ for rule in rules:
422
+ newRule: RuleDescriptor = RuleDescriptor(
423
+ name=rule.name, conditionSets=[], subs=rule.subs
424
+ )
425
+ for conditionset in rule.conditionSets:
426
+ cs = _conditionSetFrom(conditionset)
427
+ newConditionset: List[Dict[str, Any]] = []
428
+ discardConditionset = False
429
+ for selectionName, selectionValue in designRegion.items():
430
+ # TODO: Ensure that all(key in conditionset for key in region.keys())?
431
+ if selectionName not in cs:
432
+ # raise Exception("Selection has different axes than the rules")
433
+ continue
434
+ if isinstance(selectionValue, (float, int)): # is point
435
+ # Case C-AP-in
436
+ if selectionValue in cs[selectionName]:
437
+ pass # always matches, conditionset can stay empty for this one.
438
+ # Case C-AP-out
439
+ else:
440
+ discardConditionset = True
441
+ else: # is range
442
+ # Case C-AR-all
443
+ if selectionValue in cs[selectionName]:
444
+ pass # always matches, conditionset can stay empty for this one.
445
+ else:
446
+ intersection = cs[selectionName].intersection(selectionValue)
447
+ # Case C-AR-inter
448
+ if intersection is not None:
449
+ newConditionset.append(
450
+ {
451
+ "name": selectionName,
452
+ "minimum": intersection.minimum,
453
+ "maximum": intersection.maximum,
454
+ }
455
+ )
456
+ # Case C-AR-none
457
+ else:
458
+ discardConditionset = True
459
+ if not discardConditionset:
460
+ newRule.conditionSets.append(newConditionset)
461
+ if newRule.conditionSets:
462
+ newRules.append(newRule)
463
+
464
+ return newRules
465
+
466
+
467
+ def _filterLocation(
468
+ userRegion: Region,
469
+ location: Dict[str, float],
470
+ ) -> Dict[str, float]:
471
+ return {
472
+ name: value
473
+ for name, value in location.items()
474
+ if name in userRegion and isinstance(userRegion[name], Range)
475
+ }