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,664 @@
1
+ """
2
+ colorLib.builder: Build COLR/CPAL tables from scratch
3
+
4
+ """
5
+
6
+ import collections
7
+ import copy
8
+ import enum
9
+ from functools import partial
10
+ from math import ceil, log
11
+ from typing import (
12
+ Any,
13
+ Dict,
14
+ Generator,
15
+ Iterable,
16
+ List,
17
+ Mapping,
18
+ Optional,
19
+ Sequence,
20
+ Tuple,
21
+ Type,
22
+ TypeVar,
23
+ Union,
24
+ )
25
+ from fontTools.misc.arrayTools import intRect
26
+ from fontTools.misc.fixedTools import fixedToFloat
27
+ from fontTools.misc.treeTools import build_n_ary_tree
28
+ from fontTools.ttLib.tables import C_O_L_R_
29
+ from fontTools.ttLib.tables import C_P_A_L_
30
+ from fontTools.ttLib.tables import _n_a_m_e
31
+ from fontTools.ttLib.tables import otTables as ot
32
+ from fontTools.ttLib.tables.otTables import ExtendMode, CompositeMode
33
+ from .errors import ColorLibError
34
+ from .geometry import round_start_circle_stable_containment
35
+ from .table_builder import BuildCallback, TableBuilder
36
+
37
+
38
+ # TODO move type aliases to colorLib.types?
39
+ T = TypeVar("T")
40
+ _Kwargs = Mapping[str, Any]
41
+ _PaintInput = Union[int, _Kwargs, ot.Paint, Tuple[str, "_PaintInput"]]
42
+ _PaintInputList = Sequence[_PaintInput]
43
+ _ColorGlyphsDict = Dict[str, Union[_PaintInputList, _PaintInput]]
44
+ _ColorGlyphsV0Dict = Dict[str, Sequence[Tuple[str, int]]]
45
+ _ClipBoxInput = Union[
46
+ Tuple[int, int, int, int, int], # format 1, variable
47
+ Tuple[int, int, int, int], # format 0, non-variable
48
+ ot.ClipBox,
49
+ ]
50
+
51
+
52
+ MAX_PAINT_COLR_LAYER_COUNT = 255
53
+ _DEFAULT_ALPHA = 1.0
54
+ _MAX_REUSE_LEN = 32
55
+
56
+
57
+ def _beforeBuildPaintRadialGradient(paint, source):
58
+ x0 = source["x0"]
59
+ y0 = source["y0"]
60
+ r0 = source["r0"]
61
+ x1 = source["x1"]
62
+ y1 = source["y1"]
63
+ r1 = source["r1"]
64
+
65
+ # TODO apparently no builder_test confirms this works (?)
66
+
67
+ # avoid abrupt change after rounding when c0 is near c1's perimeter
68
+ c = round_start_circle_stable_containment((x0, y0), r0, (x1, y1), r1)
69
+ x0, y0 = c.centre
70
+ r0 = c.radius
71
+
72
+ # update source to ensure paint is built with corrected values
73
+ source["x0"] = x0
74
+ source["y0"] = y0
75
+ source["r0"] = r0
76
+ source["x1"] = x1
77
+ source["y1"] = y1
78
+ source["r1"] = r1
79
+
80
+ return paint, source
81
+
82
+
83
+ def _defaultColorStop():
84
+ colorStop = ot.ColorStop()
85
+ colorStop.Alpha = _DEFAULT_ALPHA
86
+ return colorStop
87
+
88
+
89
+ def _defaultVarColorStop():
90
+ colorStop = ot.VarColorStop()
91
+ colorStop.Alpha = _DEFAULT_ALPHA
92
+ return colorStop
93
+
94
+
95
+ def _defaultColorLine():
96
+ colorLine = ot.ColorLine()
97
+ colorLine.Extend = ExtendMode.PAD
98
+ return colorLine
99
+
100
+
101
+ def _defaultVarColorLine():
102
+ colorLine = ot.VarColorLine()
103
+ colorLine.Extend = ExtendMode.PAD
104
+ return colorLine
105
+
106
+
107
+ def _defaultPaintSolid():
108
+ paint = ot.Paint()
109
+ paint.Alpha = _DEFAULT_ALPHA
110
+ return paint
111
+
112
+
113
+ def _buildPaintCallbacks():
114
+ return {
115
+ (
116
+ BuildCallback.BEFORE_BUILD,
117
+ ot.Paint,
118
+ ot.PaintFormat.PaintRadialGradient,
119
+ ): _beforeBuildPaintRadialGradient,
120
+ (
121
+ BuildCallback.BEFORE_BUILD,
122
+ ot.Paint,
123
+ ot.PaintFormat.PaintVarRadialGradient,
124
+ ): _beforeBuildPaintRadialGradient,
125
+ (BuildCallback.CREATE_DEFAULT, ot.ColorStop): _defaultColorStop,
126
+ (BuildCallback.CREATE_DEFAULT, ot.VarColorStop): _defaultVarColorStop,
127
+ (BuildCallback.CREATE_DEFAULT, ot.ColorLine): _defaultColorLine,
128
+ (BuildCallback.CREATE_DEFAULT, ot.VarColorLine): _defaultVarColorLine,
129
+ (
130
+ BuildCallback.CREATE_DEFAULT,
131
+ ot.Paint,
132
+ ot.PaintFormat.PaintSolid,
133
+ ): _defaultPaintSolid,
134
+ (
135
+ BuildCallback.CREATE_DEFAULT,
136
+ ot.Paint,
137
+ ot.PaintFormat.PaintVarSolid,
138
+ ): _defaultPaintSolid,
139
+ }
140
+
141
+
142
+ def populateCOLRv0(
143
+ table: ot.COLR,
144
+ colorGlyphsV0: _ColorGlyphsV0Dict,
145
+ glyphMap: Optional[Mapping[str, int]] = None,
146
+ ):
147
+ """Build v0 color layers and add to existing COLR table.
148
+
149
+ Args:
150
+ table: a raw ``otTables.COLR()`` object (not ttLib's ``table_C_O_L_R_``).
151
+ colorGlyphsV0: map of base glyph names to lists of (layer glyph names,
152
+ color palette index) tuples. Can be empty.
153
+ glyphMap: a map from glyph names to glyph indices, as returned from
154
+ ``TTFont.getReverseGlyphMap()``, to optionally sort base records by GID.
155
+ """
156
+ if glyphMap is not None:
157
+ colorGlyphItems = sorted(
158
+ colorGlyphsV0.items(), key=lambda item: glyphMap[item[0]]
159
+ )
160
+ else:
161
+ colorGlyphItems = colorGlyphsV0.items()
162
+ baseGlyphRecords = []
163
+ layerRecords = []
164
+ for baseGlyph, layers in colorGlyphItems:
165
+ baseRec = ot.BaseGlyphRecord()
166
+ baseRec.BaseGlyph = baseGlyph
167
+ baseRec.FirstLayerIndex = len(layerRecords)
168
+ baseRec.NumLayers = len(layers)
169
+ baseGlyphRecords.append(baseRec)
170
+
171
+ for layerGlyph, paletteIndex in layers:
172
+ layerRec = ot.LayerRecord()
173
+ layerRec.LayerGlyph = layerGlyph
174
+ layerRec.PaletteIndex = paletteIndex
175
+ layerRecords.append(layerRec)
176
+
177
+ table.BaseGlyphRecordArray = table.LayerRecordArray = None
178
+ if baseGlyphRecords:
179
+ table.BaseGlyphRecordArray = ot.BaseGlyphRecordArray()
180
+ table.BaseGlyphRecordArray.BaseGlyphRecord = baseGlyphRecords
181
+ if layerRecords:
182
+ table.LayerRecordArray = ot.LayerRecordArray()
183
+ table.LayerRecordArray.LayerRecord = layerRecords
184
+ table.BaseGlyphRecordCount = len(baseGlyphRecords)
185
+ table.LayerRecordCount = len(layerRecords)
186
+
187
+
188
+ def buildCOLR(
189
+ colorGlyphs: _ColorGlyphsDict,
190
+ version: Optional[int] = None,
191
+ *,
192
+ glyphMap: Optional[Mapping[str, int]] = None,
193
+ varStore: Optional[ot.VarStore] = None,
194
+ varIndexMap: Optional[ot.DeltaSetIndexMap] = None,
195
+ clipBoxes: Optional[Dict[str, _ClipBoxInput]] = None,
196
+ allowLayerReuse: bool = True,
197
+ ) -> C_O_L_R_.table_C_O_L_R_:
198
+ """Build COLR table from color layers mapping.
199
+
200
+ Args:
201
+
202
+ colorGlyphs: map of base glyph name to, either list of (layer glyph name,
203
+ color palette index) tuples for COLRv0; or a single ``Paint`` (dict) or
204
+ list of ``Paint`` for COLRv1.
205
+ version: the version of COLR table. If None, the version is determined
206
+ by the presence of COLRv1 paints or variation data (varStore), which
207
+ require version 1; otherwise, if all base glyphs use only simple color
208
+ layers, version 0 is used.
209
+ glyphMap: a map from glyph names to glyph indices, as returned from
210
+ TTFont.getReverseGlyphMap(), to optionally sort base records by GID.
211
+ varStore: Optional ItemVarationStore for deltas associated with v1 layer.
212
+ varIndexMap: Optional DeltaSetIndexMap for deltas associated with v1 layer.
213
+ clipBoxes: Optional map of base glyph name to clip box 4- or 5-tuples:
214
+ (xMin, yMin, xMax, yMax) or (xMin, yMin, xMax, yMax, varIndexBase).
215
+
216
+ Returns:
217
+ A new COLR table.
218
+ """
219
+ self = C_O_L_R_.table_C_O_L_R_()
220
+
221
+ if varStore is not None and version == 0:
222
+ raise ValueError("Can't add VarStore to COLRv0")
223
+
224
+ if version in (None, 0) and not varStore:
225
+ # split color glyphs into v0 and v1 and encode separately
226
+ colorGlyphsV0, colorGlyphsV1 = _split_color_glyphs_by_version(colorGlyphs)
227
+ if version == 0 and colorGlyphsV1:
228
+ raise ValueError("Can't encode COLRv1 glyphs in COLRv0")
229
+ else:
230
+ # unless explicitly requested for v1 or have variations, in which case
231
+ # we encode all color glyph as v1
232
+ colorGlyphsV0, colorGlyphsV1 = {}, colorGlyphs
233
+
234
+ colr = ot.COLR()
235
+
236
+ populateCOLRv0(colr, colorGlyphsV0, glyphMap)
237
+
238
+ colr.LayerList, colr.BaseGlyphList = buildColrV1(
239
+ colorGlyphsV1,
240
+ glyphMap,
241
+ allowLayerReuse=allowLayerReuse,
242
+ )
243
+
244
+ if version is None:
245
+ version = 1 if (varStore or colorGlyphsV1) else 0
246
+ elif version not in (0, 1):
247
+ raise NotImplementedError(version)
248
+ self.version = colr.Version = version
249
+
250
+ if version == 0:
251
+ self.ColorLayers = self._decompileColorLayersV0(colr)
252
+ else:
253
+ colr.ClipList = buildClipList(clipBoxes) if clipBoxes else None
254
+ colr.VarIndexMap = varIndexMap
255
+ colr.VarStore = varStore
256
+ self.table = colr
257
+
258
+ return self
259
+
260
+
261
+ def buildClipList(clipBoxes: Dict[str, _ClipBoxInput]) -> ot.ClipList:
262
+ clipList = ot.ClipList()
263
+ clipList.Format = 1
264
+ clipList.clips = {name: buildClipBox(box) for name, box in clipBoxes.items()}
265
+ return clipList
266
+
267
+
268
+ def buildClipBox(clipBox: _ClipBoxInput) -> ot.ClipBox:
269
+ if isinstance(clipBox, ot.ClipBox):
270
+ return clipBox
271
+ n = len(clipBox)
272
+ clip = ot.ClipBox()
273
+ if n not in (4, 5):
274
+ raise ValueError(f"Invalid ClipBox: expected 4 or 5 values, found {n}")
275
+ clip.xMin, clip.yMin, clip.xMax, clip.yMax = intRect(clipBox[:4])
276
+ clip.Format = int(n == 5) + 1
277
+ if n == 5:
278
+ clip.VarIndexBase = int(clipBox[4])
279
+ return clip
280
+
281
+
282
+ class ColorPaletteType(enum.IntFlag):
283
+ USABLE_WITH_LIGHT_BACKGROUND = 0x0001
284
+ USABLE_WITH_DARK_BACKGROUND = 0x0002
285
+
286
+ @classmethod
287
+ def _missing_(cls, value):
288
+ # enforce reserved bits
289
+ if isinstance(value, int) and (value < 0 or value & 0xFFFC != 0):
290
+ raise ValueError(f"{value} is not a valid {cls.__name__}")
291
+ return super()._missing_(value)
292
+
293
+
294
+ # None, 'abc' or {'en': 'abc', 'de': 'xyz'}
295
+ _OptionalLocalizedString = Union[None, str, Dict[str, str]]
296
+
297
+
298
+ def buildPaletteLabels(
299
+ labels: Iterable[_OptionalLocalizedString], nameTable: _n_a_m_e.table__n_a_m_e
300
+ ) -> List[Optional[int]]:
301
+ return [
302
+ (
303
+ nameTable.addMultilingualName(l, mac=False)
304
+ if isinstance(l, dict)
305
+ else (
306
+ C_P_A_L_.table_C_P_A_L_.NO_NAME_ID
307
+ if l is None
308
+ else nameTable.addMultilingualName({"en": l}, mac=False)
309
+ )
310
+ )
311
+ for l in labels
312
+ ]
313
+
314
+
315
+ def buildCPAL(
316
+ palettes: Sequence[Sequence[Tuple[float, float, float, float]]],
317
+ paletteTypes: Optional[Sequence[ColorPaletteType]] = None,
318
+ paletteLabels: Optional[Sequence[_OptionalLocalizedString]] = None,
319
+ paletteEntryLabels: Optional[Sequence[_OptionalLocalizedString]] = None,
320
+ nameTable: Optional[_n_a_m_e.table__n_a_m_e] = None,
321
+ ) -> C_P_A_L_.table_C_P_A_L_:
322
+ """Build CPAL table from list of color palettes.
323
+
324
+ Args:
325
+ palettes: list of lists of colors encoded as tuples of (R, G, B, A) floats
326
+ in the range [0..1].
327
+ paletteTypes: optional list of ColorPaletteType, one for each palette.
328
+ paletteLabels: optional list of palette labels. Each lable can be either:
329
+ None (no label), a string (for for default English labels), or a
330
+ localized string (as a dict keyed with BCP47 language codes).
331
+ paletteEntryLabels: optional list of palette entry labels, one for each
332
+ palette entry (see paletteLabels).
333
+ nameTable: optional name table where to store palette and palette entry
334
+ labels. Required if either paletteLabels or paletteEntryLabels is set.
335
+
336
+ Return:
337
+ A new CPAL v0 or v1 table, if custom palette types or labels are specified.
338
+ """
339
+ if len({len(p) for p in palettes}) != 1:
340
+ raise ColorLibError("color palettes have different lengths")
341
+
342
+ if (paletteLabels or paletteEntryLabels) and not nameTable:
343
+ raise TypeError(
344
+ "nameTable is required if palette or palette entries have labels"
345
+ )
346
+
347
+ cpal = C_P_A_L_.table_C_P_A_L_()
348
+ cpal.numPaletteEntries = len(palettes[0])
349
+
350
+ cpal.palettes = []
351
+ for i, palette in enumerate(palettes):
352
+ colors = []
353
+ for j, color in enumerate(palette):
354
+ if not isinstance(color, tuple) or len(color) != 4:
355
+ raise ColorLibError(
356
+ f"In palette[{i}][{j}]: expected (R, G, B, A) tuple, got {color!r}"
357
+ )
358
+ if any(v > 1 or v < 0 for v in color):
359
+ raise ColorLibError(
360
+ f"palette[{i}][{j}] has invalid out-of-range [0..1] color: {color!r}"
361
+ )
362
+ # input colors are RGBA, CPAL encodes them as BGRA
363
+ red, green, blue, alpha = color
364
+ colors.append(
365
+ C_P_A_L_.Color(*(round(v * 255) for v in (blue, green, red, alpha)))
366
+ )
367
+ cpal.palettes.append(colors)
368
+
369
+ if any(v is not None for v in (paletteTypes, paletteLabels, paletteEntryLabels)):
370
+ cpal.version = 1
371
+
372
+ if paletteTypes is not None:
373
+ if len(paletteTypes) != len(palettes):
374
+ raise ColorLibError(
375
+ f"Expected {len(palettes)} paletteTypes, got {len(paletteTypes)}"
376
+ )
377
+ cpal.paletteTypes = [ColorPaletteType(t).value for t in paletteTypes]
378
+ else:
379
+ cpal.paletteTypes = [C_P_A_L_.table_C_P_A_L_.DEFAULT_PALETTE_TYPE] * len(
380
+ palettes
381
+ )
382
+
383
+ if paletteLabels is not None:
384
+ if len(paletteLabels) != len(palettes):
385
+ raise ColorLibError(
386
+ f"Expected {len(palettes)} paletteLabels, got {len(paletteLabels)}"
387
+ )
388
+ cpal.paletteLabels = buildPaletteLabels(paletteLabels, nameTable)
389
+ else:
390
+ cpal.paletteLabels = [C_P_A_L_.table_C_P_A_L_.NO_NAME_ID] * len(palettes)
391
+
392
+ if paletteEntryLabels is not None:
393
+ if len(paletteEntryLabels) != cpal.numPaletteEntries:
394
+ raise ColorLibError(
395
+ f"Expected {cpal.numPaletteEntries} paletteEntryLabels, "
396
+ f"got {len(paletteEntryLabels)}"
397
+ )
398
+ cpal.paletteEntryLabels = buildPaletteLabels(paletteEntryLabels, nameTable)
399
+ else:
400
+ cpal.paletteEntryLabels = [
401
+ C_P_A_L_.table_C_P_A_L_.NO_NAME_ID
402
+ ] * cpal.numPaletteEntries
403
+ else:
404
+ cpal.version = 0
405
+
406
+ return cpal
407
+
408
+
409
+ # COLR v1 tables
410
+ # See draft proposal at: https://github.com/googlefonts/colr-gradients-spec
411
+
412
+
413
+ def _is_colrv0_layer(layer: Any) -> bool:
414
+ # Consider as COLRv0 layer any sequence of length 2 (be it tuple or list) in which
415
+ # the first element is a str (the layerGlyph) and the second element is an int
416
+ # (CPAL paletteIndex).
417
+ # https://github.com/googlefonts/ufo2ft/issues/426
418
+ try:
419
+ layerGlyph, paletteIndex = layer
420
+ except (TypeError, ValueError):
421
+ return False
422
+ else:
423
+ return isinstance(layerGlyph, str) and isinstance(paletteIndex, int)
424
+
425
+
426
+ def _split_color_glyphs_by_version(
427
+ colorGlyphs: _ColorGlyphsDict,
428
+ ) -> Tuple[_ColorGlyphsV0Dict, _ColorGlyphsDict]:
429
+ colorGlyphsV0 = {}
430
+ colorGlyphsV1 = {}
431
+ for baseGlyph, layers in colorGlyphs.items():
432
+ if all(_is_colrv0_layer(l) for l in layers):
433
+ colorGlyphsV0[baseGlyph] = layers
434
+ else:
435
+ colorGlyphsV1[baseGlyph] = layers
436
+
437
+ # sanity check
438
+ assert set(colorGlyphs) == (set(colorGlyphsV0) | set(colorGlyphsV1))
439
+
440
+ return colorGlyphsV0, colorGlyphsV1
441
+
442
+
443
+ def _reuse_ranges(num_layers: int) -> Generator[Tuple[int, int], None, None]:
444
+ # TODO feels like something itertools might have already
445
+ for lbound in range(num_layers):
446
+ # Reuse of very large #s of layers is relatively unlikely
447
+ # +2: we want sequences of at least 2
448
+ # otData handles single-record duplication
449
+ for ubound in range(
450
+ lbound + 2, min(num_layers + 1, lbound + 2 + _MAX_REUSE_LEN)
451
+ ):
452
+ yield (lbound, ubound)
453
+
454
+
455
+ class LayerReuseCache:
456
+ reusePool: Mapping[Tuple[Any, ...], int]
457
+ tuples: Mapping[int, Tuple[Any, ...]]
458
+ keepAlive: List[ot.Paint] # we need id to remain valid
459
+
460
+ def __init__(self):
461
+ self.reusePool = {}
462
+ self.tuples = {}
463
+ self.keepAlive = []
464
+
465
+ def _paint_tuple(self, paint: ot.Paint):
466
+ # start simple, who even cares about cyclic graphs or interesting field types
467
+ def _tuple_safe(value):
468
+ if isinstance(value, enum.Enum):
469
+ return value
470
+ elif hasattr(value, "__dict__"):
471
+ return tuple(
472
+ (k, _tuple_safe(v)) for k, v in sorted(value.__dict__.items())
473
+ )
474
+ elif isinstance(value, collections.abc.MutableSequence):
475
+ return tuple(_tuple_safe(e) for e in value)
476
+ return value
477
+
478
+ # Cache the tuples for individual Paint instead of the whole sequence
479
+ # because the seq could be a transient slice
480
+ result = self.tuples.get(id(paint), None)
481
+ if result is None:
482
+ result = _tuple_safe(paint)
483
+ self.tuples[id(paint)] = result
484
+ self.keepAlive.append(paint)
485
+ return result
486
+
487
+ def _as_tuple(self, paints: Sequence[ot.Paint]) -> Tuple[Any, ...]:
488
+ return tuple(self._paint_tuple(p) for p in paints)
489
+
490
+ def try_reuse(self, layers: List[ot.Paint]) -> List[ot.Paint]:
491
+ found_reuse = True
492
+ while found_reuse:
493
+ found_reuse = False
494
+
495
+ ranges = sorted(
496
+ _reuse_ranges(len(layers)),
497
+ key=lambda t: (t[1] - t[0], t[1], t[0]),
498
+ reverse=True,
499
+ )
500
+ for lbound, ubound in ranges:
501
+ reuse_lbound = self.reusePool.get(
502
+ self._as_tuple(layers[lbound:ubound]), -1
503
+ )
504
+ if reuse_lbound == -1:
505
+ continue
506
+ new_slice = ot.Paint()
507
+ new_slice.Format = int(ot.PaintFormat.PaintColrLayers)
508
+ new_slice.NumLayers = ubound - lbound
509
+ new_slice.FirstLayerIndex = reuse_lbound
510
+ layers = layers[:lbound] + [new_slice] + layers[ubound:]
511
+ found_reuse = True
512
+ break
513
+ return layers
514
+
515
+ def add(self, layers: List[ot.Paint], first_layer_index: int):
516
+ for lbound, ubound in _reuse_ranges(len(layers)):
517
+ self.reusePool[self._as_tuple(layers[lbound:ubound])] = (
518
+ lbound + first_layer_index
519
+ )
520
+
521
+
522
+ class LayerListBuilder:
523
+ layers: List[ot.Paint]
524
+ cache: LayerReuseCache
525
+ allowLayerReuse: bool
526
+
527
+ def __init__(self, *, allowLayerReuse=True):
528
+ self.layers = []
529
+ if allowLayerReuse:
530
+ self.cache = LayerReuseCache()
531
+ else:
532
+ self.cache = None
533
+
534
+ # We need to intercept construction of PaintColrLayers
535
+ callbacks = _buildPaintCallbacks()
536
+ callbacks[
537
+ (
538
+ BuildCallback.BEFORE_BUILD,
539
+ ot.Paint,
540
+ ot.PaintFormat.PaintColrLayers,
541
+ )
542
+ ] = self._beforeBuildPaintColrLayers
543
+ self.tableBuilder = TableBuilder(callbacks)
544
+
545
+ # COLR layers is unusual in that it modifies shared state
546
+ # so we need a callback into an object
547
+ def _beforeBuildPaintColrLayers(self, dest, source):
548
+ # Sketchy gymnastics: a sequence input will have dropped it's layers
549
+ # into NumLayers; get it back
550
+ if isinstance(source.get("NumLayers", None), collections.abc.Sequence):
551
+ layers = source["NumLayers"]
552
+ else:
553
+ layers = source["Layers"]
554
+
555
+ # Convert maps seqs or whatever into typed objects
556
+ layers = [self.buildPaint(l) for l in layers]
557
+
558
+ # No reason to have a colr layers with just one entry
559
+ if len(layers) == 1:
560
+ return layers[0], {}
561
+
562
+ if self.cache is not None:
563
+ # Look for reuse, with preference to longer sequences
564
+ # This may make the layer list smaller
565
+ layers = self.cache.try_reuse(layers)
566
+
567
+ # The layer list is now final; if it's too big we need to tree it
568
+ is_tree = len(layers) > MAX_PAINT_COLR_LAYER_COUNT
569
+ layers = build_n_ary_tree(layers, n=MAX_PAINT_COLR_LAYER_COUNT)
570
+
571
+ # We now have a tree of sequences with Paint leaves.
572
+ # Convert the sequences into PaintColrLayers.
573
+ def listToColrLayers(layer):
574
+ if isinstance(layer, collections.abc.Sequence):
575
+ return self.buildPaint(
576
+ {
577
+ "Format": ot.PaintFormat.PaintColrLayers,
578
+ "Layers": [listToColrLayers(l) for l in layer],
579
+ }
580
+ )
581
+ return layer
582
+
583
+ layers = [listToColrLayers(l) for l in layers]
584
+
585
+ # No reason to have a colr layers with just one entry
586
+ if len(layers) == 1:
587
+ return layers[0], {}
588
+
589
+ paint = ot.Paint()
590
+ paint.Format = int(ot.PaintFormat.PaintColrLayers)
591
+ paint.NumLayers = len(layers)
592
+ paint.FirstLayerIndex = len(self.layers)
593
+ self.layers.extend(layers)
594
+
595
+ # Register our parts for reuse provided we aren't a tree
596
+ # If we are a tree the leaves registered for reuse and that will suffice
597
+ if self.cache is not None and not is_tree:
598
+ self.cache.add(layers, paint.FirstLayerIndex)
599
+
600
+ # we've fully built dest; empty source prevents generalized build from kicking in
601
+ return paint, {}
602
+
603
+ def buildPaint(self, paint: _PaintInput) -> ot.Paint:
604
+ return self.tableBuilder.build(ot.Paint, paint)
605
+
606
+ def build(self) -> Optional[ot.LayerList]:
607
+ if not self.layers:
608
+ return None
609
+ layers = ot.LayerList()
610
+ layers.LayerCount = len(self.layers)
611
+ layers.Paint = self.layers
612
+ return layers
613
+
614
+
615
+ def buildBaseGlyphPaintRecord(
616
+ baseGlyph: str, layerBuilder: LayerListBuilder, paint: _PaintInput
617
+ ) -> ot.BaseGlyphList:
618
+ self = ot.BaseGlyphPaintRecord()
619
+ self.BaseGlyph = baseGlyph
620
+ self.Paint = layerBuilder.buildPaint(paint)
621
+ return self
622
+
623
+
624
+ def _format_glyph_errors(errors: Mapping[str, Exception]) -> str:
625
+ lines = []
626
+ for baseGlyph, error in sorted(errors.items()):
627
+ lines.append(f" {baseGlyph} => {type(error).__name__}: {error}")
628
+ return "\n".join(lines)
629
+
630
+
631
+ def buildColrV1(
632
+ colorGlyphs: _ColorGlyphsDict,
633
+ glyphMap: Optional[Mapping[str, int]] = None,
634
+ *,
635
+ allowLayerReuse: bool = True,
636
+ ) -> Tuple[Optional[ot.LayerList], ot.BaseGlyphList]:
637
+ if glyphMap is not None:
638
+ colorGlyphItems = sorted(
639
+ colorGlyphs.items(), key=lambda item: glyphMap[item[0]]
640
+ )
641
+ else:
642
+ colorGlyphItems = colorGlyphs.items()
643
+
644
+ errors = {}
645
+ baseGlyphs = []
646
+ layerBuilder = LayerListBuilder(allowLayerReuse=allowLayerReuse)
647
+ for baseGlyph, paint in colorGlyphItems:
648
+ try:
649
+ baseGlyphs.append(buildBaseGlyphPaintRecord(baseGlyph, layerBuilder, paint))
650
+
651
+ except (ColorLibError, OverflowError, ValueError, TypeError) as e:
652
+ errors[baseGlyph] = e
653
+
654
+ if errors:
655
+ failed_glyphs = _format_glyph_errors(errors)
656
+ exc = ColorLibError(f"Failed to build BaseGlyphList:\n{failed_glyphs}")
657
+ exc.errors = errors
658
+ raise exc from next(iter(errors.values()))
659
+
660
+ layers = layerBuilder.build()
661
+ glyphs = ot.BaseGlyphList()
662
+ glyphs.BaseGlyphCount = len(baseGlyphs)
663
+ glyphs.BaseGlyphPaintRecord = baseGlyphs
664
+ return (layers, glyphs)
@@ -0,0 +1,2 @@
1
+ class ColorLibError(Exception):
2
+ pass