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,5 @@
1
+ import sys
2
+ from fontTools.varLib.instancer import main
3
+
4
+ if __name__ == "__main__":
5
+ sys.exit(main())
@@ -0,0 +1,190 @@
1
+ from fontTools.ttLib.tables import otTables as ot
2
+ from copy import deepcopy
3
+ import logging
4
+
5
+
6
+ log = logging.getLogger("fontTools.varLib.instancer")
7
+
8
+
9
+ def _featureVariationRecordIsUnique(rec, seen):
10
+ conditionSet = []
11
+ conditionSets = (
12
+ rec.ConditionSet.ConditionTable if rec.ConditionSet is not None else []
13
+ )
14
+ for cond in conditionSets:
15
+ if cond.Format != 1:
16
+ # can't tell whether this is duplicate, assume is unique
17
+ return True
18
+ conditionSet.append(
19
+ (cond.AxisIndex, cond.FilterRangeMinValue, cond.FilterRangeMaxValue)
20
+ )
21
+ # besides the set of conditions, we also include the FeatureTableSubstitution
22
+ # version to identify unique FeatureVariationRecords, even though only one
23
+ # version is currently defined. It's theoretically possible that multiple
24
+ # records with same conditions but different substitution table version be
25
+ # present in the same font for backward compatibility.
26
+ recordKey = frozenset([rec.FeatureTableSubstitution.Version] + conditionSet)
27
+ if recordKey in seen:
28
+ return False
29
+ else:
30
+ seen.add(recordKey) # side effect
31
+ return True
32
+
33
+
34
+ def _limitFeatureVariationConditionRange(condition, axisLimit):
35
+ minValue = condition.FilterRangeMinValue
36
+ maxValue = condition.FilterRangeMaxValue
37
+
38
+ if (
39
+ minValue > maxValue
40
+ or minValue > axisLimit.maximum
41
+ or maxValue < axisLimit.minimum
42
+ ):
43
+ # condition invalid or out of range
44
+ return
45
+
46
+ return tuple(
47
+ axisLimit.renormalizeValue(v, extrapolate=False) for v in (minValue, maxValue)
48
+ )
49
+
50
+
51
+ def _instantiateFeatureVariationRecord(
52
+ record, recIdx, axisLimits, fvarAxes, axisIndexMap
53
+ ):
54
+ applies = True
55
+ shouldKeep = False
56
+ newConditions = []
57
+ from fontTools.varLib.instancer import NormalizedAxisTripleAndDistances
58
+
59
+ default_triple = NormalizedAxisTripleAndDistances(-1, 0, +1)
60
+ if record.ConditionSet is None:
61
+ record.ConditionSet = ot.ConditionSet()
62
+ record.ConditionSet.ConditionTable = []
63
+ record.ConditionSet.ConditionCount = 0
64
+ for i, condition in enumerate(record.ConditionSet.ConditionTable):
65
+ if condition.Format == 1:
66
+ axisIdx = condition.AxisIndex
67
+ axisTag = fvarAxes[axisIdx].axisTag
68
+
69
+ minValue = condition.FilterRangeMinValue
70
+ maxValue = condition.FilterRangeMaxValue
71
+ triple = axisLimits.get(axisTag, default_triple)
72
+
73
+ if not (minValue <= triple.default <= maxValue):
74
+ applies = False
75
+
76
+ # if condition not met, remove entire record
77
+ if triple.minimum > maxValue or triple.maximum < minValue:
78
+ newConditions = None
79
+ break
80
+
81
+ if axisTag in axisIndexMap:
82
+ # remap axis index
83
+ condition.AxisIndex = axisIndexMap[axisTag]
84
+
85
+ # remap condition limits
86
+ newRange = _limitFeatureVariationConditionRange(condition, triple)
87
+ if newRange:
88
+ # keep condition with updated limits
89
+ minimum, maximum = newRange
90
+ condition.FilterRangeMinValue = minimum
91
+ condition.FilterRangeMaxValue = maximum
92
+ shouldKeep = True
93
+ if minimum != -1 or maximum != +1:
94
+ newConditions.append(condition)
95
+ else:
96
+ # condition out of range, remove entire record
97
+ newConditions = None
98
+ break
99
+
100
+ else:
101
+ log.warning(
102
+ "Condition table {0} of FeatureVariationRecord {1} has "
103
+ "unsupported format ({2}); ignored".format(i, recIdx, condition.Format)
104
+ )
105
+ applies = False
106
+ newConditions.append(condition)
107
+
108
+ if newConditions is not None and shouldKeep:
109
+ record.ConditionSet.ConditionTable = newConditions
110
+ if not newConditions:
111
+ record.ConditionSet = None
112
+ shouldKeep = True
113
+ else:
114
+ shouldKeep = False
115
+
116
+ # Does this *always* apply?
117
+ universal = shouldKeep and not newConditions
118
+
119
+ return applies, shouldKeep, universal
120
+
121
+
122
+ def _instantiateFeatureVariations(table, fvarAxes, axisLimits):
123
+ pinnedAxes = set(axisLimits.pinnedLocation())
124
+ axisOrder = [axis.axisTag for axis in fvarAxes if axis.axisTag not in pinnedAxes]
125
+ axisIndexMap = {axisTag: axisOrder.index(axisTag) for axisTag in axisOrder}
126
+
127
+ featureVariationApplied = False
128
+ uniqueRecords = set()
129
+ newRecords = []
130
+ defaultsSubsts = None
131
+
132
+ for i, record in enumerate(table.FeatureVariations.FeatureVariationRecord):
133
+ applies, shouldKeep, universal = _instantiateFeatureVariationRecord(
134
+ record, i, axisLimits, fvarAxes, axisIndexMap
135
+ )
136
+
137
+ if shouldKeep and _featureVariationRecordIsUnique(record, uniqueRecords):
138
+ newRecords.append(record)
139
+
140
+ if applies and not featureVariationApplied:
141
+ assert record.FeatureTableSubstitution.Version == 0x00010000
142
+ defaultsSubsts = deepcopy(record.FeatureTableSubstitution)
143
+ for default, rec in zip(
144
+ defaultsSubsts.SubstitutionRecord,
145
+ record.FeatureTableSubstitution.SubstitutionRecord,
146
+ ):
147
+ default.Feature = deepcopy(
148
+ table.FeatureList.FeatureRecord[rec.FeatureIndex].Feature
149
+ )
150
+ table.FeatureList.FeatureRecord[rec.FeatureIndex].Feature = deepcopy(
151
+ rec.Feature
152
+ )
153
+ # Set variations only once
154
+ featureVariationApplied = True
155
+
156
+ # Further records don't have a chance to apply after a universal record
157
+ if universal:
158
+ break
159
+
160
+ # Insert a catch-all record to reinstate the old features if necessary
161
+ if featureVariationApplied and newRecords and not universal:
162
+ defaultRecord = ot.FeatureVariationRecord()
163
+ defaultRecord.ConditionSet = ot.ConditionSet()
164
+ defaultRecord.ConditionSet.ConditionTable = []
165
+ defaultRecord.ConditionSet.ConditionCount = 0
166
+ defaultRecord.FeatureTableSubstitution = defaultsSubsts
167
+
168
+ newRecords.append(defaultRecord)
169
+
170
+ if newRecords:
171
+ table.FeatureVariations.FeatureVariationRecord = newRecords
172
+ table.FeatureVariations.FeatureVariationCount = len(newRecords)
173
+ else:
174
+ del table.FeatureVariations
175
+ # downgrade table version if there are no FeatureVariations left
176
+ table.Version = 0x00010000
177
+
178
+
179
+ def instantiateFeatureVariations(varfont, axisLimits):
180
+ for tableTag in ("GPOS", "GSUB"):
181
+ if tableTag not in varfont or not getattr(
182
+ varfont[tableTag].table, "FeatureVariations", None
183
+ ):
184
+ continue
185
+ log.info("Instantiating FeatureVariations of %s table", tableTag)
186
+ _instantiateFeatureVariations(
187
+ varfont[tableTag].table, varfont["fvar"].axes, axisLimits
188
+ )
189
+ # remove unreferenced lookups
190
+ varfont[tableTag].prune_lookups()
@@ -0,0 +1,388 @@
1
+ """Helpers for instantiating name table records."""
2
+
3
+ from contextlib import contextmanager
4
+ from copy import deepcopy
5
+ from enum import IntEnum
6
+ import re
7
+
8
+
9
+ class NameID(IntEnum):
10
+ FAMILY_NAME = 1
11
+ SUBFAMILY_NAME = 2
12
+ UNIQUE_FONT_IDENTIFIER = 3
13
+ FULL_FONT_NAME = 4
14
+ VERSION_STRING = 5
15
+ POSTSCRIPT_NAME = 6
16
+ TYPOGRAPHIC_FAMILY_NAME = 16
17
+ TYPOGRAPHIC_SUBFAMILY_NAME = 17
18
+ VARIATIONS_POSTSCRIPT_NAME_PREFIX = 25
19
+
20
+
21
+ ELIDABLE_AXIS_VALUE_NAME = 2
22
+
23
+
24
+ def getVariationNameIDs(varfont):
25
+ used = []
26
+ if "fvar" in varfont:
27
+ fvar = varfont["fvar"]
28
+ for axis in fvar.axes:
29
+ used.append(axis.axisNameID)
30
+ for instance in fvar.instances:
31
+ used.append(instance.subfamilyNameID)
32
+ if instance.postscriptNameID != 0xFFFF:
33
+ used.append(instance.postscriptNameID)
34
+ if "STAT" in varfont:
35
+ stat = varfont["STAT"].table
36
+ for axis in stat.DesignAxisRecord.Axis if stat.DesignAxisRecord else ():
37
+ used.append(axis.AxisNameID)
38
+ for value in stat.AxisValueArray.AxisValue if stat.AxisValueArray else ():
39
+ used.append(value.ValueNameID)
40
+ elidedFallbackNameID = getattr(stat, "ElidedFallbackNameID", None)
41
+ if elidedFallbackNameID is not None:
42
+ used.append(elidedFallbackNameID)
43
+ # nameIDs <= 255 are reserved by OT spec so we don't touch them
44
+ return {nameID for nameID in used if nameID > 255}
45
+
46
+
47
+ @contextmanager
48
+ def pruningUnusedNames(varfont):
49
+ from . import log
50
+
51
+ origNameIDs = getVariationNameIDs(varfont)
52
+
53
+ yield
54
+
55
+ log.info("Pruning name table")
56
+ exclude = origNameIDs - getVariationNameIDs(varfont)
57
+ varfont["name"].names[:] = [
58
+ record for record in varfont["name"].names if record.nameID not in exclude
59
+ ]
60
+ if "ltag" in varfont:
61
+ # Drop the whole 'ltag' table if all the language-dependent Unicode name
62
+ # records that reference it have been dropped.
63
+ # TODO: Only prune unused ltag tags, renumerating langIDs accordingly.
64
+ # Note ltag can also be used by feat or morx tables, so check those too.
65
+ if not any(
66
+ record
67
+ for record in varfont["name"].names
68
+ if record.platformID == 0 and record.langID != 0xFFFF
69
+ ):
70
+ del varfont["ltag"]
71
+
72
+
73
+ def updateNameTable(varfont, axisLimits):
74
+ """Update instatiated variable font's name table using STAT AxisValues.
75
+
76
+ Raises ValueError if the STAT table is missing or an Axis Value table is
77
+ missing for requested axis locations.
78
+
79
+ First, collect all STAT AxisValues that match the new default axis locations
80
+ (excluding "elided" ones); concatenate the strings in design axis order,
81
+ while giving priority to "synthetic" values (Format 4), to form the
82
+ typographic subfamily name associated with the new default instance.
83
+ Finally, update all related records in the name table, making sure that
84
+ legacy family/sub-family names conform to the the R/I/B/BI (Regular, Italic,
85
+ Bold, Bold Italic) naming model.
86
+
87
+ Example: Updating a partial variable font:
88
+ | >>> ttFont = TTFont("OpenSans[wdth,wght].ttf")
89
+ | >>> updateNameTable(ttFont, {"wght": (400, 900), "wdth": 75})
90
+
91
+ The name table records will be updated in the following manner:
92
+ NameID 1 familyName: "Open Sans" --> "Open Sans Condensed"
93
+ NameID 2 subFamilyName: "Regular" --> "Regular"
94
+ NameID 3 Unique font identifier: "3.000;GOOG;OpenSans-Regular" --> \
95
+ "3.000;GOOG;OpenSans-Condensed"
96
+ NameID 4 Full font name: "Open Sans Regular" --> "Open Sans Condensed"
97
+ NameID 6 PostScript name: "OpenSans-Regular" --> "OpenSans-Condensed"
98
+ NameID 16 Typographic Family name: None --> "Open Sans"
99
+ NameID 17 Typographic Subfamily name: None --> "Condensed"
100
+
101
+ References:
102
+ https://docs.microsoft.com/en-us/typography/opentype/spec/stat
103
+ https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
104
+ """
105
+ from . import AxisLimits, axisValuesFromAxisLimits
106
+
107
+ if "STAT" not in varfont:
108
+ raise ValueError("Cannot update name table since there is no STAT table.")
109
+ stat = varfont["STAT"].table
110
+ if not stat.AxisValueArray:
111
+ raise ValueError("Cannot update name table since there are no STAT Axis Values")
112
+ fvar = varfont["fvar"]
113
+
114
+ # The updated name table will reflect the new 'zero origin' of the font.
115
+ # If we're instantiating a partial font, we will populate the unpinned
116
+ # axes with their default axis values from fvar.
117
+ axisLimits = AxisLimits(axisLimits).limitAxesAndPopulateDefaults(varfont)
118
+ partialDefaults = axisLimits.defaultLocation()
119
+ fvarDefaults = {a.axisTag: a.defaultValue for a in fvar.axes}
120
+ defaultAxisCoords = AxisLimits({**fvarDefaults, **partialDefaults})
121
+ assert all(v.minimum == v.maximum for v in defaultAxisCoords.values())
122
+
123
+ axisValueTables = axisValuesFromAxisLimits(stat, defaultAxisCoords)
124
+ checkAxisValuesExist(stat, axisValueTables, defaultAxisCoords.pinnedLocation())
125
+
126
+ # ignore "elidable" axis values, should be omitted in application font menus.
127
+ axisValueTables = [
128
+ v for v in axisValueTables if not v.Flags & ELIDABLE_AXIS_VALUE_NAME
129
+ ]
130
+ axisValueTables = _sortAxisValues(axisValueTables)
131
+ _updateNameRecords(varfont, axisValueTables)
132
+
133
+
134
+ def checkAxisValuesExist(stat, axisValues, axisCoords):
135
+ seen = set()
136
+ designAxes = stat.DesignAxisRecord.Axis
137
+ hasValues = set()
138
+ for value in stat.AxisValueArray.AxisValue:
139
+ if value.Format in (1, 2, 3):
140
+ hasValues.add(designAxes[value.AxisIndex].AxisTag)
141
+ elif value.Format == 4:
142
+ for rec in value.AxisValueRecord:
143
+ hasValues.add(designAxes[rec.AxisIndex].AxisTag)
144
+
145
+ for axisValueTable in axisValues:
146
+ axisValueFormat = axisValueTable.Format
147
+ if axisValueTable.Format in (1, 2, 3):
148
+ axisTag = designAxes[axisValueTable.AxisIndex].AxisTag
149
+ if axisValueFormat == 2:
150
+ axisValue = axisValueTable.NominalValue
151
+ else:
152
+ axisValue = axisValueTable.Value
153
+ if axisTag in axisCoords and axisValue == axisCoords[axisTag]:
154
+ seen.add(axisTag)
155
+ elif axisValueTable.Format == 4:
156
+ for rec in axisValueTable.AxisValueRecord:
157
+ axisTag = designAxes[rec.AxisIndex].AxisTag
158
+ if axisTag in axisCoords and rec.Value == axisCoords[axisTag]:
159
+ seen.add(axisTag)
160
+
161
+ missingAxes = (set(axisCoords) - seen) & hasValues
162
+ if missingAxes:
163
+ missing = ", ".join(f"'{i}': {axisCoords[i]}" for i in missingAxes)
164
+ raise ValueError(f"Cannot find Axis Values {{{missing}}}")
165
+
166
+
167
+ def _sortAxisValues(axisValues):
168
+ # Sort by axis index, remove duplicates and ensure that format 4 AxisValues
169
+ # are dominant.
170
+ # The MS Spec states: "if a format 1, format 2 or format 3 table has a
171
+ # (nominal) value used in a format 4 table that also has values for
172
+ # other axes, the format 4 table, being the more specific match, is used",
173
+ # https://docs.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-4
174
+ results = []
175
+ seenAxes = set()
176
+ # Sort format 4 axes so the tables with the most AxisValueRecords are first
177
+ format4 = sorted(
178
+ [v for v in axisValues if v.Format == 4],
179
+ key=lambda v: len(v.AxisValueRecord),
180
+ reverse=True,
181
+ )
182
+
183
+ for val in format4:
184
+ axisIndexes = set(r.AxisIndex for r in val.AxisValueRecord)
185
+ minIndex = min(axisIndexes)
186
+ if not seenAxes & axisIndexes:
187
+ seenAxes |= axisIndexes
188
+ results.append((minIndex, val))
189
+
190
+ for val in axisValues:
191
+ if val in format4:
192
+ continue
193
+ axisIndex = val.AxisIndex
194
+ if axisIndex not in seenAxes:
195
+ seenAxes.add(axisIndex)
196
+ results.append((axisIndex, val))
197
+
198
+ return [axisValue for _, axisValue in sorted(results)]
199
+
200
+
201
+ def _updateNameRecords(varfont, axisValues):
202
+ # Update nametable based on the axisValues using the R/I/B/BI model.
203
+ nametable = varfont["name"]
204
+ stat = varfont["STAT"].table
205
+
206
+ axisValueNameIDs = [a.ValueNameID for a in axisValues]
207
+ ribbiNameIDs = [n for n in axisValueNameIDs if _isRibbi(nametable, n)]
208
+ nonRibbiNameIDs = [n for n in axisValueNameIDs if n not in ribbiNameIDs]
209
+ elidedNameID = stat.ElidedFallbackNameID
210
+ elidedNameIsRibbi = _isRibbi(nametable, elidedNameID)
211
+
212
+ getName = nametable.getName
213
+ platforms = set((r.platformID, r.platEncID, r.langID) for r in nametable.names)
214
+ for platform in platforms:
215
+ if not all(getName(i, *platform) for i in (1, 2, elidedNameID)):
216
+ # Since no family name and subfamily name records were found,
217
+ # we cannot update this set of name Records.
218
+ continue
219
+
220
+ subFamilyName = " ".join(
221
+ getName(n, *platform).toUnicode() for n in ribbiNameIDs
222
+ )
223
+ if nonRibbiNameIDs:
224
+ typoSubFamilyName = " ".join(
225
+ getName(n, *platform).toUnicode() for n in axisValueNameIDs
226
+ )
227
+ else:
228
+ typoSubFamilyName = None
229
+
230
+ # If neither subFamilyName and typographic SubFamilyName exist,
231
+ # we will use the STAT's elidedFallbackName
232
+ if not typoSubFamilyName and not subFamilyName:
233
+ if elidedNameIsRibbi:
234
+ subFamilyName = getName(elidedNameID, *platform).toUnicode()
235
+ else:
236
+ typoSubFamilyName = getName(elidedNameID, *platform).toUnicode()
237
+
238
+ familyNameSuffix = " ".join(
239
+ getName(n, *platform).toUnicode() for n in nonRibbiNameIDs
240
+ )
241
+
242
+ _updateNameTableStyleRecords(
243
+ varfont,
244
+ familyNameSuffix,
245
+ subFamilyName,
246
+ typoSubFamilyName,
247
+ *platform,
248
+ )
249
+
250
+
251
+ def _isRibbi(nametable, nameID):
252
+ englishRecord = nametable.getName(nameID, 3, 1, 0x409)
253
+ return (
254
+ True
255
+ if englishRecord is not None
256
+ and englishRecord.toUnicode() in ("Regular", "Italic", "Bold", "Bold Italic")
257
+ else False
258
+ )
259
+
260
+
261
+ def _updateNameTableStyleRecords(
262
+ varfont,
263
+ familyNameSuffix,
264
+ subFamilyName,
265
+ typoSubFamilyName,
266
+ platformID=3,
267
+ platEncID=1,
268
+ langID=0x409,
269
+ ):
270
+ # TODO (Marc F) It may be nice to make this part a standalone
271
+ # font renamer in the future.
272
+ nametable = varfont["name"]
273
+ platform = (platformID, platEncID, langID)
274
+
275
+ currentFamilyName = nametable.getName(
276
+ NameID.TYPOGRAPHIC_FAMILY_NAME, *platform
277
+ ) or nametable.getName(NameID.FAMILY_NAME, *platform)
278
+
279
+ currentStyleName = nametable.getName(
280
+ NameID.TYPOGRAPHIC_SUBFAMILY_NAME, *platform
281
+ ) or nametable.getName(NameID.SUBFAMILY_NAME, *platform)
282
+
283
+ if not all([currentFamilyName, currentStyleName]):
284
+ raise ValueError(f"Missing required NameIDs 1 and 2 for platform {platform}")
285
+
286
+ currentFamilyName = currentFamilyName.toUnicode()
287
+ currentStyleName = currentStyleName.toUnicode()
288
+
289
+ nameIDs = {
290
+ NameID.FAMILY_NAME: currentFamilyName,
291
+ NameID.SUBFAMILY_NAME: subFamilyName or "Regular",
292
+ }
293
+ if typoSubFamilyName:
294
+ nameIDs[NameID.FAMILY_NAME] = f"{currentFamilyName} {familyNameSuffix}".strip()
295
+ nameIDs[NameID.TYPOGRAPHIC_FAMILY_NAME] = currentFamilyName
296
+ nameIDs[NameID.TYPOGRAPHIC_SUBFAMILY_NAME] = typoSubFamilyName
297
+ else:
298
+ # Remove previous Typographic Family and SubFamily names since they're
299
+ # no longer required
300
+ for nameID in (
301
+ NameID.TYPOGRAPHIC_FAMILY_NAME,
302
+ NameID.TYPOGRAPHIC_SUBFAMILY_NAME,
303
+ ):
304
+ nametable.removeNames(nameID=nameID)
305
+
306
+ newFamilyName = (
307
+ nameIDs.get(NameID.TYPOGRAPHIC_FAMILY_NAME) or nameIDs[NameID.FAMILY_NAME]
308
+ )
309
+ newStyleName = (
310
+ nameIDs.get(NameID.TYPOGRAPHIC_SUBFAMILY_NAME) or nameIDs[NameID.SUBFAMILY_NAME]
311
+ )
312
+
313
+ nameIDs[NameID.FULL_FONT_NAME] = f"{newFamilyName} {newStyleName}"
314
+ nameIDs[NameID.POSTSCRIPT_NAME] = _updatePSNameRecord(
315
+ varfont, newFamilyName, newStyleName, platform
316
+ )
317
+
318
+ uniqueID = _updateUniqueIdNameRecord(varfont, nameIDs, platform)
319
+ if uniqueID:
320
+ nameIDs[NameID.UNIQUE_FONT_IDENTIFIER] = uniqueID
321
+
322
+ for nameID, string in nameIDs.items():
323
+ assert string, nameID
324
+ nametable.setName(string, nameID, *platform)
325
+
326
+ if "fvar" not in varfont:
327
+ nametable.removeNames(NameID.VARIATIONS_POSTSCRIPT_NAME_PREFIX)
328
+
329
+
330
+ def _updatePSNameRecord(varfont, familyName, styleName, platform):
331
+ # Implementation based on Adobe Technical Note #5902 :
332
+ # https://wwwimages2.adobe.com/content/dam/acom/en/devnet/font/pdfs/5902.AdobePSNameGeneration.pdf
333
+ nametable = varfont["name"]
334
+
335
+ family_prefix = nametable.getName(
336
+ NameID.VARIATIONS_POSTSCRIPT_NAME_PREFIX, *platform
337
+ )
338
+ if family_prefix:
339
+ family_prefix = family_prefix.toUnicode()
340
+ else:
341
+ family_prefix = familyName
342
+
343
+ psName = f"{family_prefix}-{styleName}"
344
+ # Remove any characters other than uppercase Latin letters, lowercase
345
+ # Latin letters, digits and hyphens.
346
+ psName = re.sub(r"[^A-Za-z0-9-]", r"", psName)
347
+
348
+ if len(psName) > 127:
349
+ # Abbreviating the stylename so it fits within 127 characters whilst
350
+ # conforming to every vendor's specification is too complex. Instead
351
+ # we simply truncate the psname and add the required "..."
352
+ return f"{psName[:124]}..."
353
+ return psName
354
+
355
+
356
+ def _updateUniqueIdNameRecord(varfont, nameIDs, platform):
357
+ nametable = varfont["name"]
358
+ currentRecord = nametable.getName(NameID.UNIQUE_FONT_IDENTIFIER, *platform)
359
+ if not currentRecord:
360
+ return None
361
+
362
+ # Check if full name and postscript name are a substring of currentRecord
363
+ for nameID in (NameID.FULL_FONT_NAME, NameID.POSTSCRIPT_NAME):
364
+ nameRecord = nametable.getName(nameID, *platform)
365
+ if not nameRecord:
366
+ continue
367
+ if nameRecord.toUnicode() in currentRecord.toUnicode():
368
+ return currentRecord.toUnicode().replace(
369
+ nameRecord.toUnicode(), nameIDs[nameRecord.nameID]
370
+ )
371
+
372
+ # Create a new string since we couldn't find any substrings.
373
+ fontVersion = _fontVersion(varfont, platform)
374
+ achVendID = varfont["OS/2"].achVendID
375
+ # Remove non-ASCII characers and trailing spaces
376
+ vendor = re.sub(r"[^\x00-\x7F]", "", achVendID).strip()
377
+ psName = nameIDs[NameID.POSTSCRIPT_NAME]
378
+ return f"{fontVersion};{vendor};{psName}"
379
+
380
+
381
+ def _fontVersion(font, platform=(3, 1, 0x409)):
382
+ nameRecord = font["name"].getName(NameID.VERSION_STRING, *platform)
383
+ if nameRecord is None:
384
+ return f'{font["head"].fontRevision:.3f}'
385
+ # "Version 1.101; ttfautohint (v1.8.1.43-b0c9)" --> "1.101"
386
+ # Also works fine with inputs "Version 1.101" or "1.101" etc
387
+ versionNumber = nameRecord.toUnicode().split(";")[0]
388
+ return versionNumber.lstrip("Version ").strip()