meerk40t 0.9.7051__py2.py3-none-any.whl → 0.9.7900__py2.py3-none-any.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.
- meerk40t/balormk/controller.py +3 -3
- meerk40t/balormk/device.py +7 -0
- meerk40t/balormk/driver.py +23 -14
- meerk40t/balormk/galvo_commands.py +18 -3
- meerk40t/balormk/gui/balorconfig.py +6 -0
- meerk40t/balormk/livelightjob.py +36 -14
- meerk40t/camera/camera.py +1 -0
- meerk40t/camera/gui/camerapanel.py +154 -58
- meerk40t/camera/plugin.py +46 -5
- meerk40t/core/elements/branches.py +90 -20
- meerk40t/core/elements/elements.py +59 -37
- meerk40t/core/elements/trace.py +10 -6
- meerk40t/core/node/node.py +2 -0
- meerk40t/core/plotplanner.py +7 -4
- meerk40t/device/gui/defaultactions.py +78 -14
- meerk40t/dxf/dxf_io.py +42 -0
- meerk40t/grbl/controller.py +245 -35
- meerk40t/grbl/device.py +102 -26
- meerk40t/grbl/driver.py +8 -2
- meerk40t/grbl/gui/grblconfiguration.py +6 -0
- meerk40t/grbl/gui/grblcontroller.py +1 -1
- meerk40t/gui/about.py +7 -0
- meerk40t/gui/choicepropertypanel.py +20 -30
- meerk40t/gui/devicepanel.py +27 -16
- meerk40t/gui/icons.py +15 -0
- meerk40t/gui/laserpanel.py +102 -54
- meerk40t/gui/materialtest.py +10 -0
- meerk40t/gui/mkdebug.py +268 -9
- meerk40t/gui/navigationpanels.py +65 -7
- meerk40t/gui/propertypanels/operationpropertymain.py +185 -91
- meerk40t/gui/scenewidgets/elementswidget.py +7 -1
- meerk40t/gui/scenewidgets/selectionwidget.py +24 -9
- meerk40t/gui/simulation.py +1 -1
- meerk40t/gui/statusbarwidgets/shapepropwidget.py +50 -40
- meerk40t/gui/statusbarwidgets/statusbar.py +2 -2
- meerk40t/gui/toolwidgets/toolmeasure.py +1 -1
- meerk40t/gui/toolwidgets/toolnodeedit.py +4 -1
- meerk40t/gui/toolwidgets/tooltabedit.py +9 -7
- meerk40t/gui/wxmeerk40t.py +2 -0
- meerk40t/gui/wxmmain.py +23 -9
- meerk40t/gui/wxmribbon.py +36 -0
- meerk40t/gui/wxutils.py +66 -42
- meerk40t/kernel/inhibitor.py +120 -0
- meerk40t/kernel/kernel.py +38 -0
- meerk40t/lihuiyu/controller.py +33 -3
- meerk40t/lihuiyu/device.py +99 -4
- meerk40t/lihuiyu/driver.py +62 -5
- meerk40t/lihuiyu/gui/lhycontrollergui.py +69 -24
- meerk40t/lihuiyu/gui/lhydrivergui.py +6 -0
- meerk40t/lihuiyu/laserspeed.py +17 -10
- meerk40t/lihuiyu/parser.py +23 -0
- meerk40t/main.py +1 -1
- meerk40t/moshi/gui/moshidrivergui.py +7 -0
- meerk40t/newly/controller.py +3 -2
- meerk40t/newly/device.py +23 -2
- meerk40t/newly/driver.py +8 -3
- meerk40t/newly/gui/newlyconfig.py +7 -0
- meerk40t/ruida/gui/ruidaconfig.py +7 -0
- meerk40t/tools/geomstr.py +68 -48
- meerk40t/tools/rasterplotter.py +0 -5
- meerk40t/tools/ttfparser.py +155 -82
- {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/METADATA +1 -1
- {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/RECORD +68 -67
- {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/LICENSE +0 -0
- {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/WHEEL +0 -0
- {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/entry_points.txt +0 -0
- {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/top_level.txt +0 -0
- {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/zip-safe +0 -0
meerk40t/tools/geomstr.py
CHANGED
@@ -199,13 +199,17 @@ def stitcheable_nodes(data, tolerance) -> list:
|
|
199
199
|
if tolerance == 0:
|
200
200
|
tolerance = 1e-6
|
201
201
|
for idx1, (nodeidx1, g1) in enumerate(geoms):
|
202
|
+
fp1 = g1.first_point
|
203
|
+
lp1 = g1.last_point
|
204
|
+
if fp1 is None or lp1 is None:
|
205
|
+
continue
|
202
206
|
for idx2 in range(idx1 + 1, len(geoms)):
|
203
207
|
nodeidx2 = geoms[idx2][0]
|
204
208
|
g2 = geoms[idx2][1]
|
205
|
-
fp1 = g1.first_point
|
206
209
|
fp2 = g2.first_point
|
207
|
-
lp1 = g1.last_point
|
208
210
|
lp2 = g2.last_point
|
211
|
+
if fp2 is None or lp2 is None:
|
212
|
+
continue
|
209
213
|
if (
|
210
214
|
abs(lp1 - lp2) <= tolerance
|
211
215
|
or abs(lp1 - fp2) <= tolerance
|
@@ -251,11 +255,16 @@ def stitch_geometries(geometry_list: list, tolerance: float = 0.0) -> list:
|
|
251
255
|
while geometries:
|
252
256
|
candidate = geometries.pop(0)
|
253
257
|
stitched = False
|
258
|
+
cand_fp = candidate.first_point
|
259
|
+
cand_lp = candidate.last_point
|
260
|
+
if cand_fp is None or cand_lp is None:
|
261
|
+
stitched_geometries.append(candidate)
|
262
|
+
continue
|
254
263
|
for i, target in enumerate(stitched_geometries):
|
255
|
-
cand_fp = candidate.first_point
|
256
|
-
cand_lp = candidate.last_point
|
257
264
|
targ_fp = target.first_point
|
258
265
|
targ_lp = target.last_point
|
266
|
+
if targ_fp is None or targ_lp is None:
|
267
|
+
continue
|
259
268
|
if abs(targ_lp - cand_fp) <= tolerance:
|
260
269
|
# Just append g1 to g2
|
261
270
|
if abs(targ_lp - cand_fp) > 0:
|
@@ -496,7 +505,7 @@ class Simplifier:
|
|
496
505
|
|
497
506
|
def by_ratio(self, r):
|
498
507
|
if r <= 0 or r > 1:
|
499
|
-
raise ValueError("Ratio must be 0<r<=1. Got {}"
|
508
|
+
raise ValueError(f"Ratio must be 0<r<=1. Got {r}")
|
500
509
|
|
501
510
|
return self.by_number(r * len(self.thresholds))
|
502
511
|
|
@@ -531,7 +540,7 @@ class Clip:
|
|
531
540
|
x3 < y3,
|
532
541
|
)
|
533
542
|
).all(axis=2)
|
534
|
-
splits = [
|
543
|
+
splits = [[] for _ in range(len(subject))]
|
535
544
|
for s0, s1 in sorted(np.argwhere(checks), key=lambda e: e[0], reverse=True):
|
536
545
|
splits[s0].extend(
|
537
546
|
[t for t, _ in subject.intersections(int(s0), clip.segments[s1])]
|
@@ -546,7 +555,7 @@ class Clip:
|
|
546
555
|
@param clip:
|
547
556
|
@return:
|
548
557
|
"""
|
549
|
-
splits = [
|
558
|
+
splits = [[] for _ in range(len(subject))]
|
550
559
|
for s0 in range(len(subject)):
|
551
560
|
for s1 in range(len(clip)):
|
552
561
|
for t0, t1 in subject.intersections(int(s0), clip.segments[s1]):
|
@@ -774,12 +783,12 @@ class Pattern:
|
|
774
783
|
# Scale once, translate often
|
775
784
|
m = Matrix.scale(cw, ch)
|
776
785
|
geom = self.geomstr.as_transformed(m)
|
777
|
-
for col in range(start_value_x, cols + end_value_x
|
786
|
+
for col in range(start_value_x, cols + end_value_x):
|
778
787
|
x_offset = col * (cw + 2 * px)
|
779
788
|
x = top_left_x + x_offset
|
780
789
|
|
781
790
|
top_left_y = y0
|
782
|
-
for row in range(start_value_y, rows + end_value_y
|
791
|
+
for row in range(start_value_y, rows + end_value_y):
|
783
792
|
y_offset = row * (ch + 2 * py)
|
784
793
|
if col % 2:
|
785
794
|
y_offset += (ch + 2 * py) / 2
|
@@ -942,9 +951,9 @@ class BeamTable:
|
|
942
951
|
event = events[i]
|
943
952
|
pt, adds, removes, sorts = event
|
944
953
|
try:
|
945
|
-
|
954
|
+
next_idx, _, _, _ = events[i + 1]
|
946
955
|
except IndexError:
|
947
|
-
|
956
|
+
next_idx = complex(float("inf"), float("inf"))
|
948
957
|
|
949
958
|
for index in removes:
|
950
959
|
try:
|
@@ -978,7 +987,7 @@ class BeamTable:
|
|
978
987
|
# was removed
|
979
988
|
pass
|
980
989
|
i += 1
|
981
|
-
if pt ==
|
990
|
+
if pt == next_idx:
|
982
991
|
continue
|
983
992
|
if len(actives) > largest_actives:
|
984
993
|
largest_actives = len(actives)
|
@@ -1041,11 +1050,11 @@ class BeamTable:
|
|
1041
1050
|
pt, index, swap = event
|
1042
1051
|
|
1043
1052
|
try:
|
1044
|
-
|
1045
|
-
scanline = (pt +
|
1053
|
+
next_idx, _, _ = events[i + 1]
|
1054
|
+
scanline = (pt + next_idx) / 2
|
1046
1055
|
except IndexError:
|
1047
|
-
|
1048
|
-
scanline =
|
1056
|
+
next_idx = complex(float("inf"), float("inf"))
|
1057
|
+
scanline = next_idx
|
1049
1058
|
|
1050
1059
|
if swap is not None:
|
1051
1060
|
s1 = actives.index(swap[0])
|
@@ -1058,7 +1067,7 @@ class BeamTable:
|
|
1058
1067
|
remove_index = actives.index(~index)
|
1059
1068
|
del actives[remove_index]
|
1060
1069
|
|
1061
|
-
if pt !=
|
1070
|
+
if pt != next_idx:
|
1062
1071
|
if len(actives) > largest_actives:
|
1063
1072
|
largest_actives = len(actives)
|
1064
1073
|
# actives.sort(key=y_ints)
|
@@ -1125,9 +1134,9 @@ class BeamTable:
|
|
1125
1134
|
starts = np.ravel(np.real(from_vals) + y_start * 1j)
|
1126
1135
|
ends = np.ravel(np.real(to_vals) + y_end * 1j)
|
1127
1136
|
|
1128
|
-
|
1129
|
-
starts = starts[
|
1130
|
-
ends = ends[
|
1137
|
+
filtered = np.dstack((starts != ends, ~np.isnan(starts))).all(axis=2)[0]
|
1138
|
+
starts = starts[filtered]
|
1139
|
+
ends = ends[filtered]
|
1131
1140
|
count = starts.shape[0]
|
1132
1141
|
segments = np.dstack(
|
1133
1142
|
(starts, [0] * count, [TYPE_LINE] * count, [0] * count, ends)
|
@@ -1575,7 +1584,7 @@ class Geomstr:
|
|
1575
1584
|
"""
|
1576
1585
|
|
1577
1586
|
def __init__(self, segments=None):
|
1578
|
-
self._settings =
|
1587
|
+
self._settings = {}
|
1579
1588
|
if segments is not None:
|
1580
1589
|
if isinstance(segments, Geomstr):
|
1581
1590
|
self._settings.update(segments._settings)
|
@@ -2261,13 +2270,13 @@ class Geomstr:
|
|
2261
2270
|
@param end_pos:
|
2262
2271
|
@param start_pos:
|
2263
2272
|
"""
|
2264
|
-
segments =
|
2273
|
+
segments = []
|
2265
2274
|
for point in self.as_contiguous_points(start_pos=start_pos, end_pos=end_pos):
|
2266
2275
|
if isinstance(point, tuple):
|
2267
2276
|
point, settings = point
|
2268
2277
|
if segments:
|
2269
2278
|
yield segments, settings
|
2270
|
-
segments =
|
2279
|
+
segments = []
|
2271
2280
|
else:
|
2272
2281
|
segments.append(point)
|
2273
2282
|
|
@@ -2331,12 +2340,12 @@ class Geomstr:
|
|
2331
2340
|
@param distance:
|
2332
2341
|
@return:
|
2333
2342
|
"""
|
2334
|
-
segments =
|
2343
|
+
segments = []
|
2335
2344
|
for point in self.as_equal_interpolated_points(distance=distance):
|
2336
2345
|
if point is None:
|
2337
2346
|
if segments:
|
2338
2347
|
yield segments
|
2339
|
-
segments =
|
2348
|
+
segments = []
|
2340
2349
|
else:
|
2341
2350
|
segments.append(point)
|
2342
2351
|
if segments:
|
@@ -2445,12 +2454,12 @@ class Geomstr:
|
|
2445
2454
|
@param interpolate:
|
2446
2455
|
@return:
|
2447
2456
|
"""
|
2448
|
-
segments =
|
2457
|
+
segments = []
|
2449
2458
|
for point in self.as_interpolated_points(interpolate=interpolate):
|
2450
2459
|
if point is None:
|
2451
2460
|
if segments:
|
2452
2461
|
yield segments
|
2453
|
-
segments =
|
2462
|
+
segments = []
|
2454
2463
|
else:
|
2455
2464
|
segments.append(point)
|
2456
2465
|
if segments:
|
@@ -2871,7 +2880,7 @@ class Geomstr:
|
|
2871
2880
|
|
2872
2881
|
p_start = point_at_t(current_t)
|
2873
2882
|
|
2874
|
-
for i in range(
|
2883
|
+
for i in range(slices):
|
2875
2884
|
next_t = current_t + t_slice
|
2876
2885
|
mid_t = (next_t + current_t) / 2
|
2877
2886
|
|
@@ -2932,7 +2941,7 @@ class Geomstr:
|
|
2932
2941
|
|
2933
2942
|
p_start = point_at_t(current_t)
|
2934
2943
|
|
2935
|
-
for i in range(
|
2944
|
+
for i in range(slices):
|
2936
2945
|
next_t = current_t + t_slice
|
2937
2946
|
if i == slices - 1:
|
2938
2947
|
next_t = end_t
|
@@ -3035,7 +3044,7 @@ class Geomstr:
|
|
3035
3044
|
c = Clip(other)
|
3036
3045
|
polycut = c.polycut(self, breaks=True)
|
3037
3046
|
|
3038
|
-
geoms =
|
3047
|
+
geoms = []
|
3039
3048
|
g = Geomstr()
|
3040
3049
|
geoms.append(g)
|
3041
3050
|
for e in polycut.segments[: self.index]:
|
@@ -4009,10 +4018,9 @@ class Geomstr:
|
|
4009
4018
|
return
|
4010
4019
|
if segtype2 in NON_GEOMETRY_TYPES:
|
4011
4020
|
return
|
4012
|
-
if segtype1 == TYPE_LINE:
|
4013
|
-
|
4014
|
-
|
4015
|
-
return
|
4021
|
+
if segtype1 == TYPE_LINE and segtype2 == TYPE_LINE:
|
4022
|
+
yield from self._line_line_intersections(line1, line2)
|
4023
|
+
return
|
4016
4024
|
# if oinfo.real == TYPE_QUAD:
|
4017
4025
|
# yield from self._line_quad_intersections(line1, line2)
|
4018
4026
|
# return
|
@@ -5227,22 +5235,34 @@ class Geomstr:
|
|
5227
5235
|
def as_contiguous(self):
|
5228
5236
|
"""
|
5229
5237
|
Generate individual subpaths of contiguous segments
|
5238
|
+
Attention: this will not yield meta segments, like TYPE_NOP and others
|
5230
5239
|
|
5231
5240
|
@return:
|
5232
5241
|
"""
|
5233
|
-
|
5242
|
+
new_segments = []
|
5234
5243
|
for idx, seg in enumerate(self.segments[: self.index]):
|
5235
5244
|
segtype = self._segtype(seg)
|
5245
|
+
if segtype in META_TYPES:
|
5246
|
+
# Skip meta segments
|
5247
|
+
continue
|
5236
5248
|
if segtype == TYPE_END:
|
5237
|
-
|
5238
|
-
|
5239
|
-
|
5240
|
-
|
5241
|
-
if
|
5242
|
-
|
5243
|
-
|
5244
|
-
|
5245
|
-
|
5249
|
+
if len(new_segments) > 0:
|
5250
|
+
yield Geomstr(new_segments)
|
5251
|
+
new_segments.clear()
|
5252
|
+
else:
|
5253
|
+
if len(new_segments) == 0:
|
5254
|
+
new_segments.append(seg)
|
5255
|
+
elif seg[0] != new_segments[-1][-1]:
|
5256
|
+
# If the start of the current segment is not the end of the last segment
|
5257
|
+
yield Geomstr(new_segments)
|
5258
|
+
new_segments.clear()
|
5259
|
+
new_segments.append(seg)
|
5260
|
+
else:
|
5261
|
+
# If the start of the current segment is the end of the last segment
|
5262
|
+
new_segments.append(seg)
|
5263
|
+
if len(new_segments) > 0:
|
5264
|
+
# If there are still segments left, yield them
|
5265
|
+
yield Geomstr(new_segments)
|
5246
5266
|
|
5247
5267
|
def ensure_proper_subpaths(self):
|
5248
5268
|
"""
|
@@ -5489,7 +5509,7 @@ class Geomstr:
|
|
5489
5509
|
"""
|
5490
5510
|
infos = self.segments[: self.index, 2]
|
5491
5511
|
q = np.where(np.real(infos).astype(int) & 0b1001)[0]
|
5492
|
-
for mid in range(
|
5512
|
+
for mid in range(len(q)):
|
5493
5513
|
idxs = q[mid:]
|
5494
5514
|
p1 = idxs[0]
|
5495
5515
|
pen_downs = self.segments[idxs, 0]
|
@@ -5676,8 +5696,8 @@ class Geomstr:
|
|
5676
5696
|
if lines is None:
|
5677
5697
|
lines = self.segments[: self.index]
|
5678
5698
|
if function_dict is None:
|
5679
|
-
function_dict =
|
5680
|
-
default_dict =
|
5699
|
+
function_dict = {}
|
5700
|
+
default_dict = {}
|
5681
5701
|
defining_function = 0
|
5682
5702
|
function_start = 0
|
5683
5703
|
for index, line in enumerate(lines):
|
@@ -5735,7 +5755,7 @@ class Geomstr:
|
|
5735
5755
|
final = Geomstr()
|
5736
5756
|
for subgeom in geom.as_subpaths():
|
5737
5757
|
newgeom = Geomstr()
|
5738
|
-
points =
|
5758
|
+
points = []
|
5739
5759
|
closed = subgeom.is_closed()
|
5740
5760
|
|
5741
5761
|
def processpts():
|
meerk40t/tools/rasterplotter.py
CHANGED
@@ -467,11 +467,6 @@ class RasterPlotter:
|
|
467
467
|
return int(round(self.offset_x)), int(round(self.offset_y))
|
468
468
|
else:
|
469
469
|
return self.offset_x, self.offset_y
|
470
|
-
if self.use_integers:
|
471
|
-
if self.use_integers:
|
472
|
-
return int(round(self.offset_x)), int(round(self.offset_y))
|
473
|
-
else:
|
474
|
-
return self.offset_x, self.offset_y
|
475
470
|
if self.use_integers:
|
476
471
|
return (
|
477
472
|
int(round(self.offset_x + self.final_x * self.step_x)),
|
meerk40t/tools/ttfparser.py
CHANGED
@@ -15,6 +15,33 @@ USE_MY_METRICS = 1 << 9
|
|
15
15
|
OVERLAP_COMPOUND = 1 << 10
|
16
16
|
|
17
17
|
|
18
|
+
def flagname(flag):
|
19
|
+
if flag & ON_CURVE_POINT:
|
20
|
+
return "ON_CURVE_POINT"
|
21
|
+
elif flag & ARG_1_AND_2_ARE_WORDS:
|
22
|
+
return "ARG_1_AND_2_ARE_WORDS"
|
23
|
+
elif flag & ARGS_ARE_XY_VALUES:
|
24
|
+
return "ARGS_ARE_XY_VALUES"
|
25
|
+
elif flag & ROUND_XY_TO_GRID:
|
26
|
+
return "ROUND_XY_TO_GRID"
|
27
|
+
elif flag & WE_HAVE_A_SCALE:
|
28
|
+
return "WE_HAVE_A_SCALE"
|
29
|
+
elif flag & MORE_COMPONENTS:
|
30
|
+
return "MORE_COMPONENTS"
|
31
|
+
elif flag & WE_HAVE_AN_X_AND_Y_SCALE:
|
32
|
+
return "WE_HAVE_AN_X_AND_Y_SCALE"
|
33
|
+
elif flag & WE_HAVE_A_TWO_BY_TWO:
|
34
|
+
return "WE_HAVE_A_TWO_BY_TWO"
|
35
|
+
elif flag & WE_HAVE_INSTRUCTIONS:
|
36
|
+
return "WE_HAVE_INSTRUCTIONS"
|
37
|
+
elif flag & USE_MY_METRICS:
|
38
|
+
return "USE_MY_METRICS"
|
39
|
+
elif flag & OVERLAP_COMPOUND:
|
40
|
+
return "OVERLAP_COMPOUND"
|
41
|
+
else:
|
42
|
+
return f"UNKNOWN_FLAG_{flag}"
|
43
|
+
|
44
|
+
|
18
45
|
class TTFParsingError(ValueError):
|
19
46
|
"""Parsing error"""
|
20
47
|
|
@@ -76,7 +103,7 @@ class TrueTypeFont:
|
|
76
103
|
self.parse_cmap()
|
77
104
|
self.parse_name()
|
78
105
|
except Exception as e:
|
79
|
-
print
|
106
|
+
print(f"TTF init for {filename} crashed: {e}")
|
80
107
|
raise TTFParsingError("Error while parsing data") from e
|
81
108
|
self.glyph_data = list(self.parse_glyf())
|
82
109
|
self._line_information = []
|
@@ -209,24 +236,16 @@ class TrueTypeFont:
|
|
209
236
|
curr = contour[-1]
|
210
237
|
next = contour[0]
|
211
238
|
if curr[2] & ON_CURVE_POINT:
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
239
|
+
start_x = (offset_x + curr[0]) * scale
|
240
|
+
start_y = (offset_y + curr[1]) * scale
|
241
|
+
elif next[2] & ON_CURVE_POINT:
|
242
|
+
start_x = (offset_x + next[0]) * scale
|
243
|
+
start_y = (offset_y + next[1]) * scale
|
217
244
|
else:
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
(offset_y + next[1]) * scale,
|
223
|
-
)
|
224
|
-
else:
|
225
|
-
if self.active:
|
226
|
-
path.move(
|
227
|
-
(offset_x + (curr[0] + next[0]) / 2) * scale,
|
228
|
-
(offset_y + (curr[1] + next[1]) / 2) * scale,
|
229
|
-
)
|
245
|
+
start_x = (offset_x + (curr[0] + next[0]) / 2) * scale
|
246
|
+
start_y = (offset_y + (curr[1] + next[1]) / 2) * scale
|
247
|
+
if self.active:
|
248
|
+
path.move(start_x, start_y)
|
230
249
|
for i in range(len(contour)):
|
231
250
|
prev = curr
|
232
251
|
curr = next
|
@@ -242,9 +261,10 @@ class TrueTypeFont:
|
|
242
261
|
else:
|
243
262
|
next2 = next
|
244
263
|
if not next[2] & ON_CURVE_POINT:
|
245
|
-
next2 = (
|
246
|
-
curr[
|
247
|
-
|
264
|
+
next2 = (
|
265
|
+
(curr[0] + next[0]) / 2,
|
266
|
+
(curr[1] + next[1]) / 2,
|
267
|
+
)
|
248
268
|
if self.active:
|
249
269
|
path.quad(
|
250
270
|
None,
|
@@ -321,10 +341,7 @@ class TrueTypeFont:
|
|
321
341
|
)
|
322
342
|
self._raw_tables[tag] = data
|
323
343
|
except Exception as e:
|
324
|
-
raise TTFParsingError(
|
325
|
-
f"invalid format: {e}"
|
326
|
-
) from e
|
327
|
-
|
344
|
+
raise TTFParsingError(f"invalid format: {e}") from e
|
328
345
|
|
329
346
|
def parse_head(self):
|
330
347
|
data = self._raw_tables[b"head"]
|
@@ -576,71 +593,127 @@ class TrueTypeFont:
|
|
576
593
|
yield from self._parse_compound_glyph(data)
|
577
594
|
|
578
595
|
def _parse_compound_glyph(self, data):
|
596
|
+
"""
|
597
|
+
Parses a compound glyph, which can consist of multiple components.
|
598
|
+
Each component can have its own transformation matrix applied to it.
|
599
|
+
The transformation matrix can include scaling, translation, and rotation.
|
600
|
+
The flags indicate how the arguments are interpreted, whether they are
|
601
|
+
absolute coordinates or relative offsets, and whether the glyph is
|
602
|
+
transformed by a scale, x and y scale, or a two-by-two matrix.
|
603
|
+
The glyphs are returned as a list of contours, where each contour is a
|
604
|
+
list of points. Each point is a tuple of (x, y, flag), where
|
605
|
+
x and y are the coordinates of the point, and flag indicates whether
|
606
|
+
the point is an on-curve point or a control point.
|
607
|
+
|
608
|
+
The flags used in the compound glyphs are defined as follows:
|
609
|
+
- ON_CURVE_POINT: Indicates that the point is an on-curve point.
|
610
|
+
- ARG_1_AND_2_ARE_WORDS: Indicates that the first two arguments are
|
611
|
+
16-bit signed integers instead of 8-bit unsigned integers.
|
612
|
+
- ARGS_ARE_XY_VALUES: Indicates that the arguments are interpreted as
|
613
|
+
x and y coordinates instead of relative offsets.
|
614
|
+
- ROUND_XY_TO_GRID: Indicates that the x and y coordinates should be
|
615
|
+
rounded to the nearest grid point.
|
616
|
+
- WE_HAVE_A_SCALE: Indicates that the glyph is transformed by a single
|
617
|
+
scale factor applied to both x and y coordinates.
|
618
|
+
- MORE_COMPONENTS: Indicates that there are more components in the
|
619
|
+
compound glyph. This flag is used to indicate that the glyph has
|
620
|
+
additional components that need to be processed.
|
621
|
+
- WE_HAVE_AN_X_AND_Y_SCALE: Indicates that the glyph is transformed by
|
622
|
+
separate scale factors for x and y coordinates.
|
623
|
+
- WE_HAVE_A_TWO_BY_TWO: Indicates that the glyph is transformed by a
|
624
|
+
two-by-two matrix, which allows for more complex transformations
|
625
|
+
including rotation and shearing.
|
626
|
+
- WE_HAVE_INSTRUCTIONS: Indicates that the glyph has instructions that
|
627
|
+
modify the rendering of the glyph. These instructions can include
|
628
|
+
additional transformations or adjustments to the glyph's shape.
|
629
|
+
- USE_MY_METRICS: Indicates that the glyph should use its own metrics
|
630
|
+
instead of the metrics defined in the font's horizontal metrics table.
|
631
|
+
- OVERLAP_COMPOUND: Indicates that the components of the compound glyph
|
632
|
+
may overlap. This flag is used to indicate that the components of the
|
633
|
+
compound glyph may overlap, which can affect how the glyph is rendered.
|
634
|
+
|
635
|
+
"""
|
579
636
|
flags = MORE_COMPONENTS
|
580
|
-
|
637
|
+
scale_factor = 1 << 14 # Fixed point scale factor (16384)
|
638
|
+
|
639
|
+
# Collect all contours from all components
|
640
|
+
all_contours = []
|
641
|
+
|
581
642
|
while flags & MORE_COMPONENTS:
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
dest, src = -1, -1
|
643
|
+
# Initialize transformation matrix as identity
|
644
|
+
# Matrix format: [xx, xy, yx, yy, dx, dy]
|
645
|
+
# Represents: [x'] = [xx xy] [x] + [dx]
|
646
|
+
# [y'] [yx yy] [y] [dy]
|
647
|
+
transform_xx, transform_xy, transform_yx, transform_yy = 1.0, 0.0, 0.0, 1.0
|
648
|
+
transform_dx, transform_dy = 0.0, 0.0
|
649
|
+
|
650
|
+
# Read component header
|
591
651
|
flags, glyph_index = struct.unpack(">HH", data.read(4))
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
e, f = args1 / s, args2 / s
|
652
|
+
|
653
|
+
# Read arguments (either offsets or point indices)
|
654
|
+
if flags & ARG_1_AND_2_ARE_WORDS:
|
655
|
+
# 16-bit arguments
|
656
|
+
arg1, arg2 = struct.unpack(">hh", data.read(4))
|
598
657
|
else:
|
599
|
-
|
600
|
-
|
658
|
+
# 8-bit arguments
|
659
|
+
if flags & ARGS_ARE_XY_VALUES:
|
660
|
+
# Signed bytes for offsets
|
661
|
+
arg1, arg2 = struct.unpack(">bb", data.read(2))
|
601
662
|
else:
|
602
|
-
|
603
|
-
|
663
|
+
# Unsigned bytes for point indices
|
664
|
+
arg1, arg2 = struct.unpack(">BB", data.read(2))
|
665
|
+
|
666
|
+
# Interpret arguments
|
667
|
+
if flags & ARGS_ARE_XY_VALUES:
|
668
|
+
# Arguments are x,y offsets
|
669
|
+
transform_dx, transform_dy = float(arg1), float(arg2)
|
670
|
+
else:
|
671
|
+
# Arguments are point indices for point matching
|
672
|
+
dest_point_index, src_point_index = arg1, arg2
|
673
|
+
# Point matching not fully implemented - would need to find
|
674
|
+
# matching points in already processed contours and source glyph
|
675
|
+
transform_dx, transform_dy = 0.0, 0.0
|
676
|
+
|
677
|
+
# Read transformation matrix components
|
604
678
|
if flags & WE_HAVE_A_SCALE:
|
605
|
-
|
606
|
-
|
679
|
+
# Single scale factor for both x and y
|
680
|
+
scale = struct.unpack(">h", data.read(2))[0] / scale_factor
|
681
|
+
transform_xx = transform_yy = scale
|
607
682
|
elif flags & WE_HAVE_AN_X_AND_Y_SCALE:
|
608
|
-
|
609
|
-
|
683
|
+
# Separate scale factors for x and y
|
684
|
+
scale_x, scale_y = struct.unpack(">hh", data.read(4))
|
685
|
+
transform_xx = scale_x / scale_factor
|
686
|
+
transform_yy = scale_y / scale_factor
|
610
687
|
elif flags & WE_HAVE_A_TWO_BY_TWO:
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
for
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
)
|
641
|
-
for x, y, flag in contour
|
642
|
-
]
|
643
|
-
data.seek(original)
|
688
|
+
# Full 2x2 transformation matrix
|
689
|
+
xx, xy, yx, yy = struct.unpack(">hhhh", data.read(8))
|
690
|
+
transform_xx = xx / scale_factor
|
691
|
+
transform_xy = xy / scale_factor
|
692
|
+
transform_yx = yx / scale_factor
|
693
|
+
transform_yy = yy / scale_factor
|
694
|
+
|
695
|
+
# Get the component glyph's contours
|
696
|
+
component_contours = list(self._parse_glyph_index(glyph_index))
|
697
|
+
|
698
|
+
# Apply transformation to each contour
|
699
|
+
for contour in component_contours:
|
700
|
+
transformed_contour = []
|
701
|
+
for x, y, flag in contour:
|
702
|
+
# Apply 2D transformation matrix
|
703
|
+
new_x = transform_xx * x + transform_xy * y + transform_dx
|
704
|
+
new_y = transform_yx * x + transform_yy * y + transform_dy
|
705
|
+
|
706
|
+
# Round to grid if requested
|
707
|
+
if flags & ROUND_XY_TO_GRID:
|
708
|
+
new_x = round(new_x)
|
709
|
+
new_y = round(new_y)
|
710
|
+
|
711
|
+
transformed_contour.append((new_x, new_y, flag))
|
712
|
+
|
713
|
+
# Add transformed contour to our collection
|
714
|
+
all_contours.append(transformed_contour)
|
715
|
+
# Yield all collected contours
|
716
|
+
yield from all_contours
|
644
717
|
|
645
718
|
def _parse_simple_glyph(self, num_contours, data):
|
646
719
|
end_pts = struct.unpack(f">{num_contours}H", data.read(2 * num_contours))
|