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.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