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
+ """fontTools.voltLib -- a package for dealing with Visual OpenType Layout Tool
2
+ (VOLT) files."""
3
+
4
+ # See
5
+ # http://www.microsoft.com/typography/VOLT.mspx
@@ -0,0 +1,206 @@
1
+ import argparse
2
+ import logging
3
+ import sys
4
+ from io import StringIO
5
+ from pathlib import Path
6
+
7
+ from fontTools import configLogger
8
+ from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
9
+ from fontTools.feaLib.error import FeatureLibError
10
+ from fontTools.feaLib.lexer import Lexer
11
+ from fontTools.misc.cliTools import makeOutputFileName
12
+ from fontTools.ttLib import TTFont, TTLibError
13
+ from fontTools.voltLib.parser import Parser
14
+ from fontTools.voltLib.voltToFea import TABLES, VoltToFea
15
+
16
+ log = logging.getLogger("fontTools.feaLib")
17
+
18
+ SUPPORTED_TABLES = TABLES + ["cmap"]
19
+
20
+
21
+ def invalid_fea_glyph_name(name):
22
+ """Check if the glyph name is valid according to FEA syntax."""
23
+ if name[0] not in Lexer.CHAR_NAME_START_:
24
+ return True
25
+ if any(c not in Lexer.CHAR_NAME_CONTINUATION_ for c in name[1:]):
26
+ return True
27
+ return False
28
+
29
+
30
+ def sanitize_glyph_name(name):
31
+ """Sanitize the glyph name to ensure it is valid according to FEA syntax."""
32
+ sanitized = ""
33
+ for i, c in enumerate(name):
34
+ if i == 0 and c not in Lexer.CHAR_NAME_START_:
35
+ sanitized += "a" + c
36
+ elif c not in Lexer.CHAR_NAME_CONTINUATION_:
37
+ sanitized += "_"
38
+ else:
39
+ sanitized += c
40
+
41
+ return sanitized
42
+
43
+
44
+ def main(args=None):
45
+ """Build tables from a MS VOLT project into an OTF font"""
46
+ parser = argparse.ArgumentParser(
47
+ description="Use fontTools to compile MS VOLT projects."
48
+ )
49
+ parser.add_argument(
50
+ "input",
51
+ metavar="INPUT",
52
+ help="Path to the input font/VTP file to process",
53
+ type=Path,
54
+ )
55
+ parser.add_argument(
56
+ "-f",
57
+ "--font",
58
+ metavar="INPUT_FONT",
59
+ help="Path to the input font (if INPUT is a VTP file)",
60
+ type=Path,
61
+ )
62
+ parser.add_argument(
63
+ "-o",
64
+ "--output",
65
+ dest="output",
66
+ metavar="OUTPUT",
67
+ help="Path to the output font.",
68
+ type=Path,
69
+ )
70
+ parser.add_argument(
71
+ "-t",
72
+ "--tables",
73
+ metavar="TABLE_TAG",
74
+ choices=SUPPORTED_TABLES,
75
+ nargs="+",
76
+ help="Specify the table(s) to be built.",
77
+ )
78
+ parser.add_argument(
79
+ "-F",
80
+ "--debug-feature-file",
81
+ help="Write the generated feature file to disk.",
82
+ action="store_true",
83
+ )
84
+ parser.add_argument(
85
+ "--ship",
86
+ help="Remove source VOLT tables from output font.",
87
+ action="store_true",
88
+ )
89
+ parser.add_argument(
90
+ "-v",
91
+ "--verbose",
92
+ help="Increase the logger verbosity. Multiple -v options are allowed.",
93
+ action="count",
94
+ default=0,
95
+ )
96
+ parser.add_argument(
97
+ "-T",
98
+ "--traceback",
99
+ help="show traceback for exceptions.",
100
+ action="store_true",
101
+ )
102
+ options = parser.parse_args(args)
103
+
104
+ levels = ["WARNING", "INFO", "DEBUG"]
105
+ configLogger(level=levels[min(len(levels) - 1, options.verbose)])
106
+
107
+ output_font = options.output or Path(
108
+ makeOutputFileName(options.font or options.input)
109
+ )
110
+ log.info(f"Compiling MS VOLT to '{output_font}'")
111
+
112
+ file_or_path = options.input
113
+ font = None
114
+
115
+ # If the input is a font file, extract the VOLT data from the "TSIV" table
116
+ try:
117
+ font = TTFont(file_or_path)
118
+ if "TSIV" in font:
119
+ file_or_path = StringIO(font["TSIV"].data.decode("utf-8"))
120
+ else:
121
+ log.error('"TSIV" table is missing')
122
+ return 1
123
+ except TTLibError:
124
+ pass
125
+
126
+ # If input is not a font file, the font must be provided
127
+ if font is None:
128
+ if not options.font:
129
+ log.error("Please provide an input font")
130
+ return 1
131
+ font = TTFont(options.font)
132
+
133
+ # FEA syntax does not allow some glyph names that VOLT accepts, so if we
134
+ # found such glyph name we will temporarily rename such glyphs.
135
+ glyphOrder = font.getGlyphOrder()
136
+ tempGlyphOrder = None
137
+ if any(invalid_fea_glyph_name(n) for n in glyphOrder):
138
+ tempGlyphOrder = []
139
+ for n in glyphOrder:
140
+ if invalid_fea_glyph_name(n):
141
+ n = sanitize_glyph_name(n)
142
+ existing = set(tempGlyphOrder) | set(glyphOrder)
143
+ while n in existing:
144
+ n = "a" + n
145
+ tempGlyphOrder.append(n)
146
+ font.setGlyphOrder(tempGlyphOrder)
147
+
148
+ doc = Parser(file_or_path).parse()
149
+
150
+ log.info("Converting VTP data to FEA")
151
+ converter = VoltToFea(doc, font)
152
+ try:
153
+ fea = converter.convert(options.tables, ignore_unsupported_settings=True)
154
+ except NotImplementedError as e:
155
+ if options.traceback:
156
+ raise
157
+ location = getattr(e.args[0], "location", None)
158
+ message = f'"{e}" is not supported'
159
+ if location:
160
+ path, line, column = location
161
+ log.error(f"{path}:{line}:{column}: {message}")
162
+ else:
163
+ log.error(message)
164
+ return 1
165
+
166
+ fea_filename = options.input
167
+ if options.debug_feature_file:
168
+ fea_filename = output_font.with_suffix(".fea")
169
+ log.info(f"Writing FEA to '{fea_filename}'")
170
+ with open(fea_filename, "w") as fp:
171
+ fp.write(fea)
172
+
173
+ log.info("Compiling FEA to OpenType tables")
174
+ try:
175
+ addOpenTypeFeaturesFromString(
176
+ font,
177
+ fea,
178
+ filename=fea_filename,
179
+ tables=options.tables,
180
+ )
181
+ except FeatureLibError as e:
182
+ if options.traceback:
183
+ raise
184
+ log.error(e)
185
+ return 1
186
+
187
+ if options.ship:
188
+ for tag in ["TSIV", "TSIS", "TSIP", "TSID"]:
189
+ if tag in font:
190
+ del font[tag]
191
+
192
+ # Restore original glyph names.
193
+ if tempGlyphOrder:
194
+ import io
195
+
196
+ f = io.BytesIO()
197
+ font.save(f)
198
+ font = TTFont(f)
199
+ font.setGlyphOrder(glyphOrder)
200
+ font["post"].extraNames = []
201
+
202
+ font.save(output_font)
203
+
204
+
205
+ if __name__ == "__main__":
206
+ sys.exit(main())
@@ -0,0 +1,452 @@
1
+ from fontTools.voltLib.error import VoltLibError
2
+ from typing import NamedTuple
3
+
4
+
5
+ class Pos(NamedTuple):
6
+ adv: int
7
+ dx: int
8
+ dy: int
9
+ adv_adjust_by: dict
10
+ dx_adjust_by: dict
11
+ dy_adjust_by: dict
12
+
13
+ def __str__(self):
14
+ res = " POS"
15
+ for attr in ("adv", "dx", "dy"):
16
+ value = getattr(self, attr)
17
+ if value is not None:
18
+ res += f" {attr.upper()} {value}"
19
+ adjust_by = getattr(self, f"{attr}_adjust_by", {})
20
+ for size, adjustment in adjust_by.items():
21
+ res += f" ADJUST_BY {adjustment} AT {size}"
22
+ res += " END_POS"
23
+ return res
24
+
25
+
26
+ class Element(object):
27
+ def __init__(self, location=None):
28
+ self.location = location
29
+
30
+ def build(self, builder):
31
+ pass
32
+
33
+ def __str__(self):
34
+ raise NotImplementedError
35
+
36
+
37
+ class Statement(Element):
38
+ pass
39
+
40
+
41
+ class Expression(Element):
42
+ pass
43
+
44
+
45
+ class VoltFile(Statement):
46
+ def __init__(self):
47
+ Statement.__init__(self, location=None)
48
+ self.statements = []
49
+
50
+ def build(self, builder):
51
+ for s in self.statements:
52
+ s.build(builder)
53
+
54
+ def __str__(self):
55
+ return "\n" + "\n".join(str(s) for s in self.statements) + " END\n"
56
+
57
+
58
+ class GlyphDefinition(Statement):
59
+ def __init__(self, name, gid, gunicode, gtype, components, location=None):
60
+ Statement.__init__(self, location)
61
+ self.name = name
62
+ self.id = gid
63
+ self.unicode = gunicode
64
+ self.type = gtype
65
+ self.components = components
66
+
67
+ def __str__(self):
68
+ res = f'DEF_GLYPH "{self.name}" ID {self.id}'
69
+ if self.unicode is not None:
70
+ if len(self.unicode) > 1:
71
+ unicodes = ",".join(f"U+{u:04X}" for u in self.unicode)
72
+ res += f' UNICODEVALUES "{unicodes}"'
73
+ else:
74
+ res += f" UNICODE {self.unicode[0]}"
75
+ if self.type is not None:
76
+ res += f" TYPE {self.type}"
77
+ if self.components is not None:
78
+ res += f" COMPONENTS {self.components}"
79
+ res += " END_GLYPH"
80
+ return res
81
+
82
+
83
+ class GroupDefinition(Statement):
84
+ def __init__(self, name, enum, location=None):
85
+ Statement.__init__(self, location)
86
+ self.name = name
87
+ self.enum = enum
88
+ self.glyphs_ = None
89
+
90
+ def glyphSet(self, groups=None):
91
+ if groups is not None and self.name in groups:
92
+ raise VoltLibError(
93
+ 'Group "%s" contains itself.' % (self.name), self.location
94
+ )
95
+ if self.glyphs_ is None:
96
+ if groups is None:
97
+ groups = set({self.name})
98
+ else:
99
+ groups.add(self.name)
100
+ self.glyphs_ = self.enum.glyphSet(groups)
101
+ return self.glyphs_
102
+
103
+ def __str__(self):
104
+ enum = self.enum and str(self.enum) or ""
105
+ return f'DEF_GROUP "{self.name}"\n{enum}\nEND_GROUP'
106
+
107
+
108
+ class GlyphName(Expression):
109
+ """A single glyph name, such as cedilla."""
110
+
111
+ def __init__(self, glyph, location=None):
112
+ Expression.__init__(self, location)
113
+ self.glyph = glyph
114
+
115
+ def glyphSet(self):
116
+ return (self.glyph,)
117
+
118
+ def __str__(self):
119
+ return f' GLYPH "{self.glyph}"'
120
+
121
+
122
+ class Enum(Expression):
123
+ """An enum"""
124
+
125
+ def __init__(self, enum, location=None):
126
+ Expression.__init__(self, location)
127
+ self.enum = enum
128
+
129
+ def __iter__(self):
130
+ for e in self.glyphSet():
131
+ yield e
132
+
133
+ def glyphSet(self, groups=None):
134
+ glyphs = []
135
+ for element in self.enum:
136
+ if isinstance(element, (GroupName, Enum)):
137
+ glyphs.extend(element.glyphSet(groups))
138
+ else:
139
+ glyphs.extend(element.glyphSet())
140
+ return tuple(glyphs)
141
+
142
+ def __str__(self):
143
+ enum = "".join(str(e) for e in self.enum)
144
+ return f" ENUM{enum} END_ENUM"
145
+
146
+
147
+ class GroupName(Expression):
148
+ """A glyph group"""
149
+
150
+ def __init__(self, group, parser, location=None):
151
+ Expression.__init__(self, location)
152
+ self.group = group
153
+ self.parser_ = parser
154
+
155
+ def glyphSet(self, groups=None):
156
+ group = self.parser_.resolve_group(self.group)
157
+ if group is not None:
158
+ self.glyphs_ = group.glyphSet(groups)
159
+ return self.glyphs_
160
+ else:
161
+ raise VoltLibError(
162
+ 'Group "%s" is used but undefined.' % (self.group), self.location
163
+ )
164
+
165
+ def __str__(self):
166
+ return f' GROUP "{self.group}"'
167
+
168
+
169
+ class Range(Expression):
170
+ """A glyph range"""
171
+
172
+ def __init__(self, start, end, parser, location=None):
173
+ Expression.__init__(self, location)
174
+ self.start = start
175
+ self.end = end
176
+ self.parser = parser
177
+
178
+ def glyphSet(self):
179
+ return tuple(self.parser.glyph_range(self.start, self.end))
180
+
181
+ def __str__(self):
182
+ return f' RANGE "{self.start}" TO "{self.end}"'
183
+
184
+
185
+ class ScriptDefinition(Statement):
186
+ def __init__(self, name, tag, langs, location=None):
187
+ Statement.__init__(self, location)
188
+ self.name = name
189
+ self.tag = tag
190
+ self.langs = langs
191
+
192
+ def __str__(self):
193
+ res = "DEF_SCRIPT"
194
+ if self.name is not None:
195
+ res += f' NAME "{self.name}"'
196
+ res += f' TAG "{self.tag}"\n\n'
197
+ for lang in self.langs:
198
+ res += f"{lang}"
199
+ res += "END_SCRIPT"
200
+ return res
201
+
202
+
203
+ class LangSysDefinition(Statement):
204
+ def __init__(self, name, tag, features, location=None):
205
+ Statement.__init__(self, location)
206
+ self.name = name
207
+ self.tag = tag
208
+ self.features = features
209
+
210
+ def __str__(self):
211
+ res = "DEF_LANGSYS"
212
+ if self.name is not None:
213
+ res += f' NAME "{self.name}"'
214
+ res += f' TAG "{self.tag}"\n\n'
215
+ for feature in self.features:
216
+ res += f"{feature}"
217
+ res += "END_LANGSYS\n"
218
+ return res
219
+
220
+
221
+ class FeatureDefinition(Statement):
222
+ def __init__(self, name, tag, lookups, location=None):
223
+ Statement.__init__(self, location)
224
+ self.name = name
225
+ self.tag = tag
226
+ self.lookups = lookups
227
+
228
+ def __str__(self):
229
+ res = f'DEF_FEATURE NAME "{self.name}" TAG "{self.tag}"\n'
230
+ res += " " + " ".join(f'LOOKUP "{l}"' for l in self.lookups) + "\n"
231
+ res += "END_FEATURE\n"
232
+ return res
233
+
234
+
235
+ class LookupDefinition(Statement):
236
+ def __init__(
237
+ self,
238
+ name,
239
+ process_base,
240
+ process_marks,
241
+ mark_glyph_set,
242
+ direction,
243
+ reversal,
244
+ comments,
245
+ context,
246
+ sub,
247
+ pos,
248
+ location=None,
249
+ ):
250
+ Statement.__init__(self, location)
251
+ self.name = name
252
+ self.process_base = process_base
253
+ self.process_marks = process_marks
254
+ self.mark_glyph_set = mark_glyph_set
255
+ self.direction = direction
256
+ self.reversal = reversal
257
+ self.comments = comments
258
+ self.context = context
259
+ self.sub = sub
260
+ self.pos = pos
261
+
262
+ def __str__(self):
263
+ res = f'DEF_LOOKUP "{self.name}"'
264
+ res += f' {self.process_base and "PROCESS_BASE" or "SKIP_BASE"}'
265
+ if self.process_marks:
266
+ res += " PROCESS_MARKS "
267
+ if self.mark_glyph_set:
268
+ res += f'MARK_GLYPH_SET "{self.mark_glyph_set}"'
269
+ elif isinstance(self.process_marks, str):
270
+ res += f'"{self.process_marks}"'
271
+ else:
272
+ res += "ALL"
273
+ else:
274
+ res += " SKIP_MARKS"
275
+ if self.direction is not None:
276
+ res += f" DIRECTION {self.direction}"
277
+ if self.reversal:
278
+ res += " REVERSAL"
279
+ if self.comments is not None:
280
+ comments = self.comments.replace("\n", r"\n")
281
+ res += f'\nCOMMENTS "{comments}"'
282
+ if self.context:
283
+ res += "\n" + "\n".join(str(c) for c in self.context)
284
+ else:
285
+ res += "\nIN_CONTEXT\nEND_CONTEXT"
286
+ if self.sub:
287
+ res += f"\n{self.sub}"
288
+ if self.pos:
289
+ res += f"\n{self.pos}"
290
+ return res
291
+
292
+
293
+ class SubstitutionDefinition(Statement):
294
+ def __init__(self, mapping, location=None):
295
+ Statement.__init__(self, location)
296
+ self.mapping = mapping
297
+
298
+ def __str__(self):
299
+ res = "AS_SUBSTITUTION\n"
300
+ for src, dst in self.mapping.items():
301
+ src = "".join(str(s) for s in src)
302
+ dst = "".join(str(d) for d in dst)
303
+ res += f"SUB{src}\nWITH{dst}\nEND_SUB\n"
304
+ res += "END_SUBSTITUTION"
305
+ return res
306
+
307
+
308
+ class SubstitutionSingleDefinition(SubstitutionDefinition):
309
+ pass
310
+
311
+
312
+ class SubstitutionMultipleDefinition(SubstitutionDefinition):
313
+ pass
314
+
315
+
316
+ class SubstitutionLigatureDefinition(SubstitutionDefinition):
317
+ pass
318
+
319
+
320
+ class SubstitutionAlternateDefinition(SubstitutionDefinition):
321
+ pass
322
+
323
+
324
+ class SubstitutionReverseChainingSingleDefinition(SubstitutionDefinition):
325
+ pass
326
+
327
+
328
+ class PositionAttachDefinition(Statement):
329
+ def __init__(self, coverage, coverage_to, location=None):
330
+ Statement.__init__(self, location)
331
+ self.coverage = coverage
332
+ self.coverage_to = coverage_to
333
+
334
+ def __str__(self):
335
+ coverage = "".join(str(c) for c in self.coverage)
336
+ res = f"AS_POSITION\nATTACH{coverage}\nTO"
337
+ for coverage, anchor in self.coverage_to:
338
+ coverage = "".join(str(c) for c in coverage)
339
+ res += f'{coverage} AT ANCHOR "{anchor}"'
340
+ res += "\nEND_ATTACH\nEND_POSITION"
341
+ return res
342
+
343
+
344
+ class PositionAttachCursiveDefinition(Statement):
345
+ def __init__(self, coverages_exit, coverages_enter, location=None):
346
+ Statement.__init__(self, location)
347
+ self.coverages_exit = coverages_exit
348
+ self.coverages_enter = coverages_enter
349
+
350
+ def __str__(self):
351
+ res = "AS_POSITION\nATTACH_CURSIVE"
352
+ for coverage in self.coverages_exit:
353
+ coverage = "".join(str(c) for c in coverage)
354
+ res += f"\nEXIT {coverage}"
355
+ for coverage in self.coverages_enter:
356
+ coverage = "".join(str(c) for c in coverage)
357
+ res += f"\nENTER {coverage}"
358
+ res += "\nEND_ATTACH\nEND_POSITION"
359
+ return res
360
+
361
+
362
+ class PositionAdjustPairDefinition(Statement):
363
+ def __init__(self, coverages_1, coverages_2, adjust_pair, location=None):
364
+ Statement.__init__(self, location)
365
+ self.coverages_1 = coverages_1
366
+ self.coverages_2 = coverages_2
367
+ self.adjust_pair = adjust_pair
368
+
369
+ def __str__(self):
370
+ res = "AS_POSITION\nADJUST_PAIR\n"
371
+ for coverage in self.coverages_1:
372
+ coverage = " ".join(str(c) for c in coverage)
373
+ res += f" FIRST {coverage}"
374
+ res += "\n"
375
+ for coverage in self.coverages_2:
376
+ coverage = " ".join(str(c) for c in coverage)
377
+ res += f" SECOND {coverage}"
378
+ res += "\n"
379
+ for (id_1, id_2), (pos_1, pos_2) in self.adjust_pair.items():
380
+ res += f" {id_1} {id_2} BY{pos_1}{pos_2}\n"
381
+ res += "\nEND_ADJUST\nEND_POSITION"
382
+ return res
383
+
384
+
385
+ class PositionAdjustSingleDefinition(Statement):
386
+ def __init__(self, adjust_single, location=None):
387
+ Statement.__init__(self, location)
388
+ self.adjust_single = adjust_single
389
+
390
+ def __str__(self):
391
+ res = "AS_POSITION\nADJUST_SINGLE"
392
+ for coverage, pos in self.adjust_single:
393
+ coverage = "".join(str(c) for c in coverage)
394
+ res += f"{coverage} BY{pos}"
395
+ res += "\nEND_ADJUST\nEND_POSITION"
396
+ return res
397
+
398
+
399
+ class ContextDefinition(Statement):
400
+ def __init__(self, ex_or_in, left=None, right=None, location=None):
401
+ Statement.__init__(self, location)
402
+ self.ex_or_in = ex_or_in
403
+ self.left = left if left is not None else []
404
+ self.right = right if right is not None else []
405
+
406
+ def __str__(self):
407
+ res = self.ex_or_in + "\n"
408
+ for coverage in self.left:
409
+ coverage = "".join(str(c) for c in coverage)
410
+ res += f" LEFT{coverage}\n"
411
+ for coverage in self.right:
412
+ coverage = "".join(str(c) for c in coverage)
413
+ res += f" RIGHT{coverage}\n"
414
+ res += "END_CONTEXT"
415
+ return res
416
+
417
+
418
+ class AnchorDefinition(Statement):
419
+ def __init__(self, name, gid, glyph_name, component, locked, pos, location=None):
420
+ Statement.__init__(self, location)
421
+ self.name = name
422
+ self.gid = gid
423
+ self.glyph_name = glyph_name
424
+ self.component = component
425
+ self.locked = locked
426
+ self.pos = pos
427
+
428
+ def __str__(self):
429
+ locked = self.locked and " LOCKED" or ""
430
+ return (
431
+ f'DEF_ANCHOR "{self.name}"'
432
+ f" ON {self.gid}"
433
+ f" GLYPH {self.glyph_name}"
434
+ f" COMPONENT {self.component}"
435
+ f"{locked}"
436
+ f" AT {self.pos} END_ANCHOR"
437
+ )
438
+
439
+
440
+ class SettingDefinition(Statement):
441
+ def __init__(self, name, value, location=None):
442
+ Statement.__init__(self, location)
443
+ self.name = name
444
+ self.value = value
445
+
446
+ def __str__(self):
447
+ if self.value is True:
448
+ return f"{self.name}"
449
+ if isinstance(self.value, (tuple, list)):
450
+ value = " ".join(str(v) for v in self.value)
451
+ return f"{self.name} {value}"
452
+ return f"{self.name} {self.value}"
@@ -0,0 +1,12 @@
1
+ class VoltLibError(Exception):
2
+ def __init__(self, message, location):
3
+ Exception.__init__(self, message)
4
+ self.location = location
5
+
6
+ def __str__(self):
7
+ message = Exception.__str__(self)
8
+ if self.location:
9
+ path, line, column = self.location
10
+ return "%s:%d:%d: %s" % (path, line, column, message)
11
+ else:
12
+ return message