The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

StrokeTool.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

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

Revisions

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