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,143 @@
1
+ """Helpers for manipulating 2D points and vectors in COLR table."""
2
+
3
+ from math import copysign, cos, hypot, isclose, pi
4
+ from fontTools.misc.roundTools import otRound
5
+
6
+
7
+ def _vector_between(origin, target):
8
+ return (target[0] - origin[0], target[1] - origin[1])
9
+
10
+
11
+ def _round_point(pt):
12
+ return (otRound(pt[0]), otRound(pt[1]))
13
+
14
+
15
+ def _unit_vector(vec):
16
+ length = hypot(*vec)
17
+ if length == 0:
18
+ return None
19
+ return (vec[0] / length, vec[1] / length)
20
+
21
+
22
+ _CIRCLE_INSIDE_TOLERANCE = 1e-4
23
+
24
+
25
+ # The unit vector's X and Y components are respectively
26
+ # U = (cos(α), sin(α))
27
+ # where α is the angle between the unit vector and the positive x axis.
28
+ _UNIT_VECTOR_THRESHOLD = cos(3 / 8 * pi) # == sin(1/8 * pi) == 0.38268343236508984
29
+
30
+
31
+ def _rounding_offset(direction):
32
+ # Return 2-tuple of -/+ 1.0 or 0.0 approximately based on the direction vector.
33
+ # We divide the unit circle in 8 equal slices oriented towards the cardinal
34
+ # (N, E, S, W) and intermediate (NE, SE, SW, NW) directions. To each slice we
35
+ # map one of the possible cases: -1, 0, +1 for either X and Y coordinate.
36
+ # E.g. Return (+1.0, -1.0) if unit vector is oriented towards SE, or
37
+ # (-1.0, 0.0) if it's pointing West, etc.
38
+ uv = _unit_vector(direction)
39
+ if not uv:
40
+ return (0, 0)
41
+
42
+ result = []
43
+ for uv_component in uv:
44
+ if -_UNIT_VECTOR_THRESHOLD <= uv_component < _UNIT_VECTOR_THRESHOLD:
45
+ # unit vector component near 0: direction almost orthogonal to the
46
+ # direction of the current axis, thus keep coordinate unchanged
47
+ result.append(0)
48
+ else:
49
+ # nudge coord by +/- 1.0 in direction of unit vector
50
+ result.append(copysign(1.0, uv_component))
51
+ return tuple(result)
52
+
53
+
54
+ class Circle:
55
+ def __init__(self, centre, radius):
56
+ self.centre = centre
57
+ self.radius = radius
58
+
59
+ def __repr__(self):
60
+ return f"Circle(centre={self.centre}, radius={self.radius})"
61
+
62
+ def round(self):
63
+ return Circle(_round_point(self.centre), otRound(self.radius))
64
+
65
+ def inside(self, outer_circle, tolerance=_CIRCLE_INSIDE_TOLERANCE):
66
+ dist = self.radius + hypot(*_vector_between(self.centre, outer_circle.centre))
67
+ return (
68
+ isclose(outer_circle.radius, dist, rel_tol=_CIRCLE_INSIDE_TOLERANCE)
69
+ or outer_circle.radius > dist
70
+ )
71
+
72
+ def concentric(self, other):
73
+ return self.centre == other.centre
74
+
75
+ def move(self, dx, dy):
76
+ self.centre = (self.centre[0] + dx, self.centre[1] + dy)
77
+
78
+
79
+ def round_start_circle_stable_containment(c0, r0, c1, r1):
80
+ """Round start circle so that it stays inside/outside end circle after rounding.
81
+
82
+ The rounding of circle coordinates to integers may cause an abrupt change
83
+ if the start circle c0 is so close to the end circle c1's perimiter that
84
+ it ends up falling outside (or inside) as a result of the rounding.
85
+ To keep the gradient unchanged, we nudge it in the right direction.
86
+
87
+ See:
88
+ https://github.com/googlefonts/colr-gradients-spec/issues/204
89
+ https://github.com/googlefonts/picosvg/issues/158
90
+ """
91
+ start, end = Circle(c0, r0), Circle(c1, r1)
92
+
93
+ inside_before_round = start.inside(end)
94
+
95
+ round_start = start.round()
96
+ round_end = end.round()
97
+ inside_after_round = round_start.inside(round_end)
98
+
99
+ if inside_before_round == inside_after_round:
100
+ return round_start
101
+ elif inside_after_round:
102
+ # start was outside before rounding: we need to push start away from end
103
+ direction = _vector_between(round_end.centre, round_start.centre)
104
+ radius_delta = +1.0
105
+ else:
106
+ # start was inside before rounding: we need to push start towards end
107
+ direction = _vector_between(round_start.centre, round_end.centre)
108
+ radius_delta = -1.0
109
+ dx, dy = _rounding_offset(direction)
110
+
111
+ # At most 2 iterations ought to be enough to converge. Before the loop, we
112
+ # know the start circle didn't keep containment after normal rounding; thus
113
+ # we continue adjusting by -/+ 1.0 until containment is restored.
114
+ # Normal rounding can at most move each coordinates -/+0.5; in the worst case
115
+ # both the start and end circle's centres and radii will be rounded in opposite
116
+ # directions, e.g. when they move along a 45 degree diagonal:
117
+ # c0 = (1.5, 1.5) ===> (2.0, 2.0)
118
+ # r0 = 0.5 ===> 1.0
119
+ # c1 = (0.499, 0.499) ===> (0.0, 0.0)
120
+ # r1 = 2.499 ===> 2.0
121
+ # In this example, the relative distance between the circles, calculated
122
+ # as r1 - (r0 + distance(c0, c1)) is initially 0.57437 (c0 is inside c1), and
123
+ # -1.82842 after rounding (c0 is now outside c1). Nudging c0 by -1.0 on both
124
+ # x and y axes moves it towards c1 by hypot(-1.0, -1.0) = 1.41421. Two of these
125
+ # moves cover twice that distance, which is enough to restore containment.
126
+ max_attempts = 2
127
+ for _ in range(max_attempts):
128
+ if round_start.concentric(round_end):
129
+ # can't move c0 towards c1 (they are the same), so we change the radius
130
+ round_start.radius += radius_delta
131
+ assert round_start.radius >= 0
132
+ else:
133
+ round_start.move(dx, dy)
134
+ if inside_before_round == round_start.inside(round_end):
135
+ break
136
+ else: # likely a bug
137
+ raise AssertionError(
138
+ f"Rounding circle {start} "
139
+ f"{'inside' if inside_before_round else 'outside'} "
140
+ f"{end} failed after {max_attempts} attempts!"
141
+ )
142
+
143
+ return round_start
@@ -0,0 +1,223 @@
1
+ """
2
+ colorLib.table_builder: Generic helper for filling in BaseTable derivatives from tuples and maps and such.
3
+
4
+ """
5
+
6
+ import collections
7
+ import enum
8
+ from fontTools.ttLib.tables.otBase import (
9
+ BaseTable,
10
+ FormatSwitchingBaseTable,
11
+ UInt8FormatSwitchingBaseTable,
12
+ )
13
+ from fontTools.ttLib.tables.otConverters import (
14
+ ComputedInt,
15
+ SimpleValue,
16
+ Struct,
17
+ Short,
18
+ UInt8,
19
+ UShort,
20
+ IntValue,
21
+ FloatValue,
22
+ OptionalValue,
23
+ )
24
+ from fontTools.misc.roundTools import otRound
25
+
26
+
27
+ class BuildCallback(enum.Enum):
28
+ """Keyed on (BEFORE_BUILD, class[, Format if available]).
29
+ Receives (dest, source).
30
+ Should return (dest, source), which can be new objects.
31
+ """
32
+
33
+ BEFORE_BUILD = enum.auto()
34
+
35
+ """Keyed on (AFTER_BUILD, class[, Format if available]).
36
+ Receives (dest).
37
+ Should return dest, which can be a new object.
38
+ """
39
+ AFTER_BUILD = enum.auto()
40
+
41
+ """Keyed on (CREATE_DEFAULT, class[, Format if available]).
42
+ Receives no arguments.
43
+ Should return a new instance of class.
44
+ """
45
+ CREATE_DEFAULT = enum.auto()
46
+
47
+
48
+ def _assignable(convertersByName):
49
+ return {k: v for k, v in convertersByName.items() if not isinstance(v, ComputedInt)}
50
+
51
+
52
+ def _isNonStrSequence(value):
53
+ return isinstance(value, collections.abc.Sequence) and not isinstance(value, str)
54
+
55
+
56
+ def _split_format(cls, source):
57
+ if _isNonStrSequence(source):
58
+ assert len(source) > 0, f"{cls} needs at least format from {source}"
59
+ fmt, remainder = source[0], source[1:]
60
+ elif isinstance(source, collections.abc.Mapping):
61
+ assert "Format" in source, f"{cls} needs at least Format from {source}"
62
+ remainder = source.copy()
63
+ fmt = remainder.pop("Format")
64
+ else:
65
+ raise ValueError(f"Not sure how to populate {cls} from {source}")
66
+
67
+ assert isinstance(
68
+ fmt, collections.abc.Hashable
69
+ ), f"{cls} Format is not hashable: {fmt!r}"
70
+ assert fmt in cls.convertersByName, f"{cls} invalid Format: {fmt!r}"
71
+
72
+ return fmt, remainder
73
+
74
+
75
+ class TableBuilder:
76
+ """
77
+ Helps to populate things derived from BaseTable from maps, tuples, etc.
78
+
79
+ A table of lifecycle callbacks may be provided to add logic beyond what is possible
80
+ based on otData info for the target class. See BuildCallbacks.
81
+ """
82
+
83
+ def __init__(self, callbackTable=None):
84
+ if callbackTable is None:
85
+ callbackTable = {}
86
+ self._callbackTable = callbackTable
87
+
88
+ def _convert(self, dest, field, converter, value):
89
+ enumClass = getattr(converter, "enumClass", None)
90
+
91
+ if enumClass:
92
+ if isinstance(value, enumClass):
93
+ pass
94
+ elif isinstance(value, str):
95
+ try:
96
+ value = getattr(enumClass, value.upper())
97
+ except AttributeError:
98
+ raise ValueError(f"{value} is not a valid {enumClass}")
99
+ else:
100
+ value = enumClass(value)
101
+
102
+ elif isinstance(converter, IntValue):
103
+ value = otRound(value)
104
+ elif isinstance(converter, FloatValue):
105
+ value = float(value)
106
+
107
+ elif isinstance(converter, Struct):
108
+ if converter.repeat:
109
+ if _isNonStrSequence(value):
110
+ value = [self.build(converter.tableClass, v) for v in value]
111
+ else:
112
+ value = [self.build(converter.tableClass, value)]
113
+ setattr(dest, converter.repeat, len(value))
114
+ else:
115
+ value = self.build(converter.tableClass, value)
116
+ elif callable(converter):
117
+ value = converter(value)
118
+
119
+ setattr(dest, field, value)
120
+
121
+ def build(self, cls, source):
122
+ assert issubclass(cls, BaseTable)
123
+
124
+ if isinstance(source, cls):
125
+ return source
126
+
127
+ callbackKey = (cls,)
128
+ fmt = None
129
+ if issubclass(cls, FormatSwitchingBaseTable):
130
+ fmt, source = _split_format(cls, source)
131
+ callbackKey = (cls, fmt)
132
+
133
+ dest = self._callbackTable.get(
134
+ (BuildCallback.CREATE_DEFAULT,) + callbackKey, lambda: cls()
135
+ )()
136
+ assert isinstance(dest, cls)
137
+
138
+ convByName = _assignable(cls.convertersByName)
139
+ skippedFields = set()
140
+
141
+ # For format switchers we need to resolve converters based on format
142
+ if issubclass(cls, FormatSwitchingBaseTable):
143
+ dest.Format = fmt
144
+ convByName = _assignable(convByName[dest.Format])
145
+ skippedFields.add("Format")
146
+
147
+ # Convert sequence => mapping so before thunk only has to handle one format
148
+ if _isNonStrSequence(source):
149
+ # Sequence (typically list or tuple) assumed to match fields in declaration order
150
+ assert len(source) <= len(
151
+ convByName
152
+ ), f"Sequence of {len(source)} too long for {cls}; expected <= {len(convByName)} values"
153
+ source = dict(zip(convByName.keys(), source))
154
+
155
+ dest, source = self._callbackTable.get(
156
+ (BuildCallback.BEFORE_BUILD,) + callbackKey, lambda d, s: (d, s)
157
+ )(dest, source)
158
+
159
+ if isinstance(source, collections.abc.Mapping):
160
+ for field, value in source.items():
161
+ if field in skippedFields:
162
+ continue
163
+ converter = convByName.get(field, None)
164
+ if not converter:
165
+ raise ValueError(
166
+ f"Unrecognized field {field} for {cls}; expected one of {sorted(convByName.keys())}"
167
+ )
168
+ self._convert(dest, field, converter, value)
169
+ else:
170
+ # let's try as a 1-tuple
171
+ dest = self.build(cls, (source,))
172
+
173
+ for field, conv in convByName.items():
174
+ if not hasattr(dest, field) and isinstance(conv, OptionalValue):
175
+ setattr(dest, field, conv.DEFAULT)
176
+
177
+ dest = self._callbackTable.get(
178
+ (BuildCallback.AFTER_BUILD,) + callbackKey, lambda d: d
179
+ )(dest)
180
+
181
+ return dest
182
+
183
+
184
+ class TableUnbuilder:
185
+ def __init__(self, callbackTable=None):
186
+ if callbackTable is None:
187
+ callbackTable = {}
188
+ self._callbackTable = callbackTable
189
+
190
+ def unbuild(self, table):
191
+ assert isinstance(table, BaseTable)
192
+
193
+ source = {}
194
+
195
+ callbackKey = (type(table),)
196
+ if isinstance(table, FormatSwitchingBaseTable):
197
+ source["Format"] = int(table.Format)
198
+ callbackKey += (table.Format,)
199
+
200
+ for converter in table.getConverters():
201
+ if isinstance(converter, ComputedInt):
202
+ continue
203
+ value = getattr(table, converter.name)
204
+
205
+ enumClass = getattr(converter, "enumClass", None)
206
+ if enumClass:
207
+ source[converter.name] = value.name.lower()
208
+ elif isinstance(converter, Struct):
209
+ if converter.repeat:
210
+ source[converter.name] = [self.unbuild(v) for v in value]
211
+ else:
212
+ source[converter.name] = self.unbuild(value)
213
+ elif isinstance(converter, SimpleValue):
214
+ # "simple" values (e.g. int, float, str) need no further un-building
215
+ source[converter.name] = value
216
+ else:
217
+ raise NotImplementedError(
218
+ "Don't know how unbuild {value!r} with {converter!r}"
219
+ )
220
+
221
+ source = self._callbackTable.get(callbackKey, lambda s: s)(source)
222
+
223
+ return source
@@ -0,0 +1,81 @@
1
+ from fontTools.ttLib.tables import otTables as ot
2
+ from .table_builder import TableUnbuilder
3
+
4
+
5
+ def unbuildColrV1(layerList, baseGlyphList):
6
+ layers = []
7
+ if layerList:
8
+ layers = layerList.Paint
9
+ unbuilder = LayerListUnbuilder(layers)
10
+ return {
11
+ rec.BaseGlyph: unbuilder.unbuildPaint(rec.Paint)
12
+ for rec in baseGlyphList.BaseGlyphPaintRecord
13
+ }
14
+
15
+
16
+ def _flatten_layers(lst):
17
+ for paint in lst:
18
+ if paint["Format"] == ot.PaintFormat.PaintColrLayers:
19
+ yield from _flatten_layers(paint["Layers"])
20
+ else:
21
+ yield paint
22
+
23
+
24
+ class LayerListUnbuilder:
25
+ def __init__(self, layers):
26
+ self.layers = layers
27
+
28
+ callbacks = {
29
+ (
30
+ ot.Paint,
31
+ ot.PaintFormat.PaintColrLayers,
32
+ ): self._unbuildPaintColrLayers,
33
+ }
34
+ self.tableUnbuilder = TableUnbuilder(callbacks)
35
+
36
+ def unbuildPaint(self, paint):
37
+ assert isinstance(paint, ot.Paint)
38
+ return self.tableUnbuilder.unbuild(paint)
39
+
40
+ def _unbuildPaintColrLayers(self, source):
41
+ assert source["Format"] == ot.PaintFormat.PaintColrLayers
42
+
43
+ layers = list(
44
+ _flatten_layers(
45
+ [
46
+ self.unbuildPaint(childPaint)
47
+ for childPaint in self.layers[
48
+ source["FirstLayerIndex"] : source["FirstLayerIndex"]
49
+ + source["NumLayers"]
50
+ ]
51
+ ]
52
+ )
53
+ )
54
+
55
+ if len(layers) == 1:
56
+ return layers[0]
57
+
58
+ return {"Format": source["Format"], "Layers": layers}
59
+
60
+
61
+ if __name__ == "__main__":
62
+ from pprint import pprint
63
+ import sys
64
+ from fontTools.ttLib import TTFont
65
+
66
+ try:
67
+ fontfile = sys.argv[1]
68
+ except IndexError:
69
+ sys.exit("usage: fonttools colorLib.unbuilder FONTFILE")
70
+
71
+ font = TTFont(fontfile)
72
+ colr = font["COLR"]
73
+ if colr.version < 1:
74
+ sys.exit(f"error: No COLR table version=1 found in {fontfile}")
75
+
76
+ colorGlyphs = unbuildColrV1(
77
+ colr.table.LayerList,
78
+ colr.table.BaseGlyphList,
79
+ )
80
+
81
+ pprint(colorGlyphs)
@@ -0,0 +1,90 @@
1
+ """
2
+ Define all configuration options that can affect the working of fontTools
3
+ modules. E.g. optimization levels of varLib IUP, otlLib GPOS compression level,
4
+ etc. If this file gets too big, split it into smaller files per-module.
5
+
6
+ An instance of the Config class can be attached to a TTFont object, so that
7
+ the various modules can access their configuration options from it.
8
+ """
9
+
10
+ from textwrap import dedent
11
+
12
+ from fontTools.misc.configTools import *
13
+
14
+
15
+ class Config(AbstractConfig):
16
+ options = Options()
17
+
18
+
19
+ OPTIONS = Config.options
20
+
21
+
22
+ Config.register_option(
23
+ name="fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL",
24
+ help=dedent(
25
+ """\
26
+ GPOS Lookup type 2 (PairPos) compression level:
27
+ 0 = do not attempt to compact PairPos lookups;
28
+ 1 to 8 = create at most 1 to 8 new subtables for each existing
29
+ subtable, provided that it would yield a 50%% file size saving;
30
+ 9 = create as many new subtables as needed to yield a file size saving.
31
+ Default: 0.
32
+
33
+ This compaction aims to save file size, by splitting large class
34
+ kerning subtables (Format 2) that contain many zero values into
35
+ smaller and denser subtables. It's a trade-off between the overhead
36
+ of several subtables versus the sparseness of one big subtable.
37
+
38
+ See the pull request: https://github.com/fonttools/fonttools/pull/2326
39
+ """
40
+ ),
41
+ default=0,
42
+ parse=int,
43
+ validate=lambda v: v in range(10),
44
+ )
45
+
46
+ Config.register_option(
47
+ name="fontTools.ttLib.tables.otBase:USE_HARFBUZZ_REPACKER",
48
+ help=dedent(
49
+ """\
50
+ FontTools tries to use the HarfBuzz Repacker to serialize GPOS/GSUB tables
51
+ if the uharfbuzz python bindings are importable, otherwise falls back to its
52
+ slower, less efficient serializer. Set to False to always use the latter.
53
+ Set to True to explicitly request the HarfBuzz Repacker (will raise an
54
+ error if uharfbuzz cannot be imported).
55
+ """
56
+ ),
57
+ default=None,
58
+ parse=Option.parse_optional_bool,
59
+ validate=Option.validate_optional_bool,
60
+ )
61
+
62
+ Config.register_option(
63
+ name="fontTools.otlLib.builder:WRITE_GPOS7",
64
+ help=dedent(
65
+ """\
66
+ macOS before 13.2 didn’t support GPOS LookupType 7 (non-chaining
67
+ ContextPos lookups), so FontTools.otlLib.builder disables a file size
68
+ optimisation that would use LookupType 7 instead of 8 when there is no
69
+ chaining (no prefix or suffix). Set to True to enable the optimization.
70
+ """
71
+ ),
72
+ default=False,
73
+ parse=Option.parse_optional_bool,
74
+ validate=Option.validate_optional_bool,
75
+ )
76
+
77
+ Config.register_option(
78
+ name="fontTools.ttLib:OPTIMIZE_FONT_SPEED",
79
+ help=dedent(
80
+ """\
81
+ Enable optimizations that prioritize speed over file size. This
82
+ mainly affects how glyf table and gvar / VARC tables are compiled.
83
+ The produced fonts will be larger, but rendering performance will
84
+ be improved with HarfBuzz and other text layout engines.
85
+ """
86
+ ),
87
+ default=False,
88
+ parse=Option.parse_optional_bool,
89
+ validate=Option.validate_optional_bool,
90
+ )
@@ -0,0 +1,15 @@
1
+ # Copyright 2016 Google Inc. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from .cu2qu import *
@@ -0,0 +1,6 @@
1
+ import sys
2
+ from .cli import _main as main
3
+
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())
@@ -0,0 +1,54 @@
1
+ """Benchmark the cu2qu algorithm performance."""
2
+
3
+ from .cu2qu import *
4
+ import random
5
+ import timeit
6
+
7
+ MAX_ERR = 0.05
8
+
9
+
10
+ def generate_curve():
11
+ return [
12
+ tuple(float(random.randint(0, 2048)) for coord in range(2))
13
+ for point in range(4)
14
+ ]
15
+
16
+
17
+ def setup_curve_to_quadratic():
18
+ return generate_curve(), MAX_ERR
19
+
20
+
21
+ def setup_curves_to_quadratic():
22
+ num_curves = 3
23
+ return ([generate_curve() for curve in range(num_curves)], [MAX_ERR] * num_curves)
24
+
25
+
26
+ def run_benchmark(module, function, setup_suffix="", repeat=5, number=1000):
27
+ setup_func = "setup_" + function
28
+ if setup_suffix:
29
+ print("%s with %s:" % (function, setup_suffix), end="")
30
+ setup_func += "_" + setup_suffix
31
+ else:
32
+ print("%s:" % function, end="")
33
+
34
+ def wrapper(function, setup_func):
35
+ function = globals()[function]
36
+ setup_func = globals()[setup_func]
37
+
38
+ def wrapped():
39
+ return function(*setup_func())
40
+
41
+ return wrapped
42
+
43
+ results = timeit.repeat(wrapper(function, setup_func), repeat=repeat, number=number)
44
+ print("\t%5.1fus" % (min(results) * 1000000.0 / number))
45
+
46
+
47
+ def main():
48
+ run_benchmark("cu2qu", "curve_to_quadratic")
49
+ run_benchmark("cu2qu", "curves_to_quadratic")
50
+
51
+
52
+ if __name__ == "__main__":
53
+ random.seed(1)
54
+ main()