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.
@@ -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