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