.
1 /*
2 Copyright (C) 2014 2015 Johan Mattsson
3
4 This library is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 3 of the
7 License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13 */
14
15 using Cairo;
16 using Math;
17
18 namespace BirdFont {
19
20 public class StrokeTool : Tool {
21
22 static bool stroke_selected = false;
23 static int iterations = 0;
24
25 public StrokeTool (string tooltip) {
26 iterations = 10;
27 select_action.connect((self) => {
28 stroke_selected = true;
29 iterations++;
30 stroke_selected_paths ();
31 stroke_selected = false;
32 });
33 }
34
35 public static void set_stroke_for_selected_paths (double width) {
36 Glyph g = MainWindow.get_current_glyph ();
37
38 foreach (Path p in g.active_paths) {
39 p.set_stroke (width);
40 }
41
42 GlyphCanvas.redraw ();
43 }
44
45 /** Create strokes for the selected outlines. */
46 void stroke_selected_paths () {
47 Glyph g = MainWindow.get_current_glyph ();
48 PathList paths = new PathList ();
49
50 foreach (Path p in g.active_paths) {
51 if (p.stroke > 0) {
52 paths.append (get_stroke (p, p.stroke));
53 }
54 }
55
56 // FIXME: delete
57 Path? last = null;
58 foreach (Path p in g.active_paths) {
59 if (p.stroke == 0) {
60 if (last != null) {
61 PathList pl;
62 bool error;
63 if (merge_path (p, (!) last, out pl, out error)) {
64 paths.paths.remove (p);
65 paths.paths.remove ((!) last);
66 paths.append (pl);
67 g.path_list.remove (p);
68 g.path_list.remove ((!) last);
69 } else {
70 print (@"Paths can't be merged.\n");
71 }
72 }
73
74 last = p;
75 }
76 }
77
78 foreach (Path np in paths.paths) {
79 g.add_path (np);
80 }
81 }
82
83 public static PathList get_stroke (Path path, double thickness) {
84 PathList n;
85 PathList o = new PathList ();
86 Path original = path.copy ();
87
88 original.remove_points_on_points ();
89
90 n = get_stroke_outline (original, thickness);
91
92 o = split_corners (n);
93 remove_self_intersecting_corners (o);
94
95 o = merge (o);
96
97 /*
98 PathList parts = new PathList ();
99 foreach (Path p in o.paths) {
100 parts.append (split (p));
101 }
102
103 return parts;
104 */
105
106 return o;
107 }
108
109 static bool is_corner_self_intersection (Path p) {
110 EditPointHandle l, r;
111 bool corner, i, remove;
112
113 remove = false;
114 i = false;
115 p.remove_points_on_points ();
116 foreach (EditPoint ep in p.points) {
117
118 corner = (ep.flags & EditPoint.NEW_CORNER) > 0;
119
120 if ((ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) {
121 return false;
122 }
123
124 if (corner || i) {
125 l = ep.get_left_handle ();
126 r = ep.get_right_handle ();
127
128 if (fabs (l.angle - r.angle) < 0.005) {
129 remove = true;
130 }
131 }
132
133 i = corner && p.points.size == 4;
134 }
135
136 return remove;
137 }
138
139 static void remove_self_intersecting_corners (PathList pl) {
140 PathList parts;
141 foreach (Path p in pl.paths) {
142
143 if (is_corner_self_intersection (p)) {
144 parts = get_parts (p);
145 if (parts.paths.size > 1) {
146 pl.append (parts);
147 remove_self_intersecting_corners (pl);
148 return;
149 } else {
150
151 if (has_self_intersection (p)) { // FIXME: DELETE
152 warning ("Path ha self intersection.");
153 }
154
155 pl.paths.remove (p);
156 remove_self_intersecting_corners (pl);
157 return;
158 }
159 }
160 }
161 }
162
163 public static PathList get_stroke_outline (Path path, double thickness) {
164 return get_strokes (path, thickness);
165 }
166
167 public static PathList get_strokes (Path p, double thickness) {
168 Path counter = new Path ();
169 Path outline = new Path ();
170 Path merged = new Path ();
171 PathList paths = new PathList ();
172
173 if (!p.is_open () && p.is_filled ()) {
174 outline = create_stroke (p, thickness);
175 outline.close ();
176
177 outline.update_region_boundaries ();
178 paths.add (outline);
179 } else if (!p.is_open () && !p.is_filled ()) {
180 outline = create_stroke (p, thickness);
181 counter = create_stroke (p, -1 * thickness);
182
183 if (p.is_clockwise ()) {
184 outline.force_direction (Direction.CLOCKWISE);
185 } else {
186 outline.force_direction (Direction.COUNTER_CLOCKWISE);
187 }
188
189 if (outline.is_clockwise ()) {
190 counter.force_direction (Direction.COUNTER_CLOCKWISE);
191 } else {
192 counter.force_direction (Direction.CLOCKWISE);
193 }
194
195 outline.update_region_boundaries ();
196 paths.add (outline);
197
198 counter.update_region_boundaries ();
199 paths.add (counter);
200 } else if (p.is_open ()) { // FIXME: this can create many parts
201 outline = create_stroke (p, thickness);
202 counter = create_stroke (p, -1 * thickness);
203 merged = merge_strokes (p, outline, counter, thickness);
204
205 if (p.is_clockwise ()) {
206 merged.force_direction (Direction.CLOCKWISE);
207 } else {
208 merged.force_direction (Direction.COUNTER_CLOCKWISE);
209 }
210
211 merged.update_region_boundaries ();
212 paths.add (merged);
213 } else {
214 warning ("Can not create stroke.");
215 paths.add (p);
216 }
217
218 return paths;
219 }
220
221 /** Create one stroke from the outline and counter stroke and close the
222 * open endings.
223 *
224 * @param path the path to create stroke for
225 * @param stroke for the outline of path
226 * @param stroke for the counter path
227 */
228 static Path merge_strokes (Path path, Path stroke, Path counter, double thickness) {
229 Path merged;
230
231 counter.reverse ();
232 merged = stroke.copy ();
233
234 if (path.is_open ()) {
235 merged.delete_last_point ();
236 counter.delete_first_point ();
237 merged.delete_last_point ();
238 counter.delete_first_point ();
239 }
240
241 merged.append_path (counter);
242
243 merged.close ();
244 merged.create_list ();
245 merged.recalculate_linear_handles ();
246
247 return merged;
248 }
249
250 static Path create_stroke (Path p, double thickness) {
251 Path stroked;
252 Path path;
253
254 if (p.points.size >= 2) {
255 path = p.copy ();
256 //FIXME: DELETE path.remove_points_on_points ();
257 stroked = generate_stroke (path, thickness);
258
259 if (!p.is_open ()) {
260 stroked.reverse ();
261 stroked.close ();
262 }
263 } else {
264 // TODO: create stroke for a path with one point
265 warning ("One point.");
266 stroked = new Path ();
267 }
268
269 return stroked;
270 }
271
272 static Path generate_stroke (Path p, double thickness) {
273 Path stroked = new Path ();
274 EditPoint start = new EditPoint ();
275 EditPoint end;
276 EditPoint previous;
277 int i;
278
279 previous = p.get_first_point ().copy ();
280 move_segment (start, previous, thickness);
281
282 i = 0;
283 foreach (EditPoint ep in p.points) {
284 start = ep.copy ();
285 end = ep.get_next ().copy ();
286
287 move_segment (start, end, thickness);
288
289 if (start == p.get_last_point ()) {
290 end = p.get_first_point ();
291 }
292
293 if (!p.is_open () || (i != 0 && i != p.points.size - 1)) {
294 add_corner (stroked, previous, start, ep.copy (), thickness);
295 }
296
297 stroked.add_point (start);
298
299 if (end.get_left_handle ().length > 0) {
300 stroked.add_point (end);
301 }
302
303 // open ends around corner
304 start.get_left_handle ().convert_to_line ();
305 end.get_right_handle ().convert_to_line ();
306
307 start.flags |= EditPoint.STROKE_OFFSET;
308 end.flags |= EditPoint.STROKE_OFFSET;
309
310 previous = end;
311
312 i++;
313 }
314
315 stroked.recalculate_linear_handles ();
316
317 return stroked;
318 }
319
320 static void move_segment (EditPoint stroke_start, EditPoint stroke_stop, double thickness) {
321 EditPointHandle r, l;
322 double m, n;
323 double qx, qy;
324
325 stroke_start.set_tie_handle (false);
326 stroke_stop.set_tie_handle (false);
327
328 r = stroke_start.get_right_handle ();
329 l = stroke_stop.get_left_handle ();
330
331 m = cos (r.angle + PI / 2) * thickness;
332 n = sin (r.angle + PI / 2) * thickness;
333
334 stroke_start.get_right_handle ().move_to_coordinate_delta (m, n);
335 stroke_start.get_left_handle ().move_to_coordinate_delta (m, n);
336
337 stroke_start.independent_x += m;
338 stroke_start.independent_y += n;
339
340 qx = cos (l.angle - PI / 2) * thickness;
341 qy = sin (l.angle - PI / 2) * thickness;
342
343 stroke_stop.get_right_handle ().move_to_coordinate_delta (qx, qy);
344 stroke_stop.get_left_handle ().move_to_coordinate_delta (qx, qy);
345
346 stroke_stop.independent_x += qx;
347 stroke_stop.independent_y += qy;
348 }
349
350 static void add_corner (Path stroked, EditPoint previous, EditPoint next,
351 EditPoint original, double stroke_width) {
352
353 double ratio;
354 double distance;
355 EditPoint corner;
356 double corner_x, corner_y;
357 EditPointHandle previous_handle;
358 EditPointHandle next_handle;
359 EditPoint cutoff1, cutoff2;
360
361 previous_handle = previous.get_left_handle ();
362 next_handle = next.get_right_handle ();
363
364 previous_handle.angle += PI;
365 next_handle.angle += PI;
366
367 Path.find_intersection_handle (previous_handle, next_handle, out corner_x, out corner_y);
368 corner = new EditPoint (corner_x, corner_y, previous.type);
369 corner.convert_to_line ();
370
371 previous_handle.angle -= PI;
372 next_handle.angle -= PI;
373
374 distance = Path.distance_to_point (corner, original);
375 ratio = 1.5 * fabs (stroke_width) / distance;
376
377 if (distance < 0.1) {
378 previous.flags |= EditPoint.NEW_CORNER;
379 next.flags |= EditPoint.NEW_CORNER;
380 } else {
381 if (ratio > 1) {
382 stroked.add_point (corner);
383 } else {
384 cutoff1 = new EditPoint ();
385 cutoff1.set_point_type (previous.type);
386 cutoff1.convert_to_line ();
387
388 cutoff2 = new EditPoint ();
389 cutoff2.set_point_type (previous.type);
390 cutoff2.convert_to_line ();
391
392 cutoff1.x = previous.x + (corner.x - previous.x) * ratio;
393 cutoff1.y = previous.y + (corner.y - previous.y) * ratio;
394
395 cutoff2.x = next.x + (corner.x - next.x) * ratio;
396 cutoff2.y = next.y + (corner.y - next.y) * ratio;
397
398 cutoff1 = stroked.add_point (cutoff1);
399 cutoff2 = stroked.add_point (cutoff2);
400
401 cutoff1.recalculate_linear_handles ();
402 cutoff2.recalculate_linear_handles ();
403
404 previous.flags |= EditPoint.NEW_CORNER;
405 next.flags |= EditPoint.NEW_CORNER;
406 }
407 }
408 }
409
410 static PathList get_parts (Path path) {
411 PathList intersections;
412 PathList r;
413
414 r = get_parts_self (path);
415 intersections = new PathList ();
416
417 foreach (Path p in r.paths) {
418 intersections.add (p);
419 }
420
421 // return split_paths (intersections);
422 return intersections;
423 }
424
425 static PathList split_corners (PathList result) {
426 split_corner (result);
427 return result;
428 }
429
430 static bool split_corner (PathList pl) {
431 EditPoint p1, p2;
432 EditPoint a1, a2;
433 PathList r;
434 bool split;
435
436 foreach (Path p in pl.paths) {
437 if (p.points.size == 0) {
438 continue;
439 }
440
441 for (int index = 1; index < p.points.size + 2; index++) {
442 p1 = p.points.get ((index - 1) % p.points.size);
443 p2 = p.points.get (index % p.points.size);
444 a1 = p.points.get ((index + 3) % p.points.size); // two points ahead
445 a2 = p.points.get ((index + 4) % p.points.size);
446
447 if ((p1.flags & EditPoint.STROKE_OFFSET) > 0
448 || (p2.flags & EditPoint.STROKE_OFFSET) > 0
449 || (a1.flags & EditPoint.STROKE_OFFSET) > 0
450 || (a2.flags & EditPoint.STROKE_OFFSET) > 0) { // FIXME: safe?
451
452 split = split_segment (p, a1, a2, p1, p2, out r);
453
454 if (split) {
455 pl.append (r);
456 pl.paths.remove (p);
457 split_corner (pl);
458 return true;
459 } else {
460 p1 = p.points.get ((index - 1) % p.points.size);
461 p2 = p.points.get (index % p.points.size);
462 a1 = p.points.get ((index + 2) % p.points.size); // one point ahead
463 a2 = p.points.get ((index + 3) % p.points.size);
464
465 split = split_segment (p, a1, a2, p1, p2, out r);
466
467 if (split) {
468 pl.append (r);
469 pl.paths.remove (p);
470 split_corner (pl);
471 return true;
472 } else {
473
474 // FIXME: the special case, merge counter path with outline here
475 p1 = p.points.get ((index - 1) % p.points.size);
476 p2 = p.points.get (index % p.points.size);
477 a1 = p.points.get ((index + 3) % p.points.size); // two points ahead
478 a2 = p.points.get ((index + 4) % p.points.size);
479
480 if ((p1.flags & EditPoint.STROKE_OFFSET) > 0
481 && (a1.flags & EditPoint.STROKE_OFFSET) > 0) {
482 p1.flags = EditPoint.COUNTER_TO_OUTLINE;
483 a1.flags = EditPoint.COUNTER_TO_OUTLINE;
484
485 p1.counter_to_outline = true;
486 a1.counter_to_outline = true;
487 }
488 }
489 }
490 }
491 }
492 }
493
494 return false;
495 }
496
497 static bool split_segment (Path p, EditPoint first, EditPoint next, EditPoint p1, EditPoint p2, out PathList result) {
498 double ix, iy;
499 bool intersection;
500 int i;
501
502 result = new PathList ();
503 intersection = segments_intersects (first, next, p1, p2, out ix, out iy, true);
504
505 if (intersection) {
506 add_intersection (p, first, next, ix, iy);
507 add_intersection (p, p1, p2, ix, iy);
508
509 i = mark_intersection_as_deleted (p);
510 return_val_if_fail (i == 2, false);
511
512 result.append (get_remaining_points (p.copy ()));
513 }
514
515 return intersection;
516 }
517
518 static bool split_path (Path path1, Path path2, PathList result) {
519 PathList pl1, pl2;
520 Path a1, a2, b1, b2;
521 Path m1, m2;
522 int i;
523
524 if (path1 == path2) {
525 return false;
526 }
527
528 if (add_intersection_points (path1, path2, 2)) {
529 i = mark_intersection_as_deleted (path1);
530 return_if_fail (i == 2);
531
532 i = mark_intersection_as_deleted (path2);
533 return_if_fail (i == 2);
534
535 pl1 = get_remaining_points (path1.copy ());
536 pl2 = get_remaining_points (path2.copy ());
537
538 return_if_fail (pl1.paths.size == 2);
539 return_if_fail (pl2.paths.size == 2);
540
541 a1 = pl1.paths.get (0);
542 a2 = pl1.paths.get (1);
543 b1 = pl2.paths.get (0);
544 b2 = pl2.paths.get (1);
545
546 m1 = PenTool.merge_open_paths (a1, b2);
547 m2 = PenTool.merge_open_paths (b1, a2);
548
549 result.add (m1);
550 result.add (m2);
551
552 return true;
553 }
554
555 return false;
556 }
557
558 static PathList split_paths (PathList pl) {
559 PathList n = new PathList ();
560
561 n.append (pl);
562
563 foreach (Path p1 in pl.paths) {
564 foreach (Path p2 in pl.paths) {
565 if (p1 != p2) {
566 if (split_path (p1, p2, n)) {
567 n.paths.remove (p1);
568 n.paths.remove (p2);
569 return split_paths (n);
570 }
571 }
572 }
573 }
574
575 return n;
576 }
577
578 static PathList get_parts_self (Path path, PathList? paths = null) {
579 PathList pl;
580 PathList r;
581
582 r = paths == null ? new PathList () : (!) paths;
583 pl = split (path);
584
585 foreach (Path part in pl.paths) {
586 if (!has_self_intersection (part)) {
587 r.add (part);
588 } else {
589 get_parts_self (part, r);
590 }
591 }
592
593 if (r.paths.size == 0) {
594 warning ("No parts in path");
595 }
596
597 return r;
598 }
599
600 static bool has_intersection_points (Path path) {
601 foreach (EditPoint p in path.points) {
602 if ((p.flags & EditPoint.INTERSECTION) > 0) {
603 return true;
604 }
605 }
606 return false;
607 }
608
609 /** Split one path at intersection points in two parts. */
610 static PathList split (Path path) {
611 PathList pl;
612 int i;
613
614 if (!has_intersection_points (path)) {
615 add_self_intersection_points (path);
616 } else {
617 warning ("points already created.");
618 }
619
620 i = mark_intersection_as_deleted (path);
621
622 if (!(i == 0 || i == 2)) {
623 warning (@"Split should only create two parts, $i points will be deleted.");
624 }
625
626 pl = get_remaining_points (path.copy ());
627
628 return pl;
629 }
630
631 static PathList process_deleted_control_points (Path path) {
632 PathList paths, nl, pl, rl;
633
634 paths = new PathList ();
635 rl = new PathList ();
636 pl = new PathList ();
637 nl = new PathList ();
638
639 if (!path.has_deleted_point ()) {
640 return pl;
641 }
642
643 pl.add (path);
644
645 foreach (Path p in pl.paths) {
646 nl = p.process_deleted_points ();
647
648 if (nl.paths.size > 0) {
649 rl.append (nl);
650 rl.paths.remove (p);
651 }
652 }
653
654 foreach (Path p in rl.paths) {
655 pl = process_deleted_control_points (p);
656
657 if (pl.paths.size > 0) {
658 paths.append (pl);
659 } else {
660 paths.add (p);
661 }
662 }
663
664 return paths;
665 }
666
667 static PathList get_remaining_points (Path old_path) {
668 PathList pl;
669
670 old_path.close ();
671 pl = process_deleted_control_points (old_path);
672
673 if (pl.paths.size == 0) {
674 pl.add (old_path);
675 }
676
677 foreach (Path pn in pl.paths) {
678 pn.close ();
679 }
680
681 return pl;
682 }
683
684 static bool has_self_intersection (Path path) {
685 bool intersection = false;
686
687 path.all_segments ((ep1, ep2) => {
688 double ix, iy;
689 EditPoint p1, p2;
690
691 if (segment_intersects (path, ep1, ep2, out ix, out iy, out p1, out p2)) {
692 intersection = true;
693 return false;
694 }
695
696 return true;
697 });
698
699 return intersection;
700 }
701
702 static bool add_self_intersection_points (Path path, bool only_offsets = false) {
703 bool intersection = false;
704
705 path.all_segments ((ep1, ep2) => {
706 double ix, iy;
707 EditPoint p1, p2;
708
709 if (segment_intersects (path, ep1, ep2, out ix, out iy, out p1, out p2, true, only_offsets)) {
710 add_intersection (path, ep1, ep2, ix, iy);
711 add_intersection (path, p1, p2, ix, iy);
712
713 intersection = true;
714 return false;
715 }
716
717 return true;
718 });
719
720 if (intersection) {
721 // FIXME: path.create_list ();
722 }
723
724 return intersection;
725 }
726
727 static EditPoint add_intersection (Path path, EditPoint prev, EditPoint next, double px, double py, Color? c = null) {
728 Gee.ArrayList<EditPoint> n = new Gee.ArrayList<EditPoint> ();
729 EditPoint ep1 = new EditPoint ();
730 EditPoint ep2 = new EditPoint ();
731 EditPoint ep3 = new EditPoint ();
732
733 if (next == path.get_first_point ()) { // FIXME: double check
734 ep1.prev = null;
735 } else {
736 ep1.prev = prev;
737 }
738
739 ep1.prev = prev;
740 ep1.next = ep2;
741 ep1.flags |= EditPoint.NEW_CORNER;
742 ep1.type = PointType.LINE_CUBIC;
743 ep1.x = px;
744 ep1.y = py;
745 ep1.color = c;
746 n.add (ep1);
747
748 ep2.prev = ep1;
749 ep2.next = ep3;
750 ep2.flags |= EditPoint.INTERSECTION;
751 ep2.type = PointType.LINE_QUADRATIC;
752 ep2.x = px;
753 ep2.y = py;
754 ep2.color = c;
755 n.add (ep2);
756
757 ep3.prev = ep2;
758 ep3.next = next;
759 ep3.flags |= EditPoint.NEW_CORNER;
760 ep3.type = PointType.LINE_CUBIC;
761 ep3.x = px;
762 ep3.y = py;
763 ep3.color = c;
764 n.add (ep3);
765
766 foreach (EditPoint np in n) {
767 np = path.add_point_after (np, np.prev);
768 path.create_list ();
769 }
770
771 PenTool.convert_point_to_line (ep1, true);
772 PenTool.convert_point_to_line (ep2, true);
773 PenTool.convert_point_to_line (ep3, true);
774
775 ep1.recalculate_linear_handles ();
776 ep2.recalculate_linear_handles ();
777 ep3.recalculate_linear_handles ();
778
779 return ep2;
780 }
781
782 static bool segments_intersects (EditPoint p1, EditPoint p2, EditPoint ep, EditPoint next,
783 out double ix, out double iy,
784 bool skip_points_on_points = false) {
785 double cross_x, cross_y;
786
787 ix = 0;
788 iy = 0;
789
790 Path.find_intersection_point (ep, next, p1, p2, out cross_x, out cross_y);
791
792 if (Glyph.CANVAS_MIN < cross_x < Glyph.CANVAS_MAX
793 && Glyph.CANVAS_MIN < cross_y < Glyph.CANVAS_MAX) {
794 // iterate to find intersection.
795
796 if (skip_points_on_points ||
797 !((ep.x == cross_x && ep.y == cross_y)
798 || (next.x == cross_x && next.y == cross_y)
799 || (p1.x == cross_x && p1.y == cross_y)
800 || (p2.x == cross_x && p2.y == cross_y))) {
801
802 if (is_line (ep.x, ep.y, cross_x, cross_y, next.x, next.y)
803 && is_line (p1.x, p1.y, cross_x, cross_y, p2.x, p2.y)) {
804
805 ix = cross_x;
806 iy = cross_y;
807
808 return true;
809 }
810 }
811 }
812
813 return false;
814 }
815
816 static bool segment_intersects (Path path, EditPoint ep, EditPoint next,
817 out double ix, out double iy,
818 out EditPoint ia, out EditPoint ib,
819 bool skip_points_on_points = false,
820 bool only_offsets = false) {
821
822 EditPoint p1, p2;
823 bool intersection, inter;
824 double iix, iiy;
825
826 double distance, min_distance;
827
828 intersection = false;
829
830 ix = 0;
831 iy = 0;
832
833 iix = 0;
834 iiy = 0;
835
836 ia = new EditPoint ();
837 ib = new EditPoint ();
838
839 if (path.points.size == 0) {
840 return false;
841 }
842
843 min_distance = double.MAX;
844 p1 = path.points.get (path.points.size - 1);
845 for (int i = 0; i < path.points.size; i++) {
846 p2 = path.points.get (i);
847
848 bool is_offset = ((p1.flags & EditPoint.STROKE_OFFSET) > 0)
849 && ((p2.flags & EditPoint.STROKE_OFFSET) > 0)
850 && ((ep.flags & EditPoint.STROKE_OFFSET) > 0)
851 && ((next.flags & EditPoint.STROKE_OFFSET) > 0);
852
853 if (!only_offsets || is_offset) {
854 if (p1 != ep && p2 != next) {
855 inter = segments_intersects (p1, p2, ep, next, out iix, out iiy,
856 skip_points_on_points);
857
858 if (inter) {
859 distance = Path.distance (ep.x, iix, ep.y, iiy);
860 if (distance < min_distance) {
861 ia = p1;
862 ib = p2;
863 ix = iix;
864 iy = iiy;
865 intersection = true;
866 min_distance = distance;
867 }
868 }
869 }
870 }
871
872 p1 = p2;
873 }
874
875 return intersection;
876 }
877
878 static bool same (EditPoint a, EditPoint b) {
879 return a.x == b.x && a.y == b.y;
880 }
881
882 /** @return true if p2 is on the line p1 to p3 */
883 static bool is_line (double x1, double y1, double x2, double y2, double x3, double y3) {
884 double ds = Path.distance (x1, x3, y1, y3);
885 double d1 = Path.distance (x1, x2, y1, y2);
886 double d2 = Path.distance (x2, x3, y2, y3);
887 double p = d1 / ds;
888 double x = fabs ((x3 - x1) * p - (x2 - x1));
889 double y = fabs ((y3 - y1) * p - (y2 - y1));
890 double d = fabs (ds - (d1 + d2));
891
892 // FIXME: delete print (@"$(fmin (x1, x3)) < $x2 && $x2 < $(fmax (x1, x3))\n");
893 // FIXME: delete print (@"$(fmin (y1, y3)) < $y2 && $y2 < $(fmax (y1, y3))\n");
894
895 return ds > 0.01 && d1 > 0.01 && d2 > 0.01
896 && d < 0.01 && x < 0.01 && y < 0.01
897 && fmin (x1, x3) <= x2 && x2 <= fmax (x1, x3)
898 && fmin (y1, y3) <= y2 && y2 <= fmax (y1, y3);
899 }
900
901 static Path get_outline (Path path) {
902 PathList pl = get_parts (path);
903 Path outline = new Path ();
904 int inside;
905 int min_inside = int.MAX;
906 int points = 0;
907 int i = 0;
908
909 foreach (Path p in pl.paths) {
910 inside = Path.counters (pl, p);
911
912 if (inside < min_inside) {
913 outline = p;
914 min_inside = inside;
915 }
916
917 i++;
918 }
919
920 if (min_inside == 0) {
921 foreach (Path p in pl.paths) {
922 if (p.points.size > points) {
923 outline = p;
924 points = p.points.size;
925 }
926 }
927 }
928
929 return outline;
930 }
931
932 static void remove_points_in_stroke (PathList pl) {
933 PathList result;
934 PathList r;
935 PathList parts;
936
937 foreach (Path p in pl.paths) {
938 if (remove_points_in_stroke_for_path (p, pl, out r)) {
939 pl.append (r);
940 remove_points_in_stroke (pl);
941 return;
942 }
943 }
944 }
945
946 static bool remove_points_in_stroke_for_path (Path p, PathList pl, out PathList result) {
947 bool remove = false;
948 EditPoint start_ep;
949 EditPoint start_next;
950 EditPoint start_prev;
951 EditPoint end_ep = new EditPoint ();
952 EditPoint end_next;
953 EditPoint end_prev;
954 Path path2;
955 EditPoint found = new EditPoint ();
956
957 result = new PathList ();
958
959 for (int i = 1; i < p.points.size + 1; i++) {
960 start_prev = p.points.get ((i - 1) % p.points.size);
961 start_ep = p.points.get (i % p.points.size);
962 start_next = p.points.get ((i + 1) % p.points.size);
963
964 if ((start_ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) {
965 for (int j = i; j < p.points.size + i; j++) {
966 end_prev = p.points.get ((j - 1) % p.points.size);
967 end_ep = p.points.get (j % p.points.size);
968 end_next = p.points.get ((j + 1) % p.points.size);
969
970 // FIXME: if (!is_inside_of_path
971
972 if ((end_ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) {
973 start_ep.flags = EditPoint.NONE;
974 end_ep.flags = EditPoint.NONE;
975
976 if (merge_segments (pl, p, start_prev, start_ep, end_ep, end_next, out result)) {
977 return true;
978 }
979 }
980 }
981 }
982
983 start_ep.flags = EditPoint.NONE;
984 end_ep.flags = EditPoint.NONE;
985 }
986
987 return false;
988 }
989
990 static bool merge_segments (PathList pl,
991 Path path1, EditPoint start1, EditPoint stop1,
992 EditPoint start2, EditPoint stop2,
993 out PathList result) {
994
995 result = new PathList ();
996
997 PathList r1;
998 PathList r2;
999
1000 foreach (Path path2 in pl.paths) {
1001 if (path2 != path1) {
1002 reset_intersections (path1);
1003 reset_intersections (path2);
1004
1005 if (add_merge_intersection_point (path1, path2, start1, stop1)) {
1006 if (add_merge_intersection_point (path1, path2, start2, stop2)) {
1007
1008 r1 = get_remaining_points (path1.copy ());
1009 r2 = get_remaining_points (path2.copy ());
1010
1011 if (r1.paths.size != 2) {
1012 warning (@"Expecting two paths in r1 found $(r1.paths.size)\n");
1013 reset_intersections (path1);
1014 reset_intersections (path2);
1015 return true;
1016 }
1017
1018 if (r2.paths.size != 2) {
1019 warning (@"Expecting two paths in r2 found $(r2.paths.size)\n");
1020 reset_intersections (path1);
1021 reset_intersections (path2);
1022 return true;
1023 }
1024
1025 pl.paths.remove (path1);
1026 pl.paths.remove (path2);
1027
1028 // FIXME: find criteria
1029
1030 double d1 = Path.point_distance (r1.paths.get (0).get_first_point (),
1031 r2.paths.get (0).get_first_point ());
1032
1033 double d2 = Path.point_distance (r1.paths.get (0).get_first_point (),
1034 r2.paths.get (1).get_first_point ());
1035
1036 Path m1, m2;
1037
1038 if (d1 > d2) {
1039 m1 = PenTool.merge_open_paths (r1.paths.get (0), r2.paths.get (0));
1040 m2 = PenTool.merge_open_paths (r1.paths.get (1), r2.paths.get (1));
1041 } else {
1042 m1 = PenTool.merge_open_paths (r1.paths.get (1), r2.paths.get (0));
1043 m2 = PenTool.merge_open_paths (r1.paths.get (0), r2.paths.get (1));
1044 }
1045
1046 result.add (m1);
1047 result.add (m2);
1048
1049 return true;
1050 } else {
1051 reset_intersections (path1);
1052 reset_intersections (path2);
1053 }
1054 } else {
1055 reset_intersections (path1);
1056 reset_intersections (path2);
1057 }
1058 }
1059 }
1060
1061 return false;
1062 }
1063
1064 static void reset_intersections (Path p) {
1065 foreach (EditPoint ep in p.points) {
1066 ep.flags &= uint.MAX ^ EditPoint.INTERSECTION;
1067 ep.flags &= uint.MAX ^ EditPoint.COPIED;
1068 ep.deleted = false;
1069 }
1070 p.remove_points_on_points ();
1071 }
1072
1073 static bool has_counter_to_outline (Path p) {
1074 foreach (EditPoint ep in p.points) {
1075 if ((ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) {
1076 return true;
1077 }
1078 }
1079
1080 return false;
1081 }
1082
1083 static bool add_merge_intersection_point (Path path1, Path path2, EditPoint first, EditPoint next) {
1084 double ix, iy;
1085 bool intersection;
1086
1087 intersection = false;
1088 ix = 0;
1089 iy = 0;
1090 path2.all_segments ((p1, p2) => {
1091 int i;
1092
1093 intersection = segments_intersects (first, next, p1, p2, out ix, out iy);
1094
1095 if (intersection) {
1096 add_intersection (path1, first, next, ix, iy);
1097 add_intersection (path2, p1, p2, ix, iy);
1098
1099 i = mark_intersection_as_deleted (path1);
1100 i = mark_intersection_as_deleted (path2);
1101 }
1102
1103 return !intersection;
1104 });
1105
1106 return intersection;
1107 }
1108
1109 static bool is_inside_of_path (PointSelection ps, PathList pl, out Path outline) {
1110 outline = new Path ();
1111 foreach (Path p in pl.paths) {
1112 if (p != ps.path) {
1113 if (SvgParser.is_inside (ps.point, p)) {
1114 outline = p;
1115 return true;
1116 }
1117 }
1118 }
1119
1120 return false;
1121 }
1122
1123 static PathList merge (PathList pl) {
1124 PathList r;
1125 bool error;
1126 remove_points_in_stroke (pl);
1127 r = merge_paths (pl, out error);
1128
1129 if (!error) {
1130 remove_merged_parts (r);
1131 }
1132
1133 return r;
1134 }
1135
1136 static void remove_merged_parts (PathList r) {
1137 Gee.ArrayList<Path> remove = new Gee.ArrayList<Path> ();
1138 foreach (Path p in r.paths) {
1139
1140 if (Path.is_counter (r, p)) {
1141 if (p.is_clockwise ()) {
1142 remove.add (p);
1143 }
1144 } else {
1145 if (!p.is_clockwise ()) {
1146 remove.add (p);
1147 }
1148 }
1149
1150 if (stroke_selected) { // FIXME: DELETE
1151 ((!) BirdFont.get_current_font ().get_glyph ("a")).add_path (p);
1152 }
1153 }
1154
1155 foreach (Path p in remove) {
1156 r.paths.remove (p);
1157 }
1158 }
1159
1160 static PathList merge_paths (PathList pl, out bool error) {
1161 PathList result, parts;
1162 Path p2;
1163 bool e;
1164
1165 error = false;
1166 parts = pl;
1167
1168 try {
1169 foreach (Path p1 in parts.paths) {
1170 if (merge_next (p1, pl, out p2, out result, out e)) {
1171 pl.paths.remove (p1);
1172 pl.paths.remove (p2);
1173 pl.append (result);
1174 return merge_paths (pl, out e);
1175 }
1176
1177 if (e) {
1178 error = true;
1179 return pl;
1180 }
1181 }
1182 } catch (GLib.Error e) {
1183 warning (e.message);
1184 return pl;
1185 }
1186
1187 return pl;
1188 }
1189
1190 static bool merge_next (Path p1, PathList pl, out Path path2, out PathList result, out bool error) {
1191 PathList m;
1192
1193 result = new PathList ();
1194 path2 = new Path ();
1195
1196 error = false;
1197 foreach (Path p2 in pl.paths) {
1198 if (p1 != p2) {
1199 if (equals (p1, p2)) { // FIXME: DELETE
1200 warning ("Same path.");
1201 continue;
1202 }
1203
1204 if (p1.points.size == 0 || p2.points.size == 0) {
1205 warning ("No points.");
1206 continue;
1207 }
1208
1209 if (merge_path (p1, p2, out m, out error)) {
1210 foreach (Path np in m.paths) {
1211 if (np.points.size == 0) {
1212 warning (@"No points after merge, $(m.paths.size) paths.");
1213
1214 if (stroke_selected) { // FIXME: DELETE
1215 ((!) BirdFont.get_current_font ().get_glyph ("d")).add_path (p2);
1216 ((!) BirdFont.get_current_font ().get_glyph ("d")).add_path (p1);
1217 }
1218
1219 error = true;
1220 return false;
1221 }
1222
1223 result.add (np);
1224 }
1225
1226 print ("rec\n");
1227 path2 = p2;
1228 return true;
1229 }
1230 }
1231 }
1232
1233 return false;
1234 }
1235
1236 public static bool equals (Path p1, Path p2) {
1237 EditPoint ep1, ep2;
1238
1239 if (p1.points.size != p2.points.size) {
1240 return false;
1241 }
1242
1243 for (int i = 0; i < p1.points.size; i++) {
1244 ep1 = p1.points.get (i);
1245 ep2 = p2.points.get (i);
1246
1247 if (ep1.x != ep2.x || ep1.y != ep2.y) {
1248 return false;
1249 }
1250 }
1251
1252 return true;
1253 }
1254
1255 public class Intersection : GLib.Object {
1256 public bool done = false;
1257 public EditPoint point;
1258 public EditPoint other_point;
1259 public Path path;
1260 public Path other_path;
1261
1262 public Intersection (EditPoint point, Path path,
1263 EditPoint other_point, Path other_path) {
1264
1265 this.point = point;
1266 this.path = path;
1267 this.other_point = other_point;
1268 this.other_path = other_path;
1269 }
1270
1271 public Intersection.empty () {
1272 this.point = new EditPoint ();
1273 this.path = new Path ();
1274 this.other_point = new EditPoint ();
1275 this.other_path = new Path ();
1276 }
1277
1278 public Path get_other_path (Path p) {
1279 if (p == path) {
1280 return other_path;
1281 }
1282
1283 if (p == other_path) {
1284 return path;
1285 }
1286
1287 warning ("Wrong intersection.");
1288 return new Path ();
1289 }
1290
1291 public EditPoint get_point (Path p) {
1292 if (p == path) {
1293 return point;
1294 }
1295
1296 if (p == other_path) {
1297 return other_point;
1298 }
1299
1300 warning ("Wrong intersection.");
1301 return new EditPoint ();
1302 }
1303 }
1304
1305 public class IntersectionList : GLib.Object {
1306 public Gee.ArrayList<Intersection> points = new Gee.ArrayList<Intersection> ();
1307
1308 public IntersectionList () {
1309 }
1310
1311 public Intersection get_point (EditPoint ep, out bool other) {
1312 other = false;
1313 foreach (Intersection i in points) {
1314 if (i.other_point == ep || i.point == ep) {
1315 other = (i.other_point == ep);
1316 return i;
1317 }
1318 }
1319
1320 warning ("No intersection found for point.");
1321 return new Intersection.empty ();
1322 }
1323 }
1324
1325 static int z = 0;
1326
1327 static bool merge_path (Path path1, Path path2, out PathList merged_paths, out bool error) {
1328 IntersectionList intersections;
1329 EditPoint ep1, next, p1, p2, pp1, pp2;
1330 Path path, other, merged;
1331 PathList pl1, pl2, r, other_paths, result;
1332 bool intersects;
1333 int s, i;
1334 double ix, iy, iix, iiy;
1335 bool merge = false;
1336 EditPoint intersection_point, other_intersection_point;
1337 Intersection intersection;
1338
1339 error = false;
1340 merged_paths = new PathList ();
1341 intersections = new IntersectionList ();
1342
1343 iix = 0;
1344 iiy = 0;
1345
1346 result = new PathList ();
1347
1348 if (path1.points.size == 0 || path2.points.size == 0) {
1349 warning ("No points in path.");
1350 error = true;
1351 return false;
1352 }
1353
1354 if (!path1.boundaries_intersecting (path2)) {
1355 error = true;
1356 return false;
1357 }
1358
1359 Path original_path1 = path1.copy ();
1360 Path original_path2 = path2.copy ();
1361
1362 s = 0;
1363 foreach (EditPoint e in original_path1.points) {
1364 if (!SvgParser.is_inside (e, original_path2)) {
1365 break;
1366 }
1367 s++;
1368 }
1369
1370 if (s >= path1.points.size) {
1371 Path t;
1372 t = original_path1;
1373 original_path1 = original_path2;
1374 original_path2 = t;
1375 s = 0;
1376 foreach (EditPoint e in original_path1.points) {
1377 if (!SvgParser.is_inside (e, original_path2)) {
1378 break;
1379 }
1380 s++;
1381 }
1382 }
1383
1384 if (s >= original_path1.points.size) {
1385 warning ("No start point found.");
1386
1387 if (stroke_selected && z == 0) { // FIXME: DELETE
1388 ((!) BirdFont.get_current_font ().get_glyph ("b")).add_path (path1);
1389 ((!) BirdFont.get_current_font ().get_glyph ("b")).add_path (path2);
1390 z++;
1391 }
1392
1393 error = true;
1394 return false;
1395 }
1396
1397 path = original_path1;
1398 other = original_path2;
1399
1400 other_paths = new PathList ();
1401 r = new PathList ();
1402 other_paths.add (path);
1403 other_paths.add (other);
1404 intersects = false;
1405 p1 = new EditPoint ();
1406 p2 = new EditPoint ();
1407 pp1 = new EditPoint ();
1408 pp2 = new EditPoint ();
1409
1410 ix = 0;
1411 iy = 0;
1412 i = s;
1413 merged = new Path ();
1414
1415 while (true) {
1416 ep1 = path.points.get (i % path.points.size);
1417 next = path.points.get ((i + 1) % path.points.size);
1418
1419 if ((ep1.flags & EditPoint.COPIED) > 0) {
1420 if (merge) {
1421 merged.close ();
1422 result.add (merged.copy ());
1423 }
1424
1425 merged = new Path ();
1426
1427 bool find_parts = false;
1428 Intersection new_start = new Intersection.empty ();
1429 foreach (Intersection inter in intersections.points) {
1430 if (!inter.done && !find_parts) {
1431 find_parts = true;
1432 new_start = inter;
1433 break;
1434 }
1435 }
1436
1437 if (!find_parts) {
1438 break; // done, no more parts
1439 } else {
1440 // set start point for next part
1441 path = new_start.other_path;
1442
1443 if (path.points.size == 0) {
1444 warning ("No points.");
1445 error = true;
1446 return false;
1447 }
1448
1449 i = index_of (path, new_start.get_point (path));
1450
1451 if (i < 0) {
1452 warning ("Start point not found.");
1453 error = true;
1454 return false;
1455 }
1456
1457 ep1 = path.points.get (i % path.points.size);
1458 next = path.points.get ((i + 1) % path.points.size);
1459
1460 if ((ep1.flags & EditPoint.INTERSECTION) == 0) {
1461 warning ("Not starting on an intersection point.");
1462 error = true;
1463 return false;
1464 }
1465
1466 new_start.done = true;
1467 }
1468 }
1469
1470 intersects = false;
1471
1472 double dm;
1473 double d;
1474
1475 if ((ep1.flags & EditPoint.INTERSECTION) > 0) {
1476 Intersection current_intersection;
1477 bool continue_on_other_path;
1478 current_intersection = intersections.get_point (ep1, out continue_on_other_path);
1479 current_intersection.done = true;
1480
1481 // take the other part of an intersection
1482 if ((next.flags & EditPoint.COPIED) != 0) {
1483 bool next_is_intersection = false;
1484 bool next_continue_on_other_path;
1485
1486 next_is_intersection = ((next.flags & EditPoint.INTERSECTION) > 0);
1487
1488 if (next_is_intersection) {
1489 Intersection next_intersection = intersections.get_point (next, out next_continue_on_other_path);
1490
1491 ep1.flags |= EditPoint.COPIED;
1492 merged.add_point (ep1.copy ());
1493
1494 if (!next_intersection.done) {
1495 EditPoint new_start_point;
1496
1497 path = next_continue_on_other_path
1498 ? next_intersection.other_path : next_intersection.path;
1499
1500 new_start_point = next_continue_on_other_path
1501 ? next_intersection.other_point : next_intersection.point;
1502
1503 i = index_of (path, new_start_point);
1504
1505 if (i < 0) {
1506 warning ("Point not found in path.\n");
1507 error = true;
1508 return false;
1509 }
1510
1511 ep1 = path.points.get (i % path.points.size);
1512 next = path.points.get ((i + 1) % path.points.size);
1513 } else {
1514 warning ("Part is already created.\n");
1515 error = true;
1516 return false;
1517 }
1518 } else {
1519 ep1.flags |= EditPoint.COPIED;
1520 merged.add_point (ep1.copy ());
1521
1522 EditPoint new_start_point;
1523
1524 if ((next.flags & EditPoint.COPIED) > 0) {
1525 path = current_intersection.get_other_path (path);
1526 new_start_point = current_intersection.get_point (path);
1527 i = index_of (path, new_start_point);
1528
1529 if (i < 0) {
1530 warning ("Point not found in path.");
1531 error = true;
1532 return false;
1533 }
1534
1535 ep1 = path.points.get (i % path.points.size);
1536 next = path.points.get ((i + 1) % path.points.size);
1537
1538 if ((next.flags & EditPoint.INTERSECTION) > 0) {
1539 warning ("Wrong type.");
1540 error = true;
1541 return false;
1542 }
1543
1544 if ((next.flags & EditPoint.COPIED) > 0) {
1545 merged.add_point (ep1.copy ());
1546 continue;
1547 }
1548 } else {
1549 ep1.flags |= EditPoint.COPIED;
1550 merged.add_point (ep1.copy ());
1551 }
1552 }
1553 } else {
1554 ep1.flags |= EditPoint.COPIED;
1555 merged.add_point (ep1.copy ());
1556 }
1557
1558 current_intersection.done = true;
1559 } else {
1560 // find new intersection
1561 dm = double.MAX;
1562 foreach (Path o in other_paths.paths) {
1563 bool inter = segment_intersects (o, ep1, next, out iix, out iiy,
1564 out pp1, out pp2, true);
1565 d = Path.distance (ep1.x, iix, ep1.y, iiy);
1566 if (d < dm && inter) {
1567 other = o;
1568 dm = d;
1569 intersects = true;
1570 p1 = pp1;
1571 p2 = pp2;
1572 ix = iix;
1573 iy = iiy;
1574 }
1575 }
1576
1577 if (intersects) {
1578 merged.add_point (ep1.copy ());
1579 ep1.flags |= EditPoint.COPIED;
1580
1581 intersection_point = add_intersection (path, ep1, next, ix, iy);
1582 other_intersection_point = add_intersection (other, p1, p2, ix, iy);
1583
1584 bool g = false;
1585 foreach (Intersection old_intersection in intersections.points) {
1586 if (old_intersection.point == intersection_point
1587 || old_intersection.other_point == other_intersection_point) {
1588 old_intersection.done = true;
1589 g = true;
1590 }
1591 }
1592
1593 if (!g) {
1594 Intersection ip = new Intersection (intersection_point, path, other_intersection_point, other);
1595 intersections.points.add (ip);
1596 }
1597
1598 merged.add_point (intersection_point.copy ());
1599 intersection_point.flags |= EditPoint.COPIED;
1600
1601 i = index_of (other, other_intersection_point);
1602
1603 if (i < 0) {
1604 warning (@"Point not found ($i).");
1605 break;
1606 }
1607
1608 path = other;
1609 merge = true;
1610 } else {
1611 ep1.flags |= EditPoint.COPIED;
1612 merged.add_point (ep1.copy ());
1613 }
1614 }
1615
1616 i++;
1617 }
1618
1619 if (merge) {
1620
1621 // FIXME: double check
1622 original_path1.remove_points_on_points ();
1623 original_path2.remove_points_on_points ();
1624
1625 // FIXME: delete
1626 foreach (EditPoint ep in original_path1.points) {
1627 if ((ep.flags & EditPoint.COPIED) == 0) {
1628 print ("Points left in original_path1.\n");
1629 print (@"Result size: $(result.paths.size)\n");
1630 //result.add (original_path1);
1631 /*
1632 ep.color = new Color (1,0,0,0.5);
1633 if (stroke_selected) { // FIXME: DELETE
1634 if (((!) BirdFont.get_current_font ().get_glyph ("c")).path_list.size < 2) {
1635 ((!) BirdFont.get_current_font ().get_glyph ("c")).add_path (original_path1);
1636 ((!) BirdFont.get_current_font ().get_glyph ("c")).add_path (original_path2);
1637 }
1638 }
1639 */
1640 result.add (original_path1);
1641 error = true;
1642 break;
1643 }
1644 }
1645
1646 foreach (EditPoint ep in original_path2.points) {
1647 if ((ep.flags & EditPoint.COPIED) == 0) {
1648 print ("Points left original_path2.\n");
1649 print (@"Result size: $(result.paths.size)\n");
1650 result.add (original_path2);
1651 error = true;
1652 break;
1653 }
1654 }
1655
1656 int counter;
1657 foreach (Path p in result.paths) {
1658 p.remove_points_on_points ();
1659 counter = Path.counters (result, p);
1660 if (counter == 0) {
1661 merged.force_direction (Direction.CLOCKWISE);
1662 } else {
1663 if (original_path1.is_clockwise () != original_path2.is_clockwise ()) {
1664 merged.force_direction (Direction.COUNTER_CLOCKWISE);
1665 } else {
1666 merged.force_direction (Direction.CLOCKWISE);
1667 }
1668 }
1669
1670 p.close ();
1671 reset_intersections (p);
1672 merged_paths.add (p);
1673 p.update_region_boundaries ();
1674 p.recalculate_linear_handles ();
1675 }
1676 }
1677
1678 return merge && !error;
1679 }
1680
1681 static int index_of (Path p, EditPoint ep) {
1682 int i = 0;
1683 foreach (EditPoint e in p.points) {
1684 if (e == ep) {
1685 return i;
1686 }
1687 i++;
1688 }
1689
1690 return -1;
1691 }
1692
1693 static Path get_next_part (PathList pl, EditPoint ep) {
1694 double d, m;
1695 Path r;
1696
1697 r = new Path ();
1698 m = double.MAX;
1699 foreach (Path p in pl.paths) {
1700 d = Path.distance_to_point (p.get_last_point (), ep);
1701 if (d < m) {
1702 m = d;
1703 r = p;
1704 }
1705 }
1706
1707 return r;
1708 }
1709
1710 public static int counters_in_point_in_path (Path p, EditPoint ep) {
1711 int inside_count = 0;
1712 bool inside;
1713
1714 if (p.points.size > 1) {
1715 inside = true;
1716
1717 if (!SvgParser.is_inside (ep, p)) {
1718 inside = false;
1719 }
1720
1721 if (inside) {
1722 inside_count++;
1723 }
1724 }
1725
1726 return inside_count;
1727 }
1728
1729 static int mark_intersection_as_deleted (Path path) {
1730 int i = 0;
1731
1732 foreach (EditPoint p in path.points) {
1733
1734 if ((p.flags & EditPoint.INTERSECTION) > 0) {
1735 p.deleted = true;
1736 i++;
1737 }
1738 }
1739
1740 return i;
1741 }
1742
1743 /** @param n number of interrsections to find per path. */
1744 static bool add_intersection_points (Path path1, Path path2, int n = 1) {
1745 bool intersection = false;
1746 int found = 0;
1747
1748 path1.all_segments ((ep1, ep2) => {
1749 double ix, iy;
1750 EditPoint p1, p2;
1751 bool i;
1752
1753 i = segment_intersects (path2, ep1, ep2, out ix, out iy,
1754 out p1, out p2, true);
1755
1756 if (i) {
1757 add_intersection (path1, ep1, ep2, ix, iy);
1758 add_intersection (path2, p1, p2, ix, iy);
1759 intersection = true;
1760 found++;
1761 return found < n;
1762 }
1763
1764 return true;
1765 });
1766
1767 return intersection;
1768 }
1769
1770 }
1771
1772 }
1773