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