The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

StrokeTool.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of libbirdfont/StrokeTool.vala.
Merge strokes
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