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