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
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Copyright 2016 Google Inc. All Rights Reserved.
|
|
2
|
+
# Copyright 2023 Behdad Esfahbod. All Rights Reserved.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
from fontTools.qu2cu import quadratic_to_curves
|
|
17
|
+
from fontTools.pens.filterPen import ContourFilterPen
|
|
18
|
+
from fontTools.pens.reverseContourPen import ReverseContourPen
|
|
19
|
+
import math
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Qu2CuPen(ContourFilterPen):
|
|
23
|
+
"""A filter pen to convert quadratic bezier splines to cubic curves
|
|
24
|
+
using the FontTools SegmentPen protocol.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
|
|
28
|
+
other_pen: another SegmentPen used to draw the transformed outline.
|
|
29
|
+
max_err: maximum approximation error in font units. For optimal results,
|
|
30
|
+
if you know the UPEM of the font, we recommend setting this to a
|
|
31
|
+
value equal, or close to UPEM / 1000.
|
|
32
|
+
reverse_direction: flip the contours' direction but keep starting point.
|
|
33
|
+
stats: a dictionary counting the point numbers of cubic segments.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
other_pen,
|
|
39
|
+
max_err,
|
|
40
|
+
all_cubic=False,
|
|
41
|
+
reverse_direction=False,
|
|
42
|
+
stats=None,
|
|
43
|
+
):
|
|
44
|
+
if reverse_direction:
|
|
45
|
+
other_pen = ReverseContourPen(other_pen)
|
|
46
|
+
super().__init__(other_pen)
|
|
47
|
+
self.all_cubic = all_cubic
|
|
48
|
+
self.max_err = max_err
|
|
49
|
+
self.stats = stats
|
|
50
|
+
|
|
51
|
+
def _quadratics_to_curve(self, q):
|
|
52
|
+
curves = quadratic_to_curves(q, self.max_err, all_cubic=self.all_cubic)
|
|
53
|
+
if self.stats is not None:
|
|
54
|
+
for curve in curves:
|
|
55
|
+
n = str(len(curve) - 2)
|
|
56
|
+
self.stats[n] = self.stats.get(n, 0) + 1
|
|
57
|
+
for curve in curves:
|
|
58
|
+
if len(curve) == 4:
|
|
59
|
+
yield ("curveTo", curve[1:])
|
|
60
|
+
else:
|
|
61
|
+
yield ("qCurveTo", curve[1:])
|
|
62
|
+
|
|
63
|
+
def filterContour(self, contour):
|
|
64
|
+
quadratics = []
|
|
65
|
+
currentPt = None
|
|
66
|
+
newContour = []
|
|
67
|
+
for op, args in contour:
|
|
68
|
+
if op == "qCurveTo" and (
|
|
69
|
+
self.all_cubic or (len(args) > 2 and args[-1] is not None)
|
|
70
|
+
):
|
|
71
|
+
if args[-1] is None:
|
|
72
|
+
raise NotImplementedError(
|
|
73
|
+
"oncurve-less contours with all_cubic not implemented"
|
|
74
|
+
)
|
|
75
|
+
quadratics.append((currentPt,) + args)
|
|
76
|
+
else:
|
|
77
|
+
if quadratics:
|
|
78
|
+
newContour.extend(self._quadratics_to_curve(quadratics))
|
|
79
|
+
quadratics = []
|
|
80
|
+
newContour.append((op, args))
|
|
81
|
+
currentPt = args[-1] if args else None
|
|
82
|
+
if quadratics:
|
|
83
|
+
newContour.extend(self._quadratics_to_curve(quadratics))
|
|
84
|
+
|
|
85
|
+
if not self.all_cubic:
|
|
86
|
+
# Add back implicit oncurve points
|
|
87
|
+
contour = newContour
|
|
88
|
+
newContour = []
|
|
89
|
+
for op, args in contour:
|
|
90
|
+
if op == "qCurveTo" and newContour and newContour[-1][0] == "qCurveTo":
|
|
91
|
+
pt0 = newContour[-1][1][-2]
|
|
92
|
+
pt1 = newContour[-1][1][-1]
|
|
93
|
+
pt2 = args[0]
|
|
94
|
+
if (
|
|
95
|
+
pt1 is not None
|
|
96
|
+
and math.isclose(pt2[0] - pt1[0], pt1[0] - pt0[0])
|
|
97
|
+
and math.isclose(pt2[1] - pt1[1], pt1[1] - pt0[1])
|
|
98
|
+
):
|
|
99
|
+
newArgs = newContour[-1][1][:-1] + args
|
|
100
|
+
newContour[-1] = (op, newArgs)
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
newContour.append((op, args))
|
|
104
|
+
|
|
105
|
+
return newContour
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from fontTools.pens.basePen import BasePen
|
|
2
|
+
|
|
3
|
+
from Quartz.CoreGraphics import CGPathCreateMutable, CGPathMoveToPoint
|
|
4
|
+
from Quartz.CoreGraphics import CGPathAddLineToPoint, CGPathAddCurveToPoint
|
|
5
|
+
from Quartz.CoreGraphics import CGPathAddQuadCurveToPoint, CGPathCloseSubpath
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__ = ["QuartzPen"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class QuartzPen(BasePen):
|
|
12
|
+
"""A pen that creates a CGPath
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
- path: an optional CGPath to add to
|
|
16
|
+
- xform: an optional CGAffineTransform to apply to the path
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, glyphSet, path=None, xform=None):
|
|
20
|
+
BasePen.__init__(self, glyphSet)
|
|
21
|
+
if path is None:
|
|
22
|
+
path = CGPathCreateMutable()
|
|
23
|
+
self.path = path
|
|
24
|
+
self.xform = xform
|
|
25
|
+
|
|
26
|
+
def _moveTo(self, pt):
|
|
27
|
+
x, y = pt
|
|
28
|
+
CGPathMoveToPoint(self.path, self.xform, x, y)
|
|
29
|
+
|
|
30
|
+
def _lineTo(self, pt):
|
|
31
|
+
x, y = pt
|
|
32
|
+
CGPathAddLineToPoint(self.path, self.xform, x, y)
|
|
33
|
+
|
|
34
|
+
def _curveToOne(self, p1, p2, p3):
|
|
35
|
+
(x1, y1), (x2, y2), (x3, y3) = p1, p2, p3
|
|
36
|
+
CGPathAddCurveToPoint(self.path, self.xform, x1, y1, x2, y2, x3, y3)
|
|
37
|
+
|
|
38
|
+
def _qCurveToOne(self, p1, p2):
|
|
39
|
+
(x1, y1), (x2, y2) = p1, p2
|
|
40
|
+
CGPathAddQuadCurveToPoint(self.path, self.xform, x1, y1, x2, y2)
|
|
41
|
+
|
|
42
|
+
def _closePath(self):
|
|
43
|
+
CGPathCloseSubpath(self.path)
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"""Pen recording operations that can be accessed or replayed."""
|
|
2
|
+
|
|
3
|
+
from fontTools.pens.basePen import AbstractPen, DecomposingPen
|
|
4
|
+
from fontTools.pens.pointPen import AbstractPointPen, DecomposingPointPen
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"replayRecording",
|
|
9
|
+
"RecordingPen",
|
|
10
|
+
"DecomposingRecordingPen",
|
|
11
|
+
"DecomposingRecordingPointPen",
|
|
12
|
+
"RecordingPointPen",
|
|
13
|
+
"lerpRecordings",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def replayRecording(recording, pen):
|
|
18
|
+
"""Replay a recording, as produced by RecordingPen or DecomposingRecordingPen,
|
|
19
|
+
to a pen.
|
|
20
|
+
|
|
21
|
+
Note that recording does not have to be produced by those pens.
|
|
22
|
+
It can be any iterable of tuples of method name and tuple-of-arguments.
|
|
23
|
+
Likewise, pen can be any objects receiving those method calls.
|
|
24
|
+
"""
|
|
25
|
+
for operator, operands in recording:
|
|
26
|
+
getattr(pen, operator)(*operands)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RecordingPen(AbstractPen):
|
|
30
|
+
"""Pen recording operations that can be accessed or replayed.
|
|
31
|
+
|
|
32
|
+
The recording can be accessed as pen.value; or replayed using
|
|
33
|
+
pen.replay(otherPen).
|
|
34
|
+
|
|
35
|
+
:Example:
|
|
36
|
+
.. code-block::
|
|
37
|
+
|
|
38
|
+
from fontTools.ttLib import TTFont
|
|
39
|
+
from fontTools.pens.recordingPen import RecordingPen
|
|
40
|
+
|
|
41
|
+
glyph_name = 'dollar'
|
|
42
|
+
font_path = 'MyFont.otf'
|
|
43
|
+
|
|
44
|
+
font = TTFont(font_path)
|
|
45
|
+
glyphset = font.getGlyphSet()
|
|
46
|
+
glyph = glyphset[glyph_name]
|
|
47
|
+
|
|
48
|
+
pen = RecordingPen()
|
|
49
|
+
glyph.draw(pen)
|
|
50
|
+
print(pen.value)
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self):
|
|
54
|
+
self.value = []
|
|
55
|
+
|
|
56
|
+
def moveTo(self, p0):
|
|
57
|
+
self.value.append(("moveTo", (p0,)))
|
|
58
|
+
|
|
59
|
+
def lineTo(self, p1):
|
|
60
|
+
self.value.append(("lineTo", (p1,)))
|
|
61
|
+
|
|
62
|
+
def qCurveTo(self, *points):
|
|
63
|
+
self.value.append(("qCurveTo", points))
|
|
64
|
+
|
|
65
|
+
def curveTo(self, *points):
|
|
66
|
+
self.value.append(("curveTo", points))
|
|
67
|
+
|
|
68
|
+
def closePath(self):
|
|
69
|
+
self.value.append(("closePath", ()))
|
|
70
|
+
|
|
71
|
+
def endPath(self):
|
|
72
|
+
self.value.append(("endPath", ()))
|
|
73
|
+
|
|
74
|
+
def addComponent(self, glyphName, transformation):
|
|
75
|
+
self.value.append(("addComponent", (glyphName, transformation)))
|
|
76
|
+
|
|
77
|
+
def addVarComponent(self, glyphName, transformation, location):
|
|
78
|
+
self.value.append(("addVarComponent", (glyphName, transformation, location)))
|
|
79
|
+
|
|
80
|
+
def replay(self, pen):
|
|
81
|
+
replayRecording(self.value, pen)
|
|
82
|
+
|
|
83
|
+
draw = replay
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class DecomposingRecordingPen(DecomposingPen, RecordingPen):
|
|
87
|
+
"""Same as RecordingPen, except that it doesn't keep components
|
|
88
|
+
as references, but draws them decomposed as regular contours.
|
|
89
|
+
|
|
90
|
+
The constructor takes a required 'glyphSet' positional argument,
|
|
91
|
+
a dictionary of glyph objects (i.e. with a 'draw' method) keyed
|
|
92
|
+
by thir name; other arguments are forwarded to the DecomposingPen's
|
|
93
|
+
constructor::
|
|
94
|
+
|
|
95
|
+
>>> class SimpleGlyph(object):
|
|
96
|
+
... def draw(self, pen):
|
|
97
|
+
... pen.moveTo((0, 0))
|
|
98
|
+
... pen.curveTo((1, 1), (2, 2), (3, 3))
|
|
99
|
+
... pen.closePath()
|
|
100
|
+
>>> class CompositeGlyph(object):
|
|
101
|
+
... def draw(self, pen):
|
|
102
|
+
... pen.addComponent('a', (1, 0, 0, 1, -1, 1))
|
|
103
|
+
>>> class MissingComponent(object):
|
|
104
|
+
... def draw(self, pen):
|
|
105
|
+
... pen.addComponent('foobar', (1, 0, 0, 1, 0, 0))
|
|
106
|
+
>>> class FlippedComponent(object):
|
|
107
|
+
... def draw(self, pen):
|
|
108
|
+
... pen.addComponent('a', (-1, 0, 0, 1, 0, 0))
|
|
109
|
+
>>> glyphSet = {
|
|
110
|
+
... 'a': SimpleGlyph(),
|
|
111
|
+
... 'b': CompositeGlyph(),
|
|
112
|
+
... 'c': MissingComponent(),
|
|
113
|
+
... 'd': FlippedComponent(),
|
|
114
|
+
... }
|
|
115
|
+
>>> for name, glyph in sorted(glyphSet.items()):
|
|
116
|
+
... pen = DecomposingRecordingPen(glyphSet)
|
|
117
|
+
... try:
|
|
118
|
+
... glyph.draw(pen)
|
|
119
|
+
... except pen.MissingComponentError:
|
|
120
|
+
... pass
|
|
121
|
+
... print("{}: {}".format(name, pen.value))
|
|
122
|
+
a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())]
|
|
123
|
+
b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())]
|
|
124
|
+
c: []
|
|
125
|
+
d: [('moveTo', ((0, 0),)), ('curveTo', ((-1, 1), (-2, 2), (-3, 3))), ('closePath', ())]
|
|
126
|
+
|
|
127
|
+
>>> for name, glyph in sorted(glyphSet.items()):
|
|
128
|
+
... pen = DecomposingRecordingPen(
|
|
129
|
+
... glyphSet, skipMissingComponents=True, reverseFlipped=True,
|
|
130
|
+
... )
|
|
131
|
+
... glyph.draw(pen)
|
|
132
|
+
... print("{}: {}".format(name, pen.value))
|
|
133
|
+
a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())]
|
|
134
|
+
b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())]
|
|
135
|
+
c: []
|
|
136
|
+
d: [('moveTo', ((0, 0),)), ('lineTo', ((-3, 3),)), ('curveTo', ((-2, 2), (-1, 1), (0, 0))), ('closePath', ())]
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
# raises MissingComponentError(KeyError) if base glyph is not found in glyphSet
|
|
140
|
+
skipMissingComponents = False
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class RecordingPointPen(AbstractPointPen):
|
|
144
|
+
"""PointPen recording operations that can be accessed or replayed.
|
|
145
|
+
|
|
146
|
+
The recording can be accessed as pen.value; or replayed using
|
|
147
|
+
pointPen.replay(otherPointPen).
|
|
148
|
+
|
|
149
|
+
:Example:
|
|
150
|
+
.. code-block::
|
|
151
|
+
|
|
152
|
+
from defcon import Font
|
|
153
|
+
from fontTools.pens.recordingPen import RecordingPointPen
|
|
154
|
+
|
|
155
|
+
glyph_name = 'a'
|
|
156
|
+
font_path = 'MyFont.ufo'
|
|
157
|
+
|
|
158
|
+
font = Font(font_path)
|
|
159
|
+
glyph = font[glyph_name]
|
|
160
|
+
|
|
161
|
+
pen = RecordingPointPen()
|
|
162
|
+
glyph.drawPoints(pen)
|
|
163
|
+
print(pen.value)
|
|
164
|
+
|
|
165
|
+
new_glyph = font.newGlyph('b')
|
|
166
|
+
pen.replay(new_glyph.getPointPen())
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def __init__(self):
|
|
170
|
+
self.value = []
|
|
171
|
+
|
|
172
|
+
def beginPath(self, identifier=None, **kwargs):
|
|
173
|
+
if identifier is not None:
|
|
174
|
+
kwargs["identifier"] = identifier
|
|
175
|
+
self.value.append(("beginPath", (), kwargs))
|
|
176
|
+
|
|
177
|
+
def endPath(self):
|
|
178
|
+
self.value.append(("endPath", (), {}))
|
|
179
|
+
|
|
180
|
+
def addPoint(
|
|
181
|
+
self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
|
|
182
|
+
):
|
|
183
|
+
if identifier is not None:
|
|
184
|
+
kwargs["identifier"] = identifier
|
|
185
|
+
self.value.append(("addPoint", (pt, segmentType, smooth, name), kwargs))
|
|
186
|
+
|
|
187
|
+
def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
|
|
188
|
+
if identifier is not None:
|
|
189
|
+
kwargs["identifier"] = identifier
|
|
190
|
+
self.value.append(("addComponent", (baseGlyphName, transformation), kwargs))
|
|
191
|
+
|
|
192
|
+
def addVarComponent(
|
|
193
|
+
self, baseGlyphName, transformation, location, identifier=None, **kwargs
|
|
194
|
+
):
|
|
195
|
+
if identifier is not None:
|
|
196
|
+
kwargs["identifier"] = identifier
|
|
197
|
+
self.value.append(
|
|
198
|
+
("addVarComponent", (baseGlyphName, transformation, location), kwargs)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def replay(self, pointPen):
|
|
202
|
+
for operator, args, kwargs in self.value:
|
|
203
|
+
getattr(pointPen, operator)(*args, **kwargs)
|
|
204
|
+
|
|
205
|
+
drawPoints = replay
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class DecomposingRecordingPointPen(DecomposingPointPen, RecordingPointPen):
|
|
209
|
+
"""Same as RecordingPointPen, except that it doesn't keep components
|
|
210
|
+
as references, but draws them decomposed as regular contours.
|
|
211
|
+
|
|
212
|
+
The constructor takes a required 'glyphSet' positional argument,
|
|
213
|
+
a dictionary of pointPen-drawable glyph objects (i.e. with a 'drawPoints' method)
|
|
214
|
+
keyed by thir name; other arguments are forwarded to the DecomposingPointPen's
|
|
215
|
+
constructor::
|
|
216
|
+
|
|
217
|
+
>>> from pprint import pprint
|
|
218
|
+
>>> class SimpleGlyph(object):
|
|
219
|
+
... def drawPoints(self, pen):
|
|
220
|
+
... pen.beginPath()
|
|
221
|
+
... pen.addPoint((0, 0), "line")
|
|
222
|
+
... pen.addPoint((1, 1))
|
|
223
|
+
... pen.addPoint((2, 2))
|
|
224
|
+
... pen.addPoint((3, 3), "curve")
|
|
225
|
+
... pen.endPath()
|
|
226
|
+
>>> class CompositeGlyph(object):
|
|
227
|
+
... def drawPoints(self, pen):
|
|
228
|
+
... pen.addComponent('a', (1, 0, 0, 1, -1, 1))
|
|
229
|
+
>>> class MissingComponent(object):
|
|
230
|
+
... def drawPoints(self, pen):
|
|
231
|
+
... pen.addComponent('foobar', (1, 0, 0, 1, 0, 0))
|
|
232
|
+
>>> class FlippedComponent(object):
|
|
233
|
+
... def drawPoints(self, pen):
|
|
234
|
+
... pen.addComponent('a', (-1, 0, 0, 1, 0, 0))
|
|
235
|
+
>>> glyphSet = {
|
|
236
|
+
... 'a': SimpleGlyph(),
|
|
237
|
+
... 'b': CompositeGlyph(),
|
|
238
|
+
... 'c': MissingComponent(),
|
|
239
|
+
... 'd': FlippedComponent(),
|
|
240
|
+
... }
|
|
241
|
+
>>> for name, glyph in sorted(glyphSet.items()):
|
|
242
|
+
... pen = DecomposingRecordingPointPen(glyphSet)
|
|
243
|
+
... try:
|
|
244
|
+
... glyph.drawPoints(pen)
|
|
245
|
+
... except pen.MissingComponentError:
|
|
246
|
+
... pass
|
|
247
|
+
... pprint({name: pen.value})
|
|
248
|
+
{'a': [('beginPath', (), {}),
|
|
249
|
+
('addPoint', ((0, 0), 'line', False, None), {}),
|
|
250
|
+
('addPoint', ((1, 1), None, False, None), {}),
|
|
251
|
+
('addPoint', ((2, 2), None, False, None), {}),
|
|
252
|
+
('addPoint', ((3, 3), 'curve', False, None), {}),
|
|
253
|
+
('endPath', (), {})]}
|
|
254
|
+
{'b': [('beginPath', (), {}),
|
|
255
|
+
('addPoint', ((-1, 1), 'line', False, None), {}),
|
|
256
|
+
('addPoint', ((0, 2), None, False, None), {}),
|
|
257
|
+
('addPoint', ((1, 3), None, False, None), {}),
|
|
258
|
+
('addPoint', ((2, 4), 'curve', False, None), {}),
|
|
259
|
+
('endPath', (), {})]}
|
|
260
|
+
{'c': []}
|
|
261
|
+
{'d': [('beginPath', (), {}),
|
|
262
|
+
('addPoint', ((0, 0), 'line', False, None), {}),
|
|
263
|
+
('addPoint', ((-1, 1), None, False, None), {}),
|
|
264
|
+
('addPoint', ((-2, 2), None, False, None), {}),
|
|
265
|
+
('addPoint', ((-3, 3), 'curve', False, None), {}),
|
|
266
|
+
('endPath', (), {})]}
|
|
267
|
+
|
|
268
|
+
>>> for name, glyph in sorted(glyphSet.items()):
|
|
269
|
+
... pen = DecomposingRecordingPointPen(
|
|
270
|
+
... glyphSet, skipMissingComponents=True, reverseFlipped=True,
|
|
271
|
+
... )
|
|
272
|
+
... glyph.drawPoints(pen)
|
|
273
|
+
... pprint({name: pen.value})
|
|
274
|
+
{'a': [('beginPath', (), {}),
|
|
275
|
+
('addPoint', ((0, 0), 'line', False, None), {}),
|
|
276
|
+
('addPoint', ((1, 1), None, False, None), {}),
|
|
277
|
+
('addPoint', ((2, 2), None, False, None), {}),
|
|
278
|
+
('addPoint', ((3, 3), 'curve', False, None), {}),
|
|
279
|
+
('endPath', (), {})]}
|
|
280
|
+
{'b': [('beginPath', (), {}),
|
|
281
|
+
('addPoint', ((-1, 1), 'line', False, None), {}),
|
|
282
|
+
('addPoint', ((0, 2), None, False, None), {}),
|
|
283
|
+
('addPoint', ((1, 3), None, False, None), {}),
|
|
284
|
+
('addPoint', ((2, 4), 'curve', False, None), {}),
|
|
285
|
+
('endPath', (), {})]}
|
|
286
|
+
{'c': []}
|
|
287
|
+
{'d': [('beginPath', (), {}),
|
|
288
|
+
('addPoint', ((0, 0), 'curve', False, None), {}),
|
|
289
|
+
('addPoint', ((-3, 3), 'line', False, None), {}),
|
|
290
|
+
('addPoint', ((-2, 2), None, False, None), {}),
|
|
291
|
+
('addPoint', ((-1, 1), None, False, None), {}),
|
|
292
|
+
('endPath', (), {})]}
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
# raises MissingComponentError(KeyError) if base glyph is not found in glyphSet
|
|
296
|
+
skipMissingComponents = False
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def lerpRecordings(recording1, recording2, factor=0.5):
|
|
300
|
+
"""Linearly interpolate between two recordings. The recordings
|
|
301
|
+
must be decomposed, i.e. they must not contain any components.
|
|
302
|
+
|
|
303
|
+
Factor is typically between 0 and 1. 0 means the first recording,
|
|
304
|
+
1 means the second recording, and 0.5 means the average of the
|
|
305
|
+
two recordings. Other values are possible, and can be useful to
|
|
306
|
+
extrapolate. Defaults to 0.5.
|
|
307
|
+
|
|
308
|
+
Returns a generator with the new recording.
|
|
309
|
+
"""
|
|
310
|
+
if len(recording1) != len(recording2):
|
|
311
|
+
raise ValueError(
|
|
312
|
+
"Mismatched lengths: %d and %d" % (len(recording1), len(recording2))
|
|
313
|
+
)
|
|
314
|
+
for (op1, args1), (op2, args2) in zip(recording1, recording2):
|
|
315
|
+
if op1 != op2:
|
|
316
|
+
raise ValueError("Mismatched operations: %s, %s" % (op1, op2))
|
|
317
|
+
if op1 == "addComponent":
|
|
318
|
+
raise ValueError("Cannot interpolate components")
|
|
319
|
+
else:
|
|
320
|
+
mid_args = [
|
|
321
|
+
(x1 + (x2 - x1) * factor, y1 + (y2 - y1) * factor)
|
|
322
|
+
for (x1, y1), (x2, y2) in zip(args1, args2)
|
|
323
|
+
]
|
|
324
|
+
yield (op1, mid_args)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
if __name__ == "__main__":
|
|
328
|
+
pen = RecordingPen()
|
|
329
|
+
pen.moveTo((0, 0))
|
|
330
|
+
pen.lineTo((0, 100))
|
|
331
|
+
pen.curveTo((50, 75), (60, 50), (50, 25))
|
|
332
|
+
pen.closePath()
|
|
333
|
+
from pprint import pprint
|
|
334
|
+
|
|
335
|
+
pprint(pen.value)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from fontTools.pens.basePen import BasePen
|
|
2
|
+
from reportlab.graphics.shapes import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
__all__ = ["ReportLabPen"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ReportLabPen(BasePen):
|
|
9
|
+
"""A pen for drawing onto a ``reportlab.graphics.shapes.Path`` object."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, glyphSet, path=None):
|
|
12
|
+
BasePen.__init__(self, glyphSet)
|
|
13
|
+
if path is None:
|
|
14
|
+
path = Path()
|
|
15
|
+
self.path = path
|
|
16
|
+
|
|
17
|
+
def _moveTo(self, p):
|
|
18
|
+
(x, y) = p
|
|
19
|
+
self.path.moveTo(x, y)
|
|
20
|
+
|
|
21
|
+
def _lineTo(self, p):
|
|
22
|
+
(x, y) = p
|
|
23
|
+
self.path.lineTo(x, y)
|
|
24
|
+
|
|
25
|
+
def _curveToOne(self, p1, p2, p3):
|
|
26
|
+
(x1, y1) = p1
|
|
27
|
+
(x2, y2) = p2
|
|
28
|
+
(x3, y3) = p3
|
|
29
|
+
self.path.curveTo(x1, y1, x2, y2, x3, y3)
|
|
30
|
+
|
|
31
|
+
def _closePath(self):
|
|
32
|
+
self.path.closePath()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
import sys
|
|
37
|
+
|
|
38
|
+
if len(sys.argv) < 3:
|
|
39
|
+
print(
|
|
40
|
+
"Usage: reportLabPen.py <OTF/TTF font> <glyphname> [<image file to create>]"
|
|
41
|
+
)
|
|
42
|
+
print(
|
|
43
|
+
" If no image file name is created, by default <glyphname>.png is created."
|
|
44
|
+
)
|
|
45
|
+
print(" example: reportLabPen.py Arial.TTF R test.png")
|
|
46
|
+
print(
|
|
47
|
+
" (The file format will be PNG, regardless of the image file name supplied)"
|
|
48
|
+
)
|
|
49
|
+
sys.exit(0)
|
|
50
|
+
|
|
51
|
+
from fontTools.ttLib import TTFont
|
|
52
|
+
from reportlab.lib import colors
|
|
53
|
+
|
|
54
|
+
path = sys.argv[1]
|
|
55
|
+
glyphName = sys.argv[2]
|
|
56
|
+
if len(sys.argv) > 3:
|
|
57
|
+
imageFile = sys.argv[3]
|
|
58
|
+
else:
|
|
59
|
+
imageFile = "%s.png" % glyphName
|
|
60
|
+
|
|
61
|
+
font = TTFont(path) # it would work just as well with fontTools.t1Lib.T1Font
|
|
62
|
+
gs = font.getGlyphSet()
|
|
63
|
+
pen = ReportLabPen(gs, Path(fillColor=colors.red, strokeWidth=5))
|
|
64
|
+
g = gs[glyphName]
|
|
65
|
+
g.draw(pen)
|
|
66
|
+
|
|
67
|
+
w, h = g.width, 1000
|
|
68
|
+
from reportlab.graphics import renderPM
|
|
69
|
+
from reportlab.graphics.shapes import Group, Drawing, scale
|
|
70
|
+
|
|
71
|
+
# Everything is wrapped in a group to allow transformations.
|
|
72
|
+
g = Group(pen.path)
|
|
73
|
+
g.translate(0, 200)
|
|
74
|
+
g.scale(0.3, 0.3)
|
|
75
|
+
|
|
76
|
+
d = Drawing(w, h)
|
|
77
|
+
d.add(g)
|
|
78
|
+
|
|
79
|
+
renderPM.drawToFile(d, imageFile, fmt="PNG")
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from fontTools.misc.arrayTools import pairwise
|
|
2
|
+
from fontTools.pens.filterPen import ContourFilterPen
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
__all__ = ["reversedContour", "ReverseContourPen"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ReverseContourPen(ContourFilterPen):
|
|
9
|
+
"""Filter pen that passes outline data to another pen, but reversing
|
|
10
|
+
the winding direction of all contours. Components are simply passed
|
|
11
|
+
through unchanged.
|
|
12
|
+
|
|
13
|
+
Closed contours are reversed in such a way that the first point remains
|
|
14
|
+
the first point.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, outPen, outputImpliedClosingLine=False):
|
|
18
|
+
super().__init__(outPen)
|
|
19
|
+
self.outputImpliedClosingLine = outputImpliedClosingLine
|
|
20
|
+
|
|
21
|
+
def filterContour(self, contour):
|
|
22
|
+
return reversedContour(contour, self.outputImpliedClosingLine)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def reversedContour(contour, outputImpliedClosingLine=False):
|
|
26
|
+
"""Generator that takes a list of pen's (operator, operands) tuples,
|
|
27
|
+
and yields them with the winding direction reversed.
|
|
28
|
+
"""
|
|
29
|
+
if not contour:
|
|
30
|
+
return # nothing to do, stop iteration
|
|
31
|
+
|
|
32
|
+
# valid contours must have at least a starting and ending command,
|
|
33
|
+
# can't have one without the other
|
|
34
|
+
assert len(contour) > 1, "invalid contour"
|
|
35
|
+
|
|
36
|
+
# the type of the last command determines if the contour is closed
|
|
37
|
+
contourType = contour.pop()[0]
|
|
38
|
+
assert contourType in ("endPath", "closePath")
|
|
39
|
+
closed = contourType == "closePath"
|
|
40
|
+
|
|
41
|
+
firstType, firstPts = contour.pop(0)
|
|
42
|
+
assert firstType in ("moveTo", "qCurveTo"), (
|
|
43
|
+
"invalid initial segment type: %r" % firstType
|
|
44
|
+
)
|
|
45
|
+
firstOnCurve = firstPts[-1]
|
|
46
|
+
if firstType == "qCurveTo":
|
|
47
|
+
# special case for TrueType paths contaning only off-curve points
|
|
48
|
+
assert firstOnCurve is None, "off-curve only paths must end with 'None'"
|
|
49
|
+
assert not contour, "only one qCurveTo allowed per off-curve path"
|
|
50
|
+
firstPts = (firstPts[0],) + tuple(reversed(firstPts[1:-1])) + (None,)
|
|
51
|
+
|
|
52
|
+
if not contour:
|
|
53
|
+
# contour contains only one segment, nothing to reverse
|
|
54
|
+
if firstType == "moveTo":
|
|
55
|
+
closed = False # single-point paths can't be closed
|
|
56
|
+
else:
|
|
57
|
+
closed = True # off-curve paths are closed by definition
|
|
58
|
+
yield firstType, firstPts
|
|
59
|
+
else:
|
|
60
|
+
lastType, lastPts = contour[-1]
|
|
61
|
+
lastOnCurve = lastPts[-1]
|
|
62
|
+
if closed:
|
|
63
|
+
# for closed paths, we keep the starting point
|
|
64
|
+
yield firstType, firstPts
|
|
65
|
+
if firstOnCurve != lastOnCurve:
|
|
66
|
+
# emit an implied line between the last and first points
|
|
67
|
+
yield "lineTo", (lastOnCurve,)
|
|
68
|
+
contour[-1] = (lastType, tuple(lastPts[:-1]) + (firstOnCurve,))
|
|
69
|
+
|
|
70
|
+
if len(contour) > 1:
|
|
71
|
+
secondType, secondPts = contour[0]
|
|
72
|
+
else:
|
|
73
|
+
# contour has only two points, the second and last are the same
|
|
74
|
+
secondType, secondPts = lastType, lastPts
|
|
75
|
+
|
|
76
|
+
if not outputImpliedClosingLine:
|
|
77
|
+
# if a lineTo follows the initial moveTo, after reversing it
|
|
78
|
+
# will be implied by the closePath, so we don't emit one;
|
|
79
|
+
# unless the lineTo and moveTo overlap, in which case we keep the
|
|
80
|
+
# duplicate points
|
|
81
|
+
if secondType == "lineTo" and firstPts != secondPts:
|
|
82
|
+
del contour[0]
|
|
83
|
+
if contour:
|
|
84
|
+
contour[-1] = (lastType, tuple(lastPts[:-1]) + secondPts)
|
|
85
|
+
else:
|
|
86
|
+
# for open paths, the last point will become the first
|
|
87
|
+
yield firstType, (lastOnCurve,)
|
|
88
|
+
contour[-1] = (lastType, tuple(lastPts[:-1]) + (firstOnCurve,))
|
|
89
|
+
|
|
90
|
+
# we iterate over all segment pairs in reverse order, and yield
|
|
91
|
+
# each one with the off-curve points reversed (if any), and
|
|
92
|
+
# with the on-curve point of the following segment
|
|
93
|
+
for (curType, curPts), (_, nextPts) in pairwise(contour, reverse=True):
|
|
94
|
+
yield curType, tuple(reversed(curPts[:-1])) + (nextPts[-1],)
|
|
95
|
+
|
|
96
|
+
yield "closePath" if closed else "endPath", ()
|