fontgeometry 0.4.0__tar.gz
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.
- fontgeometry-0.4.0/LICENSE +21 -0
- fontgeometry-0.4.0/PKG-INFO +51 -0
- fontgeometry-0.4.0/README.md +31 -0
- fontgeometry-0.4.0/pyproject.toml +75 -0
- fontgeometry-0.4.0/src/fontgeometry/__init__.py +1 -0
- fontgeometry-0.4.0/src/fontgeometry/beziertools.py +357 -0
- fontgeometry-0.4.0/src/fontgeometry/cubics.py +467 -0
- fontgeometry-0.4.0/src/fontgeometry/extract.py +37 -0
- fontgeometry-0.4.0/src/fontgeometry/ftbeziertools.py +142 -0
- fontgeometry-0.4.0/src/fontgeometry/geometry.py +203 -0
- fontgeometry-0.4.0/src/fontgeometry/geometryPoints.py +110 -0
- fontgeometry-0.4.0/src/fontgeometry/py.typed +0 -0
- fontgeometry-0.4.0/src/fontgeometry/typing.py +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Jens Kutilek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fontgeometry
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Font geometry tools
|
|
5
|
+
Author: Jens Kutílek
|
|
6
|
+
Author-email: Jens Kutílek <webmail@kutilek.de>
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Project-URL: Homepage, https://github.com/jenskutilek/jkFontGeometry
|
|
17
|
+
Project-URL: Bug Tracker, https://github.com/jenskutilek/jkFontGeometry/issues
|
|
18
|
+
Project-URL: Changelog, https://github.com/jenskutilek/jkFontGeometry/blob/master/CHANGELOG.md
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# jkFontGeometry
|
|
22
|
+
|
|
23
|
+
Font-related geometry tools
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
$ pip install --user .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
There is a demo script for Glyphs.app in the `Scripts/Glyphs` folder.
|
|
32
|
+
|
|
33
|
+
## Type-Checking and Compilation
|
|
34
|
+
|
|
35
|
+
To run mypy:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
MYPYPATH=stubs/ mypy Lib/jkFontGeometry
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
To build a wheel with binary code compiled by mypyc:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
MYPYPATH=stubs/ python3 setup.py bdist_wheel
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
or compile in place:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
MYPYPATH=stubs/ python3 setup.py build_ext --inplace
|
|
51
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# jkFontGeometry
|
|
2
|
+
|
|
3
|
+
Font-related geometry tools
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
$ pip install --user .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
There is a demo script for Glyphs.app in the `Scripts/Glyphs` folder.
|
|
12
|
+
|
|
13
|
+
## Type-Checking and Compilation
|
|
14
|
+
|
|
15
|
+
To run mypy:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
MYPYPATH=stubs/ mypy Lib/jkFontGeometry
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
To build a wheel with binary code compiled by mypyc:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
MYPYPATH=stubs/ python3 setup.py bdist_wheel
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
or compile in place:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
MYPYPATH=stubs/ python3 setup.py build_ext --inplace
|
|
31
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fontgeometry"
|
|
3
|
+
description = "Font geometry tools"
|
|
4
|
+
version = "0.4.0"
|
|
5
|
+
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
|
|
9
|
+
authors = [{ name = "Jens Kutílek", email = "webmail@kutilek.de" }]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Environment :: Console",
|
|
12
|
+
"Operating System :: OS Independent",
|
|
13
|
+
"Programming Language :: Python",
|
|
14
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Programming Language :: Python :: 3.14",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
requires-python = ">=3.12"
|
|
21
|
+
dependencies = []
|
|
22
|
+
|
|
23
|
+
[dependency-groups]
|
|
24
|
+
dev = [
|
|
25
|
+
"flake8 >= 7.3.0",
|
|
26
|
+
"ruff >= 0.15.13",
|
|
27
|
+
]
|
|
28
|
+
# doc = [
|
|
29
|
+
# "sphinx >= 9.1.0",
|
|
30
|
+
# "sphinx_rtd_theme >= 3.1.0",
|
|
31
|
+
# ]
|
|
32
|
+
test = [
|
|
33
|
+
{ include-group = "dev"},
|
|
34
|
+
"pytest >= 9.0.3",
|
|
35
|
+
"pytest-cov >= 7.1.0",
|
|
36
|
+
"tox >= 4.53.0",
|
|
37
|
+
"tox-gh >= 1.7.1",
|
|
38
|
+
"tox-uv >= 1.35.1",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
"Homepage" = "https://github.com/jenskutilek/jkFontGeometry"
|
|
44
|
+
# "Documentation" = "https://fontgeometry.readthedocs.io/en/latest/"
|
|
45
|
+
"Bug Tracker" = "https://github.com/jenskutilek/jkFontGeometry/issues"
|
|
46
|
+
"Changelog" = "https://github.com/jenskutilek/jkFontGeometry/blob/master/CHANGELOG.md"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
[build-system]
|
|
50
|
+
requires = ["uv_build>=0.11.13,<0.12"]
|
|
51
|
+
build-backend = "uv_build"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
[tool.pylint.main]
|
|
55
|
+
py-version = "3.12"
|
|
56
|
+
# ignore-paths = ["src/fontgeometry/_version.py"]
|
|
57
|
+
# extension-pkg-allow-list = ["pycurl"]
|
|
58
|
+
|
|
59
|
+
[tool.pylint.format]
|
|
60
|
+
expected-line-ending-format = "LF"
|
|
61
|
+
max-line-length = 88
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
[tool.pytest.ini_options]
|
|
65
|
+
# addopts = "--mypy --pylint --black --isort --cov --cov-report=term --cov-report=html --cov-report=xml"
|
|
66
|
+
testpaths = ["tests", "src"] # "src" required for pylint and mypy (whereas cov considers imported files)
|
|
67
|
+
cache_dir = "build/tests/pytest_cache"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint.isort]
|
|
71
|
+
known-first-party = ["fontgeometry"]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
[tool.uv.build-backend]
|
|
75
|
+
module-name = "fontgeometry"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Hello
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
from math import sqrt
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from fontgeometry.ftbeziertools import (
|
|
5
|
+
calcCubicParameters,
|
|
6
|
+
calcQuadraticParameters,
|
|
7
|
+
epsilon,
|
|
8
|
+
solveQuadratic,
|
|
9
|
+
)
|
|
10
|
+
from fontgeometry.geometry import distance_between_points, half_point
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from fontgeometry.typing import PointTuple
|
|
14
|
+
|
|
15
|
+
# Adapted from robofab.pens.filterPen
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def estimateCubicCurveLength(
|
|
19
|
+
pt1: "PointTuple",
|
|
20
|
+
pt2: "PointTuple",
|
|
21
|
+
pt3: "PointTuple",
|
|
22
|
+
pt4: "PointTuple",
|
|
23
|
+
precision: int = 10,
|
|
24
|
+
) -> float:
|
|
25
|
+
"""
|
|
26
|
+
Estimate the length of this curve by iterating through it and averaging the length
|
|
27
|
+
of the flat bits.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
length = 0.0
|
|
31
|
+
step = 1.0 / precision
|
|
32
|
+
points = getPointListForCubic(
|
|
33
|
+
[f * step for f in range(precision + 1)], pt1, pt2, pt3, pt4
|
|
34
|
+
)
|
|
35
|
+
for i in range(len(points) - 1):
|
|
36
|
+
pta = points[i]
|
|
37
|
+
ptb = points[i + 1]
|
|
38
|
+
length += distance_between_points(pta, ptb)
|
|
39
|
+
return length
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def getPointOnCubic(
|
|
43
|
+
t: float, pt1: "PointTuple", pt2: "PointTuple", pt3: "PointTuple", pt4: "PointTuple"
|
|
44
|
+
) -> "PointTuple":
|
|
45
|
+
"""
|
|
46
|
+
Return the point for t on the cubic curve defined by pt1, pt2, pt3, pt4.
|
|
47
|
+
"""
|
|
48
|
+
if t == 0:
|
|
49
|
+
return pt1
|
|
50
|
+
if t == 1:
|
|
51
|
+
return pt4
|
|
52
|
+
if t == 0.5:
|
|
53
|
+
a = half_point(pt1, pt2)
|
|
54
|
+
b = half_point(pt2, pt3)
|
|
55
|
+
c = half_point(pt3, pt4)
|
|
56
|
+
d = half_point(a, b)
|
|
57
|
+
e = half_point(b, c)
|
|
58
|
+
return half_point(d, e)
|
|
59
|
+
else:
|
|
60
|
+
cx = (pt2[0] - pt1[0]) * 3
|
|
61
|
+
cy = (pt2[1] - pt1[1]) * 3
|
|
62
|
+
bx = (pt3[0] - pt2[0]) * 3 - cx
|
|
63
|
+
by = (pt3[1] - pt2[1]) * 3 - cy
|
|
64
|
+
ax = pt4[0] - pt1[0] - cx - bx
|
|
65
|
+
ay = pt4[1] - pt1[1] - cy - by
|
|
66
|
+
t3 = t**3
|
|
67
|
+
t2 = t * t
|
|
68
|
+
x = ax * t3 + bx * t2 + cx * t + pt1[0]
|
|
69
|
+
y = ay * t3 + by * t2 + cy * t + pt1[1]
|
|
70
|
+
return x, y
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def getPointListForCubic(
|
|
74
|
+
ts: list[float],
|
|
75
|
+
pt1: "PointTuple",
|
|
76
|
+
pt2: "PointTuple",
|
|
77
|
+
pt3: "PointTuple",
|
|
78
|
+
pt4: "PointTuple",
|
|
79
|
+
) -> "list[PointTuple]":
|
|
80
|
+
"""
|
|
81
|
+
Return a list of points for increments of t on the cubic curve defined by pt1, pt2,
|
|
82
|
+
pt3, pt4.
|
|
83
|
+
"""
|
|
84
|
+
(x0, y0), (x1, y1) = pt1, pt2
|
|
85
|
+
cx = (x1 - x0) * 3
|
|
86
|
+
cy = (y1 - y0) * 3
|
|
87
|
+
bx = (pt3[0] - x1) * 3 - cx
|
|
88
|
+
by = (pt3[1] - y1) * 3 - cy
|
|
89
|
+
ax = pt4[0] - x0 - cx - bx
|
|
90
|
+
ay = pt4[1] - y0 - cy - by
|
|
91
|
+
path: "list[PointTuple]" = []
|
|
92
|
+
for t in ts:
|
|
93
|
+
t3 = t**3
|
|
94
|
+
t2 = t * t
|
|
95
|
+
x = ax * t3 + bx * t2 + cx * t + x0
|
|
96
|
+
y = ay * t3 + by * t2 + cy * t + y0
|
|
97
|
+
path.append((x, y))
|
|
98
|
+
return path
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def getExtremaForCubic(
|
|
102
|
+
pt1: "PointTuple",
|
|
103
|
+
pt2: "PointTuple",
|
|
104
|
+
pt3: "PointTuple",
|
|
105
|
+
pt4: "PointTuple",
|
|
106
|
+
h: bool = True,
|
|
107
|
+
v: bool = False,
|
|
108
|
+
include_start_end: bool = False,
|
|
109
|
+
) -> list[float]:
|
|
110
|
+
"""
|
|
111
|
+
Return a list of t values at which the cubic curve defined by pt1, pt2, pt3, pt4 has
|
|
112
|
+
extrema.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
pt1 (PointTuple): The first point of the cubic
|
|
116
|
+
pt2 (PointTuple): The second point of the cubic, a control point
|
|
117
|
+
pt3 (PointTuple): The third point of the cubic, a control point
|
|
118
|
+
pt4 (PointTuple): The fourth point of the cubic
|
|
119
|
+
h (bool, optional): Calculate extrema for horizontal derivative == 0 (= what
|
|
120
|
+
type designers call vertical extrema!). Defaults to True.
|
|
121
|
+
v (bool, optional): Calculate extrema for vertical derivative == 0 (= what type
|
|
122
|
+
designers call horizontal extrema!). Defaults to False.
|
|
123
|
+
include_start_end (bool, optional): Whether to include extrema that lie at the
|
|
124
|
+
start or end point of the curve. Defaults to False.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
list[PointTuple]: The list of t values.
|
|
128
|
+
"""
|
|
129
|
+
(ax, ay), (bx, by), c, _d = calcCubicParameters(pt1, pt2, pt3, pt4)
|
|
130
|
+
ax *= 3.0
|
|
131
|
+
ay *= 3.0
|
|
132
|
+
bx *= 2.0
|
|
133
|
+
by *= 2.0
|
|
134
|
+
roots = []
|
|
135
|
+
if include_start_end:
|
|
136
|
+
if h:
|
|
137
|
+
roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 <= t <= 1]
|
|
138
|
+
if v:
|
|
139
|
+
roots += [t for t in solveQuadratic(ax, bx, c[0]) if 0 <= t <= 1]
|
|
140
|
+
else:
|
|
141
|
+
if h:
|
|
142
|
+
roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 < t < 1]
|
|
143
|
+
if v:
|
|
144
|
+
roots += [t for t in solveQuadratic(ax, bx, c[0]) if 0 < t < 1]
|
|
145
|
+
return roots
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def getExtremumPointsForCubic(
|
|
149
|
+
pt1: "PointTuple",
|
|
150
|
+
pt2: "PointTuple",
|
|
151
|
+
pt3: "PointTuple",
|
|
152
|
+
pt4: "PointTuple",
|
|
153
|
+
h: bool = True,
|
|
154
|
+
v: bool = False,
|
|
155
|
+
include_start_end: bool = False,
|
|
156
|
+
) -> "list[PointTuple]":
|
|
157
|
+
"""
|
|
158
|
+
Return a list of points as (x, y) tuples at which the cubic curve defined by pt1,
|
|
159
|
+
pt2, pt3, pt4 has extrema.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
pt1 (PointTuple): The first point of the cubic
|
|
163
|
+
pt2 (PointTuple): The second point of the cubic, a control point
|
|
164
|
+
pt3 (PointTuple): The third point of the cubic, a control point
|
|
165
|
+
pt4 (PointTuple): The fourth point of the cubic
|
|
166
|
+
h (bool, optional): Calculate extrema for horizontal derivative == 0 (= what
|
|
167
|
+
type designers call vertical extrema!). Defaults to True.
|
|
168
|
+
v (bool, optional): Calculate extrema for vertical derivative == 0 (= what type
|
|
169
|
+
designers call horizontal extrema!). Defaults to False.
|
|
170
|
+
include_start_end (bool, optional): Whether to include extrema that lie at the
|
|
171
|
+
start or end point of the curve. Defaults to False.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
list[PointTuple]: The list of point tuples.
|
|
175
|
+
"""
|
|
176
|
+
return getPointListForCubic(
|
|
177
|
+
getExtremaForCubic(
|
|
178
|
+
pt1, pt2, pt3, pt4, h=h, v=v, include_start_end=include_start_end
|
|
179
|
+
),
|
|
180
|
+
pt1,
|
|
181
|
+
pt2,
|
|
182
|
+
pt3,
|
|
183
|
+
pt4,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def getInflectionsForCubic(
|
|
188
|
+
pt1: "PointTuple", pt2: "PointTuple", pt3: "PointTuple", pt4: "PointTuple"
|
|
189
|
+
) -> list[float]:
|
|
190
|
+
"""
|
|
191
|
+
Return a list of t values at which the cubic curve defined by pt1, pt2, pt3, pt4 has
|
|
192
|
+
inflections.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
pt1 (PointTuple): The first point of the cubic
|
|
196
|
+
pt2 (PointTuple): The second point of the cubic, a control point
|
|
197
|
+
pt3 (PointTuple): The third point of the cubic, a control point
|
|
198
|
+
pt4 (PointTuple): The fourth point of the cubic
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
list[float]: _description_
|
|
202
|
+
"""
|
|
203
|
+
# After https://github.com/mekkablue/InsertInflections
|
|
204
|
+
roots: list[float] = []
|
|
205
|
+
|
|
206
|
+
x1, y1 = pt1
|
|
207
|
+
x2, y2 = pt2
|
|
208
|
+
x3, y3 = pt3
|
|
209
|
+
x4, y4 = pt4
|
|
210
|
+
|
|
211
|
+
ax = x2 - x1
|
|
212
|
+
ay = y2 - y1
|
|
213
|
+
bx = x3 - x2 - ax
|
|
214
|
+
by = y3 - y2 - ay
|
|
215
|
+
cx = x4 - x3 - ax - bx - bx
|
|
216
|
+
cy = y4 - y3 - ay - by - by
|
|
217
|
+
|
|
218
|
+
c0 = (ax * by) - (ay * bx)
|
|
219
|
+
c1 = (ax * cy) - (ay * cx)
|
|
220
|
+
c2 = (bx * cy) - (by * cx)
|
|
221
|
+
|
|
222
|
+
if abs(c2) > 0.00001:
|
|
223
|
+
discr = (c1**2) - (4 * c0 * c2)
|
|
224
|
+
c2 *= 2
|
|
225
|
+
if abs(discr) < 0.000001:
|
|
226
|
+
root = -c1 / c2
|
|
227
|
+
if (root > 0.001) and (root < 0.99):
|
|
228
|
+
roots.append(root)
|
|
229
|
+
elif discr > 0:
|
|
230
|
+
discr = discr**0.5
|
|
231
|
+
root = (-c1 - discr) / c2
|
|
232
|
+
if (root > 0.001) and (root < 0.99):
|
|
233
|
+
roots.append(root)
|
|
234
|
+
|
|
235
|
+
root = (-c1 + discr) / c2
|
|
236
|
+
if (root > 0.001) and (root < 0.99):
|
|
237
|
+
roots.append(root)
|
|
238
|
+
elif c1 != 0.0:
|
|
239
|
+
root = -c0 / c1
|
|
240
|
+
if (root > 0.001) and (root < 0.99):
|
|
241
|
+
roots.append(root)
|
|
242
|
+
|
|
243
|
+
return roots
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def getPointListForQuadratic(
|
|
247
|
+
ts: list[float], pt1: "PointTuple", pt2: "PointTuple", pt3: "PointTuple"
|
|
248
|
+
) -> "list[PointTuple]":
|
|
249
|
+
"""
|
|
250
|
+
Return a list of points for increments of t on the quadratic curve defined by pt1,
|
|
251
|
+
pt2, pt3.
|
|
252
|
+
"""
|
|
253
|
+
(x0, y0), (x1, y1), (x2, y2) = pt1, pt2, pt3
|
|
254
|
+
path: "list[PointTuple]" = []
|
|
255
|
+
for t in ts:
|
|
256
|
+
t0 = (1 - t) * (1 - t)
|
|
257
|
+
t1 = 2 * (1 - t) * t
|
|
258
|
+
t2 = t * t
|
|
259
|
+
x = t0 * x0 + t1 * x1 + t2 * x2
|
|
260
|
+
y = t0 * y0 + t1 * y1 + t2 * y2
|
|
261
|
+
path.append((x, y))
|
|
262
|
+
return path
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def getExtremaForQuadratic(
|
|
266
|
+
pt1: "PointTuple",
|
|
267
|
+
pt2: "PointTuple",
|
|
268
|
+
pt3: "PointTuple",
|
|
269
|
+
h: bool = True,
|
|
270
|
+
v: bool = False,
|
|
271
|
+
include_start_end: bool = False,
|
|
272
|
+
) -> list[float]:
|
|
273
|
+
"""
|
|
274
|
+
Return a list of t values at which the quadratic curve defined by pt1, pt2, pt3, pt4
|
|
275
|
+
has extrema.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
pt1 (PointTuple): The first point of the curve
|
|
279
|
+
pt2 (PointTuple): The second point of the curve, an offcurve point
|
|
280
|
+
pt3 (PointTuple): The third point of the curve
|
|
281
|
+
h (bool, optional): Calculate extrema for horizontal derivative == 0 (= what
|
|
282
|
+
type designers call vertical extrema!). Defaults to True.
|
|
283
|
+
v (bool, optional): Calculate extrema for vertical derivative == 0 (= what type
|
|
284
|
+
designers call horizontal extrema!). Defaults to False.
|
|
285
|
+
include_start_end (bool, optional): Whether to include extrema that lie at the
|
|
286
|
+
start or end point of the curve. Defaults to False.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
list[PointTuple]: The list of t values.
|
|
290
|
+
"""
|
|
291
|
+
(ax, ay), (bx, by), _c = calcQuadraticParameters(pt1, pt2, pt3)
|
|
292
|
+
ax *= 2.0
|
|
293
|
+
ay *= 2.0
|
|
294
|
+
roots = []
|
|
295
|
+
if include_start_end:
|
|
296
|
+
if h:
|
|
297
|
+
roots = [t for t in solveLinear(ay, by) if 0 <= t <= 1]
|
|
298
|
+
if v:
|
|
299
|
+
roots += [t for t in solveLinear(ax, bx) if 0 <= t <= 1]
|
|
300
|
+
else:
|
|
301
|
+
if h:
|
|
302
|
+
roots = [t for t in solveLinear(ay, by) if 0 < t < 1]
|
|
303
|
+
if v:
|
|
304
|
+
roots += [t for t in solveLinear(ax, bx) if 0 < t < 1]
|
|
305
|
+
return roots
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def getExtremumPointsForQuadratic(
|
|
309
|
+
pt1: "PointTuple",
|
|
310
|
+
pt2: "PointTuple",
|
|
311
|
+
pt3: "PointTuple",
|
|
312
|
+
h: bool = True,
|
|
313
|
+
v: bool = False,
|
|
314
|
+
include_start_end: bool = False,
|
|
315
|
+
) -> "list[PointTuple]":
|
|
316
|
+
"""
|
|
317
|
+
Return a list of points as (x, y) tuples at which the quadratic curve defined by
|
|
318
|
+
pt1, pt2, pt3, pt4 has extrema.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
pt1 (PointTuple): The first point of the curve
|
|
322
|
+
pt2 (PointTuple): The second point of the curve, an offcurve point
|
|
323
|
+
pt3 (PointTuple): The third point of the curve
|
|
324
|
+
h (bool, optional): Calculate extrema for horizontal derivative == 0 (= what
|
|
325
|
+
type designers call vertical extrema!). Defaults to True.
|
|
326
|
+
v (bool, optional): Calculate extrema for vertical derivative == 0 (= what type
|
|
327
|
+
designers call horizontal extrema!). Defaults to False.
|
|
328
|
+
include_start_end (bool, optional): Whether to include extrema that lie at the
|
|
329
|
+
start or end point of the curve. Defaults to False.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
list[PointTuple]: The list of point tuples.
|
|
333
|
+
"""
|
|
334
|
+
return getPointListForQuadratic(
|
|
335
|
+
getExtremaForQuadratic(
|
|
336
|
+
pt1, pt2, pt3, h=h, v=v, include_start_end=include_start_end
|
|
337
|
+
),
|
|
338
|
+
pt1,
|
|
339
|
+
pt2,
|
|
340
|
+
pt3,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def solveLinear(a: float, b: float) -> list[float]:
|
|
345
|
+
if abs(a) < epsilon:
|
|
346
|
+
if abs(b) < epsilon:
|
|
347
|
+
roots = []
|
|
348
|
+
else:
|
|
349
|
+
roots = [0.0]
|
|
350
|
+
else:
|
|
351
|
+
DD = b * b
|
|
352
|
+
if DD >= 0.0:
|
|
353
|
+
rDD = sqrt(DD)
|
|
354
|
+
roots = [(-b + rDD) / 2.0 / a, (-b - rDD) / 2.0 / a]
|
|
355
|
+
else:
|
|
356
|
+
roots = []
|
|
357
|
+
return roots
|