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,1591 @@
1
+ from fontTools.misc.textTools import bytesjoin, safeEval, readHex
2
+ from fontTools.misc.encodingTools import getEncoding
3
+ from fontTools.ttLib import getSearchRange
4
+ from fontTools.unicode import Unicode
5
+ from . import DefaultTable
6
+ import sys
7
+ import struct
8
+ import array
9
+ import logging
10
+
11
+
12
+ log = logging.getLogger(__name__)
13
+
14
+
15
+ def _make_map(font, chars, gids):
16
+ assert len(chars) == len(gids)
17
+ glyphNames = font.getGlyphNameMany(gids)
18
+ cmap = {}
19
+ for char, gid, name in zip(chars, gids, glyphNames):
20
+ if gid == 0:
21
+ continue
22
+ cmap[char] = name
23
+ return cmap
24
+
25
+
26
+ class table__c_m_a_p(DefaultTable.DefaultTable):
27
+ """Character to Glyph Index Mapping Table
28
+
29
+ This class represents the `cmap <https://docs.microsoft.com/en-us/typography/opentype/spec/cmap>`_
30
+ table, which maps between input characters (in Unicode or other system encodings)
31
+ and glyphs within the font. The ``cmap`` table contains one or more subtables
32
+ which determine the mapping of of characters to glyphs across different platforms
33
+ and encoding systems.
34
+
35
+ ``table__c_m_a_p`` objects expose an accessor ``.tables`` which provides access
36
+ to the subtables, although it is normally easier to retrieve individual subtables
37
+ through the utility methods described below. To add new subtables to a font,
38
+ first determine the subtable format (if in doubt use format 4 for glyphs within
39
+ the BMP, format 12 for glyphs outside the BMP, and format 14 for Unicode Variation
40
+ Sequences) construct subtable objects with ``CmapSubtable.newSubtable(format)``,
41
+ and append them to the ``.tables`` list.
42
+
43
+ Within a subtable, the mapping of characters to glyphs is provided by the ``.cmap``
44
+ attribute.
45
+
46
+ Example::
47
+
48
+ cmap4_0_3 = CmapSubtable.newSubtable(4)
49
+ cmap4_0_3.platformID = 0
50
+ cmap4_0_3.platEncID = 3
51
+ cmap4_0_3.language = 0
52
+ cmap4_0_3.cmap = { 0xC1: "Aacute" }
53
+
54
+ cmap = newTable("cmap")
55
+ cmap.tableVersion = 0
56
+ cmap.tables = [cmap4_0_3]
57
+
58
+ See also https://learn.microsoft.com/en-us/typography/opentype/spec/cmap
59
+ """
60
+
61
+ def getcmap(self, platformID, platEncID):
62
+ """Returns the first subtable which matches the given platform and encoding.
63
+
64
+ Args:
65
+ platformID (int): The platform ID. Use 0 for Unicode, 1 for Macintosh
66
+ (deprecated for new fonts), 2 for ISO (deprecated) and 3 for Windows.
67
+ encodingID (int): Encoding ID. Interpretation depends on the platform ID.
68
+ See the OpenType specification for details.
69
+
70
+ Returns:
71
+ An object which is a subclass of :py:class:`CmapSubtable` if a matching
72
+ subtable is found within the font, or ``None`` otherwise.
73
+ """
74
+
75
+ for subtable in self.tables:
76
+ if subtable.platformID == platformID and subtable.platEncID == platEncID:
77
+ return subtable
78
+ return None # not found
79
+
80
+ def getBestCmap(
81
+ self,
82
+ cmapPreferences=(
83
+ (3, 10),
84
+ (0, 6),
85
+ (0, 4),
86
+ (3, 1),
87
+ (0, 3),
88
+ (0, 2),
89
+ (0, 1),
90
+ (0, 0),
91
+ ),
92
+ ):
93
+ """Returns the 'best' Unicode cmap dictionary available in the font
94
+ or ``None``, if no Unicode cmap subtable is available.
95
+
96
+ By default it will search for the following (platformID, platEncID)
97
+ pairs in order::
98
+
99
+ (3, 10), # Windows Unicode full repertoire
100
+ (0, 6), # Unicode full repertoire (format 13 subtable)
101
+ (0, 4), # Unicode 2.0 full repertoire
102
+ (3, 1), # Windows Unicode BMP
103
+ (0, 3), # Unicode 2.0 BMP
104
+ (0, 2), # Unicode ISO/IEC 10646
105
+ (0, 1), # Unicode 1.1
106
+ (0, 0) # Unicode 1.0
107
+
108
+ This particular order matches what HarfBuzz uses to choose what
109
+ subtable to use by default. This order prefers the largest-repertoire
110
+ subtable, and among those, prefers the Windows-platform over the
111
+ Unicode-platform as the former has wider support.
112
+
113
+ This order can be customized via the ``cmapPreferences`` argument.
114
+ """
115
+ for platformID, platEncID in cmapPreferences:
116
+ cmapSubtable = self.getcmap(platformID, platEncID)
117
+ if cmapSubtable is not None:
118
+ return cmapSubtable.cmap
119
+ return None # None of the requested cmap subtables were found
120
+
121
+ def buildReversed(self):
122
+ """Builds a reverse mapping dictionary
123
+
124
+ Iterates over all Unicode cmap tables and returns a dictionary mapping
125
+ glyphs to sets of codepoints, such as::
126
+
127
+ {
128
+ 'one': {0x31}
129
+ 'A': {0x41,0x391}
130
+ }
131
+
132
+ The values are sets of Unicode codepoints because
133
+ some fonts map different codepoints to the same glyph.
134
+ For example, ``U+0041 LATIN CAPITAL LETTER A`` and ``U+0391
135
+ GREEK CAPITAL LETTER ALPHA`` are sometimes the same glyph.
136
+ """
137
+ result = {}
138
+ for subtable in self.tables:
139
+ if subtable.isUnicode():
140
+ for codepoint, name in subtable.cmap.items():
141
+ result.setdefault(name, set()).add(codepoint)
142
+ return result
143
+
144
+ def buildReversedMin(self):
145
+ result = {}
146
+ for subtable in self.tables:
147
+ if subtable.isUnicode():
148
+ for codepoint, name in subtable.cmap.items():
149
+ if name in result:
150
+ result[name] = min(result[name], codepoint)
151
+ else:
152
+ result[name] = codepoint
153
+ return result
154
+
155
+ def decompile(self, data, ttFont):
156
+ tableVersion, numSubTables = struct.unpack(">HH", data[:4])
157
+ self.tableVersion = int(tableVersion)
158
+ self.tables = tables = []
159
+ seenOffsets = {}
160
+ for i in range(numSubTables):
161
+ platformID, platEncID, offset = struct.unpack(
162
+ ">HHl", data[4 + i * 8 : 4 + (i + 1) * 8]
163
+ )
164
+ platformID, platEncID = int(platformID), int(platEncID)
165
+ format, length = struct.unpack(">HH", data[offset : offset + 4])
166
+ if format in [8, 10, 12, 13]:
167
+ format, reserved, length = struct.unpack(
168
+ ">HHL", data[offset : offset + 8]
169
+ )
170
+ elif format in [14]:
171
+ format, length = struct.unpack(">HL", data[offset : offset + 6])
172
+
173
+ if not length:
174
+ log.error(
175
+ "cmap subtable is reported as having zero length: platformID %s, "
176
+ "platEncID %s, format %s offset %s. Skipping table.",
177
+ platformID,
178
+ platEncID,
179
+ format,
180
+ offset,
181
+ )
182
+ continue
183
+ table = CmapSubtable.newSubtable(format)
184
+ table.platformID = platformID
185
+ table.platEncID = platEncID
186
+ # Note that by default we decompile only the subtable header info;
187
+ # any other data gets decompiled only when an attribute of the
188
+ # subtable is referenced.
189
+ table.decompileHeader(data[offset : offset + int(length)], ttFont)
190
+ if offset in seenOffsets:
191
+ table.data = None # Mark as decompiled
192
+ table.cmap = tables[seenOffsets[offset]].cmap
193
+ else:
194
+ seenOffsets[offset] = i
195
+ tables.append(table)
196
+ if ttFont.lazy is False: # Be lazy for None and True
197
+ self.ensureDecompiled()
198
+
199
+ def ensureDecompiled(self, recurse=False):
200
+ # The recurse argument is unused, but part of the signature of
201
+ # ensureDecompiled across the library.
202
+ for st in self.tables:
203
+ st.ensureDecompiled()
204
+
205
+ def compile(self, ttFont):
206
+ self.tables.sort() # sort according to the spec; see CmapSubtable.__lt__()
207
+ numSubTables = len(self.tables)
208
+ totalOffset = 4 + 8 * numSubTables
209
+ data = struct.pack(">HH", self.tableVersion, numSubTables)
210
+ tableData = b""
211
+ seen = (
212
+ {}
213
+ ) # Some tables are the same object reference. Don't compile them twice.
214
+ done = (
215
+ {}
216
+ ) # Some tables are different objects, but compile to the same data chunk
217
+ for table in self.tables:
218
+ offset = seen.get(id(table.cmap))
219
+ if offset is None:
220
+ chunk = table.compile(ttFont)
221
+ offset = done.get(chunk)
222
+ if offset is None:
223
+ offset = seen[id(table.cmap)] = done[chunk] = totalOffset + len(
224
+ tableData
225
+ )
226
+ tableData = tableData + chunk
227
+ data = data + struct.pack(">HHl", table.platformID, table.platEncID, offset)
228
+ return data + tableData
229
+
230
+ def toXML(self, writer, ttFont):
231
+ writer.simpletag("tableVersion", version=self.tableVersion)
232
+ writer.newline()
233
+ for table in self.tables:
234
+ table.toXML(writer, ttFont)
235
+
236
+ def fromXML(self, name, attrs, content, ttFont):
237
+ if name == "tableVersion":
238
+ self.tableVersion = safeEval(attrs["version"])
239
+ return
240
+ if name[:12] != "cmap_format_":
241
+ return
242
+ if not hasattr(self, "tables"):
243
+ self.tables = []
244
+ format = safeEval(name[12:])
245
+ table = CmapSubtable.newSubtable(format)
246
+ table.platformID = safeEval(attrs["platformID"])
247
+ table.platEncID = safeEval(attrs["platEncID"])
248
+ table.fromXML(name, attrs, content, ttFont)
249
+ self.tables.append(table)
250
+
251
+
252
+ class CmapSubtable(object):
253
+ """Base class for all cmap subtable formats.
254
+
255
+ Subclasses which handle the individual subtable formats are named
256
+ ``cmap_format_0``, ``cmap_format_2`` etc. Use :py:meth:`getSubtableClass`
257
+ to retrieve the concrete subclass, or :py:meth:`newSubtable` to get a
258
+ new subtable object for a given format.
259
+
260
+ The object exposes a ``.cmap`` attribute, which contains a dictionary mapping
261
+ character codepoints to glyph names.
262
+ """
263
+
264
+ @staticmethod
265
+ def getSubtableClass(format):
266
+ """Return the subtable class for a format."""
267
+ return cmap_classes.get(format, cmap_format_unknown)
268
+
269
+ @staticmethod
270
+ def newSubtable(format):
271
+ """Return a new instance of a subtable for the given format
272
+ ."""
273
+ subtableClass = CmapSubtable.getSubtableClass(format)
274
+ return subtableClass(format)
275
+
276
+ def __init__(self, format):
277
+ self.format = format
278
+ self.data = None
279
+ self.ttFont = None
280
+ self.platformID = None #: The platform ID of this subtable
281
+ self.platEncID = None #: The encoding ID of this subtable (interpretation depends on ``platformID``)
282
+ self.language = (
283
+ None #: The language ID of this subtable (Macintosh platform only)
284
+ )
285
+
286
+ def ensureDecompiled(self, recurse=False):
287
+ # The recurse argument is unused, but part of the signature of
288
+ # ensureDecompiled across the library.
289
+ if self.data is None:
290
+ return
291
+ self.decompile(None, None) # use saved data.
292
+ self.data = None # Once this table has been decompiled, make sure we don't
293
+ # just return the original data. Also avoids recursion when
294
+ # called with an attribute that the cmap subtable doesn't have.
295
+
296
+ def __getattr__(self, attr):
297
+ # allow lazy decompilation of subtables.
298
+ if attr[:2] == "__": # don't handle requests for member functions like '__lt__'
299
+ raise AttributeError(attr)
300
+ if self.data is None:
301
+ raise AttributeError(attr)
302
+ self.ensureDecompiled()
303
+ return getattr(self, attr)
304
+
305
+ def decompileHeader(self, data, ttFont):
306
+ format, length, language = struct.unpack(">HHH", data[:6])
307
+ assert (
308
+ len(data) == length
309
+ ), "corrupt cmap table format %d (data length: %d, header length: %d)" % (
310
+ format,
311
+ len(data),
312
+ length,
313
+ )
314
+ self.format = int(format)
315
+ self.length = int(length)
316
+ self.language = int(language)
317
+ self.data = data[6:]
318
+ self.ttFont = ttFont
319
+
320
+ def toXML(self, writer, ttFont):
321
+ writer.begintag(
322
+ self.__class__.__name__,
323
+ [
324
+ ("platformID", self.platformID),
325
+ ("platEncID", self.platEncID),
326
+ ("language", self.language),
327
+ ],
328
+ )
329
+ writer.newline()
330
+ codes = sorted(self.cmap.items())
331
+ self._writeCodes(codes, writer)
332
+ writer.endtag(self.__class__.__name__)
333
+ writer.newline()
334
+
335
+ def getEncoding(self, default=None):
336
+ """Returns the Python encoding name for this cmap subtable based on its platformID,
337
+ platEncID, and language. If encoding for these values is not known, by default
338
+ ``None`` is returned. That can be overridden by passing a value to the ``default``
339
+ argument.
340
+
341
+ Note that if you want to choose a "preferred" cmap subtable, most of the time
342
+ ``self.isUnicode()`` is what you want as that one only returns true for the modern,
343
+ commonly used, Unicode-compatible triplets, not the legacy ones.
344
+ """
345
+ return getEncoding(self.platformID, self.platEncID, self.language, default)
346
+
347
+ def isUnicode(self):
348
+ """Returns true if the characters are interpreted as Unicode codepoints."""
349
+ return self.platformID == 0 or (
350
+ self.platformID == 3 and self.platEncID in [0, 1, 10]
351
+ )
352
+
353
+ def isSymbol(self):
354
+ """Returns true if the subtable is for the Symbol encoding (3,0)"""
355
+ return self.platformID == 3 and self.platEncID == 0
356
+
357
+ def _writeCodes(self, codes, writer):
358
+ isUnicode = self.isUnicode()
359
+ for code, name in codes:
360
+ writer.simpletag("map", code=hex(code), name=name)
361
+ if isUnicode:
362
+ writer.comment(Unicode[code])
363
+ writer.newline()
364
+
365
+ def __lt__(self, other):
366
+ if not isinstance(other, CmapSubtable):
367
+ return NotImplemented
368
+
369
+ # implemented so that list.sort() sorts according to the spec.
370
+ selfTuple = (
371
+ getattr(self, "platformID", None),
372
+ getattr(self, "platEncID", None),
373
+ getattr(self, "language", None),
374
+ self.__dict__,
375
+ )
376
+ otherTuple = (
377
+ getattr(other, "platformID", None),
378
+ getattr(other, "platEncID", None),
379
+ getattr(other, "language", None),
380
+ other.__dict__,
381
+ )
382
+ return selfTuple < otherTuple
383
+
384
+
385
+ class cmap_format_0(CmapSubtable):
386
+ def decompile(self, data, ttFont):
387
+ # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None.
388
+ # If not, someone is calling the subtable decompile() directly, and must provide both args.
389
+ if data is not None and ttFont is not None:
390
+ self.decompileHeader(data, ttFont)
391
+ else:
392
+ assert (
393
+ data is None and ttFont is None
394
+ ), "Need both data and ttFont arguments"
395
+ data = (
396
+ self.data
397
+ ) # decompileHeader assigns the data after the header to self.data
398
+ assert 262 == self.length, "Format 0 cmap subtable not 262 bytes"
399
+ gids = array.array("B")
400
+ gids.frombytes(self.data)
401
+ charCodes = range(len(gids))
402
+ self.cmap = _make_map(self.ttFont, charCodes, gids)
403
+
404
+ def compile(self, ttFont):
405
+ if self.data:
406
+ return struct.pack(">HHH", 0, 262, self.language) + self.data
407
+
408
+ cmap = self.cmap
409
+ assert set(cmap.keys()).issubset(range(256))
410
+ getGlyphID = ttFont.getGlyphID
411
+ valueList = [getGlyphID(cmap[i]) if i in cmap else 0 for i in range(256)]
412
+
413
+ gids = array.array("B", valueList)
414
+ data = struct.pack(">HHH", 0, 262, self.language) + gids.tobytes()
415
+ assert len(data) == 262
416
+ return data
417
+
418
+ def fromXML(self, name, attrs, content, ttFont):
419
+ self.language = safeEval(attrs["language"])
420
+ if not hasattr(self, "cmap"):
421
+ self.cmap = {}
422
+ cmap = self.cmap
423
+ for element in content:
424
+ if not isinstance(element, tuple):
425
+ continue
426
+ name, attrs, content = element
427
+ if name != "map":
428
+ continue
429
+ cmap[safeEval(attrs["code"])] = attrs["name"]
430
+
431
+
432
+ subHeaderFormat = ">HHhH"
433
+
434
+
435
+ class SubHeader(object):
436
+ def __init__(self):
437
+ self.firstCode = None
438
+ self.entryCount = None
439
+ self.idDelta = None
440
+ self.idRangeOffset = None
441
+ self.glyphIndexArray = []
442
+
443
+
444
+ class cmap_format_2(CmapSubtable):
445
+ def setIDDelta(self, subHeader):
446
+ subHeader.idDelta = 0
447
+ # find the minGI which is not zero.
448
+ minGI = subHeader.glyphIndexArray[0]
449
+ for gid in subHeader.glyphIndexArray:
450
+ if (gid != 0) and (gid < minGI):
451
+ minGI = gid
452
+ # The lowest gid in glyphIndexArray, after subtracting idDelta, must be 1.
453
+ # idDelta is a short, and must be between -32K and 32K. minGI can be between 1 and 64K.
454
+ # We would like to pick an idDelta such that the first glyphArray GID is 1,
455
+ # so that we are more likely to be able to combine glypharray GID subranges.
456
+ # This means that we have a problem when minGI is > 32K
457
+ # Since the final gi is reconstructed from the glyphArray GID by:
458
+ # (short)finalGID = (gid + idDelta) % 0x10000),
459
+ # we can get from a glypharray GID of 1 to a final GID of 65K by subtracting 2, and casting the
460
+ # negative number to an unsigned short.
461
+
462
+ if minGI > 1:
463
+ if minGI > 0x7FFF:
464
+ subHeader.idDelta = -(0x10000 - minGI) - 1
465
+ else:
466
+ subHeader.idDelta = minGI - 1
467
+ idDelta = subHeader.idDelta
468
+ for i in range(subHeader.entryCount):
469
+ gid = subHeader.glyphIndexArray[i]
470
+ if gid > 0:
471
+ subHeader.glyphIndexArray[i] = gid - idDelta
472
+
473
+ def decompile(self, data, ttFont):
474
+ # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None.
475
+ # If not, someone is calling the subtable decompile() directly, and must provide both args.
476
+ if data is not None and ttFont is not None:
477
+ self.decompileHeader(data, ttFont)
478
+ else:
479
+ assert (
480
+ data is None and ttFont is None
481
+ ), "Need both data and ttFont arguments"
482
+
483
+ data = (
484
+ self.data
485
+ ) # decompileHeader assigns the data after the header to self.data
486
+ subHeaderKeys = []
487
+ maxSubHeaderindex = 0
488
+ # get the key array, and determine the number of subHeaders.
489
+ allKeys = array.array("H")
490
+ allKeys.frombytes(data[:512])
491
+ data = data[512:]
492
+ if sys.byteorder != "big":
493
+ allKeys.byteswap()
494
+ subHeaderKeys = [key // 8 for key in allKeys]
495
+ maxSubHeaderindex = max(subHeaderKeys)
496
+
497
+ # Load subHeaders
498
+ subHeaderList = []
499
+ pos = 0
500
+ for i in range(maxSubHeaderindex + 1):
501
+ subHeader = SubHeader()
502
+ (
503
+ subHeader.firstCode,
504
+ subHeader.entryCount,
505
+ subHeader.idDelta,
506
+ subHeader.idRangeOffset,
507
+ ) = struct.unpack(subHeaderFormat, data[pos : pos + 8])
508
+ pos += 8
509
+ giDataPos = pos + subHeader.idRangeOffset - 2
510
+ giList = array.array("H")
511
+ giList.frombytes(data[giDataPos : giDataPos + subHeader.entryCount * 2])
512
+ if sys.byteorder != "big":
513
+ giList.byteswap()
514
+ subHeader.glyphIndexArray = giList
515
+ subHeaderList.append(subHeader)
516
+ # How this gets processed.
517
+ # Charcodes may be one or two bytes.
518
+ # The first byte of a charcode is mapped through the subHeaderKeys, to select
519
+ # a subHeader. For any subheader but 0, the next byte is then mapped through the
520
+ # selected subheader. If subheader Index 0 is selected, then the byte itself is
521
+ # mapped through the subheader, and there is no second byte.
522
+ # Then assume that the subsequent byte is the first byte of the next charcode,and repeat.
523
+ #
524
+ # Each subheader references a range in the glyphIndexArray whose length is entryCount.
525
+ # The range in glyphIndexArray referenced by a sunheader may overlap with the range in glyphIndexArray
526
+ # referenced by another subheader.
527
+ # The only subheader that will be referenced by more than one first-byte value is the subheader
528
+ # that maps the entire range of glyphID values to glyphIndex 0, e.g notdef:
529
+ # {firstChar 0, EntryCount 0,idDelta 0,idRangeOffset xx}
530
+ # A byte being mapped though a subheader is treated as in index into a mapping of array index to font glyphIndex.
531
+ # A subheader specifies a subrange within (0...256) by the
532
+ # firstChar and EntryCount values. If the byte value is outside the subrange, then the glyphIndex is zero
533
+ # (e.g. glyph not in font).
534
+ # If the byte index is in the subrange, then an offset index is calculated as (byteIndex - firstChar).
535
+ # The index to glyphIndex mapping is a subrange of the glyphIndexArray. You find the start of the subrange by
536
+ # counting idRangeOffset bytes from the idRangeOffset word. The first value in this subrange is the
537
+ # glyphIndex for the index firstChar. The offset index should then be used in this array to get the glyphIndex.
538
+ # Example for Logocut-Medium
539
+ # first byte of charcode = 129; selects subheader 1.
540
+ # subheader 1 = {firstChar 64, EntryCount 108,idDelta 42,idRangeOffset 0252}
541
+ # second byte of charCode = 66
542
+ # the index offset = 66-64 = 2.
543
+ # The subrange of the glyphIndexArray starting at 0x0252 bytes from the idRangeOffset word is:
544
+ # [glyphIndexArray index], [subrange array index] = glyphIndex
545
+ # [256], [0]=1 from charcode [129, 64]
546
+ # [257], [1]=2 from charcode [129, 65]
547
+ # [258], [2]=3 from charcode [129, 66]
548
+ # [259], [3]=4 from charcode [129, 67]
549
+ # So, the glyphIndex = 3 from the array. Then if idDelta is not zero and the glyph ID is not zero,
550
+ # add it to the glyphID to get the final glyphIndex
551
+ # value. In this case the final glyph index = 3+ 42 -> 45 for the final glyphIndex. Whew!
552
+
553
+ self.data = b""
554
+ cmap = {}
555
+ notdefGI = 0
556
+ for firstByte in range(256):
557
+ subHeadindex = subHeaderKeys[firstByte]
558
+ subHeader = subHeaderList[subHeadindex]
559
+ if subHeadindex == 0:
560
+ if (firstByte < subHeader.firstCode) or (
561
+ firstByte >= subHeader.firstCode + subHeader.entryCount
562
+ ):
563
+ continue # gi is notdef.
564
+ else:
565
+ charCode = firstByte
566
+ offsetIndex = firstByte - subHeader.firstCode
567
+ gi = subHeader.glyphIndexArray[offsetIndex]
568
+ if gi != 0:
569
+ gi = (gi + subHeader.idDelta) % 0x10000
570
+ else:
571
+ continue # gi is notdef.
572
+ cmap[charCode] = gi
573
+ else:
574
+ if subHeader.entryCount:
575
+ charCodeOffset = firstByte * 256 + subHeader.firstCode
576
+ for offsetIndex in range(subHeader.entryCount):
577
+ charCode = charCodeOffset + offsetIndex
578
+ gi = subHeader.glyphIndexArray[offsetIndex]
579
+ if gi != 0:
580
+ gi = (gi + subHeader.idDelta) % 0x10000
581
+ else:
582
+ continue
583
+ cmap[charCode] = gi
584
+ # If not subHeader.entryCount, then all char codes with this first byte are
585
+ # mapped to .notdef. We can skip this subtable, and leave the glyphs un-encoded, which is the
586
+ # same as mapping it to .notdef.
587
+
588
+ gids = list(cmap.values())
589
+ charCodes = list(cmap.keys())
590
+ self.cmap = _make_map(self.ttFont, charCodes, gids)
591
+
592
+ def compile(self, ttFont):
593
+ if self.data:
594
+ return (
595
+ struct.pack(">HHH", self.format, self.length, self.language) + self.data
596
+ )
597
+ kEmptyTwoCharCodeRange = -1
598
+ notdefGI = 0
599
+
600
+ items = sorted(self.cmap.items())
601
+ charCodes = [item[0] for item in items]
602
+ names = [item[1] for item in items]
603
+ nameMap = ttFont.getReverseGlyphMap()
604
+ try:
605
+ gids = [nameMap[name] for name in names]
606
+ except KeyError:
607
+ nameMap = ttFont.getReverseGlyphMap(rebuild=True)
608
+ try:
609
+ gids = [nameMap[name] for name in names]
610
+ except KeyError:
611
+ # allow virtual GIDs in format 2 tables
612
+ gids = []
613
+ for name in names:
614
+ try:
615
+ gid = nameMap[name]
616
+ except KeyError:
617
+ try:
618
+ if name[:3] == "gid":
619
+ gid = int(name[3:])
620
+ else:
621
+ gid = ttFont.getGlyphID(name)
622
+ except:
623
+ raise KeyError(name)
624
+
625
+ gids.append(gid)
626
+
627
+ # Process the (char code to gid) item list in char code order.
628
+ # By definition, all one byte char codes map to subheader 0.
629
+ # For all the two byte char codes, we assume that the first byte maps maps to the empty subhead (with an entry count of 0,
630
+ # which defines all char codes in its range to map to notdef) unless proven otherwise.
631
+ # Note that since the char code items are processed in char code order, all the char codes with the
632
+ # same first byte are in sequential order.
633
+
634
+ subHeaderKeys = [
635
+ kEmptyTwoCharCodeRange for x in range(256)
636
+ ] # list of indices into subHeaderList.
637
+ subHeaderList = []
638
+
639
+ # We force this subheader entry 0 to exist in the subHeaderList in the case where some one comes up
640
+ # with a cmap where all the one byte char codes map to notdef,
641
+ # with the result that the subhead 0 would not get created just by processing the item list.
642
+ charCode = charCodes[0]
643
+ if charCode > 255:
644
+ subHeader = SubHeader()
645
+ subHeader.firstCode = 0
646
+ subHeader.entryCount = 0
647
+ subHeader.idDelta = 0
648
+ subHeader.idRangeOffset = 0
649
+ subHeaderList.append(subHeader)
650
+
651
+ lastFirstByte = -1
652
+ items = zip(charCodes, gids)
653
+ for charCode, gid in items:
654
+ if gid == 0:
655
+ continue
656
+ firstbyte = charCode >> 8
657
+ secondByte = charCode & 0x00FF
658
+
659
+ if (
660
+ firstbyte != lastFirstByte
661
+ ): # Need to update the current subhead, and start a new one.
662
+ if lastFirstByte > -1:
663
+ # fix GI's and iDelta of current subheader.
664
+ self.setIDDelta(subHeader)
665
+
666
+ # If it was sunheader 0 for one-byte charCodes, then we need to set the subHeaderKeys value to zero
667
+ # for the indices matching the char codes.
668
+ if lastFirstByte == 0:
669
+ for index in range(subHeader.entryCount):
670
+ charCode = subHeader.firstCode + index
671
+ subHeaderKeys[charCode] = 0
672
+
673
+ assert subHeader.entryCount == len(
674
+ subHeader.glyphIndexArray
675
+ ), "Error - subhead entry count does not match len of glyphID subrange."
676
+ # init new subheader
677
+ subHeader = SubHeader()
678
+ subHeader.firstCode = secondByte
679
+ subHeader.entryCount = 1
680
+ subHeader.glyphIndexArray.append(gid)
681
+ subHeaderList.append(subHeader)
682
+ subHeaderKeys[firstbyte] = len(subHeaderList) - 1
683
+ lastFirstByte = firstbyte
684
+ else:
685
+ # need to fill in with notdefs all the code points between the last charCode and the current charCode.
686
+ codeDiff = secondByte - (subHeader.firstCode + subHeader.entryCount)
687
+ for i in range(codeDiff):
688
+ subHeader.glyphIndexArray.append(notdefGI)
689
+ subHeader.glyphIndexArray.append(gid)
690
+ subHeader.entryCount = subHeader.entryCount + codeDiff + 1
691
+
692
+ # fix GI's and iDelta of last subheader that we we added to the subheader array.
693
+ self.setIDDelta(subHeader)
694
+
695
+ # Now we add a final subheader for the subHeaderKeys which maps to empty two byte charcode ranges.
696
+ subHeader = SubHeader()
697
+ subHeader.firstCode = 0
698
+ subHeader.entryCount = 0
699
+ subHeader.idDelta = 0
700
+ subHeader.idRangeOffset = 2
701
+ subHeaderList.append(subHeader)
702
+ emptySubheadIndex = len(subHeaderList) - 1
703
+ for index in range(256):
704
+ if subHeaderKeys[index] == kEmptyTwoCharCodeRange:
705
+ subHeaderKeys[index] = emptySubheadIndex
706
+ # Since this is the last subheader, the GlyphIndex Array starts two bytes after the start of the
707
+ # idRangeOffset word of this subHeader. We can safely point to the first entry in the GlyphIndexArray,
708
+ # since the first subrange of the GlyphIndexArray is for subHeader 0, which always starts with
709
+ # charcode 0 and GID 0.
710
+
711
+ idRangeOffset = (
712
+ len(subHeaderList) - 1
713
+ ) * 8 + 2 # offset to beginning of glyphIDArray from first subheader idRangeOffset.
714
+ subheadRangeLen = (
715
+ len(subHeaderList) - 1
716
+ ) # skip last special empty-set subheader; we've already hardocodes its idRangeOffset to 2.
717
+ for index in range(subheadRangeLen):
718
+ subHeader = subHeaderList[index]
719
+ subHeader.idRangeOffset = 0
720
+ for j in range(index):
721
+ prevSubhead = subHeaderList[j]
722
+ if (
723
+ prevSubhead.glyphIndexArray == subHeader.glyphIndexArray
724
+ ): # use the glyphIndexArray subarray
725
+ subHeader.idRangeOffset = (
726
+ prevSubhead.idRangeOffset - (index - j) * 8
727
+ )
728
+ subHeader.glyphIndexArray = []
729
+ break
730
+ if subHeader.idRangeOffset == 0: # didn't find one.
731
+ subHeader.idRangeOffset = idRangeOffset
732
+ idRangeOffset = (
733
+ idRangeOffset - 8
734
+ ) + subHeader.entryCount * 2 # one less subheader, one more subArray.
735
+ else:
736
+ idRangeOffset = idRangeOffset - 8 # one less subheader
737
+
738
+ # Now we can write out the data!
739
+ length = (
740
+ 6 + 512 + 8 * len(subHeaderList)
741
+ ) # header, 256 subHeaderKeys, and subheader array.
742
+ for subhead in subHeaderList[:-1]:
743
+ length = (
744
+ length + len(subhead.glyphIndexArray) * 2
745
+ ) # We can't use subhead.entryCount, as some of the subhead may share subArrays.
746
+ dataList = [struct.pack(">HHH", 2, length, self.language)]
747
+ for index in subHeaderKeys:
748
+ dataList.append(struct.pack(">H", index * 8))
749
+ for subhead in subHeaderList:
750
+ dataList.append(
751
+ struct.pack(
752
+ subHeaderFormat,
753
+ subhead.firstCode,
754
+ subhead.entryCount,
755
+ subhead.idDelta,
756
+ subhead.idRangeOffset,
757
+ )
758
+ )
759
+ for subhead in subHeaderList[:-1]:
760
+ for gi in subhead.glyphIndexArray:
761
+ dataList.append(struct.pack(">H", gi))
762
+ data = bytesjoin(dataList)
763
+ assert len(data) == length, (
764
+ "Error: cmap format 2 is not same length as calculated! actual: "
765
+ + str(len(data))
766
+ + " calc : "
767
+ + str(length)
768
+ )
769
+ return data
770
+
771
+ def fromXML(self, name, attrs, content, ttFont):
772
+ self.language = safeEval(attrs["language"])
773
+ if not hasattr(self, "cmap"):
774
+ self.cmap = {}
775
+ cmap = self.cmap
776
+
777
+ for element in content:
778
+ if not isinstance(element, tuple):
779
+ continue
780
+ name, attrs, content = element
781
+ if name != "map":
782
+ continue
783
+ cmap[safeEval(attrs["code"])] = attrs["name"]
784
+
785
+
786
+ cmap_format_4_format = ">7H"
787
+
788
+ # uint16 endCode[segCount] # Ending character code for each segment, last = 0xFFFF.
789
+ # uint16 reservedPad # This value should be zero
790
+ # uint16 startCode[segCount] # Starting character code for each segment
791
+ # uint16 idDelta[segCount] # Delta for all character codes in segment
792
+ # uint16 idRangeOffset[segCount] # Offset in bytes to glyph indexArray, or 0
793
+ # uint16 glyphIndexArray[variable] # Glyph index array
794
+
795
+
796
+ def splitRange(startCode, endCode, cmap):
797
+ # Try to split a range of character codes into subranges with consecutive
798
+ # glyph IDs in such a way that the cmap4 subtable can be stored "most"
799
+ # efficiently. I can't prove I've got the optimal solution, but it seems
800
+ # to do well with the fonts I tested: none became bigger, many became smaller.
801
+ if startCode == endCode:
802
+ return [], [endCode]
803
+
804
+ lastID = cmap[startCode]
805
+ lastCode = startCode
806
+ inOrder = None
807
+ orderedBegin = None
808
+ subRanges = []
809
+
810
+ # Gather subranges in which the glyph IDs are consecutive.
811
+ for code in range(startCode + 1, endCode + 1):
812
+ glyphID = cmap[code]
813
+
814
+ if glyphID - 1 == lastID:
815
+ if inOrder is None or not inOrder:
816
+ inOrder = 1
817
+ orderedBegin = lastCode
818
+ else:
819
+ if inOrder:
820
+ inOrder = 0
821
+ subRanges.append((orderedBegin, lastCode))
822
+ orderedBegin = None
823
+
824
+ lastID = glyphID
825
+ lastCode = code
826
+
827
+ if inOrder:
828
+ subRanges.append((orderedBegin, lastCode))
829
+ assert lastCode == endCode
830
+
831
+ # Now filter out those new subranges that would only make the data bigger.
832
+ # A new segment cost 8 bytes, not using a new segment costs 2 bytes per
833
+ # character.
834
+ newRanges = []
835
+ for b, e in subRanges:
836
+ if b == startCode and e == endCode:
837
+ break # the whole range, we're fine
838
+ if b == startCode or e == endCode:
839
+ threshold = 4 # split costs one more segment
840
+ else:
841
+ threshold = 8 # split costs two more segments
842
+ if (e - b + 1) > threshold:
843
+ newRanges.append((b, e))
844
+ subRanges = newRanges
845
+
846
+ if not subRanges:
847
+ return [], [endCode]
848
+
849
+ if subRanges[0][0] != startCode:
850
+ subRanges.insert(0, (startCode, subRanges[0][0] - 1))
851
+ if subRanges[-1][1] != endCode:
852
+ subRanges.append((subRanges[-1][1] + 1, endCode))
853
+
854
+ # Fill the "holes" in the segments list -- those are the segments in which
855
+ # the glyph IDs are _not_ consecutive.
856
+ i = 1
857
+ while i < len(subRanges):
858
+ if subRanges[i - 1][1] + 1 != subRanges[i][0]:
859
+ subRanges.insert(i, (subRanges[i - 1][1] + 1, subRanges[i][0] - 1))
860
+ i = i + 1
861
+ i = i + 1
862
+
863
+ # Transform the ranges into startCode/endCode lists.
864
+ start = []
865
+ end = []
866
+ for b, e in subRanges:
867
+ start.append(b)
868
+ end.append(e)
869
+ start.pop(0)
870
+
871
+ assert len(start) + 1 == len(end)
872
+ return start, end
873
+
874
+
875
+ class cmap_format_4(CmapSubtable):
876
+ def decompile(self, data, ttFont):
877
+ # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None.
878
+ # If not, someone is calling the subtable decompile() directly, and must provide both args.
879
+ if data is not None and ttFont is not None:
880
+ self.decompileHeader(data, ttFont)
881
+ else:
882
+ assert (
883
+ data is None and ttFont is None
884
+ ), "Need both data and ttFont arguments"
885
+
886
+ data = (
887
+ self.data
888
+ ) # decompileHeader assigns the data after the header to self.data
889
+ (segCountX2, searchRange, entrySelector, rangeShift) = struct.unpack(
890
+ ">4H", data[:8]
891
+ )
892
+ data = data[8:]
893
+ segCount = segCountX2 // 2
894
+
895
+ allCodes = array.array("H")
896
+ allCodes.frombytes(data)
897
+ self.data = data = None
898
+
899
+ if sys.byteorder != "big":
900
+ allCodes.byteswap()
901
+
902
+ # divide the data
903
+ endCode = allCodes[:segCount]
904
+ allCodes = allCodes[segCount + 1 :] # the +1 is skipping the reservedPad field
905
+ startCode = allCodes[:segCount]
906
+ allCodes = allCodes[segCount:]
907
+ idDelta = allCodes[:segCount]
908
+ allCodes = allCodes[segCount:]
909
+ idRangeOffset = allCodes[:segCount]
910
+ glyphIndexArray = allCodes[segCount:]
911
+ lenGIArray = len(glyphIndexArray)
912
+
913
+ # build 2-byte character mapping
914
+ charCodes = []
915
+ gids = []
916
+ for i in range(len(startCode) - 1): # don't do 0xffff!
917
+ start = startCode[i]
918
+ delta = idDelta[i]
919
+ rangeOffset = idRangeOffset[i]
920
+ partial = rangeOffset // 2 - start + i - len(idRangeOffset)
921
+
922
+ rangeCharCodes = list(range(startCode[i], endCode[i] + 1))
923
+ charCodes.extend(rangeCharCodes)
924
+ if rangeOffset == 0:
925
+ gids.extend(
926
+ [(charCode + delta) & 0xFFFF for charCode in rangeCharCodes]
927
+ )
928
+ else:
929
+ for charCode in rangeCharCodes:
930
+ index = charCode + partial
931
+ assert index < lenGIArray, (
932
+ "In format 4 cmap, range (%d), the calculated index (%d) into the glyph index array is not less than the length of the array (%d) !"
933
+ % (i, index, lenGIArray)
934
+ )
935
+ if glyphIndexArray[index] != 0: # if not missing glyph
936
+ glyphID = glyphIndexArray[index] + delta
937
+ else:
938
+ glyphID = 0 # missing glyph
939
+ gids.append(glyphID & 0xFFFF)
940
+
941
+ self.cmap = _make_map(self.ttFont, charCodes, gids)
942
+
943
+ def compile(self, ttFont):
944
+ if self.data:
945
+ return (
946
+ struct.pack(">HHH", self.format, self.length, self.language) + self.data
947
+ )
948
+
949
+ charCodes = list(self.cmap.keys())
950
+ if not charCodes:
951
+ startCode = [0xFFFF]
952
+ endCode = [0xFFFF]
953
+ else:
954
+ charCodes.sort()
955
+ names = [self.cmap[code] for code in charCodes]
956
+ nameMap = ttFont.getReverseGlyphMap()
957
+ try:
958
+ gids = [nameMap[name] for name in names]
959
+ except KeyError:
960
+ nameMap = ttFont.getReverseGlyphMap(rebuild=True)
961
+ try:
962
+ gids = [nameMap[name] for name in names]
963
+ except KeyError:
964
+ # allow virtual GIDs in format 4 tables
965
+ gids = []
966
+ for name in names:
967
+ try:
968
+ gid = nameMap[name]
969
+ except KeyError:
970
+ try:
971
+ if name[:3] == "gid":
972
+ gid = int(name[3:])
973
+ else:
974
+ gid = ttFont.getGlyphID(name)
975
+ except:
976
+ raise KeyError(name)
977
+
978
+ gids.append(gid)
979
+ cmap = {} # code:glyphID mapping
980
+ for code, gid in zip(charCodes, gids):
981
+ cmap[code] = gid
982
+
983
+ # Build startCode and endCode lists.
984
+ # Split the char codes in ranges of consecutive char codes, then split
985
+ # each range in more ranges of consecutive/not consecutive glyph IDs.
986
+ # See splitRange().
987
+ lastCode = charCodes[0]
988
+ endCode = []
989
+ startCode = [lastCode]
990
+ for charCode in charCodes[
991
+ 1:
992
+ ]: # skip the first code, it's the first start code
993
+ if charCode == lastCode + 1:
994
+ lastCode = charCode
995
+ continue
996
+ start, end = splitRange(startCode[-1], lastCode, cmap)
997
+ startCode.extend(start)
998
+ endCode.extend(end)
999
+ startCode.append(charCode)
1000
+ lastCode = charCode
1001
+ start, end = splitRange(startCode[-1], lastCode, cmap)
1002
+ startCode.extend(start)
1003
+ endCode.extend(end)
1004
+ startCode.append(0xFFFF)
1005
+ endCode.append(0xFFFF)
1006
+
1007
+ # build up rest of cruft
1008
+ idDelta = []
1009
+ idRangeOffset = []
1010
+ glyphIndexArray = []
1011
+ for i in range(len(endCode) - 1): # skip the closing codes (0xffff)
1012
+ indices = []
1013
+ for charCode in range(startCode[i], endCode[i] + 1):
1014
+ indices.append(cmap[charCode])
1015
+ if indices == list(range(indices[0], indices[0] + len(indices))):
1016
+ idDelta.append((indices[0] - startCode[i]) % 0x10000)
1017
+ idRangeOffset.append(0)
1018
+ else:
1019
+ idDelta.append(0)
1020
+ idRangeOffset.append(2 * (len(endCode) + len(glyphIndexArray) - i))
1021
+ glyphIndexArray.extend(indices)
1022
+ idDelta.append(1) # 0xffff + 1 == (tadaa!) 0. So this end code maps to .notdef
1023
+ idRangeOffset.append(0)
1024
+
1025
+ # Insane.
1026
+ segCount = len(endCode)
1027
+ segCountX2 = segCount * 2
1028
+ searchRange, entrySelector, rangeShift = getSearchRange(segCount, 2)
1029
+
1030
+ charCodeArray = array.array("H", endCode + [0] + startCode)
1031
+ idDeltaArray = array.array("H", idDelta)
1032
+ restArray = array.array("H", idRangeOffset + glyphIndexArray)
1033
+ if sys.byteorder != "big":
1034
+ charCodeArray.byteswap()
1035
+ if sys.byteorder != "big":
1036
+ idDeltaArray.byteswap()
1037
+ if sys.byteorder != "big":
1038
+ restArray.byteswap()
1039
+ data = charCodeArray.tobytes() + idDeltaArray.tobytes() + restArray.tobytes()
1040
+
1041
+ length = struct.calcsize(cmap_format_4_format) + len(data)
1042
+ header = struct.pack(
1043
+ cmap_format_4_format,
1044
+ self.format,
1045
+ length,
1046
+ self.language,
1047
+ segCountX2,
1048
+ searchRange,
1049
+ entrySelector,
1050
+ rangeShift,
1051
+ )
1052
+ return header + data
1053
+
1054
+ def fromXML(self, name, attrs, content, ttFont):
1055
+ self.language = safeEval(attrs["language"])
1056
+ if not hasattr(self, "cmap"):
1057
+ self.cmap = {}
1058
+ cmap = self.cmap
1059
+
1060
+ for element in content:
1061
+ if not isinstance(element, tuple):
1062
+ continue
1063
+ nameMap, attrsMap, dummyContent = element
1064
+ if nameMap != "map":
1065
+ assert 0, "Unrecognized keyword in cmap subtable"
1066
+ cmap[safeEval(attrsMap["code"])] = attrsMap["name"]
1067
+
1068
+
1069
+ class cmap_format_6(CmapSubtable):
1070
+ def decompile(self, data, ttFont):
1071
+ # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None.
1072
+ # If not, someone is calling the subtable decompile() directly, and must provide both args.
1073
+ if data is not None and ttFont is not None:
1074
+ self.decompileHeader(data, ttFont)
1075
+ else:
1076
+ assert (
1077
+ data is None and ttFont is None
1078
+ ), "Need both data and ttFont arguments"
1079
+
1080
+ data = (
1081
+ self.data
1082
+ ) # decompileHeader assigns the data after the header to self.data
1083
+ firstCode, entryCount = struct.unpack(">HH", data[:4])
1084
+ firstCode = int(firstCode)
1085
+ data = data[4:]
1086
+ # assert len(data) == 2 * entryCount # XXX not true in Apple's Helvetica!!!
1087
+ gids = array.array("H")
1088
+ gids.frombytes(data[: 2 * int(entryCount)])
1089
+ if sys.byteorder != "big":
1090
+ gids.byteswap()
1091
+ self.data = data = None
1092
+
1093
+ charCodes = list(range(firstCode, firstCode + len(gids)))
1094
+ self.cmap = _make_map(self.ttFont, charCodes, gids)
1095
+
1096
+ def compile(self, ttFont):
1097
+ if self.data:
1098
+ return (
1099
+ struct.pack(">HHH", self.format, self.length, self.language) + self.data
1100
+ )
1101
+ cmap = self.cmap
1102
+ codes = sorted(cmap.keys())
1103
+ if codes: # yes, there are empty cmap tables.
1104
+ codes = list(range(codes[0], codes[-1] + 1))
1105
+ firstCode = codes[0]
1106
+ valueList = [
1107
+ ttFont.getGlyphID(cmap[code]) if code in cmap else 0 for code in codes
1108
+ ]
1109
+ gids = array.array("H", valueList)
1110
+ if sys.byteorder != "big":
1111
+ gids.byteswap()
1112
+ data = gids.tobytes()
1113
+ else:
1114
+ data = b""
1115
+ firstCode = 0
1116
+ header = struct.pack(
1117
+ ">HHHHH", 6, len(data) + 10, self.language, firstCode, len(codes)
1118
+ )
1119
+ return header + data
1120
+
1121
+ def fromXML(self, name, attrs, content, ttFont):
1122
+ self.language = safeEval(attrs["language"])
1123
+ if not hasattr(self, "cmap"):
1124
+ self.cmap = {}
1125
+ cmap = self.cmap
1126
+
1127
+ for element in content:
1128
+ if not isinstance(element, tuple):
1129
+ continue
1130
+ name, attrs, content = element
1131
+ if name != "map":
1132
+ continue
1133
+ cmap[safeEval(attrs["code"])] = attrs["name"]
1134
+
1135
+
1136
+ class cmap_format_12_or_13(CmapSubtable):
1137
+ def __init__(self, format):
1138
+ self.format = format
1139
+ self.reserved = 0
1140
+ self.data = None
1141
+ self.ttFont = None
1142
+
1143
+ def decompileHeader(self, data, ttFont):
1144
+ format, reserved, length, language, nGroups = struct.unpack(">HHLLL", data[:16])
1145
+ assert (
1146
+ len(data) == (16 + nGroups * 12) == (length)
1147
+ ), "corrupt cmap table format %d (data length: %d, header length: %d)" % (
1148
+ self.format,
1149
+ len(data),
1150
+ length,
1151
+ )
1152
+ self.format = format
1153
+ self.reserved = reserved
1154
+ self.length = length
1155
+ self.language = language
1156
+ self.nGroups = nGroups
1157
+ self.data = data[16:]
1158
+ self.ttFont = ttFont
1159
+
1160
+ def decompile(self, data, ttFont):
1161
+ # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None.
1162
+ # If not, someone is calling the subtable decompile() directly, and must provide both args.
1163
+ if data is not None and ttFont is not None:
1164
+ self.decompileHeader(data, ttFont)
1165
+ else:
1166
+ assert (
1167
+ data is None and ttFont is None
1168
+ ), "Need both data and ttFont arguments"
1169
+
1170
+ data = (
1171
+ self.data
1172
+ ) # decompileHeader assigns the data after the header to self.data
1173
+ charCodes = []
1174
+ gids = []
1175
+ pos = 0
1176
+ groups = array.array("I", data[: self.nGroups * 12])
1177
+ if sys.byteorder != "big":
1178
+ groups.byteswap()
1179
+ for i in range(self.nGroups):
1180
+ startCharCode = groups[i * 3]
1181
+ endCharCode = groups[i * 3 + 1]
1182
+ glyphID = groups[i * 3 + 2]
1183
+ lenGroup = 1 + endCharCode - startCharCode
1184
+ charCodes.extend(range(startCharCode, endCharCode + 1))
1185
+ gids.extend(self._computeGIDs(glyphID, lenGroup))
1186
+ self.data = data = None
1187
+ self.cmap = _make_map(self.ttFont, charCodes, gids)
1188
+
1189
+ def compile(self, ttFont):
1190
+ if self.data:
1191
+ return (
1192
+ struct.pack(
1193
+ ">HHLLL",
1194
+ self.format,
1195
+ self.reserved,
1196
+ self.length,
1197
+ self.language,
1198
+ self.nGroups,
1199
+ )
1200
+ + self.data
1201
+ )
1202
+ charCodes = list(self.cmap.keys())
1203
+ names = list(self.cmap.values())
1204
+ nameMap = ttFont.getReverseGlyphMap()
1205
+ try:
1206
+ gids = [nameMap[name] for name in names]
1207
+ except KeyError:
1208
+ nameMap = ttFont.getReverseGlyphMap(rebuild=True)
1209
+ try:
1210
+ gids = [nameMap[name] for name in names]
1211
+ except KeyError:
1212
+ # allow virtual GIDs in format 12 tables
1213
+ gids = []
1214
+ for name in names:
1215
+ try:
1216
+ gid = nameMap[name]
1217
+ except KeyError:
1218
+ try:
1219
+ if name[:3] == "gid":
1220
+ gid = int(name[3:])
1221
+ else:
1222
+ gid = ttFont.getGlyphID(name)
1223
+ except:
1224
+ raise KeyError(name)
1225
+
1226
+ gids.append(gid)
1227
+
1228
+ cmap = {} # code:glyphID mapping
1229
+ for code, gid in zip(charCodes, gids):
1230
+ cmap[code] = gid
1231
+
1232
+ charCodes.sort()
1233
+ index = 0
1234
+ startCharCode = charCodes[0]
1235
+ startGlyphID = cmap[startCharCode]
1236
+ lastGlyphID = startGlyphID - self._format_step
1237
+ lastCharCode = startCharCode - 1
1238
+ nGroups = 0
1239
+ dataList = []
1240
+ maxIndex = len(charCodes)
1241
+ for index in range(maxIndex):
1242
+ charCode = charCodes[index]
1243
+ glyphID = cmap[charCode]
1244
+ if not self._IsInSameRun(glyphID, lastGlyphID, charCode, lastCharCode):
1245
+ dataList.append(
1246
+ struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID)
1247
+ )
1248
+ startCharCode = charCode
1249
+ startGlyphID = glyphID
1250
+ nGroups = nGroups + 1
1251
+ lastGlyphID = glyphID
1252
+ lastCharCode = charCode
1253
+ dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID))
1254
+ nGroups = nGroups + 1
1255
+ data = bytesjoin(dataList)
1256
+ lengthSubtable = len(data) + 16
1257
+ assert len(data) == (nGroups * 12) == (lengthSubtable - 16)
1258
+ return (
1259
+ struct.pack(
1260
+ ">HHLLL",
1261
+ self.format,
1262
+ self.reserved,
1263
+ lengthSubtable,
1264
+ self.language,
1265
+ nGroups,
1266
+ )
1267
+ + data
1268
+ )
1269
+
1270
+ def toXML(self, writer, ttFont):
1271
+ writer.begintag(
1272
+ self.__class__.__name__,
1273
+ [
1274
+ ("platformID", self.platformID),
1275
+ ("platEncID", self.platEncID),
1276
+ ("format", self.format),
1277
+ ("reserved", self.reserved),
1278
+ ("length", self.length),
1279
+ ("language", self.language),
1280
+ ("nGroups", self.nGroups),
1281
+ ],
1282
+ )
1283
+ writer.newline()
1284
+ codes = sorted(self.cmap.items())
1285
+ self._writeCodes(codes, writer)
1286
+ writer.endtag(self.__class__.__name__)
1287
+ writer.newline()
1288
+
1289
+ def fromXML(self, name, attrs, content, ttFont):
1290
+ self.format = safeEval(attrs["format"])
1291
+ self.reserved = safeEval(attrs["reserved"])
1292
+ self.length = safeEval(attrs["length"])
1293
+ self.language = safeEval(attrs["language"])
1294
+ self.nGroups = safeEval(attrs["nGroups"])
1295
+ if not hasattr(self, "cmap"):
1296
+ self.cmap = {}
1297
+ cmap = self.cmap
1298
+
1299
+ for element in content:
1300
+ if not isinstance(element, tuple):
1301
+ continue
1302
+ name, attrs, content = element
1303
+ if name != "map":
1304
+ continue
1305
+ cmap[safeEval(attrs["code"])] = attrs["name"]
1306
+
1307
+
1308
+ class cmap_format_12(cmap_format_12_or_13):
1309
+ _format_step = 1
1310
+
1311
+ def __init__(self, format=12):
1312
+ cmap_format_12_or_13.__init__(self, format)
1313
+
1314
+ def _computeGIDs(self, startingGlyph, numberOfGlyphs):
1315
+ return range(startingGlyph, startingGlyph + numberOfGlyphs)
1316
+
1317
+ def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode):
1318
+ return (glyphID == 1 + lastGlyphID) and (charCode == 1 + lastCharCode)
1319
+
1320
+
1321
+ class cmap_format_13(cmap_format_12_or_13):
1322
+ _format_step = 0
1323
+
1324
+ def __init__(self, format=13):
1325
+ cmap_format_12_or_13.__init__(self, format)
1326
+
1327
+ def _computeGIDs(self, startingGlyph, numberOfGlyphs):
1328
+ return [startingGlyph] * numberOfGlyphs
1329
+
1330
+ def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode):
1331
+ return (glyphID == lastGlyphID) and (charCode == 1 + lastCharCode)
1332
+
1333
+
1334
+ def cvtToUVS(threeByteString):
1335
+ data = b"\0" + threeByteString
1336
+ (val,) = struct.unpack(">L", data)
1337
+ return val
1338
+
1339
+
1340
+ def cvtFromUVS(val):
1341
+ assert 0 <= val < 0x1000000
1342
+ fourByteString = struct.pack(">L", val)
1343
+ return fourByteString[1:]
1344
+
1345
+
1346
+ class cmap_format_14(CmapSubtable):
1347
+ def decompileHeader(self, data, ttFont):
1348
+ format, length, numVarSelectorRecords = struct.unpack(">HLL", data[:10])
1349
+ self.data = data[10:]
1350
+ self.length = length
1351
+ self.numVarSelectorRecords = numVarSelectorRecords
1352
+ self.ttFont = ttFont
1353
+ self.language = 0xFF # has no language.
1354
+
1355
+ def decompile(self, data, ttFont):
1356
+ if data is not None and ttFont is not None:
1357
+ self.decompileHeader(data, ttFont)
1358
+ else:
1359
+ assert (
1360
+ data is None and ttFont is None
1361
+ ), "Need both data and ttFont arguments"
1362
+ data = self.data
1363
+
1364
+ self.cmap = (
1365
+ {}
1366
+ ) # so that clients that expect this to exist in a cmap table won't fail.
1367
+ uvsDict = {}
1368
+ recOffset = 0
1369
+ for n in range(self.numVarSelectorRecords):
1370
+ uvs, defOVSOffset, nonDefUVSOffset = struct.unpack(
1371
+ ">3sLL", data[recOffset : recOffset + 11]
1372
+ )
1373
+ recOffset += 11
1374
+ varUVS = cvtToUVS(uvs)
1375
+ if defOVSOffset:
1376
+ startOffset = defOVSOffset - 10
1377
+ (numValues,) = struct.unpack(">L", data[startOffset : startOffset + 4])
1378
+ startOffset += 4
1379
+ for r in range(numValues):
1380
+ uv, addtlCnt = struct.unpack(
1381
+ ">3sB", data[startOffset : startOffset + 4]
1382
+ )
1383
+ startOffset += 4
1384
+ firstBaseUV = cvtToUVS(uv)
1385
+ cnt = addtlCnt + 1
1386
+ baseUVList = list(range(firstBaseUV, firstBaseUV + cnt))
1387
+ glyphList = [None] * cnt
1388
+ localUVList = zip(baseUVList, glyphList)
1389
+ try:
1390
+ uvsDict[varUVS].extend(localUVList)
1391
+ except KeyError:
1392
+ uvsDict[varUVS] = list(localUVList)
1393
+
1394
+ if nonDefUVSOffset:
1395
+ startOffset = nonDefUVSOffset - 10
1396
+ (numRecs,) = struct.unpack(">L", data[startOffset : startOffset + 4])
1397
+ startOffset += 4
1398
+ localUVList = []
1399
+ for r in range(numRecs):
1400
+ uv, gid = struct.unpack(">3sH", data[startOffset : startOffset + 5])
1401
+ startOffset += 5
1402
+ uv = cvtToUVS(uv)
1403
+ glyphName = self.ttFont.getGlyphName(gid)
1404
+ localUVList.append((uv, glyphName))
1405
+ try:
1406
+ uvsDict[varUVS].extend(localUVList)
1407
+ except KeyError:
1408
+ uvsDict[varUVS] = localUVList
1409
+
1410
+ self.uvsDict = uvsDict
1411
+
1412
+ def toXML(self, writer, ttFont):
1413
+ writer.begintag(
1414
+ self.__class__.__name__,
1415
+ [
1416
+ ("platformID", self.platformID),
1417
+ ("platEncID", self.platEncID),
1418
+ ],
1419
+ )
1420
+ writer.newline()
1421
+ uvsDict = self.uvsDict
1422
+ uvsList = sorted(uvsDict.keys())
1423
+ for uvs in uvsList:
1424
+ uvList = uvsDict[uvs]
1425
+ uvList.sort(key=lambda item: (item[1] is not None, item[0], item[1]))
1426
+ for uv, gname in uvList:
1427
+ attrs = [("uv", hex(uv)), ("uvs", hex(uvs))]
1428
+ if gname is not None:
1429
+ attrs.append(("name", gname))
1430
+ writer.simpletag("map", attrs)
1431
+ writer.newline()
1432
+ writer.endtag(self.__class__.__name__)
1433
+ writer.newline()
1434
+
1435
+ def fromXML(self, name, attrs, content, ttFont):
1436
+ self.language = 0xFF # provide a value so that CmapSubtable.__lt__() won't fail
1437
+ if not hasattr(self, "cmap"):
1438
+ self.cmap = (
1439
+ {}
1440
+ ) # so that clients that expect this to exist in a cmap table won't fail.
1441
+ if not hasattr(self, "uvsDict"):
1442
+ self.uvsDict = {}
1443
+ uvsDict = self.uvsDict
1444
+
1445
+ # For backwards compatibility reasons we accept "None" as an indicator
1446
+ # for "default mapping", unless the font actually has a glyph named
1447
+ # "None".
1448
+ _hasGlyphNamedNone = None
1449
+
1450
+ for element in content:
1451
+ if not isinstance(element, tuple):
1452
+ continue
1453
+ name, attrs, content = element
1454
+ if name != "map":
1455
+ continue
1456
+ uvs = safeEval(attrs["uvs"])
1457
+ uv = safeEval(attrs["uv"])
1458
+ gname = attrs.get("name")
1459
+ if gname == "None":
1460
+ if _hasGlyphNamedNone is None:
1461
+ _hasGlyphNamedNone = "None" in ttFont.getGlyphOrder()
1462
+ if not _hasGlyphNamedNone:
1463
+ gname = None
1464
+ try:
1465
+ uvsDict[uvs].append((uv, gname))
1466
+ except KeyError:
1467
+ uvsDict[uvs] = [(uv, gname)]
1468
+
1469
+ def compile(self, ttFont):
1470
+ if self.data:
1471
+ return (
1472
+ struct.pack(
1473
+ ">HLL", self.format, self.length, self.numVarSelectorRecords
1474
+ )
1475
+ + self.data
1476
+ )
1477
+
1478
+ uvsDict = self.uvsDict
1479
+ uvsList = sorted(uvsDict.keys())
1480
+ self.numVarSelectorRecords = len(uvsList)
1481
+ offset = (
1482
+ 10 + self.numVarSelectorRecords * 11
1483
+ ) # current value is end of VarSelectorRecords block.
1484
+ data = []
1485
+ varSelectorRecords = []
1486
+ for uvs in uvsList:
1487
+ entryList = uvsDict[uvs]
1488
+
1489
+ defList = [entry for entry in entryList if entry[1] is None]
1490
+ if defList:
1491
+ defList = [entry[0] for entry in defList]
1492
+ defOVSOffset = offset
1493
+ defList.sort()
1494
+
1495
+ lastUV = defList[0]
1496
+ cnt = -1
1497
+ defRecs = []
1498
+ for defEntry in defList:
1499
+ cnt += 1
1500
+ if (lastUV + cnt) != defEntry:
1501
+ rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt - 1)
1502
+ lastUV = defEntry
1503
+ defRecs.append(rec)
1504
+ cnt = 0
1505
+
1506
+ rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt)
1507
+ defRecs.append(rec)
1508
+
1509
+ numDefRecs = len(defRecs)
1510
+ data.append(struct.pack(">L", numDefRecs))
1511
+ data.extend(defRecs)
1512
+ offset += 4 + numDefRecs * 4
1513
+ else:
1514
+ defOVSOffset = 0
1515
+
1516
+ ndefList = [entry for entry in entryList if entry[1] is not None]
1517
+ if ndefList:
1518
+ nonDefUVSOffset = offset
1519
+ ndefList.sort()
1520
+ numNonDefRecs = len(ndefList)
1521
+ data.append(struct.pack(">L", numNonDefRecs))
1522
+ offset += 4 + numNonDefRecs * 5
1523
+
1524
+ for uv, gname in ndefList:
1525
+ gid = ttFont.getGlyphID(gname)
1526
+ ndrec = struct.pack(">3sH", cvtFromUVS(uv), gid)
1527
+ data.append(ndrec)
1528
+ else:
1529
+ nonDefUVSOffset = 0
1530
+
1531
+ vrec = struct.pack(">3sLL", cvtFromUVS(uvs), defOVSOffset, nonDefUVSOffset)
1532
+ varSelectorRecords.append(vrec)
1533
+
1534
+ data = bytesjoin(varSelectorRecords) + bytesjoin(data)
1535
+ self.length = 10 + len(data)
1536
+ headerdata = struct.pack(
1537
+ ">HLL", self.format, self.length, self.numVarSelectorRecords
1538
+ )
1539
+
1540
+ return headerdata + data
1541
+
1542
+
1543
+ class cmap_format_unknown(CmapSubtable):
1544
+ def toXML(self, writer, ttFont):
1545
+ cmapName = self.__class__.__name__[:12] + str(self.format)
1546
+ writer.begintag(
1547
+ cmapName,
1548
+ [
1549
+ ("platformID", self.platformID),
1550
+ ("platEncID", self.platEncID),
1551
+ ],
1552
+ )
1553
+ writer.newline()
1554
+ writer.dumphex(self.data)
1555
+ writer.endtag(cmapName)
1556
+ writer.newline()
1557
+
1558
+ def fromXML(self, name, attrs, content, ttFont):
1559
+ self.data = readHex(content)
1560
+ self.cmap = {}
1561
+
1562
+ def decompileHeader(self, data, ttFont):
1563
+ self.language = 0 # dummy value
1564
+ self.data = data
1565
+
1566
+ def decompile(self, data, ttFont):
1567
+ # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None.
1568
+ # If not, someone is calling the subtable decompile() directly, and must provide both args.
1569
+ if data is not None and ttFont is not None:
1570
+ self.decompileHeader(data, ttFont)
1571
+ else:
1572
+ assert (
1573
+ data is None and ttFont is None
1574
+ ), "Need both data and ttFont arguments"
1575
+
1576
+ def compile(self, ttFont):
1577
+ if self.data:
1578
+ return self.data
1579
+ else:
1580
+ return None
1581
+
1582
+
1583
+ cmap_classes = {
1584
+ 0: cmap_format_0,
1585
+ 2: cmap_format_2,
1586
+ 4: cmap_format_4,
1587
+ 6: cmap_format_6,
1588
+ 12: cmap_format_12,
1589
+ 13: cmap_format_13,
1590
+ 14: cmap_format_14,
1591
+ }