meerk40t 0.9.7010__py2.py3-none-any.whl → 0.9.7030__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 (77) hide show
  1. meerk40t/balormk/galvo_commands.py +1 -2
  2. meerk40t/core/cutcode/cutcode.py +1 -1
  3. meerk40t/core/cutplan.py +70 -2
  4. meerk40t/core/elements/branches.py +18 -4
  5. meerk40t/core/elements/element_treeops.py +43 -7
  6. meerk40t/core/elements/elements.py +49 -63
  7. meerk40t/core/elements/grid.py +8 -1
  8. meerk40t/core/elements/offset_clpr.py +4 -3
  9. meerk40t/core/elements/offset_mk.py +2 -1
  10. meerk40t/core/elements/shapes.py +379 -260
  11. meerk40t/core/elements/testcases.py +105 -0
  12. meerk40t/core/node/node.py +6 -3
  13. meerk40t/core/node/op_cut.py +9 -8
  14. meerk40t/core/node/op_dots.py +8 -8
  15. meerk40t/core/node/op_engrave.py +7 -7
  16. meerk40t/core/node/op_raster.py +8 -8
  17. meerk40t/core/planner.py +23 -0
  18. meerk40t/core/undos.py +1 -1
  19. meerk40t/core/wordlist.py +1 -0
  20. meerk40t/dxf/dxf_io.py +6 -0
  21. meerk40t/extra/encode_detect.py +8 -2
  22. meerk40t/extra/hershey.py +2 -3
  23. meerk40t/extra/inkscape.py +3 -5
  24. meerk40t/extra/mk_potrace.py +1959 -0
  25. meerk40t/extra/outerworld.py +2 -3
  26. meerk40t/extra/param_functions.py +2 -2
  27. meerk40t/extra/potrace.py +14 -10
  28. meerk40t/grbl/device.py +4 -1
  29. meerk40t/grbl/gui/grblcontroller.py +2 -2
  30. meerk40t/grbl/interpreter.py +1 -1
  31. meerk40t/gui/about.py +3 -5
  32. meerk40t/gui/basicops.py +3 -3
  33. meerk40t/gui/busy.py +75 -13
  34. meerk40t/gui/choicepropertypanel.py +365 -379
  35. meerk40t/gui/consolepanel.py +3 -3
  36. meerk40t/gui/gui_mixins.py +4 -1
  37. meerk40t/gui/hersheymanager.py +13 -3
  38. meerk40t/gui/laserpanel.py +12 -7
  39. meerk40t/gui/materialmanager.py +33 -6
  40. meerk40t/gui/plugin.py +9 -3
  41. meerk40t/gui/propertypanels/operationpropertymain.py +1 -1
  42. meerk40t/gui/ribbon.py +4 -1
  43. meerk40t/gui/scene/widget.py +1 -1
  44. meerk40t/gui/scenewidgets/rectselectwidget.py +19 -16
  45. meerk40t/gui/scenewidgets/selectionwidget.py +26 -20
  46. meerk40t/gui/simpleui.py +13 -8
  47. meerk40t/gui/simulation.py +22 -2
  48. meerk40t/gui/spoolerpanel.py +8 -11
  49. meerk40t/gui/themes.py +7 -1
  50. meerk40t/gui/tips.py +2 -3
  51. meerk40t/gui/toolwidgets/toolmeasure.py +4 -1
  52. meerk40t/gui/wxmeerk40t.py +32 -3
  53. meerk40t/gui/wxmmain.py +72 -6
  54. meerk40t/gui/wxmscene.py +95 -6
  55. meerk40t/gui/wxmtree.py +17 -11
  56. meerk40t/gui/wxutils.py +1 -1
  57. meerk40t/image/imagetools.py +21 -6
  58. meerk40t/kernel/kernel.py +31 -6
  59. meerk40t/kernel/settings.py +2 -0
  60. meerk40t/lihuiyu/device.py +9 -3
  61. meerk40t/main.py +22 -5
  62. meerk40t/network/console_server.py +52 -14
  63. meerk40t/network/web_server.py +15 -1
  64. meerk40t/ruida/device.py +5 -1
  65. meerk40t/ruida/gui/gui.py +6 -6
  66. meerk40t/ruida/gui/ruidaoperationproperties.py +1 -10
  67. meerk40t/ruida/rdjob.py +3 -3
  68. meerk40t/tools/geomstr.py +88 -0
  69. meerk40t/tools/polybool.py +2 -1
  70. meerk40t/tools/shxparser.py +92 -34
  71. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/METADATA +1 -1
  72. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/RECORD +77 -75
  73. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/WHEEL +1 -1
  74. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/LICENSE +0 -0
  75. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/entry_points.txt +0 -0
  76. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/top_level.txt +0 -0
  77. {meerk40t-0.9.7010.dist-info → meerk40t-0.9.7030.dist-info}/zip-safe +0 -0
@@ -0,0 +1,1959 @@
1
+ import math
2
+ from typing import Optional, Tuple, Union
3
+
4
+ import numpy as np
5
+
6
+ try:
7
+ from numba import njit
8
+ except ImportError:
9
+
10
+ def njit(*args, **kwargs):
11
+ return lambda f: f
12
+
13
+
14
+ POTRACE_TURNPOLICY_BLACK = 0
15
+ POTRACE_TURNPOLICY_WHITE = 1
16
+ POTRACE_TURNPOLICY_LEFT = 2
17
+ POTRACE_TURNPOLICY_RIGHT = 3
18
+ POTRACE_TURNPOLICY_MINORITY = 4
19
+ POTRACE_TURNPOLICY_MAJORITY = 5
20
+ POTRACE_TURNPOLICY_RANDOM = 6
21
+
22
+ # /* segment tags */
23
+ POTRACE_CURVETO = 1
24
+ POTRACE_CORNER = 2
25
+
26
+ INFTY = float("inf")
27
+ COS179 = math.cos(math.radians(179))
28
+
29
+
30
+ class Bitmap:
31
+ def __init__(self, data, blacklevel=0.5):
32
+ if hasattr(data, "dtype"):
33
+ if data.dtype != "bool":
34
+ data = data > (255 * blacklevel)
35
+ if hasattr(data, "mode"):
36
+ if data.mode != "L":
37
+ data = data.convert("L")
38
+ data = data.point(lambda e: 0 if (e / 255.0) < blacklevel else 255)
39
+ image = data.convert("1")
40
+ data = np.array(image)
41
+ self.data = data
42
+ self.invert()
43
+
44
+ def invert(self):
45
+ self.data = np.invert(self.data)
46
+
47
+ def trace(
48
+ self,
49
+ turdsize: int = 2,
50
+ turnpolicy: int = POTRACE_TURNPOLICY_MINORITY,
51
+ alphamax=1.0,
52
+ opticurve=True,
53
+ opttolerance=0.2,
54
+ ):
55
+ bm = np.pad(self.data, [(0, 1), (0, 1)], mode="constant")
56
+
57
+ plist = bm_to_pathlist(bm, turdsize=turdsize, turnpolicy=turnpolicy)
58
+ process_path(
59
+ plist,
60
+ alphamax=alphamax,
61
+ opticurve=opticurve,
62
+ opttolerance=opttolerance,
63
+ )
64
+ return Path(plist)
65
+
66
+
67
+ class Path(list):
68
+ def __init__(self, plist):
69
+ list.__init__(self)
70
+ self.extend([Curve(p) for p in plist])
71
+
72
+ @property
73
+ def curves(self):
74
+ return self
75
+
76
+ @property
77
+ def curves_tree(self):
78
+ """
79
+ Curves tree is currently non-implemented.
80
+ :return:
81
+ """
82
+ return None
83
+
84
+
85
+ class Curve(list):
86
+ def __init__(self, p):
87
+ list.__init__(self)
88
+ last = None
89
+ self._path = p
90
+ self._curve = p._fcurve
91
+ for s in self._curve:
92
+ if s.tag == POTRACE_CORNER:
93
+ self.append(CornerSegment(s))
94
+ else:
95
+ self.append(BezierSegment(s))
96
+ last = s
97
+ self.start_point = last.c[2] if last is not None else None
98
+
99
+ @property
100
+ def decomposition_points(self):
101
+ return self._path.pt
102
+
103
+ @property
104
+ def segments(self):
105
+ return self
106
+
107
+ @property
108
+ def children(self):
109
+ """
110
+ Curves tree is non-implemented.
111
+ :return:
112
+ """
113
+ return None
114
+
115
+
116
+ class CornerSegment:
117
+ def __init__(self, s):
118
+ self._segment = s
119
+
120
+ @property
121
+ def c(self):
122
+ return self._segment.c[1]
123
+
124
+ @property
125
+ def end_point(self):
126
+ return self._segment.c[2]
127
+
128
+ @property
129
+ def is_corner(self):
130
+ return True
131
+
132
+
133
+ class BezierSegment:
134
+ def __init__(self, s):
135
+ self._segment = s
136
+
137
+ @property
138
+ def c1(self):
139
+ return self._segment.c[0]
140
+
141
+ @property
142
+ def c2(self):
143
+ return self._segment.c[1]
144
+
145
+ @property
146
+ def end_point(self):
147
+ return self._segment.c[2]
148
+
149
+ @property
150
+ def is_corner(self):
151
+ return False
152
+
153
+
154
+ # /* ---------------------------------------------------------------------- */
155
+
156
+
157
+ class _Curve:
158
+ def __init__(self, m):
159
+ self.segments = [_Segment() for _ in range(m)]
160
+ self.alphacurve = False
161
+
162
+ def __len__(self):
163
+ return len(self.segments)
164
+
165
+ @property
166
+ def n(self):
167
+ return len(self)
168
+
169
+ def __getitem__(self, item):
170
+ return self.segments[item]
171
+
172
+
173
+ class _Path:
174
+ def __init__(self, pt: list, area: int, sign: bool):
175
+ self.pt = pt # /* pt[len]: path as extracted from bitmap */
176
+
177
+ self.area = area
178
+ self.sign = sign
179
+ self.next = None
180
+ self.childlist = []
181
+ self.sibling = []
182
+
183
+ self._lon = [] # /* lon[len]: (i,lon[i]) = longest straight line from i */
184
+
185
+ self._x0 = 0 # /* origin for sums */
186
+ self._y0 = 0 # /* origin for sums */
187
+ self._sums = [] # / *sums[len + 1]: cache for fast summing * /
188
+
189
+ self._m = 0 # /* length of optimal polygon */
190
+ self._po = [] # /* po[m]: optimal polygon */
191
+ self._curve = [] # /* curve[m]: array of curve elements */
192
+ self._ocurve = [] # /* ocurve[om]: array of curve elements */
193
+ self._fcurve = [] # /* final curve: this points to either curve or ocurve.*/
194
+
195
+ def __len__(self):
196
+ return len(self.pt)
197
+
198
+ def init_curve(self, m):
199
+ pass
200
+
201
+
202
+ class _Point:
203
+ def __init__(self, x: float = 0, y: float = 0):
204
+ self.x = x
205
+ self.y = y
206
+
207
+ def __repr__(self):
208
+ return "Point(%f, %f)" % (self.x, self.y)
209
+
210
+
211
+ class _Segment:
212
+ def __init__(self):
213
+ self.tag = 0
214
+ self.c = [_Point(), _Point(), _Point()]
215
+ self.vertex = _Point()
216
+ self.alpha = 0.0
217
+ self.alpha0 = 0.0
218
+ self.beta = 0.0
219
+
220
+
221
+ class _Sums:
222
+ def __init__(self):
223
+ self.x = 0
224
+ self.y = 0
225
+ self.x2 = 0
226
+ self.xy = 0
227
+ self.y2 = 0
228
+
229
+
230
+ detrand_t = (
231
+ # /* non-linear sequence: constant term of inverse in GF(8),
232
+ # mod x^8+x^4+x^3+x+1 */
233
+ 0,
234
+ 1,
235
+ 1,
236
+ 0,
237
+ 1,
238
+ 0,
239
+ 1,
240
+ 1,
241
+ 0,
242
+ 1,
243
+ 1,
244
+ 0,
245
+ 0,
246
+ 1,
247
+ 1,
248
+ 1,
249
+ 0,
250
+ 0,
251
+ 0,
252
+ 1,
253
+ 1,
254
+ 1,
255
+ 0,
256
+ 1,
257
+ 0,
258
+ 1,
259
+ 1,
260
+ 0,
261
+ 1,
262
+ 0,
263
+ 0,
264
+ 0,
265
+ 0,
266
+ 0,
267
+ 0,
268
+ 1,
269
+ 1,
270
+ 1,
271
+ 0,
272
+ 1,
273
+ 1,
274
+ 0,
275
+ 0,
276
+ 1,
277
+ 0,
278
+ 0,
279
+ 0,
280
+ 0,
281
+ 0,
282
+ 1,
283
+ 0,
284
+ 0,
285
+ 1,
286
+ 1,
287
+ 0,
288
+ 0,
289
+ 0,
290
+ 1,
291
+ 0,
292
+ 1,
293
+ 1,
294
+ 1,
295
+ 1,
296
+ 1,
297
+ 1,
298
+ 0,
299
+ 1,
300
+ 1,
301
+ 1,
302
+ 1,
303
+ 1,
304
+ 1,
305
+ 1,
306
+ 0,
307
+ 1,
308
+ 1,
309
+ 0,
310
+ 1,
311
+ 1,
312
+ 1,
313
+ 1,
314
+ 0,
315
+ 1,
316
+ 0,
317
+ 0,
318
+ 0,
319
+ 1,
320
+ 1,
321
+ 0,
322
+ 0,
323
+ 0,
324
+ 0,
325
+ 1,
326
+ 0,
327
+ 1,
328
+ 1,
329
+ 0,
330
+ 0,
331
+ 1,
332
+ 1,
333
+ 1,
334
+ 0,
335
+ 0,
336
+ 1,
337
+ 0,
338
+ 1,
339
+ 1,
340
+ 1,
341
+ 1,
342
+ 1,
343
+ 1,
344
+ 1,
345
+ 1,
346
+ 1,
347
+ 1,
348
+ 1,
349
+ 0,
350
+ 1,
351
+ 0,
352
+ 0,
353
+ 0,
354
+ 0,
355
+ 0,
356
+ 0,
357
+ 1,
358
+ 0,
359
+ 1,
360
+ 0,
361
+ 1,
362
+ 0,
363
+ 1,
364
+ 0,
365
+ 0,
366
+ 1,
367
+ 0,
368
+ 0,
369
+ 1,
370
+ 0,
371
+ 1,
372
+ 1,
373
+ 1,
374
+ 0,
375
+ 1,
376
+ 0,
377
+ 0,
378
+ 0,
379
+ 0,
380
+ 1,
381
+ 0,
382
+ 0,
383
+ 0,
384
+ 0,
385
+ 0,
386
+ 0,
387
+ 1,
388
+ 0,
389
+ 1,
390
+ 0,
391
+ 1,
392
+ 0,
393
+ 1,
394
+ 0,
395
+ 0,
396
+ 1,
397
+ 1,
398
+ 0,
399
+ 1,
400
+ 0,
401
+ 0,
402
+ 0,
403
+ 0,
404
+ 0,
405
+ 0,
406
+ 1,
407
+ 0,
408
+ 0,
409
+ 0,
410
+ 0,
411
+ 1,
412
+ 1,
413
+ 1,
414
+ 1,
415
+ 0,
416
+ 1,
417
+ 1,
418
+ 0,
419
+ 0,
420
+ 1,
421
+ 1,
422
+ 0,
423
+ 0,
424
+ 1,
425
+ 1,
426
+ 0,
427
+ 1,
428
+ 1,
429
+ 0,
430
+ 0,
431
+ 0,
432
+ 1,
433
+ 1,
434
+ 1,
435
+ 1,
436
+ 0,
437
+ 1,
438
+ 0,
439
+ 0,
440
+ 0,
441
+ 0,
442
+ 1,
443
+ 0,
444
+ 1,
445
+ 1,
446
+ 1,
447
+ 0,
448
+ 0,
449
+ 0,
450
+ 1,
451
+ 0,
452
+ 1,
453
+ 1,
454
+ 0,
455
+ 0,
456
+ 1,
457
+ 1,
458
+ 1,
459
+ 0,
460
+ 1,
461
+ 0,
462
+ 0,
463
+ 1,
464
+ 1,
465
+ 0,
466
+ 0,
467
+ 1,
468
+ 1,
469
+ 1,
470
+ 0,
471
+ 0,
472
+ 1,
473
+ 1,
474
+ 1,
475
+ 0,
476
+ 0,
477
+ 0,
478
+ 0,
479
+ 1,
480
+ 0,
481
+ 1,
482
+ 0,
483
+ 1,
484
+ 0,
485
+ 1,
486
+ 0,
487
+ 1,
488
+ 0,
489
+ )
490
+
491
+
492
+ def detrand(x: int, y: int) -> int:
493
+ """deterministically and efficiently hash (x,y) into a pseudo-random bit"""
494
+ # /* 0x04b3e375 and 0x05a8ef93 are chosen to contain every possible 5-bit sequence */
495
+ z = ((0x04B3E375 * x) ^ y) * 0x05A8EF93
496
+ z = (
497
+ detrand_t[z & 0xFF]
498
+ ^ detrand_t[(z >> 8) & 0xFF]
499
+ ^ detrand_t[(z >> 16) & 0xFF]
500
+ ^ detrand_t[(z >> 24) & 0xFF]
501
+ )
502
+ return z
503
+
504
+
505
+ def majority(bm: np.array, x: int, y: int) -> int:
506
+ """
507
+ /* return the "majority" value of bitmap bm at intersection (x,y). We
508
+ assume that the bitmap is balanced at "radius" 1. */
509
+ """
510
+ for i in range(2, 5): # /* check at "radius" i */
511
+ ct = 0
512
+ for a in range(-i + 1, i - 2):
513
+ try:
514
+ ct += 1 if bm[y + i - 1][x + a] else -1
515
+ except IndexError:
516
+ pass
517
+ try:
518
+ ct += 1 if bm[y + a - 1][x + i - 1] else -1
519
+ except IndexError:
520
+ pass
521
+ try:
522
+ ct += 1 if bm[y - i][x + a - 1] else -1
523
+ except IndexError:
524
+ pass
525
+ try:
526
+ ct += 1 if bm[y + a][x - i] else -1
527
+ except IndexError:
528
+ pass
529
+ if ct > 0:
530
+ return 1
531
+ elif ct < 0:
532
+ return 0
533
+ return 0
534
+
535
+
536
+ """
537
+ /* ---------------------------------------------------------------------- */
538
+ /* decompose image into paths */
539
+ """
540
+
541
+
542
+ def xor_to_ref(bm: np.array, x: int, y: int, xa: int) -> None:
543
+ """
544
+ /* efficiently invert bits [x,infty) and [xa,infty) in line y. Here xa
545
+ must be a multiple of BM_WORDBITS. */
546
+ """
547
+
548
+ if x < xa:
549
+ bm[y, x:xa] ^= True
550
+ elif x != xa:
551
+ bm[y, xa:x] ^= True
552
+
553
+
554
+ def xor_path(bm: np.array, p: _Path) -> None:
555
+ """
556
+ a path is represented as an array of points, which are thought to
557
+ lie on the corners of pixels (not on their centers). The path point
558
+ (x,y) is the lower left corner of the pixel (x,y). Paths are
559
+ represented by the len/pt components of a path_t object (which
560
+ also stores other information about the path) */
561
+
562
+ xor the given pixmap with the interior of the given path. Note: the
563
+ path must be within the dimensions of the pixmap.
564
+ """
565
+ if len(p) <= 0: # /* a path of length 0 is silly, but legal */
566
+ return
567
+
568
+ y1 = p.pt[-1].y
569
+ xa = p.pt[0].x
570
+ for n in p.pt:
571
+ x, y = n.x, n.y
572
+ if y != y1:
573
+ # /* efficiently invert the rectangle [x,xa] x [y,y1] */
574
+ xor_to_ref(bm, x, min(y, y1), xa)
575
+ y1 = y
576
+
577
+
578
+ def findpath(bm: np.array, x0: int, y0: int, sign: bool, turnpolicy: int) -> _Path:
579
+ """
580
+ /* compute a path in the given pixmap, separating black from white.
581
+ Start path at the point (x0,x1), which must be an upper left corner
582
+ of the path. Also compute the area enclosed by the path. Return a
583
+ new path_t object, or NULL on error (note that a legitimate path
584
+ cannot have length 0). Sign is required for correct interpretation
585
+ of turnpolicies. */"""
586
+
587
+ x = x0
588
+ y = y0
589
+ dirx = 0
590
+ diry = -1 # diry-1
591
+ pt = []
592
+ area = 0
593
+
594
+ while True: # /* while this path */
595
+ # /* add point to path */
596
+ pt.append(_Point(int(x), int(y)))
597
+
598
+ # /* move to next point */
599
+ x += dirx
600
+ y += diry
601
+ area += x * diry
602
+
603
+ # /* path complete? */
604
+ if x == x0 and y == y0:
605
+ break
606
+
607
+ # /* determine next direction */
608
+ cy = y + (diry - dirx - 1) // 2
609
+ cx = x + (dirx + diry - 1) // 2
610
+ try:
611
+ c = bm[cy][cx]
612
+ except IndexError:
613
+ c = 0
614
+ dy = y + (diry + dirx - 1) // 2
615
+ dx = x + (dirx - diry - 1) // 2
616
+ try:
617
+ d = bm[dy][dx]
618
+ except IndexError:
619
+ d = 0
620
+
621
+ if c and not d: # /* ambiguous turn */
622
+ if (
623
+ turnpolicy == POTRACE_TURNPOLICY_RIGHT
624
+ or (turnpolicy == POTRACE_TURNPOLICY_BLACK and sign)
625
+ or (turnpolicy == POTRACE_TURNPOLICY_WHITE and not sign)
626
+ or (turnpolicy == POTRACE_TURNPOLICY_RANDOM and detrand(x, y))
627
+ or (turnpolicy == POTRACE_TURNPOLICY_MAJORITY and majority(bm, x, y))
628
+ or (
629
+ turnpolicy == POTRACE_TURNPOLICY_MINORITY and not majority(bm, x, y)
630
+ )
631
+ ):
632
+ tmp = dirx # /* right turn */
633
+ dirx = diry
634
+ diry = -tmp
635
+ else:
636
+ tmp = dirx # /* left turn */
637
+ dirx = -diry
638
+ diry = tmp
639
+ elif c: # /* right turn */
640
+ tmp = dirx
641
+ dirx = diry
642
+ diry = -tmp
643
+ elif not d: # /* left turn */
644
+ tmp = dirx
645
+ dirx = -diry
646
+ diry = tmp
647
+
648
+ # /* allocate new path object */
649
+ return _Path(pt, area, sign)
650
+
651
+
652
+ @njit()
653
+ def findnext(bm: np.array) -> Optional[Tuple[Union[int], int]]:
654
+ """
655
+ /* find the next set pixel in a row <= y. Pixels are searched first
656
+ left-to-right, then top-down. In other words, (x,y)<(x',y') if y>y'
657
+ or y=y' and x<x'. If found, return 0 and store pixel in
658
+ (*xp,*yp). Else return 1. Note that this function assumes that
659
+ excess bytes have been cleared with bm_clearexcess. */
660
+ """
661
+ w = np.nonzero(bm)
662
+ if len(w[0]) == 0:
663
+ return None
664
+
665
+ q = np.where(w[0] == w[0][-1])
666
+ y = w[0][q]
667
+ x = w[1][q]
668
+ return y[0], x[0]
669
+
670
+
671
+ def setbbox_path(p: _Path):
672
+ """
673
+ /* Find the bounding box of a given path. Path is assumed to be of
674
+ non-zero length. */
675
+ """
676
+ y0 = float("inf")
677
+ y1 = 0
678
+ x0 = float("inf")
679
+ x1 = 0
680
+ for k in range(len(p)):
681
+ x = p.pt[k].x
682
+ y = p.pt[k].y
683
+
684
+ if x < x0:
685
+ x0 = x
686
+ if x > x1:
687
+ x1 = x
688
+ if y < y0:
689
+ y0 = y
690
+ if y > y1:
691
+ y1 = y
692
+ return x0, y0, x1, y1
693
+
694
+
695
+ def pathlist_to_tree(plist: list, bm: np.array) -> None:
696
+ """
697
+ /* Give a tree structure to the given path list, based on "insideness"
698
+ testing. I.e., path A is considered "below" path B if it is inside
699
+ path B. The input pathlist is assumed to be ordered so that "outer"
700
+ paths occur before "inner" paths. The tree structure is stored in
701
+ the "childlist" and "sibling" components of the path_t
702
+ structure. The linked list structure is also changed so that
703
+ negative path components are listed immediately after their
704
+ positive parent. Note: some backends may ignore the tree
705
+ structure, others may use it e.g. to group path components. We
706
+ assume that in the input, point 0 of each path is an "upper left"
707
+ corner of the path, as returned by bm_to_pathlist. This makes it
708
+ easy to find an "interior" point. The bm argument should be a
709
+ bitmap of the correct size (large enough to hold all the paths),
710
+ and will be used as scratch space. Return 0 on success or -1 on
711
+ error with errno set. */
712
+ """
713
+ # path_t *p, *p1
714
+ # path_t *heap, *heap1
715
+ # path_t *cur
716
+ # path_t *head
717
+ # path_t **plist_hook #/* for fast appending to linked list */
718
+ # path_t **hook_in, **hook_out #/* for fast appending to linked list */
719
+ # bbox_t bbox
720
+
721
+ bm = bm.copy()
722
+
723
+ # /* save original "next" pointers */
724
+
725
+ for p in plist:
726
+ p.sibling = p.next
727
+ p.childlist = None
728
+
729
+ heap = plist
730
+ """
731
+ /* the heap holds a list of lists of paths. Use "childlist" field
732
+ for outer list, "next" field for inner list. Each of the sublists
733
+ is to be turned into a tree. This code is messy, but it is
734
+ actually fast. Each path is rendered exactly once. We use the
735
+ heap to get a tail recursive algorithm: the heap holds a list of
736
+ pathlists which still need to be transformed. */
737
+ """
738
+ while heap:
739
+ # /* unlink first sublist */
740
+ cur = heap
741
+ heap = heap.childlist
742
+ cur.childlist = None
743
+
744
+ # /* unlink first path */
745
+ head = cur
746
+ cur = cur.next
747
+ head.next = None
748
+
749
+ # /* render path */
750
+ xor_path(bm, head)
751
+ x0, y0, x1, y1 = setbbox_path(head)
752
+
753
+ """
754
+ /* now do insideness test for each element of cur; append it to
755
+ head->childlist if it's inside head, else append it to
756
+ head->next. */
757
+ """
758
+ # hook_in= head.childlist
759
+ # hook_out= head.next
760
+ # list_forall_unlink(p, cur):
761
+ # if p.priv.pt[0].y <= bbox.y0:
762
+ # list_insert_beforehook(p, hook_out)
763
+ # # /* append the remainder of the list to hook_out */
764
+ # hook_out = cur
765
+ # break
766
+ # if (BM_GET(bm, p.priv.pt[0].x, p.priv.pt[0].y-1)):
767
+ # list_insert_beforehook(p, hook_in)
768
+ # else:
769
+ # list_insert_beforehook(p, hook_out)
770
+
771
+ # /* clear bm */
772
+ # clear_bm_with_bbox(bm, &bbox)
773
+ #
774
+ # #/* now schedule head->childlist and head->next for further processing */
775
+ # if head.next:
776
+ # head.next.childlist = heap
777
+ # heap = head.next
778
+ # if head.childlist:
779
+ # head.childlist.childlist = heap
780
+ # heap = head->childlist
781
+ #
782
+ # #/* copy sibling structure from "next" to "sibling" component */
783
+ # p = plist
784
+ # while p:
785
+ # p1 = p.sibling
786
+ # p.sibling = p.next
787
+ # p = p1
788
+ #
789
+ # """
790
+ # /* reconstruct a new linked list ("next") structure from tree
791
+ # ("childlist", "sibling") structure. This code is slightly messy,
792
+ # because we use a heap to make it tail recursive: the heap
793
+ # contains a list of childlists which still need to be
794
+ # processed. */"""
795
+ # heap = plist
796
+ # if heap:
797
+ # heap.next = None #/* heap is a linked list of childlists */
798
+ # plist = None
799
+ # plist_hook = plist
800
+ # while heap:
801
+ # heap1 = heap.next
802
+ # for (p=heap; p; p=p->sibling):
803
+ # #/* p is a positive path */
804
+ # #/* append to linked list */
805
+ # list_insert_beforehook(p, plist_hook)
806
+ #
807
+ # #/* go through its children */
808
+ # for (p1=p->childlist; p1; p1=p1->sibling):
809
+ # #/* append to linked list */
810
+
811
+
812
+ # list_insert_beforehook(p1, plist_hook);
813
+ # #/* append its childlist to heap, if non-empty */
814
+ # if (p1.childlist):
815
+ # list_append(path_t, heap1, p1->childlist)
816
+ # heap = heap1
817
+
818
+
819
+ def bm_to_pathlist(
820
+ bm: np.array, turdsize: int = 2, turnpolicy: int = POTRACE_TURNPOLICY_MINORITY
821
+ ) -> list:
822
+ """
823
+ /* Decompose the given bitmap into paths. Returns a linked list of
824
+ path_t objects with the fields len, pt, area, sign filled
825
+ in. Returns 0 on success with plistp set, or -1 on error with errno
826
+ set. */
827
+ """
828
+ plist = [] # /* linked list of path objects */
829
+ original = bm.copy()
830
+
831
+ """/* be sure the byte padding on the right is set to 0, as the fast
832
+ pixel search below relies on it */"""
833
+ # /* iterate through components */
834
+ while True:
835
+ n = findnext(bm)
836
+ if n is None:
837
+ break
838
+ y, x = n
839
+ # /* calculate the sign by looking at the original */
840
+ sign = original[y][x]
841
+ # /* calculate the path */
842
+ path = findpath(bm, x, y + 1, sign, turnpolicy)
843
+ if path is None:
844
+ raise ValueError
845
+
846
+ # /* update buffered image */
847
+ xor_path(bm, path)
848
+
849
+ # /* if it's a turd, eliminate it, else append it to the list */
850
+ if path.area > turdsize:
851
+ plist.append(path)
852
+
853
+ # pathlist_to_tree(plist, original)
854
+ return plist
855
+
856
+
857
+ # END DECOMPOSE SECTION.
858
+
859
+ # /* auxiliary functions */
860
+
861
+
862
+ def sign(x):
863
+ if x > 0:
864
+ return 1
865
+ if x < 0:
866
+ return -1
867
+ else:
868
+ return 0
869
+
870
+
871
+ def mod(a: int, n: int) -> int:
872
+ """Note: the "mod" macro works correctly for
873
+ negative a. Also note that the test for a>=n, while redundant,
874
+ speeds up the mod function by 70% in the average case (significant
875
+ since the program spends about 16% of its time here - or 40%
876
+ without the test)."""
877
+ return a % n if a >= n else a if a >= 0 else n - 1 - (-1 - a) % n
878
+
879
+
880
+ def floordiv(a: int, n: int):
881
+ """
882
+ The "floordiv" macro returns the largest integer <= a/n,
883
+ and again this works correctly for negative a, as long as
884
+ a,n are integers and n>0.
885
+ """
886
+ return a // n if a >= 0 else -1 - (-1 - a) // n
887
+
888
+
889
+ def interval(t: float, a: _Point, b: _Point):
890
+ return _Point(a.x + t * (b.x - a.x), a.y + t * (b.y - a.y))
891
+
892
+
893
+ def dorth_infty(p0: _Point, p2: _Point):
894
+ """
895
+ return a direction that is 90 degrees counterclockwise from p2-p0,
896
+ but then restricted to one of the major wind directions (n, nw, w, etc)
897
+ """
898
+ return _Point(-sign(p2.y - p0.y), sign(p2.x - p0.x))
899
+
900
+
901
+ def dpara(p0: _Point, p1: _Point, p2: _Point) -> float:
902
+ """
903
+ /* return (p1-p0)x(p2-p0), the area of the parallelogram */
904
+ """
905
+ x1 = p1.x - p0.x
906
+ y1 = p1.y - p0.y
907
+ x2 = p2.x - p0.x
908
+ y2 = p2.y - p0.y
909
+ return x1 * y2 - x2 * y1
910
+
911
+
912
+ def ddenom(p0: _Point, p2: _Point) -> float:
913
+ """
914
+ ddenom/dpara have the property that the square of radius 1 centered
915
+ at p1 intersects the line p0p2 iff |dpara(p0,p1,p2)| <= ddenom(p0,p2)
916
+ """
917
+ r = dorth_infty(p0, p2)
918
+ return r.y * (p2.x - p0.x) - r.x * (p2.y - p0.y)
919
+
920
+
921
+ def cyclic(a: int, b: int, c: int) -> int:
922
+ """
923
+ /* return 1 if a <= b < c < a, in a cyclic sense (mod n) */
924
+ """
925
+ if a <= c:
926
+ return a <= b < c
927
+ else:
928
+ return a <= b or b < c
929
+
930
+
931
+ def pointslope(pp: _Path, i: int, j: int, ctr: _Point, dir: _Point) -> None:
932
+ """
933
+ determine the center and slope of the line i..j. Assume i<j. Needs
934
+ "sum" components of p to be set.
935
+ """
936
+
937
+ # /* assume i<j */
938
+
939
+ n = len(pp)
940
+ sums = pp._sums
941
+
942
+ r = 0 # /* rotations from i to j */
943
+
944
+ while j >= n:
945
+ j -= n
946
+ r += 1
947
+
948
+ while i >= n:
949
+ i -= n
950
+ r -= 1
951
+
952
+ while j < 0:
953
+ j += n
954
+ r -= 1
955
+
956
+ while i < 0:
957
+ i += n
958
+ r += 1
959
+
960
+ x = sums[j + 1].x - sums[i].x + r * sums[n].x
961
+ y = sums[j + 1].y - sums[i].y + r * sums[n].y
962
+ x2 = sums[j + 1].x2 - sums[i].x2 + r * sums[n].x2
963
+ xy = sums[j + 1].xy - sums[i].xy + r * sums[n].xy
964
+ y2 = sums[j + 1].y2 - sums[i].y2 + r * sums[n].y2
965
+ k = j + 1 - i + r * n
966
+
967
+ ctr.x = x / k
968
+ ctr.y = y / k
969
+
970
+ a = float(x2 - x * x / k) / k
971
+ b = float(xy - x * y / k) / k
972
+ c = float(y2 - y * y / k) / k
973
+
974
+ lambda2 = (
975
+ a + c + math.sqrt((a - c) * (a - c) + 4 * b * b)
976
+ ) / 2 # /* larger e.value */
977
+
978
+ # /* now find e.vector for lambda2 */
979
+ a -= lambda2
980
+ c -= lambda2
981
+
982
+ if math.fabs(a) >= math.fabs(c):
983
+ l = math.sqrt(a * a + b * b)
984
+ if l != 0:
985
+ dir.x = -b / l
986
+ dir.y = a / l
987
+ else:
988
+ l = math.sqrt(c * c + b * b)
989
+ if l != 0:
990
+ dir.x = -c / l
991
+ dir.y = b / l
992
+ if l == 0:
993
+ # sometimes this can happen when k=4:
994
+ # the two eigenvalues coincide */
995
+ dir.x = 0
996
+ dir.y = 0
997
+
998
+
999
+ """
1000
+ /* the type of (affine) quadratic forms, represented as symmetric 3x3
1001
+ matrices. The value of the quadratic form at a vector (x,y) is v^t
1002
+ Q v, where v = (x,y,1)^t. */
1003
+ """
1004
+
1005
+
1006
+ def quadform(Q: list, w: _Point) -> float:
1007
+ """Apply quadratic form Q to vector w = (w.x,w.y)"""
1008
+ v = (w.x, w.y, 1.0)
1009
+ sum = 0.0
1010
+ for i in range(3):
1011
+ for j in range(3):
1012
+ sum += v[i] * Q[i][j] * v[j]
1013
+ return sum
1014
+
1015
+
1016
+ def xprod(p1x, p1y, p2x, p2y) -> float:
1017
+ """calculate p1 x p2"""
1018
+ return p1x * p2y - p1y * p2x
1019
+
1020
+
1021
+ def cprod(p0: _Point, p1: _Point, p2: _Point, p3: _Point) -> float:
1022
+ """calculate (p1-p0)x(p3-p2)"""
1023
+ x1 = p1.x - p0.x
1024
+ y1 = p1.y - p0.y
1025
+ x2 = p3.x - p2.x
1026
+ y2 = p3.y - p2.y
1027
+ return x1 * y2 - x2 * y1
1028
+
1029
+
1030
+ def iprod(p0: _Point, p1: _Point, p2: _Point) -> float:
1031
+ """calculate (p1-p0)*(p2-p0)"""
1032
+ x1 = p1.x - p0.x
1033
+ y1 = p1.y - p0.y
1034
+ x2 = p2.x - p0.x
1035
+ y2 = p2.y - p0.y
1036
+ return x1 * x2 + y1 * y2
1037
+
1038
+
1039
+ def iprod1(p0: _Point, p1: _Point, p2: _Point, p3: _Point) -> float:
1040
+ """calculate (p1-p0)*(p3-p2)"""
1041
+ x1 = p1.x - p0.x
1042
+ y1 = p1.y - p0.y
1043
+ x2 = p3.x - p2.x
1044
+ y2 = p3.y - p2.y
1045
+ return x1 * x2 + y1 * y2
1046
+
1047
+
1048
+ def sq(x: float) -> float:
1049
+ return x * x
1050
+
1051
+
1052
+ def ddist(p: _Point, q: _Point) -> float:
1053
+ """calculate distance between two points"""
1054
+ return math.sqrt(sq(p.x - q.x) + sq(p.y - q.y))
1055
+
1056
+
1057
+ def bezier(t: float, p0: _Point, p1: _Point, p2: _Point, p3: _Point) -> _Point:
1058
+ """calculate point of a bezier curve"""
1059
+ s = 1 - t
1060
+
1061
+ """
1062
+ Note: a good optimizing compiler (such as gcc-3) reduces the
1063
+ following to 16 multiplications, using common subexpression
1064
+ elimination.
1065
+ """
1066
+ return _Point(
1067
+ s * s * s * p0.x
1068
+ + 3 * (s * s * t) * p1.x
1069
+ + 3 * (t * t * s) * p2.x
1070
+ + t * t * t * p3.x,
1071
+ s * s * s * p0.y
1072
+ + 3 * (s * s * t) * p1.y
1073
+ + 3 * (t * t * s) * p2.y
1074
+ + t * t * t * p3.y,
1075
+ )
1076
+
1077
+
1078
+ def tangent(
1079
+ p0: _Point, p1: _Point, p2: _Point, p3: _Point, q0: _Point, q1: _Point
1080
+ ) -> float:
1081
+ """calculate the point t in [0..1] on the (convex) bezier curve
1082
+ (p0,p1,p2,p3) which is tangent to q1-q0. Return -1.0 if there is no
1083
+ solution in [0..1]."""
1084
+
1085
+ # (1-t)^2 A + 2(1-t)t B + t^2 C = 0
1086
+ # a t^2 + b t + c = 0
1087
+
1088
+ A = cprod(p0, p1, q0, q1)
1089
+ B = cprod(p1, p2, q0, q1)
1090
+ C = cprod(p2, p3, q0, q1)
1091
+
1092
+ a = A - 2 * B + C
1093
+ b = -2 * A + 2 * B
1094
+ c = A
1095
+
1096
+ d = b * b - 4 * a * c
1097
+
1098
+ if a == 0 or d < 0:
1099
+ return -1.0
1100
+
1101
+ s = math.sqrt(d)
1102
+
1103
+ r1 = (-b + s) / (2 * a)
1104
+ r2 = (-b - s) / (2 * a)
1105
+
1106
+ if 0 <= r1 <= 1:
1107
+ return r1
1108
+ elif 0 <= r2 <= 1:
1109
+ return r2
1110
+ else:
1111
+ return -1.0
1112
+
1113
+
1114
+ """
1115
+ /* ---------------------------------------------------------------------- */
1116
+ /* Stage 1: determine the straight subpaths (Sec. 2.2.1). Fill in the
1117
+ "lon" component of a path object (based on pt/len). For each i,
1118
+ lon[i] is the furthest index such that a straight line can be drawn
1119
+ from i to lon[i]. Return 1 on error with errno set, else 0. */
1120
+
1121
+ /* this algorithm depends on the fact that the existence of straight
1122
+ subpaths is a triplewise property. I.e., there exists a straight
1123
+ line through squares i0,...,in iff there exists a straight line
1124
+ through i,j,k, for all i0<=i<j<k<=in. (Proof?) */
1125
+
1126
+ /* this implementation of calc_lon is O(n^2). It replaces an older
1127
+ O(n^3) version. A "constraint" means that future points must
1128
+ satisfy xprod(constraint[0], cur) >= 0 and xprod(constraint[1],
1129
+ cur) <= 0. */
1130
+
1131
+ /* Remark for Potrace 1.1: the current implementation of calc_lon is
1132
+ more complex than the implementation found in Potrace 1.0, but it
1133
+ is considerably faster. The introduction of the "nc" data structure
1134
+ means that we only have to test the constraints for "corner"
1135
+ points. On a typical input file, this speeds up the calc_lon
1136
+ function by a factor of 31.2, thereby decreasing its time share
1137
+ within the overall Potrace algorithm from 72.6% to 7.82%, and
1138
+ speeding up the overall algorithm by a factor of 3.36. On another
1139
+ input file, calc_lon was sped up by a factor of 6.7, decreasing its
1140
+ time share from 51.4% to 13.61%, and speeding up the overall
1141
+ algorithm by a factor of 1.78. In any case, the savings are
1142
+ substantial. */
1143
+
1144
+ """
1145
+
1146
+
1147
+ # ----------------------------------------------------------------------
1148
+
1149
+
1150
+ def _calc_sums(path: _Path) -> int:
1151
+ """Preparation: fill in the sum* fields of a path (used for later
1152
+ rapid summing). Return 0 on success, 1 with errno set on
1153
+ failure."""
1154
+ n = len(path)
1155
+ path._sums = [_Sums() for i in range(len(path) + 1)]
1156
+
1157
+ # origin
1158
+ path._x0 = path.pt[0].x
1159
+ path._y0 = path.pt[0].y
1160
+
1161
+ # /* preparatory computation for later fast summing */
1162
+ path._sums[0].x2 = 0
1163
+ path._sums[0].xy = 0
1164
+ path._sums[0].y2 = 0
1165
+ path._sums[0].x = 0
1166
+ path._sums[0].y = 0
1167
+ for i in range(n):
1168
+ x = path.pt[i].x - path._x0
1169
+ y = path.pt[i].y - path._y0
1170
+ path._sums[i + 1].x = path._sums[i].x + x
1171
+ path._sums[i + 1].y = path._sums[i].y + y
1172
+ path._sums[i + 1].x2 = path._sums[i].x2 + float(x * x)
1173
+ path._sums[i + 1].xy = path._sums[i].xy + float(x * y)
1174
+ path._sums[i + 1].y2 = path._sums[i].y2 + float(y * y)
1175
+ return 0
1176
+
1177
+
1178
+ def _calc_lon(pp: _Path) -> int:
1179
+ """initialize the nc data structure. Point from each point to the
1180
+ furthest future point to which it is connected by a vertical or
1181
+ horizontal segment. We take advantage of the fact that there is
1182
+ always a direction change at 0 (due to the path decomposition
1183
+ algorithm). But even if this were not so, there is no harm, as
1184
+ in practice, correctness does not depend on the word "furthest"
1185
+ above.
1186
+ returns 0 on success, 1 on error with errno set
1187
+ """
1188
+
1189
+ pt = pp.pt
1190
+ n = len(pp)
1191
+ ct = [0, 0, 0, 0]
1192
+ pivk = [None] * n # pivk[n]
1193
+ nc = [None] * n # nc[n]: next corner
1194
+
1195
+ k = 0
1196
+ for i in range(n - 1, -1, -1):
1197
+ if pt[i].x != pt[k].x and pt[i].y != pt[k].y:
1198
+ k = i + 1 # /* necessarily i<n-1 in this case */
1199
+ nc[i] = k
1200
+
1201
+ pp._lon = [None] * n
1202
+
1203
+ # determine pivot points: for each i, let pivk[i] be the furthest k
1204
+ # such that all j with i<j<k lie on a line connecting i,k.
1205
+
1206
+ for i in range(n - 1, -1, -1):
1207
+ ct[0] = ct[1] = ct[2] = ct[3] = 0
1208
+
1209
+ # keep track of "directions" that have occurred
1210
+ dir = int(
1211
+ (3 + 3 * (pt[mod(i + 1, n)].x - pt[i].x) + (pt[mod(i + 1, n)].y - pt[i].y))
1212
+ // 2
1213
+ )
1214
+ ct[dir] += 1
1215
+
1216
+ constraint0x = 0
1217
+ constraint0y = 0
1218
+ constraint1x = 0
1219
+ constraint1y = 0
1220
+
1221
+ # find the next k such that no straight line from i to k
1222
+ k = nc[i]
1223
+ k1 = i
1224
+ while True:
1225
+ break_inner_loop_and_continue = False
1226
+ dir = int(3 + 3 * sign(pt[k].x - pt[k1].x) + sign(pt[k].y - pt[k1].y)) // 2
1227
+ ct[dir] += 1
1228
+
1229
+ # if all four "directions" have occurred, cut this path
1230
+ if ct[0] and ct[1] and ct[2] and ct[3]:
1231
+ pivk[i] = k1
1232
+ break_inner_loop_and_continue = True
1233
+ break # goto foundk;
1234
+
1235
+ cur_x = pt[k].x - pt[i].x
1236
+ cur_y = pt[k].y - pt[i].y
1237
+
1238
+ if (
1239
+ xprod(constraint0x, constraint0y, cur_x, cur_y) < 0
1240
+ or xprod(constraint1x, constraint1y, cur_x, cur_y) > 0
1241
+ ):
1242
+ break
1243
+ # see if current constraint is violated
1244
+ # else, update constraint
1245
+ if abs(cur_x) <= 1 and abs(cur_y) <= 1:
1246
+ pass # /* no constraint */
1247
+ else:
1248
+ off_x = cur_x + (1 if (cur_y >= 0 and (cur_y > 0 or cur_x < 0)) else -1)
1249
+ off_y = cur_y + (1 if (cur_x <= 0 and (cur_x < 0 or cur_y < 0)) else -1)
1250
+ if xprod(constraint0x, constraint0y, off_x, off_y) >= 0:
1251
+ constraint0x = off_x
1252
+ constraint0y = off_y
1253
+ off_x = cur_x + (1 if (cur_y <= 0 and (cur_y < 0 or cur_x < 0)) else -1)
1254
+ off_y = cur_y + (1 if (cur_x >= 0 and (cur_x > 0 or cur_y < 0)) else -1)
1255
+ if xprod(constraint1x, constraint1y, off_x, off_y) <= 0:
1256
+ constraint1x = off_x
1257
+ constraint1y = off_y
1258
+ k1 = k
1259
+ k = nc[k1]
1260
+ if not cyclic(k, i, k1):
1261
+ break
1262
+ if break_inner_loop_and_continue:
1263
+ # This previously was a goto to the end of the for i statement.
1264
+ continue
1265
+ # constraint_viol:
1266
+ """k1 was the last "corner" satisfying the current constraint, and
1267
+ k is the first one violating it. We now need to find the last
1268
+ point along k1..k which satisfied the constraint."""
1269
+ # dk: direction of k-k1
1270
+ dk_x = sign(pt[k].x - pt[k1].x)
1271
+ dk_y = sign(pt[k].y - pt[k1].y)
1272
+ cur_x = pt[k1].x - pt[i].x
1273
+ cur_y = pt[k1].y - pt[i].y
1274
+ """find largest integer j such that xprod(constraint[0], cur+j*dk) >= 0
1275
+ and xprod(constraint[1], cur+j*dk) <= 0. Use bilinearity of xprod. */"""
1276
+ a = xprod(constraint0x, constraint0y, cur_x, cur_y)
1277
+ b = xprod(constraint0x, constraint0y, dk_x, dk_y)
1278
+ c = xprod(constraint1x, constraint1y, cur_x, cur_y)
1279
+ d = xprod(constraint1x, constraint1y, dk_x, dk_y)
1280
+ """find largest integer j such that a+j*b>=0 and c+j*d<=0. This
1281
+ can be solved with integer arithmetic."""
1282
+ j = INFTY
1283
+ if b < 0:
1284
+ j = floordiv(a, -b)
1285
+ if d > 0:
1286
+ j = min(j, floordiv(-c, d))
1287
+ pivk[i] = mod(k1 + j, n)
1288
+ # foundk:
1289
+ # /* for i */
1290
+
1291
+ """/* clean up: for each i, let lon[i] be the largest k such that for
1292
+ all i' with i<=i'<k, i'<k<=pivk[i']. */"""
1293
+
1294
+ j = pivk[n - 1]
1295
+ pp._lon[n - 1] = j
1296
+ for i in range(n - 2, -1, -1):
1297
+ if cyclic(i + 1, pivk[i], j):
1298
+ j = pivk[i]
1299
+ pp._lon[i] = j
1300
+
1301
+ i = n - 1
1302
+ while cyclic(mod(i + 1, n), j, pp._lon[i]):
1303
+ pp._lon[i] = j
1304
+ i -= 1
1305
+ return 0
1306
+
1307
+
1308
+ """
1309
+ /* ---------------------------------------------------------------------- */
1310
+ /* Stage 2: calculate the optimal polygon (Sec. 2.2.2-2.2.4). */
1311
+ """
1312
+
1313
+
1314
+ def penalty3(pp: _Path, i: int, j: int) -> float:
1315
+ """Auxiliary function: calculate the penalty of an edge from i to j in
1316
+ the given path. This needs the "lon" and "sum*" data."""
1317
+ n = len(pp)
1318
+ pt = pp.pt
1319
+ sums = pp._sums
1320
+
1321
+ # /* assume 0<=i<j<=n */
1322
+
1323
+ r = 0 # /* rotations from i to j */
1324
+ if j >= n:
1325
+ j -= n
1326
+ r = 1
1327
+
1328
+ # /* critical inner loop: the "if" gives a 4.6 percent speedup */
1329
+ if r == 0:
1330
+ x = sums[j + 1].x - sums[i].x
1331
+ y = sums[j + 1].y - sums[i].y
1332
+ x2 = sums[j + 1].x2 - sums[i].x2
1333
+ xy = sums[j + 1].xy - sums[i].xy
1334
+ y2 = sums[j + 1].y2 - sums[i].y2
1335
+ k = j + 1 - i
1336
+ else:
1337
+ x = sums[j + 1].x - sums[i].x + sums[n].x
1338
+ y = sums[j + 1].y - sums[i].y + sums[n].y
1339
+ x2 = sums[j + 1].x2 - sums[i].x2 + sums[n].x2
1340
+ xy = sums[j + 1].xy - sums[i].xy + sums[n].xy
1341
+ y2 = sums[j + 1].y2 - sums[i].y2 + sums[n].y2
1342
+ k = j + 1 - i + n
1343
+
1344
+ px = (pt[i].x + pt[j].x) / 2.0 - pt[0].x
1345
+ py = (pt[i].y + pt[j].y) / 2.0 - pt[0].y
1346
+ ey = pt[j].x - pt[i].x
1347
+ ex = -(pt[j].y - pt[i].y)
1348
+
1349
+ a = (x2 - 2 * x * px) / k + px * px
1350
+ b = (xy - x * py - y * px) / k + px * py
1351
+ c = (y2 - 2 * y * py) / k + py * py
1352
+
1353
+ s = ex * ex * a + 2 * ex * ey * b + ey * ey * c
1354
+ return math.sqrt(s)
1355
+
1356
+
1357
+ def _bestpolygon(pp: _Path) -> int:
1358
+ """
1359
+ /* find the optimal polygon. Fill in the m and po components. Return 1
1360
+ on failure with errno set, else 0. Non-cyclic version: assumes i=0
1361
+ is in the polygon. Fixme: implement cyclic version. */
1362
+ """
1363
+ n = len(pp)
1364
+ pen = [None] * (n + 1) # /* pen[n+1]: penalty vector */
1365
+ prev = [None] * (n + 1) # /* prev[n+1]: best path pointer vector */
1366
+ clip0 = [None] * n # /* clip0[n]: longest segment pointer, non-cyclic */
1367
+ clip1 = [None] * (n + 1) # /* clip1[n+1]: backwards segment pointer, non-cyclic */
1368
+ seg0 = [None] * (n + 1) # /* seg0[m+1]: forward segment bounds, m<=n */
1369
+ seg1 = [None] * (n + 1) # /* seg1[m+1]: backward segment bounds, m<=n */
1370
+
1371
+ # /* calculate clipped paths */
1372
+ for i in range(n):
1373
+ c = mod(pp._lon[mod(i - 1, n)] - 1, n)
1374
+ if c == i:
1375
+ c = mod(i + 1, n)
1376
+ if c < i:
1377
+ clip0[i] = n
1378
+ else:
1379
+ clip0[i] = c
1380
+
1381
+ # /* calculate backwards path clipping, non-cyclic. j <= clip0[i] iff
1382
+ # clip1[j] <= i, for i,j=0..n. */
1383
+ j = 1
1384
+ for i in range(n):
1385
+ while j <= clip0[i]:
1386
+ clip1[j] = i
1387
+ j += 1
1388
+
1389
+ # calculate seg0[j] = longest path from 0 with j segments */
1390
+ i = 0
1391
+ j = 0
1392
+ while i < n:
1393
+ seg0[j] = i
1394
+ i = clip0[i]
1395
+ j += 1
1396
+ seg0[j] = n
1397
+ m = j
1398
+
1399
+ # calculate seg1[j] = longest path to n with m-j segments */
1400
+ i = n
1401
+ for j in range(m, 0, -1):
1402
+ seg1[j] = i
1403
+ i = clip1[i]
1404
+ seg1[0] = 0
1405
+
1406
+ """now find the shortest path with m segments, based on penalty3 */
1407
+ /* note: the outer 2 loops jointly have at most n iterations, thus
1408
+ the worst-case behavior here is quadratic. In practice, it is
1409
+ close to linear since the inner loop tends to be short. */
1410
+ """
1411
+ pen[0] = 0
1412
+ for j in range(1, m + 1):
1413
+ for i in range(seg1[j], seg0[j] + 1):
1414
+ best = -1
1415
+ for k in range(seg0[j - 1], clip1[i] - 1, -1):
1416
+ thispen = penalty3(pp, k, i) + pen[k]
1417
+ if best < 0 or thispen < best:
1418
+ prev[i] = k
1419
+ best = thispen
1420
+ pen[i] = best
1421
+
1422
+ pp._m = m
1423
+ pp._po = [None] * m
1424
+
1425
+ # /* read off shortest path */
1426
+ i = n
1427
+ j = m - 1
1428
+ while i > 0:
1429
+ i = prev[i]
1430
+ pp._po[j] = i
1431
+ j -= 1
1432
+ return 0
1433
+
1434
+
1435
+ """
1436
+ /* ---------------------------------------------------------------------- */
1437
+ /* Stage 3: vertex adjustment (Sec. 2.3.1). */
1438
+
1439
+ """
1440
+
1441
+
1442
+ def _adjust_vertices(pp: _Path) -> int:
1443
+ """
1444
+ /* Adjust vertices of optimal polygon: calculate the intersection of
1445
+ the two "optimal" line segments, then move it into the unit square
1446
+ if it lies outside. Return 1 with errno set on error; 0 on
1447
+ success. */
1448
+ """
1449
+ m = pp._m
1450
+ po = pp._po
1451
+ n = len(pp)
1452
+ pt = pp.pt # point_t
1453
+ x0 = pp._x0
1454
+ y0 = pp._y0
1455
+
1456
+ ctr = [_Point() for i in range(m)] # /* ctr[m] */
1457
+ dir = [_Point() for i in range(m)] # /* dir[m] */
1458
+ q = [
1459
+ [[0.0 for a in range(3)] for b in range(3)] for c in range(m)
1460
+ ] # quadform_t/* q[m] */
1461
+ v = [0.0, 0.0, 0.0]
1462
+ s = _Point(0, 0)
1463
+ pp._curve = _Curve(m)
1464
+
1465
+ # /* calculate "optimal" point-slope representation for each line segment */
1466
+ for i in range(m):
1467
+ j = po[mod(i + 1, m)]
1468
+ j = mod(j - po[i], n) + po[i]
1469
+ pointslope(pp, po[i], j, ctr[i], dir[i])
1470
+
1471
+ # /* represent each line segment as a singular quadratic form;
1472
+ # the distance of a point (x,y) from the line segment will be
1473
+ # (x,y,1)Q(x,y,1)^t, where Q=q[i]. */
1474
+ for i in range(m):
1475
+ d = sq(dir[i].x) + sq(dir[i].y)
1476
+ if d == 0.0:
1477
+ for j in range(3):
1478
+ for k in range(3):
1479
+ q[i][j][k] = 0
1480
+ else:
1481
+ v[0] = dir[i].y
1482
+ v[1] = -dir[i].x
1483
+ v[2] = -v[1] * ctr[i].y - v[0] * ctr[i].x
1484
+ for l in range(3):
1485
+ for k in range(3):
1486
+ q[i][l][k] = v[l] * v[k] / d
1487
+
1488
+ """/* now calculate the "intersections" of consecutive segments.
1489
+ Instead of using the actual intersection, we find the point
1490
+ within a given unit square which minimizes the square distance to
1491
+ the two lines. */"""
1492
+ Q = [[0.0 for a in range(3)] for b in range(3)]
1493
+ for i in range(m):
1494
+ # double min, cand; #/* minimum and candidate for minimum of quad. form */
1495
+ # double xmin, ymin; #/* coordinates of minimum */
1496
+
1497
+ # /* let s be the vertex, in coordinates relative to x0/y0 */
1498
+ s.x = pt[po[i]].x - x0
1499
+ s.y = pt[po[i]].y - y0
1500
+
1501
+ # /* intersect segments i-1 and i */
1502
+
1503
+ j = mod(i - 1, m)
1504
+
1505
+ # /* add quadratic forms */
1506
+ for l in range(3):
1507
+ for k in range(3):
1508
+ Q[l][k] = q[j][l][k] + q[i][l][k]
1509
+
1510
+ while True:
1511
+ # /* minimize the quadratic form Q on the unit square */
1512
+ # /* find intersection */
1513
+
1514
+ det = Q[0][0] * Q[1][1] - Q[0][1] * Q[1][0]
1515
+ w = None
1516
+ if det != 0.0:
1517
+ w = _Point(
1518
+ (-Q[0][2] * Q[1][1] + Q[1][2] * Q[0][1]) / det,
1519
+ (Q[0][2] * Q[1][0] - Q[1][2] * Q[0][0]) / det,
1520
+ )
1521
+ break
1522
+
1523
+ # /* matrix is singular - lines are parallel. Add another,
1524
+ # orthogonal axis, through the center of the unit square */
1525
+ if Q[0][0] > Q[1][1]:
1526
+ v[0] = -Q[0][1]
1527
+ v[1] = Q[0][0]
1528
+ elif Q[1][1]:
1529
+ v[0] = -Q[1][1]
1530
+ v[1] = Q[1][0]
1531
+ else:
1532
+ v[0] = 1
1533
+ v[1] = 0
1534
+ d = sq(v[0]) + sq(v[1])
1535
+ v[2] = -v[1] * s.y - v[0] * s.x
1536
+ for l in range(3):
1537
+ for k in range(3):
1538
+ Q[l][k] += v[l] * v[k] / d
1539
+ dx = math.fabs(w.x - s.x)
1540
+ dy = math.fabs(w.y - s.y)
1541
+ if dx <= 0.5 and dy <= 0.5:
1542
+ pp._curve[i].vertex.x = w.x + x0
1543
+ pp._curve[i].vertex.y = w.y + y0
1544
+ continue
1545
+
1546
+ # /* the minimum was not in the unit square; now minimize quadratic
1547
+ # on boundary of square */
1548
+ min = quadform(Q, s)
1549
+ xmin = s.x
1550
+ ymin = s.y
1551
+
1552
+ if Q[0][0] != 0.0:
1553
+ for z in range(2): # /* value of the y-coordinate */
1554
+ w.y = s.y - 0.5 + z
1555
+ w.x = -(Q[0][1] * w.y + Q[0][2]) / Q[0][0]
1556
+ dx = math.fabs(w.x - s.x)
1557
+ cand = quadform(Q, w)
1558
+ if dx <= 0.5 and cand < min:
1559
+ min = cand
1560
+ xmin = w.x
1561
+ ymin = w.y
1562
+ if Q[1][1] != 0.0:
1563
+ for z in range(2): # /* value of the x-coordinate */
1564
+ w.x = s.x - 0.5 + z
1565
+ w.y = -(Q[1][0] * w.x + Q[1][2]) / Q[1][1]
1566
+ dy = math.fabs(w.y - s.y)
1567
+ cand = quadform(Q, w)
1568
+ if dy <= 0.5 and cand < min:
1569
+ min = cand
1570
+ xmin = w.x
1571
+ ymin = w.y
1572
+ # /* check four corners */
1573
+ for l in range(2):
1574
+ for k in range(2):
1575
+ w = _Point(s.x - 0.5 + l, s.y - 0.5 + k)
1576
+ cand = quadform(Q, w)
1577
+ if cand < min:
1578
+ min = cand
1579
+ xmin = w.x
1580
+ ymin = w.y
1581
+ pp._curve[i].vertex.x = xmin + x0
1582
+ pp._curve[i].vertex.y = ymin + y0
1583
+ return 0
1584
+
1585
+
1586
+ """
1587
+ /* ---------------------------------------------------------------------- */
1588
+ /* Stage 4: smoothing and corner analysis (Sec. 2.3.3) */
1589
+ """
1590
+
1591
+
1592
+ def reverse(curve: _Curve) -> None:
1593
+ """/* reverse orientation of a path */"""
1594
+ m = curve.n
1595
+ i = 0
1596
+ j = m - 1
1597
+ while i < j:
1598
+ tmp = curve[i].vertex
1599
+ curve[i].vertex = curve[j].vertex
1600
+ curve[j].vertex = tmp
1601
+ i += 1
1602
+ j -= 1
1603
+
1604
+
1605
+ # /* Always succeeds */
1606
+
1607
+
1608
+ def _smooth(curve: _Curve, alphamax: float) -> None:
1609
+ m = curve.n
1610
+
1611
+ # /* examine each vertex and find its best fit */
1612
+ for i in range(m):
1613
+ j = mod(i + 1, m)
1614
+ k = mod(i + 2, m)
1615
+ p4 = interval(1 / 2.0, curve[k].vertex, curve[j].vertex)
1616
+
1617
+ denom = ddenom(curve[i].vertex, curve[k].vertex)
1618
+ if denom != 0.0:
1619
+ dd = dpara(curve[i].vertex, curve[j].vertex, curve[k].vertex) / denom
1620
+ dd = math.fabs(dd)
1621
+ alpha = (1 - 1.0 / dd) if dd > 1 else 0
1622
+ alpha = alpha / 0.75
1623
+ else:
1624
+ alpha = 4 / 3.0
1625
+ curve[j].alpha0 = alpha # /* remember "original" value of alpha */
1626
+
1627
+ if alpha >= alphamax: # /* pointed corner */
1628
+ curve[j].tag = POTRACE_CORNER
1629
+ curve[j].c[1] = curve[j].vertex
1630
+ curve[j].c[2] = p4
1631
+ else:
1632
+ if alpha < 0.55:
1633
+ alpha = 0.55
1634
+ elif alpha > 1:
1635
+ alpha = 1
1636
+ p2 = interval(0.5 + 0.5 * alpha, curve[i].vertex, curve[j].vertex)
1637
+ p3 = interval(0.5 + 0.5 * alpha, curve[k].vertex, curve[j].vertex)
1638
+ curve[j].tag = POTRACE_CURVETO
1639
+ curve[j].c[0] = p2
1640
+ curve[j].c[1] = p3
1641
+ curve[j].c[2] = p4
1642
+ curve[j].alpha = alpha # /* store the "cropped" value of alpha */
1643
+ curve[j].beta = 0.5
1644
+ curve.alphacurve = True
1645
+
1646
+
1647
+ """
1648
+ /* ---------------------------------------------------------------------- */
1649
+ /* Stage 5: Curve optimization (Sec. 2.4) */
1650
+ """
1651
+
1652
+
1653
+ class opti_t:
1654
+ def __init__(self):
1655
+ self.pen = 0 # /* penalty */
1656
+ self.c = [_Point(0, 0), _Point(0, 0)] # /* curve parameters */
1657
+ self.t = 0 # /* curve parameters */
1658
+ self.s = 0 # /* curve parameters */
1659
+ self.alpha = 0 # /* curve parameter */
1660
+
1661
+
1662
+ def opti_penalty(
1663
+ pp: _Path,
1664
+ i: int,
1665
+ j: int,
1666
+ res: opti_t,
1667
+ opttolerance: float,
1668
+ convc: int,
1669
+ areac: float,
1670
+ ) -> int:
1671
+ """
1672
+ /* calculate best fit from i+.5 to j+.5. Assume i<j (cyclically).
1673
+ Return 0 and set badness and parameters (alpha, beta), if
1674
+ possible. Return 1 if impossible. */
1675
+ """
1676
+
1677
+ m = pp._curve.n
1678
+
1679
+ # /* check convexity, corner-freeness, and maximum bend < 179 degrees */
1680
+
1681
+ if i == j: # sanity - a full loop can never be an opticurve
1682
+ return 1
1683
+
1684
+ k = i
1685
+ i1 = mod(i + 1, m)
1686
+ k1 = mod(k + 1, m)
1687
+ conv = convc[k1]
1688
+ if conv == 0:
1689
+ return 1
1690
+ d = ddist(pp._curve[i].vertex, pp._curve[i1].vertex)
1691
+ k = k1
1692
+ while k != j:
1693
+ k1 = mod(k + 1, m)
1694
+ k2 = mod(k + 2, m)
1695
+ if convc[k1] != conv:
1696
+ return 1
1697
+ if (
1698
+ sign(
1699
+ cprod(
1700
+ pp._curve[i].vertex,
1701
+ pp._curve[i1].vertex,
1702
+ pp._curve[k1].vertex,
1703
+ pp._curve[k2].vertex,
1704
+ )
1705
+ )
1706
+ != conv
1707
+ ):
1708
+ return 1
1709
+ if (
1710
+ iprod1(
1711
+ pp._curve[i].vertex,
1712
+ pp._curve[i1].vertex,
1713
+ pp._curve[k1].vertex,
1714
+ pp._curve[k2].vertex,
1715
+ )
1716
+ < d * ddist(pp._curve[k1].vertex, pp._curve[k2].vertex) * COS179
1717
+ ):
1718
+ return 1
1719
+ k = k1
1720
+
1721
+ # /* the curve we're working in: */
1722
+ p0 = pp._curve[mod(i, m)].c[2]
1723
+ p1 = pp._curve[mod(i + 1, m)].vertex
1724
+ p2 = pp._curve[mod(j, m)].vertex
1725
+ p3 = pp._curve[mod(j, m)].c[2]
1726
+
1727
+ # /* determine its area */
1728
+ area = areac[j] - areac[i]
1729
+ area -= dpara(pp._curve[0].vertex, pp._curve[i].c[2], pp._curve[j].c[2]) / 2
1730
+ if i >= j:
1731
+ area += areac[m]
1732
+
1733
+ # /* find intersection o of p0p1 and p2p3. Let t,s such that
1734
+ # o =interval(t,p0,p1) = interval(s,p3,p2). Let A be the area of the
1735
+ # triangle (p0,o,p3). */
1736
+
1737
+ A1 = dpara(p0, p1, p2)
1738
+ A2 = dpara(p0, p1, p3)
1739
+ A3 = dpara(p0, p2, p3)
1740
+ # /* A4 = dpara(p1, p2, p3); */
1741
+ A4 = A1 + A3 - A2
1742
+
1743
+ if A2 == A1: # this should never happen
1744
+ return 1
1745
+
1746
+ t = A3 / (A3 - A4)
1747
+ s = A2 / (A2 - A1)
1748
+ A = A2 * t / 2.0
1749
+
1750
+ if A == 0.0: # this should never happen
1751
+ return 1
1752
+
1753
+ R = area / A # /* relative area */
1754
+ alpha = 2 - math.sqrt(4 - R / 0.3) # /* overall alpha for p0-o-p3 curve */
1755
+
1756
+ res.c[0] = interval(t * alpha, p0, p1)
1757
+ res.c[1] = interval(s * alpha, p3, p2)
1758
+ res.alpha = alpha
1759
+ res.t = t
1760
+ res.s = s
1761
+
1762
+ p1 = res.c[0]
1763
+ p1 = _Point(p1.x, p1.y)
1764
+ p2 = res.c[1] # /* the proposed curve is now (p0,p1,p2,p3) */
1765
+ p2 = _Point(p2.x, p2.y)
1766
+
1767
+ res.pen = 0
1768
+
1769
+ # /* calculate penalty */
1770
+ # /* check tangency with edges */
1771
+ k = mod(i + 1, m)
1772
+ while k != j:
1773
+ k1 = mod(k + 1, m)
1774
+ t = tangent(p0, p1, p2, p3, pp._curve[k].vertex, pp._curve[k1].vertex)
1775
+ if t < -0.5:
1776
+ return 1
1777
+ pt = bezier(t, p0, p1, p2, p3)
1778
+ d = ddist(pp._curve[k].vertex, pp._curve[k1].vertex)
1779
+ if d == 0.0: # /* this should never happen */
1780
+ return 1
1781
+ d1 = dpara(pp._curve[k].vertex, pp._curve[k1].vertex, pt) / d
1782
+ if math.fabs(d1) > opttolerance:
1783
+ return 1
1784
+ if (
1785
+ iprod(pp._curve[k].vertex, pp._curve[k1].vertex, pt) < 0
1786
+ or iprod(pp._curve[k1].vertex, pp._curve[k].vertex, pt) < 0
1787
+ ):
1788
+ return 1
1789
+ res.pen += sq(d1)
1790
+ k = k1
1791
+
1792
+ # /* check corners */
1793
+ k = i
1794
+ while k != j:
1795
+ k1 = mod(k + 1, m)
1796
+ t = tangent(p0, p1, p2, p3, pp._curve[k].c[2], pp._curve[k1].c[2])
1797
+ if t < -0.5:
1798
+ return 1
1799
+ pt = bezier(t, p0, p1, p2, p3)
1800
+ d = ddist(pp._curve[k].c[2], pp._curve[k1].c[2])
1801
+ if d == 0.0: # /* this should never happen */
1802
+ return 1
1803
+ d1 = dpara(pp._curve[k].c[2], pp._curve[k1].c[2], pt) / d
1804
+ d2 = dpara(pp._curve[k].c[2], pp._curve[k1].c[2], pp._curve[k1].vertex) / d
1805
+ d2 *= 0.75 * pp._curve[k1].alpha
1806
+ if d2 < 0:
1807
+ d1 = -d1
1808
+ d2 = -d2
1809
+ if d1 < d2 - opttolerance:
1810
+ return 1
1811
+ if d1 < d2:
1812
+ res.pen += sq(d1 - d2)
1813
+ k = k1
1814
+ return 0
1815
+
1816
+
1817
+ def _opticurve(pp: _Path, opttolerance: float) -> int:
1818
+ """
1819
+ optimize the path p, replacing sequences of Bezier segments by a
1820
+ single segment when possible. Return 0 on success, 1 with errno set
1821
+ on failure.
1822
+ """
1823
+ m = pp._curve.n
1824
+ pt = [0] * (m + 1) # /* pt[m+1] */
1825
+ pen = [0.0] * (m + 1) # /* pen[m+1] */
1826
+ len = [0] * (m + 1) # /* len[m+1] */
1827
+ opt = [None] * (m + 1) # /* opt[m+1] */
1828
+
1829
+ convc = [0.0] * m # /* conv[m]: pre-computed convexities */
1830
+ areac = [0.0] * (m + 1) # /* cumarea[m+1]: cache for fast area computation */
1831
+
1832
+ # /* pre-calculate convexity: +1 = right turn, -1 = left turn, 0 = corner */
1833
+ for i in range(m):
1834
+ if pp._curve[i].tag == POTRACE_CURVETO:
1835
+ convc[i] = sign(
1836
+ dpara(
1837
+ pp._curve[mod(i - 1, m)].vertex,
1838
+ pp._curve[i].vertex,
1839
+ pp._curve[mod(i + 1, m)].vertex,
1840
+ )
1841
+ )
1842
+ else:
1843
+ convc[i] = 0
1844
+
1845
+ # /* pre-calculate areas */
1846
+ area = 0.0
1847
+ areac[0] = 0.0
1848
+ p0 = pp._curve[0].vertex
1849
+ for i in range(m):
1850
+ i1 = mod(i + 1, m)
1851
+ if pp._curve[i1].tag == POTRACE_CURVETO:
1852
+ alpha = pp._curve[i1].alpha
1853
+ area += (
1854
+ 0.3
1855
+ * alpha
1856
+ * (4 - alpha)
1857
+ * dpara(pp._curve[i].c[2], pp._curve[i1].vertex, pp._curve[i1].c[2])
1858
+ / 2
1859
+ )
1860
+ area += dpara(p0, pp._curve[i].c[2], pp._curve[i1].c[2]) / 2
1861
+ areac[i + 1] = area
1862
+ pt[0] = -1
1863
+ pen[0] = 0
1864
+ len[0] = 0
1865
+
1866
+ # /* Fixme: we always start from a fixed point
1867
+ # -- should find the best curve cyclically */
1868
+
1869
+ o = None
1870
+ for j in range(1, m + 1):
1871
+ # /* calculate best path from 0 to j */
1872
+ pt[j] = j - 1
1873
+ pen[j] = pen[j - 1]
1874
+ len[j] = len[j - 1] + 1
1875
+ for i in range(j - 2, -1, -1):
1876
+ if o is None:
1877
+ o = opti_t()
1878
+ if opti_penalty(pp, i, mod(j, m), o, opttolerance, convc, areac):
1879
+ break
1880
+ if len[j] > len[i] + 1 or (
1881
+ len[j] == len[i] + 1 and pen[j] > pen[i] + o.pen
1882
+ ):
1883
+ opt[j] = o
1884
+ pt[j] = i
1885
+ pen[j] = pen[i] + o.pen
1886
+ len[j] = len[i] + 1
1887
+ o = None
1888
+ om = len[m]
1889
+ pp._ocurve = _Curve(om)
1890
+ s = [None] * om
1891
+ t = [None] * om
1892
+
1893
+ j = m
1894
+ for i in range(om - 1, -1, -1):
1895
+ if pt[j] == j - 1:
1896
+ pp._ocurve[i].tag = pp._curve[mod(j, m)].tag
1897
+ pp._ocurve[i].c[0] = pp._curve[mod(j, m)].c[0]
1898
+ pp._ocurve[i].c[1] = pp._curve[mod(j, m)].c[1]
1899
+ pp._ocurve[i].c[2] = pp._curve[mod(j, m)].c[2]
1900
+ pp._ocurve[i].vertex = pp._curve[mod(j, m)].vertex
1901
+ pp._ocurve[i].alpha = pp._curve[mod(j, m)].alpha
1902
+ pp._ocurve[i].alpha0 = pp._curve[mod(j, m)].alpha0
1903
+ pp._ocurve[i].beta = pp._curve[mod(j, m)].beta
1904
+ s[i] = t[i] = 1.0
1905
+ else:
1906
+ pp._ocurve[i].tag = POTRACE_CURVETO
1907
+ pp._ocurve[i].c[0] = opt[j].c[0]
1908
+ pp._ocurve[i].c[1] = opt[j].c[1]
1909
+ pp._ocurve[i].c[2] = pp._curve[mod(j, m)].c[2]
1910
+ pp._ocurve[i].vertex = interval(
1911
+ opt[j].s, pp._curve[mod(j, m)].c[2], pp._curve[mod(j, m)].vertex
1912
+ )
1913
+ pp._ocurve[i].alpha = opt[j].alpha
1914
+ pp._ocurve[i].alpha0 = opt[j].alpha
1915
+ s[i] = opt[j].s
1916
+ t[i] = opt[j].t
1917
+ j = pt[j]
1918
+
1919
+ # /* calculate beta parameters */
1920
+ for i in range(om):
1921
+ i1 = mod(i + 1, om)
1922
+ pp._ocurve[i].beta = s[i] / (s[i] + t[i1])
1923
+ pp._ocurve.alphacurve = True
1924
+ return 0
1925
+
1926
+
1927
+ # /* ---------------------------------------------------------------------- */
1928
+
1929
+
1930
+ def process_path(
1931
+ plist: list,
1932
+ alphamax=1.0,
1933
+ opticurve=True,
1934
+ opttolerance=0.2,
1935
+ ) -> int:
1936
+ """/* return 0 on success, 1 on error with errno set. */"""
1937
+
1938
+ def TRY(x):
1939
+ if x:
1940
+ raise ValueError
1941
+
1942
+ # /* call downstream function with each path */
1943
+ for p in plist:
1944
+ TRY(_calc_sums(p))
1945
+ TRY(_calc_lon(p))
1946
+ TRY(_bestpolygon(p))
1947
+ TRY(_adjust_vertices(p))
1948
+ if not p.sign: # /* reverse orientation of negative paths */
1949
+ reverse(p._curve)
1950
+ _smooth(p._curve, alphamax)
1951
+ if opticurve:
1952
+ TRY(_opticurve(p, opttolerance))
1953
+ p._fcurve = p._ocurve
1954
+ else:
1955
+ p._fcurve = p._curve
1956
+ return 0
1957
+
1958
+
1959
+ # END TRACE SECTION.