fonttools 4.59.1__cp314-cp314t-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.

Potentially problematic release.


This version of fonttools might be problematic. Click here for more details.

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