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,1242 @@
1
+ # -*- coding: utf-8 -*-
2
+ from __future__ import annotations
3
+
4
+ from fontTools.misc import sstruct
5
+ from fontTools.misc.textTools import (
6
+ bytechr,
7
+ byteord,
8
+ bytesjoin,
9
+ strjoin,
10
+ tobytes,
11
+ tostr,
12
+ safeEval,
13
+ )
14
+ from fontTools.misc.encodingTools import getEncoding
15
+ from fontTools.ttLib import newTable
16
+ from fontTools.ttLib.ttVisitor import TTVisitor
17
+ from fontTools import ttLib
18
+ import fontTools.ttLib.tables.otTables as otTables
19
+ from fontTools.ttLib.tables import C_P_A_L_
20
+ from . import DefaultTable
21
+ import struct
22
+ import logging
23
+
24
+
25
+ log = logging.getLogger(__name__)
26
+
27
+ nameRecordFormat = """
28
+ > # big endian
29
+ platformID: H
30
+ platEncID: H
31
+ langID: H
32
+ nameID: H
33
+ length: H
34
+ offset: H
35
+ """
36
+
37
+ nameRecordSize = sstruct.calcsize(nameRecordFormat)
38
+
39
+
40
+ class table__n_a_m_e(DefaultTable.DefaultTable):
41
+ """Naming table
42
+
43
+ The ``name`` table is used to store a variety of strings that can be
44
+ associated with user-facing font information. Records in the ``name``
45
+ table can be tagged with language tags to support multilingual naming
46
+ and can support platform-specific character-encoding variants.
47
+
48
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/name
49
+ """
50
+
51
+ dependencies = ["ltag"]
52
+
53
+ def __init__(self, tag=None):
54
+ super().__init__(tag)
55
+ self.names = []
56
+
57
+ def decompile(self, data, ttFont):
58
+ format, n, stringOffset = struct.unpack(b">HHH", data[:6])
59
+ expectedStringOffset = 6 + n * nameRecordSize
60
+ if stringOffset != expectedStringOffset:
61
+ log.error(
62
+ "'name' table stringOffset incorrect. Expected: %s; Actual: %s",
63
+ expectedStringOffset,
64
+ stringOffset,
65
+ )
66
+ stringData = data[stringOffset:]
67
+ data = data[6:]
68
+ self.names: list[NameRecord] = []
69
+ for i in range(n):
70
+ if len(data) < 12:
71
+ log.error("skipping malformed name record #%d", i)
72
+ continue
73
+ name, data = sstruct.unpack2(nameRecordFormat, data, NameRecord())
74
+ name.string = stringData[name.offset : name.offset + name.length]
75
+ if name.offset + name.length > len(stringData):
76
+ log.error("skipping malformed name record #%d", i)
77
+ continue
78
+ assert len(name.string) == name.length
79
+ # if (name.platEncID, name.platformID) in ((0, 0), (1, 3)):
80
+ # if len(name.string) % 2:
81
+ # print "2-byte string doesn't have even length!"
82
+ # print name.__dict__
83
+ del name.offset, name.length
84
+ self.names.append(name)
85
+
86
+ def compile(self, ttFont):
87
+ names = self.names
88
+ names.sort() # sort according to the spec; see NameRecord.__lt__()
89
+ stringData = b""
90
+ format = 0
91
+ n = len(names)
92
+ stringOffset = 6 + n * sstruct.calcsize(nameRecordFormat)
93
+ data = struct.pack(b">HHH", format, n, stringOffset)
94
+ lastoffset = 0
95
+ done = {} # remember the data so we can reuse the "pointers"
96
+ for name in names:
97
+ string = name.toBytes()
98
+ if string in done:
99
+ name.offset, name.length = done[string]
100
+ else:
101
+ name.offset, name.length = done[string] = len(stringData), len(string)
102
+ stringData = bytesjoin([stringData, string])
103
+ data = data + sstruct.pack(nameRecordFormat, name)
104
+ return data + stringData
105
+
106
+ def toXML(self, writer, ttFont):
107
+ for name in self.names:
108
+ name.toXML(writer, ttFont)
109
+
110
+ def fromXML(self, name, attrs, content, ttFont):
111
+ if name != "namerecord":
112
+ return # ignore unknown tags
113
+ name = NameRecord()
114
+ self.names.append(name)
115
+ name.fromXML(name, attrs, content, ttFont)
116
+
117
+ def getName(
118
+ self, nameID: int, platformID: int, platEncID: int, langID: int | None = None
119
+ ) -> "NameRecord | None":
120
+ for namerecord in self.names:
121
+ if (
122
+ namerecord.nameID == nameID
123
+ and namerecord.platformID == platformID
124
+ and namerecord.platEncID == platEncID
125
+ ):
126
+ if langID is None or namerecord.langID == langID:
127
+ return namerecord
128
+ return None # not found
129
+
130
+ def getDebugName(self, nameID: int) -> str | None:
131
+ englishName: str | None = None
132
+ someName: str | None = None
133
+ for name in self.names:
134
+ if name.nameID != nameID:
135
+ continue
136
+ try:
137
+ unistr = name.toUnicode()
138
+ except UnicodeDecodeError:
139
+ continue
140
+
141
+ someName = unistr
142
+ if (name.platformID, name.langID) in ((1, 0), (3, 0x409)):
143
+ englishName = unistr
144
+ break
145
+ if englishName:
146
+ return englishName
147
+ elif someName:
148
+ return someName
149
+ else:
150
+ return None
151
+
152
+ def getFirstDebugName(self, nameIDs):
153
+ for nameID in nameIDs:
154
+ name = self.getDebugName(nameID)
155
+ if name is not None:
156
+ return name
157
+ return None
158
+
159
+ def getBestFamilyName(self):
160
+ # 21 = WWS Family Name
161
+ # 16 = Typographic Family Name
162
+ # 1 = Family Name
163
+ return self.getFirstDebugName((21, 16, 1))
164
+
165
+ def getBestSubFamilyName(self):
166
+ # 22 = WWS SubFamily Name
167
+ # 17 = Typographic SubFamily Name
168
+ # 2 = SubFamily Name
169
+ return self.getFirstDebugName((22, 17, 2))
170
+
171
+ def getBestFullName(self):
172
+ # 4 = Full Name
173
+ # 6 = PostScript Name
174
+ for nameIDs in ((21, 22), (16, 17), (1, 2), (4,), (6,)):
175
+ if len(nameIDs) == 2:
176
+ name_fam = self.getDebugName(nameIDs[0])
177
+ name_subfam = self.getDebugName(nameIDs[1])
178
+ if None in [name_fam, name_subfam]:
179
+ continue # if any is None, skip
180
+ name = f"{name_fam} {name_subfam}"
181
+ if name_subfam.lower() == "regular":
182
+ name = f"{name_fam}"
183
+ return name
184
+ else:
185
+ name = self.getDebugName(nameIDs[0])
186
+ if name is not None:
187
+ return name
188
+ return None
189
+
190
+ def setName(self, string, nameID, platformID, platEncID, langID):
191
+ """Set the 'string' for the name record identified by 'nameID', 'platformID',
192
+ 'platEncID' and 'langID'. If a record with that nameID doesn't exist, create it
193
+ and append to the name table.
194
+
195
+ 'string' can be of type `str` (`unicode` in PY2) or `bytes`. In the latter case,
196
+ it is assumed to be already encoded with the correct plaform-specific encoding
197
+ identified by the (platformID, platEncID, langID) triplet. A warning is issued
198
+ to prevent unexpected results.
199
+ """
200
+ if not isinstance(string, str):
201
+ if isinstance(string, bytes):
202
+ log.warning(
203
+ "name string is bytes, ensure it's correctly encoded: %r", string
204
+ )
205
+ else:
206
+ raise TypeError(
207
+ "expected unicode or bytes, found %s: %r"
208
+ % (type(string).__name__, string)
209
+ )
210
+ namerecord = self.getName(nameID, platformID, platEncID, langID)
211
+ if namerecord:
212
+ namerecord.string = string
213
+ else:
214
+ self.names.append(makeName(string, nameID, platformID, platEncID, langID))
215
+
216
+ def removeNames(self, nameID=None, platformID=None, platEncID=None, langID=None):
217
+ """Remove any name records identified by the given combination of 'nameID',
218
+ 'platformID', 'platEncID' and 'langID'.
219
+ """
220
+ args = {
221
+ argName: argValue
222
+ for argName, argValue in (
223
+ ("nameID", nameID),
224
+ ("platformID", platformID),
225
+ ("platEncID", platEncID),
226
+ ("langID", langID),
227
+ )
228
+ if argValue is not None
229
+ }
230
+ if not args:
231
+ # no arguments, nothing to do
232
+ return
233
+ self.names = [
234
+ rec
235
+ for rec in self.names
236
+ if any(
237
+ argValue != getattr(rec, argName) for argName, argValue in args.items()
238
+ )
239
+ ]
240
+
241
+ @staticmethod
242
+ def removeUnusedNames(ttFont):
243
+ """Remove any name records which are not in NameID range 0-255 and not utilized
244
+ within the font itself."""
245
+ visitor = NameRecordVisitor()
246
+ visitor.visit(ttFont)
247
+ toDelete = set()
248
+ for record in ttFont["name"].names:
249
+ # Name IDs 26 to 255, inclusive, are reserved for future standard names.
250
+ # https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-ids
251
+ if record.nameID < 256:
252
+ continue
253
+ if record.nameID not in visitor.seen:
254
+ toDelete.add(record.nameID)
255
+
256
+ for nameID in toDelete:
257
+ ttFont["name"].removeNames(nameID)
258
+ return toDelete
259
+
260
+ def _findUnusedNameID(self, minNameID=256):
261
+ """Finds an unused name id.
262
+
263
+ The nameID is assigned in the range between 'minNameID' and 32767 (inclusive),
264
+ following the last nameID in the name table.
265
+ """
266
+ names = self.names
267
+ nameID = 1 + max([n.nameID for n in names] + [minNameID - 1])
268
+ if nameID > 32767:
269
+ raise ValueError("nameID must be less than 32768")
270
+ return nameID
271
+
272
+ def findMultilingualName(
273
+ self, names, windows=True, mac=True, minNameID=0, ttFont=None
274
+ ):
275
+ """Return the name ID of an existing multilingual name that
276
+ matches the 'names' dictionary, or None if not found.
277
+
278
+ 'names' is a dictionary with the name in multiple languages,
279
+ such as {'en': 'Pale', 'de': 'Blaß', 'de-CH': 'Blass'}.
280
+ The keys can be arbitrary IETF BCP 47 language codes;
281
+ the values are Unicode strings.
282
+
283
+ If 'windows' is True, the returned name ID is guaranteed
284
+ exist for all requested languages for platformID=3 and
285
+ platEncID=1.
286
+ If 'mac' is True, the returned name ID is guaranteed to exist
287
+ for all requested languages for platformID=1 and platEncID=0.
288
+
289
+ The returned name ID will not be less than the 'minNameID'
290
+ argument.
291
+ """
292
+ # Gather the set of requested
293
+ # (string, platformID, platEncID, langID)
294
+ # tuples
295
+ reqNameSet = set()
296
+ for lang, name in sorted(names.items()):
297
+ if windows:
298
+ windowsName = _makeWindowsName(name, None, lang)
299
+ if windowsName is not None:
300
+ reqNameSet.add(
301
+ (
302
+ windowsName.string,
303
+ windowsName.platformID,
304
+ windowsName.platEncID,
305
+ windowsName.langID,
306
+ )
307
+ )
308
+ if mac:
309
+ macName = _makeMacName(name, None, lang, ttFont)
310
+ if macName is not None:
311
+ reqNameSet.add(
312
+ (
313
+ macName.string,
314
+ macName.platformID,
315
+ macName.platEncID,
316
+ macName.langID,
317
+ )
318
+ )
319
+
320
+ # Collect matching name IDs
321
+ matchingNames = dict()
322
+ for name in self.names:
323
+ try:
324
+ key = (name.toUnicode(), name.platformID, name.platEncID, name.langID)
325
+ except UnicodeDecodeError:
326
+ continue
327
+ if key in reqNameSet and name.nameID >= minNameID:
328
+ nameSet = matchingNames.setdefault(name.nameID, set())
329
+ nameSet.add(key)
330
+
331
+ # Return the first name ID that defines all requested strings
332
+ for nameID, nameSet in sorted(matchingNames.items()):
333
+ if nameSet == reqNameSet:
334
+ return nameID
335
+
336
+ return None # not found
337
+
338
+ def addMultilingualName(
339
+ self, names, ttFont=None, nameID=None, windows=True, mac=True, minNameID=0
340
+ ):
341
+ """Add a multilingual name, returning its name ID
342
+
343
+ 'names' is a dictionary with the name in multiple languages,
344
+ such as {'en': 'Pale', 'de': 'Blaß', 'de-CH': 'Blass'}.
345
+ The keys can be arbitrary IETF BCP 47 language codes;
346
+ the values are Unicode strings.
347
+
348
+ 'ttFont' is the TTFont to which the names are added, or None.
349
+ If present, the font's 'ltag' table can get populated
350
+ to store exotic language codes, which allows encoding
351
+ names that otherwise cannot get encoded at all.
352
+
353
+ 'nameID' is the name ID to be used, or None to let the library
354
+ find an existing set of name records that match, or pick an
355
+ unused name ID.
356
+
357
+ If 'windows' is True, a platformID=3 name record will be added.
358
+ If 'mac' is True, a platformID=1 name record will be added.
359
+
360
+ If the 'nameID' argument is None, the created nameID will not
361
+ be less than the 'minNameID' argument.
362
+ """
363
+ if nameID is None:
364
+ # Reuse nameID if possible
365
+ nameID = self.findMultilingualName(
366
+ names, windows=windows, mac=mac, minNameID=minNameID, ttFont=ttFont
367
+ )
368
+ if nameID is not None:
369
+ return nameID
370
+ nameID = self._findUnusedNameID()
371
+ # TODO: Should minimize BCP 47 language codes.
372
+ # https://github.com/fonttools/fonttools/issues/930
373
+ for lang, name in sorted(names.items()):
374
+ if windows:
375
+ windowsName = _makeWindowsName(name, nameID, lang)
376
+ if windowsName is not None:
377
+ self.names.append(windowsName)
378
+ else:
379
+ # We cannot not make a Windows name: make sure we add a
380
+ # Mac name as a fallback. This can happen for exotic
381
+ # BCP47 language tags that have no Windows language code.
382
+ mac = True
383
+ if mac:
384
+ macName = _makeMacName(name, nameID, lang, ttFont)
385
+ if macName is not None:
386
+ self.names.append(macName)
387
+ return nameID
388
+
389
+ def addName(self, string, platforms=((1, 0, 0), (3, 1, 0x409)), minNameID=255):
390
+ """Add a new name record containing 'string' for each (platformID, platEncID,
391
+ langID) tuple specified in the 'platforms' list.
392
+
393
+ The nameID is assigned in the range between 'minNameID'+1 and 32767 (inclusive),
394
+ following the last nameID in the name table.
395
+ If no 'platforms' are specified, two English name records are added, one for the
396
+ Macintosh (platformID=0), and one for the Windows platform (3).
397
+
398
+ The 'string' must be a Unicode string, so it can be encoded with different,
399
+ platform-specific encodings.
400
+
401
+ Return the new nameID.
402
+ """
403
+ assert (
404
+ len(platforms) > 0
405
+ ), "'platforms' must contain at least one (platformID, platEncID, langID) tuple"
406
+ if not isinstance(string, str):
407
+ raise TypeError(
408
+ "expected str, found %s: %r" % (type(string).__name__, string)
409
+ )
410
+ nameID = self._findUnusedNameID(minNameID + 1)
411
+ for platformID, platEncID, langID in platforms:
412
+ self.names.append(makeName(string, nameID, platformID, platEncID, langID))
413
+ return nameID
414
+
415
+
416
+ def makeName(string, nameID, platformID, platEncID, langID):
417
+ name = NameRecord()
418
+ name.string, name.nameID, name.platformID, name.platEncID, name.langID = (
419
+ string,
420
+ nameID,
421
+ platformID,
422
+ platEncID,
423
+ langID,
424
+ )
425
+ return name
426
+
427
+
428
+ def _makeWindowsName(name, nameID, language):
429
+ """Create a NameRecord for the Microsoft Windows platform
430
+
431
+ 'language' is an arbitrary IETF BCP 47 language identifier such
432
+ as 'en', 'de-CH', 'de-AT-1901', or 'fa-Latn'. If Microsoft Windows
433
+ does not support the desired language, the result will be None.
434
+ Future versions of fonttools might return a NameRecord for the
435
+ OpenType 'name' table format 1, but this is not implemented yet.
436
+ """
437
+ langID = _WINDOWS_LANGUAGE_CODES.get(language.lower())
438
+ if langID is not None:
439
+ return makeName(name, nameID, 3, 1, langID)
440
+ else:
441
+ log.warning(
442
+ "cannot add Windows name in language %s "
443
+ "because fonttools does not yet support "
444
+ "name table format 1" % language
445
+ )
446
+ return None
447
+
448
+
449
+ def _makeMacName(name, nameID, language, font=None):
450
+ """Create a NameRecord for Apple platforms
451
+
452
+ 'language' is an arbitrary IETF BCP 47 language identifier such
453
+ as 'en', 'de-CH', 'de-AT-1901', or 'fa-Latn'. When possible, we
454
+ create a Macintosh NameRecord that is understood by old applications
455
+ (platform ID 1 and an old-style Macintosh language enum). If this
456
+ is not possible, we create a Unicode NameRecord (platform ID 0)
457
+ whose language points to the font’s 'ltag' table. The latter
458
+ can encode any string in any language, but legacy applications
459
+ might not recognize the format (in which case they will ignore
460
+ those names).
461
+
462
+ 'font' should be the TTFont for which you want to create a name.
463
+ If 'font' is None, we only return NameRecords for legacy Macintosh;
464
+ in that case, the result will be None for names that need to
465
+ be encoded with an 'ltag' table.
466
+
467
+ See the section “The language identifier” in Apple’s specification:
468
+ https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
469
+ """
470
+ macLang = _MAC_LANGUAGE_CODES.get(language.lower())
471
+ macScript = _MAC_LANGUAGE_TO_SCRIPT.get(macLang)
472
+ if macLang is not None and macScript is not None:
473
+ encoding = getEncoding(1, macScript, macLang, default="ascii")
474
+ # Check if we can actually encode this name. If we can't,
475
+ # for example because we have no support for the legacy
476
+ # encoding, or because the name string contains Unicode
477
+ # characters that the legacy encoding cannot represent,
478
+ # we fall back to encoding the name in Unicode and put
479
+ # the language tag into the ltag table.
480
+ try:
481
+ _ = tobytes(name, encoding, errors="strict")
482
+ return makeName(name, nameID, 1, macScript, macLang)
483
+ except UnicodeEncodeError:
484
+ pass
485
+ if font is not None:
486
+ ltag = font.tables.get("ltag")
487
+ if ltag is None:
488
+ ltag = font["ltag"] = newTable("ltag")
489
+ # 0 = Unicode; 4 = “Unicode 2.0 or later semantics (non-BMP characters allowed)”
490
+ # “The preferred platform-specific code for Unicode would be 3 or 4.”
491
+ # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
492
+ return makeName(name, nameID, 0, 4, ltag.addTag(language))
493
+ else:
494
+ log.warning(
495
+ "cannot store language %s into 'ltag' table "
496
+ "without having access to the TTFont object" % language
497
+ )
498
+ return None
499
+
500
+
501
+ class NameRecord(object):
502
+ def getEncoding(self, default="ascii"):
503
+ """Returns the Python encoding name for this name entry based on its platformID,
504
+ platEncID, and langID. If encoding for these values is not known, by default
505
+ 'ascii' is returned. That can be overriden by passing a value to the default
506
+ argument.
507
+ """
508
+ return getEncoding(self.platformID, self.platEncID, self.langID, default)
509
+
510
+ def encodingIsUnicodeCompatible(self):
511
+ return self.getEncoding(None) in ["utf_16_be", "ucs2be", "ascii", "latin1"]
512
+
513
+ def __str__(self):
514
+ return self.toStr(errors="backslashreplace")
515
+
516
+ def isUnicode(self):
517
+ return self.platformID == 0 or (
518
+ self.platformID == 3 and self.platEncID in [0, 1, 10]
519
+ )
520
+
521
+ def toUnicode(self, errors: str = "strict") -> str:
522
+ """
523
+ If self.string is a Unicode string, return it; otherwise try decoding the
524
+ bytes in self.string to a Unicode string using the encoding of this
525
+ entry as returned by self.getEncoding(); Note that self.getEncoding()
526
+ returns 'ascii' if the encoding is unknown to the library.
527
+
528
+ Certain heuristics are performed to recover data from bytes that are
529
+ ill-formed in the chosen encoding, or that otherwise look misencoded
530
+ (mostly around bad UTF-16BE encoded bytes, or bytes that look like UTF-16BE
531
+ but marked otherwise). If the bytes are ill-formed and the heuristics fail,
532
+ the error is handled according to the errors parameter to this function, which is
533
+ passed to the underlying decode() function; by default it throws a
534
+ UnicodeDecodeError exception.
535
+
536
+ Note: The mentioned heuristics mean that roundtripping a font to XML and back
537
+ to binary might recover some misencoded data whereas just loading the font
538
+ and saving it back will not change them.
539
+ """
540
+
541
+ def isascii(b: int) -> bool:
542
+ return (b >= 0x20 and b <= 0x7E) or b in [0x09, 0x0A, 0x0D]
543
+
544
+ encoding = self.getEncoding()
545
+ string = self.string
546
+
547
+ if (
548
+ isinstance(string, bytes)
549
+ and encoding == "utf_16_be"
550
+ and len(string) % 2 == 1
551
+ ):
552
+ # Recover badly encoded UTF-16 strings that have an odd number of bytes:
553
+ # - If the last byte is zero, drop it. Otherwise,
554
+ # - If all the odd bytes are zero and all the even bytes are ASCII,
555
+ # prepend one zero byte. Otherwise,
556
+ # - If first byte is zero and all other bytes are ASCII, insert zero
557
+ # bytes between consecutive ASCII bytes.
558
+ #
559
+ # (Yes, I've seen all of these in the wild... sigh)
560
+ if byteord(string[-1]) == 0:
561
+ string = string[:-1]
562
+ elif all(
563
+ byteord(b) == 0 if i % 2 else isascii(byteord(b))
564
+ for i, b in enumerate(string)
565
+ ):
566
+ string = b"\0" + string
567
+ elif byteord(string[0]) == 0 and all(
568
+ isascii(byteord(b)) for b in string[1:]
569
+ ):
570
+ string = bytesjoin(b"\0" + bytechr(byteord(b)) for b in string[1:])
571
+
572
+ string = tostr(string, encoding=encoding, errors=errors)
573
+
574
+ # If decoded strings still looks like UTF-16BE, it suggests a double-encoding.
575
+ # Fix it up.
576
+ if all(
577
+ ord(c) == 0 if i % 2 == 0 else isascii(ord(c)) for i, c in enumerate(string)
578
+ ):
579
+ # If string claims to be Mac encoding, but looks like UTF-16BE with ASCII text,
580
+ # narrow it down.
581
+ string = "".join(c for c in string[1::2])
582
+
583
+ return string
584
+
585
+ def toBytes(self, errors="strict"):
586
+ """If self.string is a bytes object, return it; otherwise try encoding
587
+ the Unicode string in self.string to bytes using the encoding of this
588
+ entry as returned by self.getEncoding(); Note that self.getEncoding()
589
+ returns 'ascii' if the encoding is unknown to the library.
590
+
591
+ If the Unicode string cannot be encoded to bytes in the chosen encoding,
592
+ the error is handled according to the errors parameter to this function,
593
+ which is passed to the underlying encode() function; by default it throws a
594
+ UnicodeEncodeError exception.
595
+ """
596
+ return tobytes(self.string, encoding=self.getEncoding(), errors=errors)
597
+
598
+ toStr = toUnicode
599
+
600
+ def toXML(self, writer, ttFont):
601
+ try:
602
+ unistr = self.toUnicode()
603
+ except UnicodeDecodeError:
604
+ unistr = None
605
+ attrs = [
606
+ ("nameID", self.nameID),
607
+ ("platformID", self.platformID),
608
+ ("platEncID", self.platEncID),
609
+ ("langID", hex(self.langID)),
610
+ ]
611
+
612
+ if unistr is None or not self.encodingIsUnicodeCompatible():
613
+ attrs.append(("unicode", unistr is not None))
614
+
615
+ writer.begintag("namerecord", attrs)
616
+ writer.newline()
617
+ if unistr is not None:
618
+ writer.write(unistr)
619
+ else:
620
+ writer.write8bit(self.string)
621
+ writer.newline()
622
+ writer.endtag("namerecord")
623
+ writer.newline()
624
+
625
+ def fromXML(self, name, attrs, content, ttFont):
626
+ self.nameID = safeEval(attrs["nameID"])
627
+ self.platformID = safeEval(attrs["platformID"])
628
+ self.platEncID = safeEval(attrs["platEncID"])
629
+ self.langID = safeEval(attrs["langID"])
630
+ s = strjoin(content).strip()
631
+ encoding = self.getEncoding()
632
+ if self.encodingIsUnicodeCompatible() or safeEval(
633
+ attrs.get("unicode", "False")
634
+ ):
635
+ self.string = s.encode(encoding)
636
+ else:
637
+ # This is the inverse of write8bit...
638
+ self.string = s.encode("latin1")
639
+
640
+ def __lt__(self, other):
641
+ if type(self) != type(other):
642
+ return NotImplemented
643
+
644
+ try:
645
+ selfTuple = (
646
+ self.platformID,
647
+ self.platEncID,
648
+ self.langID,
649
+ self.nameID,
650
+ )
651
+ otherTuple = (
652
+ other.platformID,
653
+ other.platEncID,
654
+ other.langID,
655
+ other.nameID,
656
+ )
657
+ except AttributeError:
658
+ # This can only happen for
659
+ # 1) an object that is not a NameRecord, or
660
+ # 2) an unlikely incomplete NameRecord object which has not been
661
+ # fully populated
662
+ return NotImplemented
663
+
664
+ try:
665
+ # Include the actual NameRecord string in the comparison tuples
666
+ selfTuple = selfTuple + (self.toBytes(),)
667
+ otherTuple = otherTuple + (other.toBytes(),)
668
+ except UnicodeEncodeError as e:
669
+ # toBytes caused an encoding error in either of the two, so content
670
+ # to sorting based on IDs only
671
+ log.error("NameRecord sorting failed to encode: %s" % e)
672
+
673
+ # Implemented so that list.sort() sorts according to the spec by using
674
+ # the order of the tuple items and their comparison
675
+ return selfTuple < otherTuple
676
+
677
+ def __repr__(self):
678
+ return "<NameRecord NameID=%d; PlatformID=%d; LanguageID=%d>" % (
679
+ self.nameID,
680
+ self.platformID,
681
+ self.langID,
682
+ )
683
+
684
+
685
+ # Windows language ID → IETF BCP-47 language tag
686
+ #
687
+ # While Microsoft indicates a region/country for all its language
688
+ # IDs, we follow Unicode practice by omitting “most likely subtags”
689
+ # as per Unicode CLDR. For example, English is simply “en” and not
690
+ # “en-Latn” because according to Unicode, the default script
691
+ # for English is Latin.
692
+ #
693
+ # http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html
694
+ # http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
695
+ _WINDOWS_LANGUAGES = {
696
+ 0x0436: "af",
697
+ 0x041C: "sq",
698
+ 0x0484: "gsw",
699
+ 0x045E: "am",
700
+ 0x1401: "ar-DZ",
701
+ 0x3C01: "ar-BH",
702
+ 0x0C01: "ar",
703
+ 0x0801: "ar-IQ",
704
+ 0x2C01: "ar-JO",
705
+ 0x3401: "ar-KW",
706
+ 0x3001: "ar-LB",
707
+ 0x1001: "ar-LY",
708
+ 0x1801: "ary",
709
+ 0x2001: "ar-OM",
710
+ 0x4001: "ar-QA",
711
+ 0x0401: "ar-SA",
712
+ 0x2801: "ar-SY",
713
+ 0x1C01: "aeb",
714
+ 0x3801: "ar-AE",
715
+ 0x2401: "ar-YE",
716
+ 0x042B: "hy",
717
+ 0x044D: "as",
718
+ 0x082C: "az-Cyrl",
719
+ 0x042C: "az",
720
+ 0x046D: "ba",
721
+ 0x042D: "eu",
722
+ 0x0423: "be",
723
+ 0x0845: "bn",
724
+ 0x0445: "bn-IN",
725
+ 0x201A: "bs-Cyrl",
726
+ 0x141A: "bs",
727
+ 0x047E: "br",
728
+ 0x0402: "bg",
729
+ 0x0403: "ca",
730
+ 0x0C04: "zh-HK",
731
+ 0x1404: "zh-MO",
732
+ 0x0804: "zh",
733
+ 0x1004: "zh-SG",
734
+ 0x0404: "zh-TW",
735
+ 0x0483: "co",
736
+ 0x041A: "hr",
737
+ 0x101A: "hr-BA",
738
+ 0x0405: "cs",
739
+ 0x0406: "da",
740
+ 0x048C: "prs",
741
+ 0x0465: "dv",
742
+ 0x0813: "nl-BE",
743
+ 0x0413: "nl",
744
+ 0x0C09: "en-AU",
745
+ 0x2809: "en-BZ",
746
+ 0x1009: "en-CA",
747
+ 0x2409: "en-029",
748
+ 0x4009: "en-IN",
749
+ 0x1809: "en-IE",
750
+ 0x2009: "en-JM",
751
+ 0x4409: "en-MY",
752
+ 0x1409: "en-NZ",
753
+ 0x3409: "en-PH",
754
+ 0x4809: "en-SG",
755
+ 0x1C09: "en-ZA",
756
+ 0x2C09: "en-TT",
757
+ 0x0809: "en-GB",
758
+ 0x0409: "en",
759
+ 0x3009: "en-ZW",
760
+ 0x0425: "et",
761
+ 0x0438: "fo",
762
+ 0x0464: "fil",
763
+ 0x040B: "fi",
764
+ 0x080C: "fr-BE",
765
+ 0x0C0C: "fr-CA",
766
+ 0x040C: "fr",
767
+ 0x140C: "fr-LU",
768
+ 0x180C: "fr-MC",
769
+ 0x100C: "fr-CH",
770
+ 0x0462: "fy",
771
+ 0x0456: "gl",
772
+ 0x0437: "ka",
773
+ 0x0C07: "de-AT",
774
+ 0x0407: "de",
775
+ 0x1407: "de-LI",
776
+ 0x1007: "de-LU",
777
+ 0x0807: "de-CH",
778
+ 0x0408: "el",
779
+ 0x046F: "kl",
780
+ 0x0447: "gu",
781
+ 0x0468: "ha",
782
+ 0x040D: "he",
783
+ 0x0439: "hi",
784
+ 0x040E: "hu",
785
+ 0x040F: "is",
786
+ 0x0470: "ig",
787
+ 0x0421: "id",
788
+ 0x045D: "iu",
789
+ 0x085D: "iu-Latn",
790
+ 0x083C: "ga",
791
+ 0x0434: "xh",
792
+ 0x0435: "zu",
793
+ 0x0410: "it",
794
+ 0x0810: "it-CH",
795
+ 0x0411: "ja",
796
+ 0x044B: "kn",
797
+ 0x043F: "kk",
798
+ 0x0453: "km",
799
+ 0x0486: "quc",
800
+ 0x0487: "rw",
801
+ 0x0441: "sw",
802
+ 0x0457: "kok",
803
+ 0x0412: "ko",
804
+ 0x0440: "ky",
805
+ 0x0454: "lo",
806
+ 0x0426: "lv",
807
+ 0x0427: "lt",
808
+ 0x082E: "dsb",
809
+ 0x046E: "lb",
810
+ 0x042F: "mk",
811
+ 0x083E: "ms-BN",
812
+ 0x043E: "ms",
813
+ 0x044C: "ml",
814
+ 0x043A: "mt",
815
+ 0x0481: "mi",
816
+ 0x047A: "arn",
817
+ 0x044E: "mr",
818
+ 0x047C: "moh",
819
+ 0x0450: "mn",
820
+ 0x0850: "mn-CN",
821
+ 0x0461: "ne",
822
+ 0x0414: "nb",
823
+ 0x0814: "nn",
824
+ 0x0482: "oc",
825
+ 0x0448: "or",
826
+ 0x0463: "ps",
827
+ 0x0415: "pl",
828
+ 0x0416: "pt",
829
+ 0x0816: "pt-PT",
830
+ 0x0446: "pa",
831
+ 0x046B: "qu-BO",
832
+ 0x086B: "qu-EC",
833
+ 0x0C6B: "qu",
834
+ 0x0418: "ro",
835
+ 0x0417: "rm",
836
+ 0x0419: "ru",
837
+ 0x243B: "smn",
838
+ 0x103B: "smj-NO",
839
+ 0x143B: "smj",
840
+ 0x0C3B: "se-FI",
841
+ 0x043B: "se",
842
+ 0x083B: "se-SE",
843
+ 0x203B: "sms",
844
+ 0x183B: "sma-NO",
845
+ 0x1C3B: "sms",
846
+ 0x044F: "sa",
847
+ 0x1C1A: "sr-Cyrl-BA",
848
+ 0x0C1A: "sr",
849
+ 0x181A: "sr-Latn-BA",
850
+ 0x081A: "sr-Latn",
851
+ 0x046C: "nso",
852
+ 0x0432: "tn",
853
+ 0x045B: "si",
854
+ 0x041B: "sk",
855
+ 0x0424: "sl",
856
+ 0x2C0A: "es-AR",
857
+ 0x400A: "es-BO",
858
+ 0x340A: "es-CL",
859
+ 0x240A: "es-CO",
860
+ 0x140A: "es-CR",
861
+ 0x1C0A: "es-DO",
862
+ 0x300A: "es-EC",
863
+ 0x440A: "es-SV",
864
+ 0x100A: "es-GT",
865
+ 0x480A: "es-HN",
866
+ 0x080A: "es-MX",
867
+ 0x4C0A: "es-NI",
868
+ 0x180A: "es-PA",
869
+ 0x3C0A: "es-PY",
870
+ 0x280A: "es-PE",
871
+ 0x500A: "es-PR",
872
+ # Microsoft has defined two different language codes for
873
+ # “Spanish with modern sorting” and “Spanish with traditional
874
+ # sorting”. This makes sense for collation APIs, and it would be
875
+ # possible to express this in BCP 47 language tags via Unicode
876
+ # extensions (eg., “es-u-co-trad” is “Spanish with traditional
877
+ # sorting”). However, for storing names in fonts, this distinction
878
+ # does not make sense, so we use “es” in both cases.
879
+ 0x0C0A: "es",
880
+ 0x040A: "es",
881
+ 0x540A: "es-US",
882
+ 0x380A: "es-UY",
883
+ 0x200A: "es-VE",
884
+ 0x081D: "sv-FI",
885
+ 0x041D: "sv",
886
+ 0x045A: "syr",
887
+ 0x0428: "tg",
888
+ 0x085F: "tzm",
889
+ 0x0449: "ta",
890
+ 0x0444: "tt",
891
+ 0x044A: "te",
892
+ 0x041E: "th",
893
+ 0x0451: "bo",
894
+ 0x041F: "tr",
895
+ 0x0442: "tk",
896
+ 0x0480: "ug",
897
+ 0x0422: "uk",
898
+ 0x042E: "hsb",
899
+ 0x0420: "ur",
900
+ 0x0843: "uz-Cyrl",
901
+ 0x0443: "uz",
902
+ 0x042A: "vi",
903
+ 0x0452: "cy",
904
+ 0x0488: "wo",
905
+ 0x0485: "sah",
906
+ 0x0478: "ii",
907
+ 0x046A: "yo",
908
+ }
909
+
910
+
911
+ _MAC_LANGUAGES = {
912
+ 0: "en",
913
+ 1: "fr",
914
+ 2: "de",
915
+ 3: "it",
916
+ 4: "nl",
917
+ 5: "sv",
918
+ 6: "es",
919
+ 7: "da",
920
+ 8: "pt",
921
+ 9: "no",
922
+ 10: "he",
923
+ 11: "ja",
924
+ 12: "ar",
925
+ 13: "fi",
926
+ 14: "el",
927
+ 15: "is",
928
+ 16: "mt",
929
+ 17: "tr",
930
+ 18: "hr",
931
+ 19: "zh-Hant",
932
+ 20: "ur",
933
+ 21: "hi",
934
+ 22: "th",
935
+ 23: "ko",
936
+ 24: "lt",
937
+ 25: "pl",
938
+ 26: "hu",
939
+ 27: "es",
940
+ 28: "lv",
941
+ 29: "se",
942
+ 30: "fo",
943
+ 31: "fa",
944
+ 32: "ru",
945
+ 33: "zh",
946
+ 34: "nl-BE",
947
+ 35: "ga",
948
+ 36: "sq",
949
+ 37: "ro",
950
+ 38: "cz",
951
+ 39: "sk",
952
+ 40: "sl",
953
+ 41: "yi",
954
+ 42: "sr",
955
+ 43: "mk",
956
+ 44: "bg",
957
+ 45: "uk",
958
+ 46: "be",
959
+ 47: "uz",
960
+ 48: "kk",
961
+ 49: "az-Cyrl",
962
+ 50: "az-Arab",
963
+ 51: "hy",
964
+ 52: "ka",
965
+ 53: "mo",
966
+ 54: "ky",
967
+ 55: "tg",
968
+ 56: "tk",
969
+ 57: "mn-CN",
970
+ 58: "mn",
971
+ 59: "ps",
972
+ 60: "ks",
973
+ 61: "ku",
974
+ 62: "sd",
975
+ 63: "bo",
976
+ 64: "ne",
977
+ 65: "sa",
978
+ 66: "mr",
979
+ 67: "bn",
980
+ 68: "as",
981
+ 69: "gu",
982
+ 70: "pa",
983
+ 71: "or",
984
+ 72: "ml",
985
+ 73: "kn",
986
+ 74: "ta",
987
+ 75: "te",
988
+ 76: "si",
989
+ 77: "my",
990
+ 78: "km",
991
+ 79: "lo",
992
+ 80: "vi",
993
+ 81: "id",
994
+ 82: "tl",
995
+ 83: "ms",
996
+ 84: "ms-Arab",
997
+ 85: "am",
998
+ 86: "ti",
999
+ 87: "om",
1000
+ 88: "so",
1001
+ 89: "sw",
1002
+ 90: "rw",
1003
+ 91: "rn",
1004
+ 92: "ny",
1005
+ 93: "mg",
1006
+ 94: "eo",
1007
+ 128: "cy",
1008
+ 129: "eu",
1009
+ 130: "ca",
1010
+ 131: "la",
1011
+ 132: "qu",
1012
+ 133: "gn",
1013
+ 134: "ay",
1014
+ 135: "tt",
1015
+ 136: "ug",
1016
+ 137: "dz",
1017
+ 138: "jv",
1018
+ 139: "su",
1019
+ 140: "gl",
1020
+ 141: "af",
1021
+ 142: "br",
1022
+ 143: "iu",
1023
+ 144: "gd",
1024
+ 145: "gv",
1025
+ 146: "ga",
1026
+ 147: "to",
1027
+ 148: "el-polyton",
1028
+ 149: "kl",
1029
+ 150: "az",
1030
+ 151: "nn",
1031
+ }
1032
+
1033
+
1034
+ _WINDOWS_LANGUAGE_CODES = {
1035
+ lang.lower(): code for code, lang in _WINDOWS_LANGUAGES.items()
1036
+ }
1037
+ _MAC_LANGUAGE_CODES = {lang.lower(): code for code, lang in _MAC_LANGUAGES.items()}
1038
+
1039
+
1040
+ # MacOS language ID → MacOS script ID
1041
+ #
1042
+ # Note that the script ID is not sufficient to determine what encoding
1043
+ # to use in TrueType files. For some languages, MacOS used a modification
1044
+ # of a mainstream script. For example, an Icelandic name would be stored
1045
+ # with smRoman in the TrueType naming table, but the actual encoding
1046
+ # is a special Icelandic version of the normal Macintosh Roman encoding.
1047
+ # As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal
1048
+ # Syllables but MacOS had run out of available script codes, so this was
1049
+ # done as a (pretty radical) “modification” of Ethiopic.
1050
+ #
1051
+ # http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt
1052
+ _MAC_LANGUAGE_TO_SCRIPT = {
1053
+ 0: 0, # langEnglish → smRoman
1054
+ 1: 0, # langFrench → smRoman
1055
+ 2: 0, # langGerman → smRoman
1056
+ 3: 0, # langItalian → smRoman
1057
+ 4: 0, # langDutch → smRoman
1058
+ 5: 0, # langSwedish → smRoman
1059
+ 6: 0, # langSpanish → smRoman
1060
+ 7: 0, # langDanish → smRoman
1061
+ 8: 0, # langPortuguese → smRoman
1062
+ 9: 0, # langNorwegian → smRoman
1063
+ 10: 5, # langHebrew → smHebrew
1064
+ 11: 1, # langJapanese → smJapanese
1065
+ 12: 4, # langArabic → smArabic
1066
+ 13: 0, # langFinnish → smRoman
1067
+ 14: 6, # langGreek → smGreek
1068
+ 15: 0, # langIcelandic → smRoman (modified)
1069
+ 16: 0, # langMaltese → smRoman
1070
+ 17: 0, # langTurkish → smRoman (modified)
1071
+ 18: 0, # langCroatian → smRoman (modified)
1072
+ 19: 2, # langTradChinese → smTradChinese
1073
+ 20: 4, # langUrdu → smArabic
1074
+ 21: 9, # langHindi → smDevanagari
1075
+ 22: 21, # langThai → smThai
1076
+ 23: 3, # langKorean → smKorean
1077
+ 24: 29, # langLithuanian → smCentralEuroRoman
1078
+ 25: 29, # langPolish → smCentralEuroRoman
1079
+ 26: 29, # langHungarian → smCentralEuroRoman
1080
+ 27: 29, # langEstonian → smCentralEuroRoman
1081
+ 28: 29, # langLatvian → smCentralEuroRoman
1082
+ 29: 0, # langSami → smRoman
1083
+ 30: 0, # langFaroese → smRoman (modified)
1084
+ 31: 4, # langFarsi → smArabic (modified)
1085
+ 32: 7, # langRussian → smCyrillic
1086
+ 33: 25, # langSimpChinese → smSimpChinese
1087
+ 34: 0, # langFlemish → smRoman
1088
+ 35: 0, # langIrishGaelic → smRoman (modified)
1089
+ 36: 0, # langAlbanian → smRoman
1090
+ 37: 0, # langRomanian → smRoman (modified)
1091
+ 38: 29, # langCzech → smCentralEuroRoman
1092
+ 39: 29, # langSlovak → smCentralEuroRoman
1093
+ 40: 0, # langSlovenian → smRoman (modified)
1094
+ 41: 5, # langYiddish → smHebrew
1095
+ 42: 7, # langSerbian → smCyrillic
1096
+ 43: 7, # langMacedonian → smCyrillic
1097
+ 44: 7, # langBulgarian → smCyrillic
1098
+ 45: 7, # langUkrainian → smCyrillic (modified)
1099
+ 46: 7, # langByelorussian → smCyrillic
1100
+ 47: 7, # langUzbek → smCyrillic
1101
+ 48: 7, # langKazakh → smCyrillic
1102
+ 49: 7, # langAzerbaijani → smCyrillic
1103
+ 50: 4, # langAzerbaijanAr → smArabic
1104
+ 51: 24, # langArmenian → smArmenian
1105
+ 52: 23, # langGeorgian → smGeorgian
1106
+ 53: 7, # langMoldavian → smCyrillic
1107
+ 54: 7, # langKirghiz → smCyrillic
1108
+ 55: 7, # langTajiki → smCyrillic
1109
+ 56: 7, # langTurkmen → smCyrillic
1110
+ 57: 27, # langMongolian → smMongolian
1111
+ 58: 7, # langMongolianCyr → smCyrillic
1112
+ 59: 4, # langPashto → smArabic
1113
+ 60: 4, # langKurdish → smArabic
1114
+ 61: 4, # langKashmiri → smArabic
1115
+ 62: 4, # langSindhi → smArabic
1116
+ 63: 26, # langTibetan → smTibetan
1117
+ 64: 9, # langNepali → smDevanagari
1118
+ 65: 9, # langSanskrit → smDevanagari
1119
+ 66: 9, # langMarathi → smDevanagari
1120
+ 67: 13, # langBengali → smBengali
1121
+ 68: 13, # langAssamese → smBengali
1122
+ 69: 11, # langGujarati → smGujarati
1123
+ 70: 10, # langPunjabi → smGurmukhi
1124
+ 71: 12, # langOriya → smOriya
1125
+ 72: 17, # langMalayalam → smMalayalam
1126
+ 73: 16, # langKannada → smKannada
1127
+ 74: 14, # langTamil → smTamil
1128
+ 75: 15, # langTelugu → smTelugu
1129
+ 76: 18, # langSinhalese → smSinhalese
1130
+ 77: 19, # langBurmese → smBurmese
1131
+ 78: 20, # langKhmer → smKhmer
1132
+ 79: 22, # langLao → smLao
1133
+ 80: 30, # langVietnamese → smVietnamese
1134
+ 81: 0, # langIndonesian → smRoman
1135
+ 82: 0, # langTagalog → smRoman
1136
+ 83: 0, # langMalayRoman → smRoman
1137
+ 84: 4, # langMalayArabic → smArabic
1138
+ 85: 28, # langAmharic → smEthiopic
1139
+ 86: 28, # langTigrinya → smEthiopic
1140
+ 87: 28, # langOromo → smEthiopic
1141
+ 88: 0, # langSomali → smRoman
1142
+ 89: 0, # langSwahili → smRoman
1143
+ 90: 0, # langKinyarwanda → smRoman
1144
+ 91: 0, # langRundi → smRoman
1145
+ 92: 0, # langNyanja → smRoman
1146
+ 93: 0, # langMalagasy → smRoman
1147
+ 94: 0, # langEsperanto → smRoman
1148
+ 128: 0, # langWelsh → smRoman (modified)
1149
+ 129: 0, # langBasque → smRoman
1150
+ 130: 0, # langCatalan → smRoman
1151
+ 131: 0, # langLatin → smRoman
1152
+ 132: 0, # langQuechua → smRoman
1153
+ 133: 0, # langGuarani → smRoman
1154
+ 134: 0, # langAymara → smRoman
1155
+ 135: 7, # langTatar → smCyrillic
1156
+ 136: 4, # langUighur → smArabic
1157
+ 137: 26, # langDzongkha → smTibetan
1158
+ 138: 0, # langJavaneseRom → smRoman
1159
+ 139: 0, # langSundaneseRom → smRoman
1160
+ 140: 0, # langGalician → smRoman
1161
+ 141: 0, # langAfrikaans → smRoman
1162
+ 142: 0, # langBreton → smRoman (modified)
1163
+ 143: 28, # langInuktitut → smEthiopic (modified)
1164
+ 144: 0, # langScottishGaelic → smRoman (modified)
1165
+ 145: 0, # langManxGaelic → smRoman (modified)
1166
+ 146: 0, # langIrishGaelicScript → smRoman (modified)
1167
+ 147: 0, # langTongan → smRoman
1168
+ 148: 6, # langGreekAncient → smRoman
1169
+ 149: 0, # langGreenlandic → smRoman
1170
+ 150: 0, # langAzerbaijanRoman → smRoman
1171
+ 151: 0, # langNynorsk → smRoman
1172
+ }
1173
+
1174
+
1175
+ class NameRecordVisitor(TTVisitor):
1176
+ # Font tables that have NameIDs we need to collect.
1177
+ TABLES = ("GSUB", "GPOS", "fvar", "CPAL", "STAT")
1178
+
1179
+ def __init__(self):
1180
+ self.seen = set()
1181
+
1182
+
1183
+ @NameRecordVisitor.register_attrs(
1184
+ (
1185
+ (otTables.FeatureParamsSize, ("SubfamilyNameID",)),
1186
+ (otTables.FeatureParamsStylisticSet, ("UINameID",)),
1187
+ (otTables.STAT, ("ElidedFallbackNameID",)),
1188
+ (otTables.AxisRecord, ("AxisNameID",)),
1189
+ (otTables.AxisValue, ("ValueNameID",)),
1190
+ (otTables.FeatureName, ("FeatureNameID",)),
1191
+ (otTables.Setting, ("SettingNameID",)),
1192
+ )
1193
+ )
1194
+ def visit(visitor, obj, attr, value):
1195
+ visitor.seen.add(value)
1196
+
1197
+
1198
+ @NameRecordVisitor.register(otTables.FeatureParamsCharacterVariants)
1199
+ def visit(visitor, obj):
1200
+ for attr in ("FeatUILabelNameID", "FeatUITooltipTextNameID", "SampleTextNameID"):
1201
+ value = getattr(obj, attr)
1202
+ visitor.seen.add(value)
1203
+ # also include the sequence of UI strings for individual variants, if any
1204
+ if obj.FirstParamUILabelNameID == 0 or obj.NumNamedParameters == 0:
1205
+ return
1206
+ visitor.seen.update(
1207
+ range(
1208
+ obj.FirstParamUILabelNameID,
1209
+ obj.FirstParamUILabelNameID + obj.NumNamedParameters,
1210
+ )
1211
+ )
1212
+
1213
+
1214
+ @NameRecordVisitor.register(ttLib.getTableClass("fvar"))
1215
+ def visit(visitor, obj):
1216
+ for inst in obj.instances:
1217
+ if inst.postscriptNameID != 0xFFFF:
1218
+ visitor.seen.add(inst.postscriptNameID)
1219
+ visitor.seen.add(inst.subfamilyNameID)
1220
+
1221
+ for axis in obj.axes:
1222
+ visitor.seen.add(axis.axisNameID)
1223
+
1224
+
1225
+ @NameRecordVisitor.register(ttLib.getTableClass("CPAL"))
1226
+ def visit(visitor, obj):
1227
+ if obj.version == 1:
1228
+ visitor.seen.update(obj.paletteLabels)
1229
+ visitor.seen.update(obj.paletteEntryLabels)
1230
+
1231
+
1232
+ @NameRecordVisitor.register(ttLib.TTFont)
1233
+ def visit(visitor, font, *args, **kwargs):
1234
+ if hasattr(visitor, "font"):
1235
+ return False
1236
+
1237
+ visitor.font = font
1238
+ for tag in visitor.TABLES:
1239
+ if tag in font:
1240
+ visitor.visit(font[tag], *args, **kwargs)
1241
+ del visitor.font
1242
+ return False