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