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.
- fontTools/__init__.py +8 -0
- fontTools/__main__.py +35 -0
- fontTools/afmLib.py +439 -0
- fontTools/agl.py +5233 -0
- fontTools/annotations.py +30 -0
- fontTools/cffLib/CFF2ToCFF.py +258 -0
- fontTools/cffLib/CFFToCFF2.py +305 -0
- fontTools/cffLib/__init__.py +3694 -0
- fontTools/cffLib/specializer.py +927 -0
- fontTools/cffLib/transforms.py +495 -0
- fontTools/cffLib/width.py +210 -0
- fontTools/colorLib/__init__.py +0 -0
- fontTools/colorLib/builder.py +664 -0
- fontTools/colorLib/errors.py +2 -0
- fontTools/colorLib/geometry.py +143 -0
- fontTools/colorLib/table_builder.py +223 -0
- fontTools/colorLib/unbuilder.py +81 -0
- fontTools/config/__init__.py +90 -0
- fontTools/cu2qu/__init__.py +15 -0
- fontTools/cu2qu/__main__.py +6 -0
- fontTools/cu2qu/benchmark.py +54 -0
- fontTools/cu2qu/cli.py +198 -0
- fontTools/cu2qu/cu2qu.c +15817 -0
- fontTools/cu2qu/cu2qu.cp311-win32.pyd +0 -0
- fontTools/cu2qu/cu2qu.py +563 -0
- fontTools/cu2qu/errors.py +77 -0
- fontTools/cu2qu/ufo.py +363 -0
- fontTools/designspaceLib/__init__.py +3343 -0
- fontTools/designspaceLib/__main__.py +6 -0
- fontTools/designspaceLib/split.py +475 -0
- fontTools/designspaceLib/statNames.py +260 -0
- fontTools/designspaceLib/types.py +147 -0
- fontTools/encodings/MacRoman.py +258 -0
- fontTools/encodings/StandardEncoding.py +258 -0
- fontTools/encodings/__init__.py +1 -0
- fontTools/encodings/codecs.py +135 -0
- fontTools/feaLib/__init__.py +4 -0
- fontTools/feaLib/__main__.py +78 -0
- fontTools/feaLib/ast.py +2143 -0
- fontTools/feaLib/builder.py +1814 -0
- fontTools/feaLib/error.py +22 -0
- fontTools/feaLib/lexer.c +17029 -0
- fontTools/feaLib/lexer.cp311-win32.pyd +0 -0
- fontTools/feaLib/lexer.py +287 -0
- fontTools/feaLib/location.py +12 -0
- fontTools/feaLib/lookupDebugInfo.py +12 -0
- fontTools/feaLib/parser.py +2394 -0
- fontTools/feaLib/variableScalar.py +118 -0
- fontTools/fontBuilder.py +1014 -0
- fontTools/help.py +36 -0
- fontTools/merge/__init__.py +248 -0
- fontTools/merge/__main__.py +6 -0
- fontTools/merge/base.py +81 -0
- fontTools/merge/cmap.py +173 -0
- fontTools/merge/layout.py +526 -0
- fontTools/merge/options.py +85 -0
- fontTools/merge/tables.py +352 -0
- fontTools/merge/unicode.py +78 -0
- fontTools/merge/util.py +143 -0
- fontTools/misc/__init__.py +1 -0
- fontTools/misc/arrayTools.py +424 -0
- fontTools/misc/bezierTools.c +39731 -0
- fontTools/misc/bezierTools.cp311-win32.pyd +0 -0
- fontTools/misc/bezierTools.py +1500 -0
- fontTools/misc/classifyTools.py +170 -0
- fontTools/misc/cliTools.py +53 -0
- fontTools/misc/configTools.py +349 -0
- fontTools/misc/cython.py +27 -0
- fontTools/misc/dictTools.py +83 -0
- fontTools/misc/eexec.py +119 -0
- fontTools/misc/encodingTools.py +72 -0
- fontTools/misc/enumTools.py +23 -0
- fontTools/misc/etree.py +456 -0
- fontTools/misc/filenames.py +245 -0
- fontTools/misc/filesystem/__init__.py +68 -0
- fontTools/misc/filesystem/_base.py +134 -0
- fontTools/misc/filesystem/_copy.py +45 -0
- fontTools/misc/filesystem/_errors.py +54 -0
- fontTools/misc/filesystem/_info.py +75 -0
- fontTools/misc/filesystem/_osfs.py +164 -0
- fontTools/misc/filesystem/_path.py +67 -0
- fontTools/misc/filesystem/_subfs.py +92 -0
- fontTools/misc/filesystem/_tempfs.py +34 -0
- fontTools/misc/filesystem/_tools.py +34 -0
- fontTools/misc/filesystem/_walk.py +55 -0
- fontTools/misc/filesystem/_zipfs.py +204 -0
- fontTools/misc/fixedTools.py +253 -0
- fontTools/misc/intTools.py +25 -0
- fontTools/misc/iterTools.py +12 -0
- fontTools/misc/lazyTools.py +42 -0
- fontTools/misc/loggingTools.py +543 -0
- fontTools/misc/macCreatorType.py +56 -0
- fontTools/misc/macRes.py +261 -0
- fontTools/misc/plistlib/__init__.py +681 -0
- fontTools/misc/plistlib/py.typed +0 -0
- fontTools/misc/psCharStrings.py +1511 -0
- fontTools/misc/psLib.py +398 -0
- fontTools/misc/psOperators.py +572 -0
- fontTools/misc/py23.py +96 -0
- fontTools/misc/roundTools.py +110 -0
- fontTools/misc/sstruct.py +227 -0
- fontTools/misc/symfont.py +242 -0
- fontTools/misc/testTools.py +233 -0
- fontTools/misc/textTools.py +156 -0
- fontTools/misc/timeTools.py +88 -0
- fontTools/misc/transform.py +516 -0
- fontTools/misc/treeTools.py +45 -0
- fontTools/misc/vector.py +147 -0
- fontTools/misc/visitor.py +158 -0
- fontTools/misc/xmlReader.py +188 -0
- fontTools/misc/xmlWriter.py +231 -0
- fontTools/mtiLib/__init__.py +1400 -0
- fontTools/mtiLib/__main__.py +5 -0
- fontTools/otlLib/__init__.py +1 -0
- fontTools/otlLib/builder.py +3465 -0
- fontTools/otlLib/error.py +11 -0
- fontTools/otlLib/maxContextCalc.py +96 -0
- fontTools/otlLib/optimize/__init__.py +53 -0
- fontTools/otlLib/optimize/__main__.py +6 -0
- fontTools/otlLib/optimize/gpos.py +439 -0
- fontTools/pens/__init__.py +1 -0
- fontTools/pens/areaPen.py +52 -0
- fontTools/pens/basePen.py +475 -0
- fontTools/pens/boundsPen.py +98 -0
- fontTools/pens/cairoPen.py +26 -0
- fontTools/pens/cocoaPen.py +26 -0
- fontTools/pens/cu2quPen.py +325 -0
- fontTools/pens/explicitClosingLinePen.py +101 -0
- fontTools/pens/filterPen.py +433 -0
- fontTools/pens/freetypePen.py +462 -0
- fontTools/pens/hashPointPen.py +89 -0
- fontTools/pens/momentsPen.c +13378 -0
- fontTools/pens/momentsPen.cp311-win32.pyd +0 -0
- fontTools/pens/momentsPen.py +879 -0
- fontTools/pens/perimeterPen.py +69 -0
- fontTools/pens/pointInsidePen.py +192 -0
- fontTools/pens/pointPen.py +643 -0
- fontTools/pens/qtPen.py +29 -0
- fontTools/pens/qu2cuPen.py +105 -0
- fontTools/pens/quartzPen.py +43 -0
- fontTools/pens/recordingPen.py +335 -0
- fontTools/pens/reportLabPen.py +79 -0
- fontTools/pens/reverseContourPen.py +96 -0
- fontTools/pens/roundingPen.py +130 -0
- fontTools/pens/statisticsPen.py +312 -0
- fontTools/pens/svgPathPen.py +310 -0
- fontTools/pens/t2CharStringPen.py +88 -0
- fontTools/pens/teePen.py +55 -0
- fontTools/pens/transformPen.py +115 -0
- fontTools/pens/ttGlyphPen.py +335 -0
- fontTools/pens/wxPen.py +29 -0
- fontTools/qu2cu/__init__.py +15 -0
- fontTools/qu2cu/__main__.py +7 -0
- fontTools/qu2cu/benchmark.py +56 -0
- fontTools/qu2cu/cli.py +125 -0
- fontTools/qu2cu/qu2cu.c +16682 -0
- fontTools/qu2cu/qu2cu.cp311-win32.pyd +0 -0
- fontTools/qu2cu/qu2cu.py +405 -0
- fontTools/subset/__init__.py +4096 -0
- fontTools/subset/__main__.py +6 -0
- fontTools/subset/cff.py +184 -0
- fontTools/subset/svg.py +253 -0
- fontTools/subset/util.py +25 -0
- fontTools/svgLib/__init__.py +3 -0
- fontTools/svgLib/path/__init__.py +65 -0
- fontTools/svgLib/path/arc.py +154 -0
- fontTools/svgLib/path/parser.py +322 -0
- fontTools/svgLib/path/shapes.py +183 -0
- fontTools/t1Lib/__init__.py +648 -0
- fontTools/tfmLib.py +460 -0
- fontTools/ttLib/__init__.py +30 -0
- fontTools/ttLib/__main__.py +148 -0
- fontTools/ttLib/macUtils.py +54 -0
- fontTools/ttLib/removeOverlaps.py +395 -0
- fontTools/ttLib/reorderGlyphs.py +285 -0
- fontTools/ttLib/scaleUpem.py +436 -0
- fontTools/ttLib/sfnt.py +661 -0
- fontTools/ttLib/standardGlyphOrder.py +271 -0
- fontTools/ttLib/tables/B_A_S_E_.py +14 -0
- fontTools/ttLib/tables/BitmapGlyphMetrics.py +64 -0
- fontTools/ttLib/tables/C_B_D_T_.py +113 -0
- fontTools/ttLib/tables/C_B_L_C_.py +19 -0
- fontTools/ttLib/tables/C_F_F_.py +61 -0
- fontTools/ttLib/tables/C_F_F__2.py +26 -0
- fontTools/ttLib/tables/C_O_L_R_.py +165 -0
- fontTools/ttLib/tables/C_P_A_L_.py +305 -0
- fontTools/ttLib/tables/D_S_I_G_.py +158 -0
- fontTools/ttLib/tables/D__e_b_g.py +35 -0
- fontTools/ttLib/tables/DefaultTable.py +49 -0
- fontTools/ttLib/tables/E_B_D_T_.py +835 -0
- fontTools/ttLib/tables/E_B_L_C_.py +718 -0
- fontTools/ttLib/tables/F_F_T_M_.py +52 -0
- fontTools/ttLib/tables/F__e_a_t.py +149 -0
- fontTools/ttLib/tables/G_D_E_F_.py +13 -0
- fontTools/ttLib/tables/G_M_A_P_.py +148 -0
- fontTools/ttLib/tables/G_P_K_G_.py +133 -0
- fontTools/ttLib/tables/G_P_O_S_.py +14 -0
- fontTools/ttLib/tables/G_S_U_B_.py +13 -0
- fontTools/ttLib/tables/G_V_A_R_.py +5 -0
- fontTools/ttLib/tables/G__l_a_t.py +235 -0
- fontTools/ttLib/tables/G__l_o_c.py +85 -0
- fontTools/ttLib/tables/H_V_A_R_.py +13 -0
- fontTools/ttLib/tables/J_S_T_F_.py +13 -0
- fontTools/ttLib/tables/L_T_S_H_.py +58 -0
- fontTools/ttLib/tables/M_A_T_H_.py +13 -0
- fontTools/ttLib/tables/M_E_T_A_.py +352 -0
- fontTools/ttLib/tables/M_V_A_R_.py +13 -0
- fontTools/ttLib/tables/O_S_2f_2.py +752 -0
- fontTools/ttLib/tables/S_I_N_G_.py +99 -0
- fontTools/ttLib/tables/S_T_A_T_.py +15 -0
- fontTools/ttLib/tables/S_V_G_.py +223 -0
- fontTools/ttLib/tables/S__i_l_f.py +1040 -0
- fontTools/ttLib/tables/S__i_l_l.py +92 -0
- fontTools/ttLib/tables/T_S_I_B_.py +13 -0
- fontTools/ttLib/tables/T_S_I_C_.py +14 -0
- fontTools/ttLib/tables/T_S_I_D_.py +13 -0
- fontTools/ttLib/tables/T_S_I_J_.py +13 -0
- fontTools/ttLib/tables/T_S_I_P_.py +13 -0
- fontTools/ttLib/tables/T_S_I_S_.py +13 -0
- fontTools/ttLib/tables/T_S_I_V_.py +26 -0
- fontTools/ttLib/tables/T_S_I__0.py +70 -0
- fontTools/ttLib/tables/T_S_I__1.py +163 -0
- fontTools/ttLib/tables/T_S_I__2.py +17 -0
- fontTools/ttLib/tables/T_S_I__3.py +22 -0
- fontTools/ttLib/tables/T_S_I__5.py +60 -0
- fontTools/ttLib/tables/T_T_F_A_.py +14 -0
- fontTools/ttLib/tables/TupleVariation.py +884 -0
- fontTools/ttLib/tables/V_A_R_C_.py +12 -0
- fontTools/ttLib/tables/V_D_M_X_.py +249 -0
- fontTools/ttLib/tables/V_O_R_G_.py +165 -0
- fontTools/ttLib/tables/V_V_A_R_.py +13 -0
- fontTools/ttLib/tables/__init__.py +98 -0
- fontTools/ttLib/tables/_a_n_k_r.py +15 -0
- fontTools/ttLib/tables/_a_v_a_r.py +193 -0
- fontTools/ttLib/tables/_b_s_l_n.py +15 -0
- fontTools/ttLib/tables/_c_i_d_g.py +24 -0
- fontTools/ttLib/tables/_c_m_a_p.py +1591 -0
- fontTools/ttLib/tables/_c_v_a_r.py +94 -0
- fontTools/ttLib/tables/_c_v_t.py +56 -0
- fontTools/ttLib/tables/_f_e_a_t.py +15 -0
- fontTools/ttLib/tables/_f_p_g_m.py +62 -0
- fontTools/ttLib/tables/_f_v_a_r.py +261 -0
- fontTools/ttLib/tables/_g_a_s_p.py +63 -0
- fontTools/ttLib/tables/_g_c_i_d.py +13 -0
- fontTools/ttLib/tables/_g_l_y_f.py +2311 -0
- fontTools/ttLib/tables/_g_v_a_r.py +340 -0
- fontTools/ttLib/tables/_h_d_m_x.py +127 -0
- fontTools/ttLib/tables/_h_e_a_d.py +130 -0
- fontTools/ttLib/tables/_h_h_e_a.py +147 -0
- fontTools/ttLib/tables/_h_m_t_x.py +164 -0
- fontTools/ttLib/tables/_k_e_r_n.py +289 -0
- fontTools/ttLib/tables/_l_c_a_r.py +13 -0
- fontTools/ttLib/tables/_l_o_c_a.py +70 -0
- fontTools/ttLib/tables/_l_t_a_g.py +72 -0
- fontTools/ttLib/tables/_m_a_x_p.py +147 -0
- fontTools/ttLib/tables/_m_e_t_a.py +112 -0
- fontTools/ttLib/tables/_m_o_r_t.py +14 -0
- fontTools/ttLib/tables/_m_o_r_x.py +15 -0
- fontTools/ttLib/tables/_n_a_m_e.py +1242 -0
- fontTools/ttLib/tables/_o_p_b_d.py +14 -0
- fontTools/ttLib/tables/_p_o_s_t.py +319 -0
- fontTools/ttLib/tables/_p_r_e_p.py +16 -0
- fontTools/ttLib/tables/_p_r_o_p.py +12 -0
- fontTools/ttLib/tables/_s_b_i_x.py +129 -0
- fontTools/ttLib/tables/_t_r_a_k.py +332 -0
- fontTools/ttLib/tables/_v_h_e_a.py +139 -0
- fontTools/ttLib/tables/_v_m_t_x.py +19 -0
- fontTools/ttLib/tables/asciiTable.py +20 -0
- fontTools/ttLib/tables/grUtils.py +92 -0
- fontTools/ttLib/tables/otBase.py +1458 -0
- fontTools/ttLib/tables/otConverters.py +2068 -0
- fontTools/ttLib/tables/otData.py +6400 -0
- fontTools/ttLib/tables/otTables.py +2703 -0
- fontTools/ttLib/tables/otTraverse.py +163 -0
- fontTools/ttLib/tables/sbixGlyph.py +149 -0
- fontTools/ttLib/tables/sbixStrike.py +177 -0
- fontTools/ttLib/tables/table_API_readme.txt +91 -0
- fontTools/ttLib/tables/ttProgram.py +594 -0
- fontTools/ttLib/ttCollection.py +125 -0
- fontTools/ttLib/ttFont.py +1148 -0
- fontTools/ttLib/ttGlyphSet.py +490 -0
- fontTools/ttLib/ttVisitor.py +32 -0
- fontTools/ttLib/woff2.py +1680 -0
- fontTools/ttx.py +479 -0
- fontTools/ufoLib/__init__.py +2575 -0
- fontTools/ufoLib/converters.py +407 -0
- fontTools/ufoLib/errors.py +30 -0
- fontTools/ufoLib/etree.py +6 -0
- fontTools/ufoLib/filenames.py +356 -0
- fontTools/ufoLib/glifLib.py +2120 -0
- fontTools/ufoLib/kerning.py +141 -0
- fontTools/ufoLib/plistlib.py +47 -0
- fontTools/ufoLib/pointPen.py +6 -0
- fontTools/ufoLib/utils.py +107 -0
- fontTools/ufoLib/validators.py +1208 -0
- fontTools/unicode.py +50 -0
- fontTools/unicodedata/Blocks.py +817 -0
- fontTools/unicodedata/Mirrored.py +446 -0
- fontTools/unicodedata/OTTags.py +50 -0
- fontTools/unicodedata/ScriptExtensions.py +832 -0
- fontTools/unicodedata/Scripts.py +3639 -0
- fontTools/unicodedata/__init__.py +306 -0
- fontTools/varLib/__init__.py +1600 -0
- fontTools/varLib/__main__.py +6 -0
- fontTools/varLib/avar/__init__.py +0 -0
- fontTools/varLib/avar/__main__.py +72 -0
- fontTools/varLib/avar/build.py +79 -0
- fontTools/varLib/avar/map.py +108 -0
- fontTools/varLib/avar/plan.py +1004 -0
- fontTools/varLib/avar/unbuild.py +271 -0
- fontTools/varLib/avarPlanner.py +8 -0
- fontTools/varLib/builder.py +215 -0
- fontTools/varLib/cff.py +631 -0
- fontTools/varLib/errors.py +219 -0
- fontTools/varLib/featureVars.py +703 -0
- fontTools/varLib/hvar.py +113 -0
- fontTools/varLib/instancer/__init__.py +2052 -0
- fontTools/varLib/instancer/__main__.py +5 -0
- fontTools/varLib/instancer/featureVars.py +190 -0
- fontTools/varLib/instancer/names.py +388 -0
- fontTools/varLib/instancer/solver.py +309 -0
- fontTools/varLib/interpolatable.py +1209 -0
- fontTools/varLib/interpolatableHelpers.py +399 -0
- fontTools/varLib/interpolatablePlot.py +1269 -0
- fontTools/varLib/interpolatableTestContourOrder.py +82 -0
- fontTools/varLib/interpolatableTestStartingPoint.py +107 -0
- fontTools/varLib/interpolate_layout.py +124 -0
- fontTools/varLib/iup.c +19815 -0
- fontTools/varLib/iup.cp311-win32.pyd +0 -0
- fontTools/varLib/iup.py +490 -0
- fontTools/varLib/merger.py +1717 -0
- fontTools/varLib/models.py +642 -0
- fontTools/varLib/multiVarStore.py +253 -0
- fontTools/varLib/mutator.py +529 -0
- fontTools/varLib/mvar.py +40 -0
- fontTools/varLib/plot.py +238 -0
- fontTools/varLib/stat.py +149 -0
- fontTools/varLib/varStore.py +739 -0
- fontTools/voltLib/__init__.py +5 -0
- fontTools/voltLib/__main__.py +206 -0
- fontTools/voltLib/ast.py +452 -0
- fontTools/voltLib/error.py +12 -0
- fontTools/voltLib/lexer.py +99 -0
- fontTools/voltLib/parser.py +664 -0
- fontTools/voltLib/voltToFea.py +911 -0
- fonttools-4.60.2.data/data/share/man/man1/ttx.1 +225 -0
- fonttools-4.60.2.dist-info/METADATA +2250 -0
- fonttools-4.60.2.dist-info/RECORD +353 -0
- fonttools-4.60.2.dist-info/WHEEL +5 -0
- fonttools-4.60.2.dist-info/entry_points.txt +5 -0
- fonttools-4.60.2.dist-info/licenses/LICENSE +21 -0
- fonttools-4.60.2.dist-info/licenses/LICENSE.external +388 -0
- fonttools-4.60.2.dist-info/top_level.txt +1 -0
|
Binary file
|
fontTools/cu2qu/cu2qu.py
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# cython: language_level=3
|
|
2
|
+
# distutils: define_macros=CYTHON_TRACE_NOGIL=1
|
|
3
|
+
|
|
4
|
+
# Copyright 2015 Google Inc. All Rights Reserved.
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
import cython
|
|
20
|
+
except (AttributeError, ImportError):
|
|
21
|
+
# if cython not installed, use mock module with no-op decorators and types
|
|
22
|
+
from fontTools.misc import cython
|
|
23
|
+
COMPILED = cython.compiled
|
|
24
|
+
|
|
25
|
+
import math
|
|
26
|
+
|
|
27
|
+
from .errors import Error as Cu2QuError, ApproxNotFoundError
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
__all__ = ["curve_to_quadratic", "curves_to_quadratic"]
|
|
31
|
+
|
|
32
|
+
MAX_N = 100
|
|
33
|
+
|
|
34
|
+
NAN = float("NaN")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@cython.cfunc
|
|
38
|
+
@cython.inline
|
|
39
|
+
@cython.returns(cython.double)
|
|
40
|
+
@cython.locals(v1=cython.complex, v2=cython.complex, result=cython.double)
|
|
41
|
+
def dot(v1, v2):
|
|
42
|
+
"""Return the dot product of two vectors.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
v1 (complex): First vector.
|
|
46
|
+
v2 (complex): Second vector.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
double: Dot product.
|
|
50
|
+
"""
|
|
51
|
+
result = (v1 * v2.conjugate()).real
|
|
52
|
+
# When vectors are perpendicular (i.e. dot product is 0), the above expression may
|
|
53
|
+
# yield slightly different results when running in pure Python vs C/Cython,
|
|
54
|
+
# both of which are correct within IEEE-754 floating-point precision.
|
|
55
|
+
# It's probably due to the different order of operations and roundings in each
|
|
56
|
+
# implementation. Because we are using the result in a denominator and catching
|
|
57
|
+
# ZeroDivisionError (see `calc_intersect`), it's best to normalize the result here.
|
|
58
|
+
if abs(result) < 1e-15:
|
|
59
|
+
result = 0.0
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@cython.cfunc
|
|
64
|
+
@cython.locals(z=cython.complex, den=cython.double)
|
|
65
|
+
@cython.locals(zr=cython.double, zi=cython.double)
|
|
66
|
+
def _complex_div_by_real(z, den):
|
|
67
|
+
"""Divide complex by real using Python's method (two separate divisions).
|
|
68
|
+
|
|
69
|
+
This ensures bit-exact compatibility with Python's complex division,
|
|
70
|
+
avoiding C's multiply-by-reciprocal optimization that can cause 1 ULP differences
|
|
71
|
+
on some platforms/compilers (e.g. clang on macOS arm64).
|
|
72
|
+
|
|
73
|
+
https://github.com/fonttools/fonttools/issues/3928
|
|
74
|
+
"""
|
|
75
|
+
zr = z.real
|
|
76
|
+
zi = z.imag
|
|
77
|
+
return complex(zr / den, zi / den)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@cython.cfunc
|
|
81
|
+
@cython.inline
|
|
82
|
+
@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
|
|
83
|
+
@cython.locals(
|
|
84
|
+
_1=cython.complex, _2=cython.complex, _3=cython.complex, _4=cython.complex
|
|
85
|
+
)
|
|
86
|
+
def calc_cubic_points(a, b, c, d):
|
|
87
|
+
_1 = d
|
|
88
|
+
_2 = _complex_div_by_real(c, 3.0) + d
|
|
89
|
+
_3 = _complex_div_by_real(b + c, 3.0) + _2
|
|
90
|
+
_4 = a + d + c + b
|
|
91
|
+
return _1, _2, _3, _4
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@cython.cfunc
|
|
95
|
+
@cython.inline
|
|
96
|
+
@cython.locals(
|
|
97
|
+
p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex
|
|
98
|
+
)
|
|
99
|
+
@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
|
|
100
|
+
def calc_cubic_parameters(p0, p1, p2, p3):
|
|
101
|
+
c = (p1 - p0) * 3.0
|
|
102
|
+
b = (p2 - p1) * 3.0 - c
|
|
103
|
+
d = p0
|
|
104
|
+
a = p3 - d - c - b
|
|
105
|
+
return a, b, c, d
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@cython.cfunc
|
|
109
|
+
@cython.inline
|
|
110
|
+
@cython.locals(
|
|
111
|
+
p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex
|
|
112
|
+
)
|
|
113
|
+
def split_cubic_into_n_iter(p0, p1, p2, p3, n):
|
|
114
|
+
"""Split a cubic Bezier into n equal parts.
|
|
115
|
+
|
|
116
|
+
Splits the curve into `n` equal parts by curve time.
|
|
117
|
+
(t=0..1/n, t=1/n..2/n, ...)
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
p0 (complex): Start point of curve.
|
|
121
|
+
p1 (complex): First handle of curve.
|
|
122
|
+
p2 (complex): Second handle of curve.
|
|
123
|
+
p3 (complex): End point of curve.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
An iterator yielding the control points (four complex values) of the
|
|
127
|
+
subcurves.
|
|
128
|
+
"""
|
|
129
|
+
# Hand-coded special-cases
|
|
130
|
+
if n == 2:
|
|
131
|
+
return iter(split_cubic_into_two(p0, p1, p2, p3))
|
|
132
|
+
if n == 3:
|
|
133
|
+
return iter(split_cubic_into_three(p0, p1, p2, p3))
|
|
134
|
+
if n == 4:
|
|
135
|
+
a, b = split_cubic_into_two(p0, p1, p2, p3)
|
|
136
|
+
return iter(
|
|
137
|
+
split_cubic_into_two(a[0], a[1], a[2], a[3])
|
|
138
|
+
+ split_cubic_into_two(b[0], b[1], b[2], b[3])
|
|
139
|
+
)
|
|
140
|
+
if n == 6:
|
|
141
|
+
a, b = split_cubic_into_two(p0, p1, p2, p3)
|
|
142
|
+
return iter(
|
|
143
|
+
split_cubic_into_three(a[0], a[1], a[2], a[3])
|
|
144
|
+
+ split_cubic_into_three(b[0], b[1], b[2], b[3])
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return _split_cubic_into_n_gen(p0, p1, p2, p3, n)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@cython.locals(
|
|
151
|
+
p0=cython.complex,
|
|
152
|
+
p1=cython.complex,
|
|
153
|
+
p2=cython.complex,
|
|
154
|
+
p3=cython.complex,
|
|
155
|
+
n=cython.int,
|
|
156
|
+
)
|
|
157
|
+
@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
|
|
158
|
+
@cython.locals(
|
|
159
|
+
dt=cython.double, delta_2=cython.double, delta_3=cython.double, i=cython.int
|
|
160
|
+
)
|
|
161
|
+
@cython.locals(
|
|
162
|
+
a1=cython.complex, b1=cython.complex, c1=cython.complex, d1=cython.complex
|
|
163
|
+
)
|
|
164
|
+
def _split_cubic_into_n_gen(p0, p1, p2, p3, n):
|
|
165
|
+
a, b, c, d = calc_cubic_parameters(p0, p1, p2, p3)
|
|
166
|
+
dt = 1 / n
|
|
167
|
+
delta_2 = dt * dt
|
|
168
|
+
delta_3 = dt * delta_2
|
|
169
|
+
for i in range(n):
|
|
170
|
+
t1 = i * dt
|
|
171
|
+
t1_2 = t1 * t1
|
|
172
|
+
# calc new a, b, c and d
|
|
173
|
+
a1 = a * delta_3
|
|
174
|
+
b1 = (3 * a * t1 + b) * delta_2
|
|
175
|
+
c1 = (2 * b * t1 + c + 3 * a * t1_2) * dt
|
|
176
|
+
d1 = a * t1 * t1_2 + b * t1_2 + c * t1 + d
|
|
177
|
+
yield calc_cubic_points(a1, b1, c1, d1)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@cython.cfunc
|
|
181
|
+
@cython.inline
|
|
182
|
+
@cython.locals(
|
|
183
|
+
p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex
|
|
184
|
+
)
|
|
185
|
+
@cython.locals(mid=cython.complex, deriv3=cython.complex)
|
|
186
|
+
def split_cubic_into_two(p0, p1, p2, p3):
|
|
187
|
+
"""Split a cubic Bezier into two equal parts.
|
|
188
|
+
|
|
189
|
+
Splits the curve into two equal parts at t = 0.5
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
p0 (complex): Start point of curve.
|
|
193
|
+
p1 (complex): First handle of curve.
|
|
194
|
+
p2 (complex): Second handle of curve.
|
|
195
|
+
p3 (complex): End point of curve.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
tuple: Two cubic Beziers (each expressed as a tuple of four complex
|
|
199
|
+
values).
|
|
200
|
+
"""
|
|
201
|
+
mid = (p0 + 3 * (p1 + p2) + p3) * 0.125
|
|
202
|
+
deriv3 = (p3 + p2 - p1 - p0) * 0.125
|
|
203
|
+
return (
|
|
204
|
+
(p0, (p0 + p1) * 0.5, mid - deriv3, mid),
|
|
205
|
+
(mid, mid + deriv3, (p2 + p3) * 0.5, p3),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@cython.cfunc
|
|
210
|
+
@cython.inline
|
|
211
|
+
@cython.locals(
|
|
212
|
+
p0=cython.complex,
|
|
213
|
+
p1=cython.complex,
|
|
214
|
+
p2=cython.complex,
|
|
215
|
+
p3=cython.complex,
|
|
216
|
+
)
|
|
217
|
+
@cython.locals(
|
|
218
|
+
mid1=cython.complex,
|
|
219
|
+
deriv1=cython.complex,
|
|
220
|
+
mid2=cython.complex,
|
|
221
|
+
deriv2=cython.complex,
|
|
222
|
+
)
|
|
223
|
+
def split_cubic_into_three(p0, p1, p2, p3):
|
|
224
|
+
"""Split a cubic Bezier into three equal parts.
|
|
225
|
+
|
|
226
|
+
Splits the curve into three equal parts at t = 1/3 and t = 2/3
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
p0 (complex): Start point of curve.
|
|
230
|
+
p1 (complex): First handle of curve.
|
|
231
|
+
p2 (complex): Second handle of curve.
|
|
232
|
+
p3 (complex): End point of curve.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
tuple: Three cubic Beziers (each expressed as a tuple of four complex
|
|
236
|
+
values).
|
|
237
|
+
"""
|
|
238
|
+
mid1 = (8 * p0 + 12 * p1 + 6 * p2 + p3) * (1 / 27)
|
|
239
|
+
deriv1 = (p3 + 3 * p2 - 4 * p0) * (1 / 27)
|
|
240
|
+
mid2 = (p0 + 6 * p1 + 12 * p2 + 8 * p3) * (1 / 27)
|
|
241
|
+
deriv2 = (4 * p3 - 3 * p1 - p0) * (1 / 27)
|
|
242
|
+
return (
|
|
243
|
+
(p0, (2 * p0 + p1) / 3.0, mid1 - deriv1, mid1),
|
|
244
|
+
(mid1, mid1 + deriv1, mid2 - deriv2, mid2),
|
|
245
|
+
(mid2, mid2 + deriv2, (p2 + 2 * p3) / 3.0, p3),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@cython.cfunc
|
|
250
|
+
@cython.inline
|
|
251
|
+
@cython.returns(cython.complex)
|
|
252
|
+
@cython.locals(
|
|
253
|
+
t=cython.double,
|
|
254
|
+
p0=cython.complex,
|
|
255
|
+
p1=cython.complex,
|
|
256
|
+
p2=cython.complex,
|
|
257
|
+
p3=cython.complex,
|
|
258
|
+
)
|
|
259
|
+
@cython.locals(_p1=cython.complex, _p2=cython.complex)
|
|
260
|
+
def cubic_approx_control(t, p0, p1, p2, p3):
|
|
261
|
+
"""Approximate a cubic Bezier using a quadratic one.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
t (double): Position of control point.
|
|
265
|
+
p0 (complex): Start point of curve.
|
|
266
|
+
p1 (complex): First handle of curve.
|
|
267
|
+
p2 (complex): Second handle of curve.
|
|
268
|
+
p3 (complex): End point of curve.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
complex: Location of candidate control point on quadratic curve.
|
|
272
|
+
"""
|
|
273
|
+
_p1 = p0 + (p1 - p0) * 1.5
|
|
274
|
+
_p2 = p3 + (p2 - p3) * 1.5
|
|
275
|
+
return _p1 + (_p2 - _p1) * t
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@cython.cfunc
|
|
279
|
+
@cython.inline
|
|
280
|
+
@cython.returns(cython.complex)
|
|
281
|
+
@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
|
|
282
|
+
@cython.locals(ab=cython.complex, cd=cython.complex, p=cython.complex, h=cython.double)
|
|
283
|
+
def calc_intersect(a, b, c, d):
|
|
284
|
+
"""Calculate the intersection of two lines.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
a (complex): Start point of first line.
|
|
288
|
+
b (complex): End point of first line.
|
|
289
|
+
c (complex): Start point of second line.
|
|
290
|
+
d (complex): End point of second line.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
complex: Location of intersection if one present, ``complex(NaN,NaN)``
|
|
294
|
+
if no intersection was found.
|
|
295
|
+
"""
|
|
296
|
+
ab = b - a
|
|
297
|
+
cd = d - c
|
|
298
|
+
p = ab * 1j
|
|
299
|
+
try:
|
|
300
|
+
h = dot(p, a - c) / dot(p, cd)
|
|
301
|
+
except ZeroDivisionError:
|
|
302
|
+
# if 3 or 4 points are equal, we do have an intersection despite the zero-div:
|
|
303
|
+
# return one of the off-curves so that the algorithm can attempt a one-curve
|
|
304
|
+
# solution if it's within tolerance:
|
|
305
|
+
# https://github.com/linebender/kurbo/pull/484
|
|
306
|
+
if b == c and (a == b or c == d):
|
|
307
|
+
return b
|
|
308
|
+
return complex(NAN, NAN)
|
|
309
|
+
return c + cd * h
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@cython.cfunc
|
|
313
|
+
@cython.returns(cython.int)
|
|
314
|
+
@cython.locals(
|
|
315
|
+
tolerance=cython.double,
|
|
316
|
+
p0=cython.complex,
|
|
317
|
+
p1=cython.complex,
|
|
318
|
+
p2=cython.complex,
|
|
319
|
+
p3=cython.complex,
|
|
320
|
+
)
|
|
321
|
+
@cython.locals(mid=cython.complex, deriv3=cython.complex)
|
|
322
|
+
def cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance):
|
|
323
|
+
"""Check if a cubic Bezier lies within a given distance of the origin.
|
|
324
|
+
|
|
325
|
+
"Origin" means *the* origin (0,0), not the start of the curve. Note that no
|
|
326
|
+
checks are made on the start and end positions of the curve; this function
|
|
327
|
+
only checks the inside of the curve.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
p0 (complex): Start point of curve.
|
|
331
|
+
p1 (complex): First handle of curve.
|
|
332
|
+
p2 (complex): Second handle of curve.
|
|
333
|
+
p3 (complex): End point of curve.
|
|
334
|
+
tolerance (double): Distance from origin.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
bool: True if the cubic Bezier ``p`` entirely lies within a distance
|
|
338
|
+
``tolerance`` of the origin, False otherwise.
|
|
339
|
+
"""
|
|
340
|
+
# First check p2 then p1, as p2 has higher error early on.
|
|
341
|
+
if abs(p2) <= tolerance and abs(p1) <= tolerance:
|
|
342
|
+
return True
|
|
343
|
+
|
|
344
|
+
# Split.
|
|
345
|
+
mid = (p0 + 3 * (p1 + p2) + p3) * 0.125
|
|
346
|
+
if abs(mid) > tolerance:
|
|
347
|
+
return False
|
|
348
|
+
deriv3 = (p3 + p2 - p1 - p0) * 0.125
|
|
349
|
+
return cubic_farthest_fit_inside(
|
|
350
|
+
p0, (p0 + p1) * 0.5, mid - deriv3, mid, tolerance
|
|
351
|
+
) and cubic_farthest_fit_inside(mid, mid + deriv3, (p2 + p3) * 0.5, p3, tolerance)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@cython.cfunc
|
|
355
|
+
@cython.inline
|
|
356
|
+
@cython.locals(tolerance=cython.double)
|
|
357
|
+
@cython.locals(
|
|
358
|
+
q1=cython.complex,
|
|
359
|
+
c0=cython.complex,
|
|
360
|
+
c1=cython.complex,
|
|
361
|
+
c2=cython.complex,
|
|
362
|
+
c3=cython.complex,
|
|
363
|
+
)
|
|
364
|
+
def cubic_approx_quadratic(cubic, tolerance):
|
|
365
|
+
"""Approximate a cubic Bezier with a single quadratic within a given tolerance.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
cubic (sequence): Four complex numbers representing control points of
|
|
369
|
+
the cubic Bezier curve.
|
|
370
|
+
tolerance (double): Permitted deviation from the original curve.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Three complex numbers representing control points of the quadratic
|
|
374
|
+
curve if it fits within the given tolerance, or ``None`` if no suitable
|
|
375
|
+
curve could be calculated.
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
q1 = calc_intersect(cubic[0], cubic[1], cubic[2], cubic[3])
|
|
379
|
+
if math.isnan(q1.imag):
|
|
380
|
+
return None
|
|
381
|
+
c0 = cubic[0]
|
|
382
|
+
c3 = cubic[3]
|
|
383
|
+
c1 = c0 + (q1 - c0) * (2 / 3)
|
|
384
|
+
c2 = c3 + (q1 - c3) * (2 / 3)
|
|
385
|
+
if not cubic_farthest_fit_inside(0, c1 - cubic[1], c2 - cubic[2], 0, tolerance):
|
|
386
|
+
return None
|
|
387
|
+
return c0, q1, c3
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@cython.cfunc
|
|
391
|
+
@cython.locals(n=cython.int, tolerance=cython.double)
|
|
392
|
+
@cython.locals(i=cython.int)
|
|
393
|
+
@cython.locals(all_quadratic=cython.int)
|
|
394
|
+
@cython.locals(
|
|
395
|
+
c0=cython.complex, c1=cython.complex, c2=cython.complex, c3=cython.complex
|
|
396
|
+
)
|
|
397
|
+
@cython.locals(
|
|
398
|
+
q0=cython.complex,
|
|
399
|
+
q1=cython.complex,
|
|
400
|
+
next_q1=cython.complex,
|
|
401
|
+
q2=cython.complex,
|
|
402
|
+
d1=cython.complex,
|
|
403
|
+
)
|
|
404
|
+
def cubic_approx_spline(cubic, n, tolerance, all_quadratic):
|
|
405
|
+
"""Approximate a cubic Bezier curve with a spline of n quadratics.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
cubic (sequence): Four complex numbers representing control points of
|
|
409
|
+
the cubic Bezier curve.
|
|
410
|
+
n (int): Number of quadratic Bezier curves in the spline.
|
|
411
|
+
tolerance (double): Permitted deviation from the original curve.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
A list of ``n+2`` complex numbers, representing control points of the
|
|
415
|
+
quadratic spline if it fits within the given tolerance, or ``None`` if
|
|
416
|
+
no suitable spline could be calculated.
|
|
417
|
+
"""
|
|
418
|
+
|
|
419
|
+
if n == 1:
|
|
420
|
+
return cubic_approx_quadratic(cubic, tolerance)
|
|
421
|
+
if n == 2 and all_quadratic == False:
|
|
422
|
+
return cubic
|
|
423
|
+
|
|
424
|
+
cubics = split_cubic_into_n_iter(cubic[0], cubic[1], cubic[2], cubic[3], n)
|
|
425
|
+
|
|
426
|
+
# calculate the spline of quadratics and check errors at the same time.
|
|
427
|
+
next_cubic = next(cubics)
|
|
428
|
+
next_q1 = cubic_approx_control(
|
|
429
|
+
0, next_cubic[0], next_cubic[1], next_cubic[2], next_cubic[3]
|
|
430
|
+
)
|
|
431
|
+
q2 = cubic[0]
|
|
432
|
+
d1 = 0j
|
|
433
|
+
spline = [cubic[0], next_q1]
|
|
434
|
+
for i in range(1, n + 1):
|
|
435
|
+
# Current cubic to convert
|
|
436
|
+
c0, c1, c2, c3 = next_cubic
|
|
437
|
+
|
|
438
|
+
# Current quadratic approximation of current cubic
|
|
439
|
+
q0 = q2
|
|
440
|
+
q1 = next_q1
|
|
441
|
+
if i < n:
|
|
442
|
+
next_cubic = next(cubics)
|
|
443
|
+
next_q1 = cubic_approx_control(
|
|
444
|
+
i / (n - 1), next_cubic[0], next_cubic[1], next_cubic[2], next_cubic[3]
|
|
445
|
+
)
|
|
446
|
+
spline.append(next_q1)
|
|
447
|
+
q2 = (q1 + next_q1) * 0.5
|
|
448
|
+
else:
|
|
449
|
+
q2 = c3
|
|
450
|
+
|
|
451
|
+
# End-point deltas
|
|
452
|
+
d0 = d1
|
|
453
|
+
d1 = q2 - c3
|
|
454
|
+
|
|
455
|
+
if abs(d1) > tolerance or not cubic_farthest_fit_inside(
|
|
456
|
+
d0,
|
|
457
|
+
q0 + (q1 - q0) * (2 / 3) - c1,
|
|
458
|
+
q2 + (q1 - q2) * (2 / 3) - c2,
|
|
459
|
+
d1,
|
|
460
|
+
tolerance,
|
|
461
|
+
):
|
|
462
|
+
return None
|
|
463
|
+
spline.append(cubic[3])
|
|
464
|
+
|
|
465
|
+
return spline
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@cython.locals(max_err=cython.double)
|
|
469
|
+
@cython.locals(n=cython.int)
|
|
470
|
+
@cython.locals(all_quadratic=cython.int)
|
|
471
|
+
def curve_to_quadratic(curve, max_err, all_quadratic=True):
|
|
472
|
+
"""Approximate a cubic Bezier curve with a spline of n quadratics.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
cubic (sequence): Four 2D tuples representing control points of
|
|
476
|
+
the cubic Bezier curve.
|
|
477
|
+
max_err (double): Permitted deviation from the original curve.
|
|
478
|
+
all_quadratic (bool): If True (default) returned value is a
|
|
479
|
+
quadratic spline. If False, it's either a single quadratic
|
|
480
|
+
curve or a single cubic curve.
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
If all_quadratic is True: A list of 2D tuples, representing
|
|
484
|
+
control points of the quadratic spline if it fits within the
|
|
485
|
+
given tolerance, or ``None`` if no suitable spline could be
|
|
486
|
+
calculated.
|
|
487
|
+
|
|
488
|
+
If all_quadratic is False: Either a quadratic curve (if length
|
|
489
|
+
of output is 3), or a cubic curve (if length of output is 4).
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
curve = [complex(*p) for p in curve]
|
|
493
|
+
|
|
494
|
+
for n in range(1, MAX_N + 1):
|
|
495
|
+
spline = cubic_approx_spline(curve, n, max_err, all_quadratic)
|
|
496
|
+
if spline is not None:
|
|
497
|
+
# done. go home
|
|
498
|
+
return [(s.real, s.imag) for s in spline]
|
|
499
|
+
|
|
500
|
+
raise ApproxNotFoundError(curve)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@cython.locals(l=cython.int, last_i=cython.int, i=cython.int)
|
|
504
|
+
@cython.locals(all_quadratic=cython.int)
|
|
505
|
+
def curves_to_quadratic(curves, max_errors, all_quadratic=True):
|
|
506
|
+
"""Return quadratic Bezier splines approximating the input cubic Beziers.
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
curves: A sequence of *n* curves, each curve being a sequence of four
|
|
510
|
+
2D tuples.
|
|
511
|
+
max_errors: A sequence of *n* floats representing the maximum permissible
|
|
512
|
+
deviation from each of the cubic Bezier curves.
|
|
513
|
+
all_quadratic (bool): If True (default) returned values are a
|
|
514
|
+
quadratic spline. If False, they are either a single quadratic
|
|
515
|
+
curve or a single cubic curve.
|
|
516
|
+
|
|
517
|
+
Example::
|
|
518
|
+
|
|
519
|
+
>>> curves_to_quadratic( [
|
|
520
|
+
... [ (50,50), (100,100), (150,100), (200,50) ],
|
|
521
|
+
... [ (75,50), (120,100), (150,75), (200,60) ]
|
|
522
|
+
... ], [1,1] )
|
|
523
|
+
[[(50.0, 50.0), (75.0, 75.0), (125.0, 91.66666666666666), (175.0, 75.0), (200.0, 50.0)], [(75.0, 50.0), (97.5, 75.0), (135.41666666666666, 82.08333333333333), (175.0, 67.5), (200.0, 60.0)]]
|
|
524
|
+
|
|
525
|
+
The returned splines have "implied oncurve points" suitable for use in
|
|
526
|
+
TrueType ``glif`` outlines - i.e. in the first spline returned above,
|
|
527
|
+
the first quadratic segment runs from (50,50) to
|
|
528
|
+
( (75 + 125)/2 , (120 + 91.666..)/2 ) = (100, 83.333...).
|
|
529
|
+
|
|
530
|
+
Returns:
|
|
531
|
+
If all_quadratic is True, a list of splines, each spline being a list
|
|
532
|
+
of 2D tuples.
|
|
533
|
+
|
|
534
|
+
If all_quadratic is False, a list of curves, each curve being a quadratic
|
|
535
|
+
(length 3), or cubic (length 4).
|
|
536
|
+
|
|
537
|
+
Raises:
|
|
538
|
+
fontTools.cu2qu.Errors.ApproxNotFoundError: if no suitable approximation
|
|
539
|
+
can be found for all curves with the given parameters.
|
|
540
|
+
"""
|
|
541
|
+
|
|
542
|
+
curves = [[complex(*p) for p in curve] for curve in curves]
|
|
543
|
+
assert len(max_errors) == len(curves)
|
|
544
|
+
|
|
545
|
+
l = len(curves)
|
|
546
|
+
splines = [None] * l
|
|
547
|
+
last_i = i = 0
|
|
548
|
+
n = 1
|
|
549
|
+
while True:
|
|
550
|
+
spline = cubic_approx_spline(curves[i], n, max_errors[i], all_quadratic)
|
|
551
|
+
if spline is None:
|
|
552
|
+
if n == MAX_N:
|
|
553
|
+
break
|
|
554
|
+
n += 1
|
|
555
|
+
last_i = i
|
|
556
|
+
continue
|
|
557
|
+
splines[i] = spline
|
|
558
|
+
i = (i + 1) % l
|
|
559
|
+
if i == last_i:
|
|
560
|
+
# done. go home
|
|
561
|
+
return [[(s.real, s.imag) for s in spline] for spline in splines]
|
|
562
|
+
|
|
563
|
+
raise ApproxNotFoundError(curves)
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
|
|
16
|
+
class Error(Exception):
|
|
17
|
+
"""Base Cu2Qu exception class for all other errors."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ApproxNotFoundError(Error):
|
|
21
|
+
def __init__(self, curve):
|
|
22
|
+
message = "no approximation found: %s" % curve
|
|
23
|
+
super().__init__(message)
|
|
24
|
+
self.curve = curve
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class UnequalZipLengthsError(Error):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class IncompatibleGlyphsError(Error):
|
|
32
|
+
def __init__(self, glyphs):
|
|
33
|
+
assert len(glyphs) > 1
|
|
34
|
+
self.glyphs = glyphs
|
|
35
|
+
names = set(repr(g.name) for g in glyphs)
|
|
36
|
+
if len(names) > 1:
|
|
37
|
+
self.combined_name = "{%s}" % ", ".join(sorted(names))
|
|
38
|
+
else:
|
|
39
|
+
self.combined_name = names.pop()
|
|
40
|
+
|
|
41
|
+
def __repr__(self):
|
|
42
|
+
return "<%s %s>" % (type(self).__name__, self.combined_name)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class IncompatibleSegmentNumberError(IncompatibleGlyphsError):
|
|
46
|
+
def __str__(self):
|
|
47
|
+
return "Glyphs named %s have different number of segments" % (
|
|
48
|
+
self.combined_name
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class IncompatibleSegmentTypesError(IncompatibleGlyphsError):
|
|
53
|
+
def __init__(self, glyphs, segments):
|
|
54
|
+
IncompatibleGlyphsError.__init__(self, glyphs)
|
|
55
|
+
self.segments = segments
|
|
56
|
+
|
|
57
|
+
def __str__(self):
|
|
58
|
+
lines = []
|
|
59
|
+
ndigits = len(str(max(self.segments)))
|
|
60
|
+
for i, tags in sorted(self.segments.items()):
|
|
61
|
+
lines.append(
|
|
62
|
+
"%s: (%s)" % (str(i).rjust(ndigits), ", ".join(repr(t) for t in tags))
|
|
63
|
+
)
|
|
64
|
+
return "Glyphs named %s have incompatible segment types:\n %s" % (
|
|
65
|
+
self.combined_name,
|
|
66
|
+
"\n ".join(lines),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class IncompatibleFontsError(Error):
|
|
71
|
+
def __init__(self, glyph_errors):
|
|
72
|
+
self.glyph_errors = glyph_errors
|
|
73
|
+
|
|
74
|
+
def __str__(self):
|
|
75
|
+
return "fonts contains incompatible glyphs: %s" % (
|
|
76
|
+
", ".join(repr(g) for g in sorted(self.glyph_errors.keys()))
|
|
77
|
+
)
|