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.
Files changed (68) hide show
  1. meerk40t/balormk/controller.py +3 -3
  2. meerk40t/balormk/device.py +7 -0
  3. meerk40t/balormk/driver.py +23 -14
  4. meerk40t/balormk/galvo_commands.py +18 -3
  5. meerk40t/balormk/gui/balorconfig.py +6 -0
  6. meerk40t/balormk/livelightjob.py +36 -14
  7. meerk40t/camera/camera.py +1 -0
  8. meerk40t/camera/gui/camerapanel.py +154 -58
  9. meerk40t/camera/plugin.py +46 -5
  10. meerk40t/core/elements/branches.py +90 -20
  11. meerk40t/core/elements/elements.py +59 -37
  12. meerk40t/core/elements/trace.py +10 -6
  13. meerk40t/core/node/node.py +2 -0
  14. meerk40t/core/plotplanner.py +7 -4
  15. meerk40t/device/gui/defaultactions.py +78 -14
  16. meerk40t/dxf/dxf_io.py +42 -0
  17. meerk40t/grbl/controller.py +245 -35
  18. meerk40t/grbl/device.py +102 -26
  19. meerk40t/grbl/driver.py +8 -2
  20. meerk40t/grbl/gui/grblconfiguration.py +6 -0
  21. meerk40t/grbl/gui/grblcontroller.py +1 -1
  22. meerk40t/gui/about.py +7 -0
  23. meerk40t/gui/choicepropertypanel.py +20 -30
  24. meerk40t/gui/devicepanel.py +27 -16
  25. meerk40t/gui/icons.py +15 -0
  26. meerk40t/gui/laserpanel.py +102 -54
  27. meerk40t/gui/materialtest.py +10 -0
  28. meerk40t/gui/mkdebug.py +268 -9
  29. meerk40t/gui/navigationpanels.py +65 -7
  30. meerk40t/gui/propertypanels/operationpropertymain.py +185 -91
  31. meerk40t/gui/scenewidgets/elementswidget.py +7 -1
  32. meerk40t/gui/scenewidgets/selectionwidget.py +24 -9
  33. meerk40t/gui/simulation.py +1 -1
  34. meerk40t/gui/statusbarwidgets/shapepropwidget.py +50 -40
  35. meerk40t/gui/statusbarwidgets/statusbar.py +2 -2
  36. meerk40t/gui/toolwidgets/toolmeasure.py +1 -1
  37. meerk40t/gui/toolwidgets/toolnodeedit.py +4 -1
  38. meerk40t/gui/toolwidgets/tooltabedit.py +9 -7
  39. meerk40t/gui/wxmeerk40t.py +2 -0
  40. meerk40t/gui/wxmmain.py +23 -9
  41. meerk40t/gui/wxmribbon.py +36 -0
  42. meerk40t/gui/wxutils.py +66 -42
  43. meerk40t/kernel/inhibitor.py +120 -0
  44. meerk40t/kernel/kernel.py +38 -0
  45. meerk40t/lihuiyu/controller.py +33 -3
  46. meerk40t/lihuiyu/device.py +99 -4
  47. meerk40t/lihuiyu/driver.py +62 -5
  48. meerk40t/lihuiyu/gui/lhycontrollergui.py +69 -24
  49. meerk40t/lihuiyu/gui/lhydrivergui.py +6 -0
  50. meerk40t/lihuiyu/laserspeed.py +17 -10
  51. meerk40t/lihuiyu/parser.py +23 -0
  52. meerk40t/main.py +1 -1
  53. meerk40t/moshi/gui/moshidrivergui.py +7 -0
  54. meerk40t/newly/controller.py +3 -2
  55. meerk40t/newly/device.py +23 -2
  56. meerk40t/newly/driver.py +8 -3
  57. meerk40t/newly/gui/newlyconfig.py +7 -0
  58. meerk40t/ruida/gui/ruidaconfig.py +7 -0
  59. meerk40t/tools/geomstr.py +68 -48
  60. meerk40t/tools/rasterplotter.py +0 -5
  61. meerk40t/tools/ttfparser.py +155 -82
  62. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/METADATA +1 -1
  63. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/RECORD +68 -67
  64. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/LICENSE +0 -0
  65. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/WHEEL +0 -0
  66. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/entry_points.txt +0 -0
  67. {meerk40t-0.9.7051.dist-info → meerk40t-0.9.7900.dist-info}/top_level.txt +0 -0
  68. {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 {}".format(r))
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 = [list() for _ in range(len(subject))]
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 = [list() for _ in range(len(subject))]
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, 1):
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, 1):
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
- next, _, _, _ = events[i + 1]
954
+ next_idx, _, _, _ = events[i + 1]
946
955
  except IndexError:
947
- next = complex(float("inf"), float("inf"))
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 == next:
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
- next, _, _ = events[i + 1]
1045
- scanline = (pt + next) / 2
1053
+ next_idx, _, _ = events[i + 1]
1054
+ scanline = (pt + next_idx) / 2
1046
1055
  except IndexError:
1047
- next = complex(float("inf"), float("inf"))
1048
- scanline = next
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 != next:
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
- filter = np.dstack((starts != ends, ~np.isnan(starts))).all(axis=2)[0]
1129
- starts = starts[filter]
1130
- ends = ends[filter]
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 = dict()
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 = list()
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 = list()
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 = list()
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 = list()
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 = list()
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 = list()
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(0, slices):
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(0, slices):
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 = list()
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
- if segtype2 == TYPE_LINE:
4014
- yield from self._line_line_intersections(line1, line2)
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
- last = 0
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
- yield Geomstr(self.segments[last:idx])
5238
- last = idx + 1
5239
- elif idx > 0:
5240
- # are the start and endpositions different?
5241
- if self.segments[idx, 0] != self.segments[idx - 1, -1]:
5242
- yield Geomstr(self.segments[last:idx])
5243
- last = idx
5244
- if last != self.index:
5245
- yield Geomstr(self.segments[last : self.index])
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(0, len(q)):
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 = dict()
5680
- default_dict = 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 = list()
5758
+ points = []
5739
5759
  closed = subgeom.is_closed()
5740
5760
 
5741
5761
  def processpts():
@@ -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)),
@@ -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 (f"TTF init for {filename} crashed: {e}")
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
- if self.active:
213
- path.move(
214
- (offset_x + curr[0]) * scale,
215
- (offset_y + curr[1]) * scale,
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
- if next[2] & ON_CURVE_POINT:
219
- if self.active:
220
- path.move(
221
- (offset_x + next[0]) * scale,
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 = (curr[0] + next[0]) / 2, (
246
- curr[1] + next[1]
247
- ) / 2
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
- s = 1 << 14
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
- a, b, c, d, e, f = (
583
- 1.0,
584
- 0.0,
585
- 0.0,
586
- 1.0,
587
- 0.0,
588
- 0.0,
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
- if flags & ARGS_ARE_XY_VALUES:
593
- if flags & ARG_1_AND_2_ARE_WORDS:
594
- args1, args2 = struct.unpack(">hh", data.read(4))
595
- else:
596
- args1, args2 = struct.unpack(">bb", data.read(2))
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
- if flags & ARG_1_AND_2_ARE_WORDS:
600
- args1, args2 = struct.unpack(">HH", data.read(4))
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
- args1, args2 = struct.unpack(">BB", data.read(2))
603
- dest, src = args1, args2
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
- a = struct.unpack(">h", data.read(2))[0] / s
606
- d = a
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
- a, d = struct.unpack(">hh", data.read(4))
609
- a, d = a / s, d / s
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
- a, b, c, d = struct.unpack(">hhhh", data.read(8))
612
- a, b, c, d = a / s, b / s, c / s, d / s
613
- original = data.tell()
614
- m = max(abs(a), abs(b))
615
- if abs(abs(a) - abs(c)) < 33.0 / s:
616
- m *= 2
617
- n = max(abs(c), abs(d))
618
- if abs(abs(b) - abs(c)) < 33.0 / s:
619
- n *= 2
620
- contours = list(self._parse_glyph_index(glyph_index))
621
- if src != -1 and dest != -1:
622
- pass # Not properly supported.
623
- if flags & ROUND_XY_TO_GRID:
624
- for contour in contours:
625
- yield [
626
- (
627
- round(m * (x * a / m + y * b / m + e)),
628
- round(n * (x * c / n + y * d / n + f)),
629
- flag,
630
- )
631
- for x, y, flag in contour
632
- ]
633
- else:
634
- for contour in contours:
635
- yield [
636
- (
637
- m * (x * a / m + y * b / m + e),
638
- n * (x * c / n + y * d / n + f),
639
- flag,
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))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: meerk40t
3
- Version: 0.9.7051
3
+ Version: 0.9.7900
4
4
  Summary: MeerK40t LaserCutter Software
5
5
  Home-page: https://github.com/meerk40t/meerk40t
6
6
  Author: Tatarize