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