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.
Remove overlapping regions in merge
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 enum LineCap { 21 BUTT, 22 SQUARE, 23 ROUND 24 } 25 26 public class StrokeTool : Tool { 27 28 public static double stroke_width = 0; 29 public static bool add_stroke = false; 30 31 public static bool show_stroke_tools = false; 32 public static bool convert_stroke = false; 33 34 public static LineCap line_cap = LineCap.BUTT; 35 36 public StrokeTool (string tooltip) { 37 } 38 39 /** Create strokes for the selected outlines. */ 40 public static void stroke_selected_paths () { 41 Glyph g = MainWindow.get_current_glyph (); 42 PathList paths = new PathList (); 43 44 convert_stroke = true; 45 g.store_undo_state (); 46 47 foreach (Path p in g.active_paths) { 48 if (p.stroke > 0) { 49 paths.append (p.get_stroke ()); 50 } 51 } 52 53 if (paths.paths.size > 0) { 54 foreach (Path p in g.active_paths) { 55 g.layers.remove_path (p); 56 } 57 58 g.active_paths.clear (); 59 60 foreach (Path np in paths.paths) { 61 g.add_path (np); 62 g.active_paths.add (np); 63 } 64 65 PenTool.update_orientation (); 66 67 GlyphCanvas.redraw (); 68 } 69 70 PenTool.update_orientation (); 71 convert_stroke = false; 72 } 73 74 public static PathList get_stroke_fast (Path path, double thickness) { 75 PathList o; 76 Path stroke; 77 78 stroke = path.copy (); 79 stroke.remove_points_on_points (0.3); 80 o = create_stroke (stroke, thickness, false); // set to true for faster stroke 81 82 return o; 83 } 84 85 public static PathList get_stroke (Path path, double thickness) { 86 PathList o, m; 87 Path stroke; 88 89 stroke = path.copy (); 90 stroke.remove_points_on_points (0.3); 91 o = create_stroke (stroke, thickness, false); 92 o = get_all_parts (o); 93 o = remove_intersection_paths (o); 94 o = merge (o); 95 96 m = new PathList (); 97 foreach (Path p in o.paths) { 98 m.add (simplify_stroke (p)); 99 } 100 101 return m; 102 } 103 104 static void reset_flags (PathList o) { 105 foreach (Path p in o.paths) { 106 foreach (EditPoint ep in p.points) { 107 ep.flags &= uint.MAX ^ 108 (EditPoint.INTERSECTION 109 | EditPoint.COPIED 110 | EditPoint.NEW_CORNER 111 | EditPoint.SELF_INTERSECTION); 112 } 113 p.update_region_boundaries (); 114 } 115 } 116 117 public static void merge_selected_paths () { 118 Glyph g = MainWindow.get_current_glyph (); 119 PathList o = new PathList (); 120 PathList r; 121 PathList new_paths = new PathList (); 122 PathList removed_paths = new PathList (); 123 124 g.store_undo_state (); 125 126 foreach (Path p in g.active_paths) { 127 p.close (); 128 o.add (p); 129 } 130 131 o = remove_overlap (o); 132 133 foreach (Path p in o.paths) { 134 ((!) BirdFont.get_current_font ().get_glyph_by_name ("k")).add_path (p.copy ()); 135 } 136 137 reset_flags (o); 138 139 new_paths.append (o); 140 for (int i = 0; i < o.paths.size; i++) { 141 for (int j = 0; j < o.paths.size; j++) { 142 Path p1, p2; 143 144 p1 = o.paths.get (i); 145 p2 = o.paths.get (j); 146 147 if (i == j) { 148 continue; 149 } 150 151 r = merge_selected (p1, p2, false); 152 153 // FIXME: delete 154 foreach (Path p in r.paths) { 155 ((!) BirdFont.get_current_font ().get_glyph_by_name ("c")).add_path (p.copy ()); 156 } 157 158 // FIXME: delete 159 ((!) BirdFont.get_current_font ().get_glyph_by_name ("d")).add_path (p1.copy ()); 160 ((!) BirdFont.get_current_font ().get_glyph_by_name ("d")).add_path (p2.copy ()); 161 162 remove_merged_curve_parts (r); 163 164 // FIXME: delete 165 print (@"Merge result $(r.paths.size) ($i, $j)\n"); 166 if (r.paths.size > 0) { 167 reset_flags (r); 168 new_paths.append (r); 169 170 new_paths.remove (p1); 171 new_paths.remove (p2); 172 173 i = 0; 174 j = 0; 175 } 176 177 //FIXME: delete 178 print (@"Remaining $(o.paths.size)\n"); 179 foreach (Path p in o.paths) { 180 print (@"p.points.size $(p.points.size)\n"); 181 } 182 } 183 } 184 185 foreach (Path p in g.active_paths) { 186 g.delete_path (p); 187 } 188 189 g.clear_active_paths (); 190 191 remove_merged_curve_parts (new_paths); 192 193 foreach (Path p in new_paths.paths) { 194 g.add_path (p); 195 g.add_active_path (null, p); 196 } 197 198 PenTool.update_orientation (); 199 GlyphCanvas.redraw (); 200 } 201 202 static PathList remove_overlap (PathList pl) { 203 PathList r = new PathList (); 204 205 foreach (Path p in pl.paths) { 206 PathList m = merge_selected (p, new Path (), true); 207 208 if (m.paths.size > 0) { 209 r.append (m); 210 } else { 211 r.add (p); 212 } 213 } 214 215 return r; 216 } 217 218 static void remove_merged_curve_parts (PathList r) { 219 Gee.ArrayList<Path> remove = new Gee.ArrayList<Path> (); 220 PathList flat = new PathList (); 221 222 print (@"remove_merged_curve_parts: $(r.paths.size)\n"); 223 224 foreach (Path p in r.paths) { 225 p.update_region_boundaries (); 226 flat.add (p.flatten ()); 227 } 228 229 foreach (Path p in r.paths) { 230 PathList pl = get_insides (flat, p); 231 232 int counters = 0; 233 int clockwise = 0; 234 235 foreach (Path i in pl.paths) { 236 if (i.is_clockwise ()) { 237 clockwise++; 238 } else { 239 counters++; 240 } 241 } 242 243 print (@"clockwise $clockwise counters $counters pl.size $(pl.paths.size)\n"); 244 245 if (p.is_clockwise ()) { 246 int c = clockwise - counters; 247 if (c % 2 == 0) { 248 remove.add (p); 249 break; 250 } 251 } else { 252 int c = clockwise - counters; 253 if (c % 2 == 1) { 254 remove.add (p); 255 break; 256 } 257 } 258 } 259 260 foreach (Path p in remove) { 261 r.paths.remove (p); 262 remove_merged_curve_parts (r); 263 return; 264 } 265 } 266 267 public static PathList merge_selected (Path path1, Path path2, 268 bool self_intersection) { 269 270 PathList flat = new PathList (); 271 PathList o = new PathList (); 272 PathList pl = new PathList (); 273 PathList r = new PathList (); 274 275 pl.add (path1); 276 pl.add (path2); 277 278 if (!self_intersection) { 279 if (!path1.boundaries_intersecting (path2)) { 280 return r; 281 } 282 } 283 284 foreach (Path p in pl.paths) { 285 if (p.stroke == 0) { 286 o.add (p); 287 flat.add (p.copy ().flatten (50)); 288 } 289 } 290 291 flat = merge (flat); 292 293 foreach (Path p in flat.paths) { 294 ((!) BirdFont.get_current_font ().get_glyph_by_name ("b")).add_path (p); 295 } 296 297 bool has_split_point = false; 298 foreach (Path p in flat.paths) { 299 foreach (EditPoint ep in p.points) { 300 if ((ep.flags & EditPoint.SPLIT_POINT) > 0) { 301 302 // FIXME: DELETE 303 ep.color = Color.pink (); 304 305 foreach (Path pp in o.paths) { 306 EditPoint lep = new EditPoint (); 307 pp.get_closest_point_on_path (lep, ep.x, ep.y, null, null); 308 309 if (Path.distance_to_point (lep, (!) lep.prev) < 0.1 310 || Path.distance_to_point (lep, (!) lep.next) < 0.1) { 311 // FIXME: find a better solution 312 continue; 313 } 314 315 if (Path.distance_to_point (ep, lep) < 0.1) { 316 EditPoint lep2 = new EditPoint (); 317 pp.get_closest_point_on_path (lep2, ep.x, ep.y, lep.prev, lep.next); 318 319 if (lep.prev != null) { 320 lep.get_left_handle ().type = lep.get_prev ().get_right_handle ().type; 321 } else { 322 lep.get_left_handle ().type = pp.get_last_point ().get_right_handle ().type; 323 } 324 325 if (lep.next != null) { 326 lep.get_right_handle ().type = lep.get_next ().get_left_handle ().type; 327 } else { 328 lep.get_left_handle ().type = pp.get_first_point ().get_right_handle ().type; 329 } 330 331 if (lep2.prev != null) { 332 lep2.get_left_handle ().type = lep2.get_prev ().get_right_handle ().type; 333 } else { 334 lep2.get_left_handle ().type = pp.get_first_point ().get_right_handle ().type; 335 } 336 337 if (lep2.next != null) { 338 lep2.get_right_handle ().type = lep2.get_next ().get_left_handle ().type; 339 } else { 340 lep2.get_left_handle ().type = pp.get_last_point ().get_right_handle ().type; 341 } 342 343 // self intersection 344 if (Path.distance_to_point (ep, lep2) < 0.1 345 && Path.distance_to_point (ep, lep) < 0.1) { 346 pp.insert_new_point_on_path (lep); 347 pp.insert_new_point_on_path (lep2); 348 349 lep.flags |= EditPoint.SELF_INTERSECTION; 350 lep2.flags |= EditPoint.SELF_INTERSECTION; 351 352 lep.tie_handles = false; 353 lep.reflective_point = false; 354 lep2.tie_handles = false; 355 lep2.reflective_point = false; 356 357 // FIXME: DELETE 358 lep2.color = Color.magenta (); 359 lep.color = Color.magenta (); 360 } else { 361 pp.insert_new_point_on_path (lep); 362 lep.flags |= EditPoint.INTERSECTION; 363 lep.tie_handles = false; 364 lep.reflective_point = false; 365 366 // FIXME: DELETE 367 lep2.color = Color.pink (); 368 lep.color = Color.pink (); 369 } 370 371 has_split_point = true; 372 } 373 } 374 } 375 } 376 } 377 378 if (!has_split_point) { 379 return r; 380 } 381 382 // remove double intersection points 383 EditPoint prev = new EditPoint (); 384 foreach (Path pp in o.paths) { 385 foreach (EditPoint ep in pp.points) { 386 if (((prev.flags & EditPoint.SELF_INTERSECTION) > 0 || (prev.flags & EditPoint.INTERSECTION) > 0) 387 && ((ep.flags & EditPoint.SELF_INTERSECTION) > 0 || (ep.flags & EditPoint.INTERSECTION) > 0) 388 && fabs (ep.x - prev.x) < 0.2 389 && fabs (ep.y - prev.y) < 0.2) { 390 prev.deleted = true; 391 } 392 393 prev = ep; 394 } 395 } 396 397 foreach (Path pp in o.paths) { 398 pp.remove_deleted_points (); 399 } 400 401 foreach (Path p in o.paths) { 402 foreach (EditPoint ep in p.points) { 403 ep.flags &= uint.MAX ^ EditPoint.COPIED; 404 } 405 } 406 407 // FIXME: delete 408 foreach (Path p in o.paths) { 409 ((!) BirdFont.get_current_font ().get_glyph_by_name ("a")).add_path (p); 410 } 411 412 return_val_if_fail (o.paths.size == 2, r); 413 414 Path p1, p2; 415 416 p1 = o.paths.get (0); 417 p2 = o.paths.get (1); 418 PathList parts = new PathList (); 419 420 if (self_intersection) { 421 // remove overlap 422 423 PathList self_parts; 424 425 self_parts = remove_self_intersections (p1); 426 parts.append (self_parts); 427 428 foreach (Path p in self_parts.paths) 429 ((!) BirdFont.get_current_font ().get_glyph_by_name ("e")).add_path (p); 430 431 /* // FIXME: DELETE 432 self_parts = remove_self_intersections (p2); 433 parts.append (self_parts); 434 435 foreach (Path p in self_parts.paths) 436 ((!) BirdFont.get_current_font ().get_glyph_by_name ("f")).add_path (p); 437 */ 438 } else { 439 // merge two path 440 PathList merged_paths = merge_paths_with_curves (p1, p2); 441 442 if (merged_paths.paths.size > 0) { 443 parts.append (merged_paths); 444 } else { 445 parts.add (p1); 446 parts.add (p2); 447 } 448 } 449 // FIXME: remove split points 450 451 foreach (Path p in parts.paths) 452 ((!) BirdFont.get_current_font ().get_glyph_by_name ("g")).add_path (p); 453 454 return parts; 455 } 456 457 static PathList remove_self_intersections (Path original) { 458 Path merged = new Path (); 459 IntersectionList intersections = new IntersectionList (); 460 EditPoint ep1, ep2, found; 461 double d; 462 double min_d; 463 Path current; 464 bool found_intersection; 465 PathList parts; 466 int i = 0; 467 Path path = original.copy (); 468 469 // FIXME: DELETE 470 ((!) BirdFont.get_current_font ().get_glyph_by_name ("f")).add_path (path); 471 472 parts = new PathList (); 473 474 if (path.points.size <= 1) { 475 return parts; 476 } 477 478 // reset copied points 479 foreach (EditPoint n in path.points) { 480 n.flags &= uint.MAX ^ EditPoint.COPIED; 481 } 482 483 // build list of intersection points 484 for (i = 0; i < path.points.size; i++) { 485 ep1 = path.points.get (i); 486 487 if ((ep1.flags & EditPoint.SELF_INTERSECTION) > 0 488 && (ep1.flags & EditPoint.COPIED) == 0) { 489 ep1.flags |= EditPoint.COPIED; 490 491 found = new EditPoint (); 492 min_d = double.MAX; 493 found_intersection = false; 494 495 for (int j = 0; j < path.points.size; j++) { 496 ep2 = path.points.get (j); 497 d = Path.distance_to_point (ep1, ep2); 498 if ((ep2.flags & EditPoint.COPIED) == 0 499 && (ep2.flags & EditPoint.SELF_INTERSECTION) > 0) { 500 if (d < min_d) { 501 min_d = d; 502 found_intersection = true; 503 found = ep2; 504 } 505 } 506 } 507 508 if (!found_intersection) { 509 warning (@"No self intersection:\n$(ep1)"); 510 return parts; 511 } 512 513 ep1.tie_handles = false; 514 ep1.reflective_point = false; 515 found.tie_handles = false; 516 found.reflective_point = false; 517 518 found.flags |= EditPoint.COPIED; 519 Intersection intersection = new Intersection (ep1, path, found, path); 520 intersection.self_intersection = true; 521 intersections.points.add (intersection); 522 } 523 } 524 525 // reset copy flag 526 foreach (EditPoint n in path.points) { 527 n.flags &= uint.MAX ^ EditPoint.COPIED; 528 } 529 530 if (intersections.points.size == 0) { 531 warning ("No intersection points."); 532 return parts; 533 } 534 535 current = path; 536 537 for (i = 0; i < current.points.size; i++) { 538 ep1 = current.points.get (i); 539 print (@"POINT $(ep1.x), $(ep1.y)\n"); 540 } 541 542 current.reverse (); 543 544 for (i = 0; i < current.points.size; i++) { 545 ep1 = current.points.get (i); 546 print (@"POINT AFTER $(ep1.x), $(ep1.y)\n"); 547 } 548 549 while (true) { 550 i = 0; 551 Intersection new_start = new Intersection.empty (); 552 EditPoint previous = new EditPoint (); 553 ep1 = current.points.get (i); 554 current = path; 555 556 for (i = 0; i < current.points.size; i++) { 557 ep1 = current.points.get (i); 558 if ((ep1.flags & EditPoint.COPIED) == 0 559 && (ep1.flags & EditPoint.SELF_INTERSECTION) == 0) { 560 // FIXME: insides current.reverse (); 561 break; 562 } 563 } 564 565 if (i >= current.points.size || (ep1.flags & EditPoint.COPIED) > 0) { 566 // all points have been copied 567 break; 568 } 569 570 while (true) { 571 if ((ep1.flags & EditPoint.SELF_INTERSECTION) > 0) { 572 bool other; 573 EditPointHandle handle; 574 575 handle = ep1.get_left_handle (); 576 print (@"SELF_INTERSECTION: $(ep1.x), $(ep1.y)\n"); 577 new_start = intersections.get_point (ep1, out other); 578 579 print (@"from $i "); 580 581 // FIXME: DELETE 582 // Take the other route in some paths 583 // current.reverse (); 584 585 i = index_of (current, other ? new_start.point : new_start.other_point); 586 587 print (@"start at $i\n"); 588 589 if (!(0 <= i < current.points.size)) { 590 warning (@"Index out of bounds. ($i)"); 591 return parts; 592 } 593 594 ep1 = current.points.get (i); 595 ep1.left_handle.move_to_coordinate (handle.x, handle.y); 596 } 597 598 if ((ep1.flags & EditPoint.COPIED) > 0) { 599 merged.close (); 600 EditPoint first_point = merged.get_first_point (); 601 EditPointHandle h; 602 603 // FIXME: self intersection not intersection 604 if ((ep1.flags & EditPoint.INTERSECTION) > 0) { // FIXME SELF INTERSECTION 605 first_point.left_handle.move_to_coordinate (previous.left_handle.x, previous.left_handle.y); 606 607 if (first_point.next != null) { 608 h = first_point.get_next ().get_left_handle (); 609 h.process_connected_handle (); 610 } 611 } 612 613 if ((ep1.flags & EditPoint.SELF_INTERSECTION) > 0) { 614 bool other; 615 616 ep1.flags |= EditPoint.COPIED; 617 merged.add_point (ep1.copy ()); 618 619 new_start = intersections.get_point (ep1, out other); 620 ep2 = new_start.get_other_point (current); 621 ep1.right_handle.move_to_coordinate (ep2.right_handle.x, ep2.right_handle.y); 622 } 623 624 print (@"Break at $(ep1.x), $(ep1.y)\n"); 625 626 merged.close (); 627 merged.create_list (); 628 parts.add (merged); 629 630 foreach (EditPoint n in merged.points) { 631 n.flags &= uint.MAX ^ EditPoint.SELF_INTERSECTION; 632 } 633 634 merged.get_first_point ().color = Color.green (); 635 merged.get_last_point ().color = Color.brown (); 636 637 merged.reverse (); 638 639 merged = new Path (); 640 641 break; 642 } 643 644 // adjust the other handle 645 if ((ep1.flags & EditPoint.INTERSECTION) > 0) { 646 ep1.left_handle.convert_to_curve (); 647 ep1.right_handle.convert_to_curve (); 648 } 649 650 // add point to path 651 ep1.flags |= EditPoint.COPIED; 652 merged.add_point (ep1.copy ()); 653 654 print (@"Add point $i $(ep1.x), $(ep1.y)\n"); 655 656 i++; 657 ep1 = current.points.get (i % current.points.size); 658 } 659 660 ep1.flags |= EditPoint.COPIED; 661 } 662 663 return parts; 664 } 665 666 static PathList merge_paths_with_curves (Path path1, Path path2) { 667 PathList r = new PathList (); 668 IntersectionList intersections = new IntersectionList (); 669 EditPoint ep1, ep2, found; 670 double d; 671 double min_d; 672 Path current; 673 bool found_intersection; 674 Path flat1, flat2; 675 676 if (path1.points.size <= 1 || path2.points.size <= 1) { 677 return r; 678 } 679 680 flat1 = path1.flatten (); 681 flat2 = path2.flatten (); 682 683 // reset copied points 684 foreach (EditPoint n in path2.points) { 685 n.flags &= uint.MAX ^ EditPoint.COPIED; 686 } 687 688 // build list of intersection points 689 for (int i = 0; i < path1.points.size; i++) { 690 ep1 = path1.points.get (i); 691 /* // FIXME: DELETE 692 if ((ep1.flags & EditPoint.SELF_INTERSECTION) > 0 693 && (ep1.flags & EditPoint.COPIED) == 0) { 694 ep1.flags |= EditPoint.COPIED; 695 696 found = new EditPoint (); 697 min_d = double.MAX; 698 found_intersection = false; 699 700 for (int j = 0; j < path1.points.size; j++) { 701 ep2 = path1.points.get (j); 702 d = Path.distance_to_point (ep1, ep2); 703 if ((ep2.flags & EditPoint.COPIED) == 0 704 && (ep2.flags & EditPoint.SELF_INTERSECTION) > 0) { 705 if (d < min_d) { 706 min_d = d; 707 found_intersection = true; 708 found = ep2; 709 } 710 } 711 } 712 713 if (!found_intersection) { 714 warning (@"No self intersection:\n$(ep1)"); 715 return r; 716 } 717 718 ep1.tie_handles = false; 719 ep1.reflective_point = false; 720 found.tie_handles = false; 721 found.reflective_point = false; 722 723 found.flags |= EditPoint.COPIED; 724 Intersection intersection = new Intersection (ep1, path1, found, path1); 725 intersection.self_intersection = true; 726 intersections.points.add (intersection); 727 } 728 */ 729 730 if ((ep1.flags & EditPoint.INTERSECTION) > 0) { 731 found = new EditPoint (); 732 min_d = double.MAX; 733 found_intersection = false; 734 for (int j = 0; j < path2.points.size; j++) { 735 ep2 = path2.points.get (j); 736 d = Path.distance_to_point (ep1, ep2); 737 if ((ep2.flags & EditPoint.COPIED) == 0 738 && (ep2.flags & EditPoint.INTERSECTION) > 0) { 739 if (d < min_d) { 740 min_d = d; 741 found_intersection = true; 742 found = ep2; 743 } 744 } 745 } 746 747 if (!found_intersection) { 748 warning (@"No intersection for:\n $(ep1)"); 749 return r; 750 } 751 752 found.flags |= EditPoint.COPIED; 753 754 print (@"Intersection in $(path1.points.size) ($(ep1.x),$(ep1.y)) and $(path2.points.size) ($(found.x),$(found.y))\n"); 755 ep1.tie_handles = false; 756 ep1.reflective_point = false; 757 found.tie_handles = false; 758 found.reflective_point = false; 759 Intersection intersection = new Intersection (ep1, path1, found, path2); 760 intersections.points.add (intersection); 761 } 762 } 763 764 // reset copy flag 765 foreach (EditPoint n in path1.points) { 766 n.flags &= uint.MAX ^ EditPoint.COPIED; 767 } 768 769 foreach (EditPoint n in path2.points) { 770 n.flags &= uint.MAX ^ EditPoint.COPIED; 771 } 772 773 if (intersections.points.size == 0) { 774 warning ("No intersection points."); 775 return r; 776 } 777 778 Path new_path = new Path (); 779 current = path1; 780 while (true) { 781 // find a beginning of a new part 782 bool find_parts = false; 783 Intersection new_start = new Intersection.empty (); 784 foreach (Intersection inter in intersections.points) { 785 if (!inter.done && !find_parts) { 786 find_parts = true; 787 new_start = inter; 788 current = new_start.path; 789 } 790 } 791 792 if (new_path.points.size > 0) { 793 new_path.close (); 794 new_path.recalculate_linear_handles (); 795 new_path.update_region_boundaries (); 796 r.add (new_path); 797 } 798 799 if (!find_parts) { // no more parts 800 break; 801 } 802 803 if ((new_start.get_point (current).flags & EditPoint.COPIED) > 0) { 804 current = new_start.get_other_path (current); 805 } 806 807 int i = index_of (current, new_start.get_point (current)); 808 809 if (i < 0) { 810 warning ("i < 0"); 811 return r; 812 } 813 814 EditPoint previous = new EditPoint (); 815 new_path = new Path (); 816 ep1 = current.points.get (i); 817 current = new_start.get_other_path (current); // swap at first iteration 818 bool first = true; 819 while (true) { 820 if ((ep1.flags & EditPoint.INTERSECTION) > 0) { 821 bool other; 822 823 previous = ep1; 824 825 if (intersections.has_point (ep1)) { 826 print (@"INTERSECTION: $(ep1.x), $(ep1.y)\n"); 827 new_start = intersections.get_point (ep1, out other); 828 current = new_start.get_other_path (current); 829 i = index_of (current, new_start.get_point (current)); 830 831 if (!(0 <= i < current.points.size)) { 832 warning (@"Index out of bounds. ($i)"); 833 return r; 834 } 835 836 ep1 = current.points.get (i); 837 ep2 = current.points.get ((i + 1) % current.points.size); 838 839 double px, py; 840 841 Path.get_point_for_step (ep1, ep2, 0.5, out px, out py); 842 bool inside = (current == path1 && flat2.is_over_coordinate (px, py)) 843 || (current == path2 && flat1.is_over_coordinate (px, py)); 844 845 bool other_inside = (current != path1 && flat2.is_over_coordinate (px, py)) 846 || (current != path2 && flat1.is_over_coordinate (px, py)); 847 848 if (inside && !other_inside) { 849 current = new_start.get_other_path (current); 850 i = index_of (current, new_start.get_point (current)); 851 852 if (!(0 <= i < current.points.size)) { 853 warning (@"Index out of bounds. ($i >= $(current.points.size)) "); 854 return r; 855 } 856 857 new_start.done = true; 858 ep1 = current.points.get (i); 859 } 860 861 inside = (current == path1 && flat2.is_over_coordinate (px, py)) 862 || (current == path2 && flat1.is_over_coordinate (px, py)); 863 864 if (first) { 865 previous = new_start.get_other_path (current).get_first_point (); 866 first = false; 867 } 868 } else { 869 warning (@"Intersection not in list, $(ep1.x), $(ep1.y)\n"); 870 } 871 } 872 873 // FIXME: DELETE 874 /* 875 if ((ep1.flags & EditPoint.SELF_INTERSECTION) > 0) { 876 bool other; 877 print (@"SELF_INTERSECTION: $(ep1.x), $(ep1.y)\n"); 878 new_start = intersections.get_point (ep1, out other); 879 880 print (@"from $i "); 881 882 i = index_of (current, other ? new_start.point : new_start.other_point); 883 884 print (@"start at $i\n"); 885 886 if (!(0 <= i < current.points.size)) { 887 warning (@"Index out of bounds. ($i)"); 888 return r; 889 } 890 891 ep1 = current.points.get (i); 892 893 // take the other point if it already is copied 894 if ((ep1.flags & EditPoint.COPIED) > 0) { 895 i = index_of (current, !other ? new_start.point : new_start.other_point); 896 897 if (!(0 <= i < current.points.size)) { 898 warning (@"Index out of bounds. ($i)"); 899 return r; 900 } 901 902 ep1 = current.points.get (i); 903 } 904 } 905 */ 906 if ((ep1.flags & EditPoint.COPIED) > 0) { 907 new_path.close (); 908 EditPoint first_point = new_path.get_first_point (); 909 EditPointHandle h; 910 if ((ep1.flags & EditPoint.INTERSECTION) > 0) { // FIXME SELF INTERSECTION 911 first_point.left_handle.move_to_coordinate (previous.left_handle.x, previous.left_handle.y); 912 913 if (first_point.next != null) { 914 h = first_point.get_next ().get_left_handle (); 915 h.process_connected_handle (); 916 } 917 } 918 919 // self intersections will be copied twice 920 if ((ep1.flags & EditPoint.SELF_INTERSECTION) > 0) { 921 if ((ep1.flags & EditPoint.COPIED_SELF_INTERSECTION) == 0) { 922 ep1.flags |= EditPoint.COPIED_SELF_INTERSECTION; 923 } else { 924 print (@"DONE SELF_INTERSECTION $(ep1.x), $(ep1.y)\n"); 925 break; 926 } 927 } else { 928 print (@"DONE COPIED $(ep1.x), $(ep1.y)\n"); 929 break; 930 } 931 } 932 933 // adjust the other handle 934 if ((ep1.flags & EditPoint.INTERSECTION) > 0) { 935 ep1.left_handle.convert_to_curve (); 936 ep1.right_handle.convert_to_curve (); 937 } 938 939 // add point to path 940 ep1.flags |= EditPoint.COPIED; 941 new_path.add_point (ep1.copy ()); 942 943 if ((ep1.flags & EditPoint.INTERSECTION) > 0) { // FIXME SELF INTERSECTION 944 new_path.get_last_point ().left_handle.move_to_coordinate (previous.left_handle.x, previous.left_handle.y); 945 } 946 947 print (@"Add point $(ep1.x), $(ep1.y)\n"); 948 949 i++; 950 ep1 = current.points.get (i % current.points.size); 951 } 952 953 ep1.flags |= EditPoint.COPIED; 954 955 if (!new_start.done) { 956 new_start.done = (new_start.get_other_point (current).flags & EditPoint.COPIED) > 0; 957 } 958 } 959 960 return r; 961 } 962 963 static Path simplify_stroke (Path p) { 964 Path simplified = new Path (); 965 Path segment, added_segment; 966 EditPoint ep, ep_start, last, first, segment_last; 967 int start, stop; 968 int j; 969 970 segment_last = new EditPoint (); 971 last = new EditPoint (); 972 973 foreach (EditPoint e in p.points) { 974 PenTool.convert_point_type (e, PointType.CUBIC); 975 } 976 977 foreach (EditPoint e in p.points) { 978 if ((e.flags & EditPoint.CURVE) == 0) { 979 p.set_new_start (e); 980 break; 981 } 982 } 983 984 for (int i = 0; i < p.points.size; i++) { 985 ep = p.points.get (i); 986 987 if ((ep.flags & EditPoint.CURVE) > 0) { 988 start = i; 989 for (j = start + 1; j < p.points.size; j++) { 990 ep = p.points.get (j); 991 if ((ep.flags & EditPoint.CURVE) == 0) { 992 break; 993 } 994 } 995 996 stop = j; 997 start -= 1; 998 999 if (start < 0) { 1000 warning ("start < 0"); 1001 start = 0; 1002 } 1003 1004 if (stop >= p.points.size) { 1005 warning ("stop >= p.points.size"); 1006 stop = p.points.size - 1; 1007 } 1008 1009 ep_start = p.points.get (start); 1010 ep = p.points.get (stop); 1011 1012 double l = Path.distance_to_point (ep_start, ep); 1013 segment = fit_bezier_path (p, start, stop, 0.00001 * l * l); 1014 1015 added_segment = segment.copy (); 1016 1017 if (simplified.points.size > 0) { 1018 last = simplified.get_last_point (); 1019 } 1020 1021 if (added_segment.points.size > 0) { 1022 segment_last = added_segment.get_last_point (); 1023 first = added_segment.get_first_point (); 1024 segment_last.right_handle = ep_start.get_right_handle ().copy (); 1025 1026 if (simplified.points.size > 1) { 1027 simplified.delete_last_point (); 1028 } 1029 1030 first.set_tie_handle (false); 1031 last.set_tie_handle (false); 1032 1033 last.get_right_handle ().x = first.get_right_handle ().x; 1034 last.get_right_handle ().y = first.get_right_handle ().y; 1035 1036 first.get_left_handle ().convert_to_curve (); 1037 first.get_left_handle ().x = last.get_left_handle ().x; 1038 first.get_left_handle ().y = last.get_left_handle ().y; 1039 1040 last = added_segment.get_last_point (); 1041 last.right_handle = ep.get_right_handle ().copy (); 1042 last.recalculate_linear_handles (); 1043 1044 simplified.append_path (added_segment); 1045 1046 segment_last.right_handle = ep.get_right_handle ().copy (); 1047 1048 if (added_segment.points.size > 0) { 1049 if (ep_start.get_right_handle ().is_line ()) { 1050 first = added_segment.get_first_point (); 1051 first.recalculate_linear_handles (); 1052 } 1053 } 1054 } else { 1055 warning ("No points in segment."); 1056 } 1057 1058 i = stop; 1059 } else { 1060 simplified.add_point (ep.copy ()); 1061 } 1062 } 1063 1064 simplified.recalculate_linear_handles (); 1065 simplified.close (); 1066 1067 simplified.remove_points_on_points (0.01); 1068 1069 return simplified; 1070 } 1071 1072 static Path fit_bezier_path (Path p, int start, int stop, double error) { 1073 int index, size; 1074 Path simplified; 1075 double[] lines; 1076 double[] result; 1077 EditPoint ep; 1078 1079 simplified = new Path (); 1080 1081 return_val_if_fail (0 <= start < p.points.size, simplified); 1082 return_val_if_fail (0 <= stop < p.points.size, simplified); 1083 1084 size = stop - start + 1; 1085 lines = new double[2 * size]; 1086 1087 index = 0; 1088 1089 for (int i = start; i <= stop; i++) { 1090 ep = p.points.get (i); 1091 lines[index] = ep.x; 1092 index++; 1093 1094 lines[index] = ep.y; 1095 index++; 1096 } 1097 1098 return_val_if_fail (2 * size == index, new Path ()); 1099 1100 Gems.fit_bezier_curve_to_line (lines, error, out result); 1101 1102 return_val_if_fail (!is_null (result), simplified); 1103 1104 for (int i = 0; i + 7 < result.length; i += 8) { 1105 simplified.add_cubic_bezier_points ( 1106 result[i], result[i + 1], 1107 result[i + 2], result[i + 3], 1108 result[i + 4], result[i + 5], 1109 result[i + 6], result[i + 7]); 1110 } 1111 1112 return simplified; 1113 } 1114 1115 static PathList remove_intersection_paths (PathList pl) { 1116 PathList r = new PathList (); 1117 1118 foreach (Path p in pl.paths) { 1119 if (p.points.size > 7) { 1120 r.add (p); 1121 } else if (!has_new_corner (p)) { 1122 r.add (p); 1123 } else if (counters (pl, p) == 0) { 1124 r.add (p); 1125 } 1126 } 1127 1128 return r; 1129 } 1130 1131 static bool has_new_corner (Path p) { 1132 foreach (EditPoint ep in p.points) { 1133 if ((ep.flags & EditPoint.NEW_CORNER) > 0) { 1134 return true; 1135 } 1136 } 1137 1138 return false; 1139 } 1140 1141 static void add_line_cap (Path path, Path stroke1, Path stroke2, bool last_cap) { 1142 if (path.line_cap == LineCap.SQUARE) { 1143 add_square_cap (path, stroke1, stroke2, last_cap); 1144 } else if (path.line_cap == LineCap.ROUND) { 1145 add_round_cap (path, stroke1, stroke2, last_cap); 1146 } 1147 } 1148 1149 static void add_round_cap (Path path, Path stroke1, Path stroke2, bool last_cap) { 1150 double px, py; 1151 double step, start_angle, stop_angle; 1152 double radius; 1153 EditPoint n, nstart, nend; 1154 Path cap = new Path (); 1155 1156 EditPoint start, end; 1157 EditPointHandle last_handle; 1158 EditPoint first, last; 1159 1160 stroke1.remove_points_on_points (); 1161 stroke2.remove_points_on_points (); 1162 1163 last_handle = path.get_first_point ().get_right_handle (); 1164 start = stroke1.get_last_point (); 1165 end = stroke2.get_first_point (); 1166 1167 start_angle = last_handle.angle + PI / 2; 1168 stop_angle = start_angle + PI; 1169 1170 nstart = cap.add (start.x, start.y); 1171 radius = Path.distance_to_point (start, end) / 2; 1172 step = PI / 5; 1173 1174 for (int j = 0; j < 5; j++) { 1175 double angle = start_angle + step * j; 1176 px = radius * cos (angle) + last_handle.parent.x; 1177 py = radius * sin (angle) + last_handle.parent.y; 1178 n = cap.add (px, py); 1179 1180 n.type = PointType.LINE_CUBIC; 1181 n.get_right_handle ().type = PointType.LINE_CUBIC; 1182 n.get_left_handle ().type = PointType.LINE_CUBIC; 1183 } 1184 1185 nend = cap.add (end.x, end.y); 1186 1187 for (int i = 0; i < cap.points.size; i++) { 1188 cap.points.get (i).recalculate_linear_handles (); 1189 } 1190 1191 int size = cap.points.size; 1192 1193 for (int i = 1; i < size; i++) { 1194 n = cap.points.get (i); 1195 n.convert_to_curve (); 1196 n.set_tie_handle (true); 1197 n.process_tied_handle (); 1198 } 1199 1200 int f = stroke1.points.size - 1; 1201 1202 for (int i = 2; i < cap.points.size - 1; i++) { 1203 n = cap.points.get (i).copy (); 1204 stroke1.add_point (n); 1205 } 1206 1207 cap.remove_points_on_points (); 1208 return_if_fail (0 < f < stroke1.points.size); 1209 1210 first = stroke1.points.get (f); 1211 1212 last = stroke1.get_last_point (); 1213 last.convert_to_curve (); 1214 1215 last = stroke1.add_point (stroke2.get_first_point ()); 1216 stroke2.delete_first_point (); 1217 1218 last.convert_to_line (); 1219 last.recalculate_linear_handles (); 1220 1221 last.next = stroke1.add_point (stroke2.get_first_point ()).get_link_item (); 1222 stroke2.delete_first_point (); 1223 1224 last.get_left_handle ().convert_to_curve (); 1225 last.get_left_handle ().angle = last.get_right_handle ().angle + PI; 1226 last.flags = EditPoint.CURVE_KEEP; 1227 1228 double a; 1229 double l; 1230 1231 return_if_fail (cap.points.size > 1); 1232 1233 a = (first.get_left_handle ().angle + PI) % (2 * PI); 1234 l = cap.points.get (1).get_right_handle ().length; 1235 1236 first.get_right_handle ().convert_to_curve (); 1237 first.get_right_handle ().angle = a; 1238 first.get_right_handle ().length = l; 1239 1240 a = (first.get_left_handle ().angle + PI) % (2 * PI); 1241 1242 last.get_left_handle ().convert_to_curve (); 1243 last.get_left_handle ().angle = a; 1244 last.get_left_handle ().length = l; 1245 } 1246 1247 static void add_square_cap (Path path, Path stroke1, Path stroke2, bool last_cap) { 1248 EditPointHandle last_handle; 1249 EditPoint start; 1250 EditPoint end; 1251 EditPoint n; 1252 double x, y; 1253 double stroke_width = path.stroke / 2; 1254 1255 last_handle = path.get_first_point ().get_right_handle (); 1256 start = stroke1.get_last_point (); 1257 end = stroke2.get_first_point (); 1258 1259 y = sin (last_handle.angle - PI) * stroke_width; 1260 x = cos (last_handle.angle - PI) * stroke_width; 1261 1262 n = stroke1.add (start.x + x, start.y + y); 1263 n.type = PointType.CUBIC; 1264 n.get_right_handle ().type = PointType.CUBIC; 1265 n.get_left_handle ().type = PointType.CUBIC; 1266 n.convert_to_line (); 1267 1268 n = stroke1.add (end.x + x, end.y + y); 1269 n.type = PointType.CUBIC; 1270 n.get_right_handle ().type = PointType.CUBIC; 1271 n.get_left_handle ().type = PointType.CUBIC; 1272 n.convert_to_line (); 1273 } 1274 1275 /** Create one stroke from the outline and counter stroke and close the 1276 * open endings. 1277 * 1278 * @param path the path to create stroke for 1279 * @param stroke for the outline of path 1280 * @param stroke for the counter path 1281 */ 1282 static Path merge_strokes (Path path, Path stroke, Path counter) { 1283 1284 Path merged; 1285 EditPoint last_counter, first; 1286 1287 merged = stroke.copy (); 1288 merged.reverse (); 1289 1290 last_counter = new EditPoint (); 1291 first = new EditPoint (); 1292 1293 add_line_cap (path, merged, counter, true); 1294 path.reverse (); 1295 1296 add_line_cap (path, counter, merged, true); 1297 path.reverse (); 1298 1299 merged.append_path (counter); 1300 1301 merged.close (); 1302 merged.create_list (); 1303 merged.recalculate_linear_handles (); 1304 1305 return merged; 1306 } 1307 1308 static void move_segment (EditPoint stroke_start, EditPoint stroke_stop, double thickness) { 1309 EditPointHandle r, l; 1310 double m, n; 1311 double qx, qy; 1312 1313 stroke_start.set_tie_handle (false); 1314 stroke_stop.set_tie_handle (false); 1315 1316 r = stroke_start.get_right_handle (); 1317 l = stroke_stop.get_left_handle (); 1318 1319 m = cos (r.angle + PI / 2) * thickness; 1320 n = sin (r.angle + PI / 2) * thickness; 1321 1322 stroke_start.get_right_handle ().move_to_coordinate_delta (m, n); 1323 stroke_start.get_left_handle ().move_to_coordinate_delta (m, n); 1324 1325 stroke_start.independent_x += m; 1326 stroke_start.independent_y += n; 1327 1328 qx = cos (l.angle - PI / 2) * thickness; 1329 qy = sin (l.angle - PI / 2) * thickness; 1330 1331 stroke_stop.get_right_handle ().move_to_coordinate_delta (qx, qy); 1332 stroke_stop.get_left_handle ().move_to_coordinate_delta (qx, qy); 1333 1334 stroke_stop.independent_x += qx; 1335 stroke_stop.independent_y += qy; 1336 } 1337 1338 static void add_corner (Path stroked, EditPoint previous, EditPoint next, 1339 EditPoint original, double stroke_width) { 1340 1341 double ratio; 1342 double distance; 1343 EditPoint corner; 1344 double corner_x, corner_y; 1345 EditPointHandle previous_handle; 1346 EditPointHandle next_handle; 1347 EditPoint cutoff1, cutoff2; 1348 double adjusted_stroke = (stroke_width * 0.999999) / 2.0; 1349 bool d1, d2; 1350 1351 previous_handle = previous.get_left_handle (); 1352 next_handle = next.get_right_handle (); 1353 1354 previous_handle.angle += PI; 1355 next_handle.angle += PI; 1356 1357 Path.find_intersection_handle (previous_handle, next_handle, out corner_x, out corner_y); 1358 corner = new EditPoint (corner_x, corner_y, previous.type); 1359 corner.convert_to_line (); 1360 1361 previous_handle.angle -= PI; 1362 next_handle.angle -= PI; 1363 1364 distance = Path.distance_to_point (corner, original); 1365 ratio = 1.5 * fabs (adjusted_stroke) / distance; 1366 1367 double or = original.get_right_handle ().angle; 1368 double ol = original.get_left_handle ().angle; 1369 1370 if (previous.prev == null) { // FIXME: first point 1371 warning ("Point before corner."); 1372 d1 = false; 1373 d2 = false; 1374 } else { 1375 d1 = corner.x - previous.x >= 0 == previous.x - previous.get_prev ().x >= 0; 1376 d2 = corner.y - previous.y >= 0 == previous.y - previous.get_prev ().y >= 0; 1377 } 1378 1379 if (ratio > 1) { 1380 if (!d1 && !d2) { 1381 return; 1382 } else { 1383 stroked.add_point (corner); 1384 } 1385 } else { 1386 1387 cutoff1 = new EditPoint (); 1388 cutoff1.set_point_type (previous.type); 1389 cutoff1.convert_to_line (); 1390 1391 cutoff2 = new EditPoint (); 1392 cutoff2.set_point_type (previous.type); 1393 cutoff2.convert_to_line (); 1394 1395 if (fabs (or - ol) < 0.001) { 1396 cutoff1.x = previous.x + 1.5 * fabs (adjusted_stroke) * -cos (or); 1397 cutoff1.y = previous.y + 1.5 * fabs (adjusted_stroke) * -sin (or); 1398 1399 cutoff2.x = next.x + 1.5 * fabs (adjusted_stroke) * -cos (or); 1400 cutoff2.y = next.y + 1.5 * fabs (adjusted_stroke) * -sin (or); 1401 } else { 1402 cutoff1.x = previous.x + (corner.x - previous.x) * ratio; 1403 cutoff1.y = previous.y + (corner.y - previous.y) * ratio; 1404 1405 cutoff2.x = next.x + (corner.x - next.x) * ratio; 1406 cutoff2.y = next.y + (corner.y - next.y) * ratio; 1407 } 1408 1409 if (!cutoff1.is_valid () || cutoff2.is_valid ()) { 1410 cutoff1 = stroked.add_point (cutoff1); 1411 cutoff2 = stroked.add_point (cutoff2); 1412 } 1413 1414 cutoff1.recalculate_linear_handles (); 1415 cutoff2.recalculate_linear_handles (); 1416 1417 // self intersection 1418 if (!d1 && !d2) { 1419 cutoff1.deleted = true; 1420 cutoff2.deleted = true; 1421 1422 stroked.remove_deleted_points (); 1423 return; 1424 } 1425 1426 if (distance > 4 * stroke_width) { 1427 previous.flags = EditPoint.NONE; 1428 next.flags = EditPoint.NONE; 1429 } else { 1430 previous.flags |= EditPoint.NEW_CORNER; 1431 next.flags |= EditPoint.NEW_CORNER; 1432 } 1433 } 1434 } 1435 1436 static PathList get_parts (Path path) { 1437 PathList intersections; 1438 PathList r; 1439 1440 r = get_parts_self (path); 1441 intersections = new PathList (); 1442 1443 foreach (Path p in r.paths) { 1444 intersections.add (p); 1445 } 1446 1447 return intersections; 1448 } 1449 1450 static bool split_corner (PathList pl) { 1451 EditPoint p1, p2; 1452 EditPoint a1, a2; 1453 PathList result; 1454 bool split; 1455 1456 foreach (Path p in pl.paths) { 1457 if (p.points.size == 0) { 1458 continue; 1459 } 1460 1461 for (int index = 1; index < p.points.size + 2; index++) { 1462 p1 = p.points.get ((index - 1) % p.points.size); 1463 p2 = p.points.get (index % p.points.size); 1464 a1 = p.points.get ((index + 3) % p.points.size); // two points ahead 1465 a2 = p.points.get ((index + 4) % p.points.size); 1466 1467 if ((p1.flags & EditPoint.STROKE_OFFSET) > 0 1468 || (p2.flags & EditPoint.STROKE_OFFSET) > 0 1469 || (a1.flags & EditPoint.STROKE_OFFSET) > 0 1470 || (a2.flags & EditPoint.STROKE_OFFSET) > 0) { 1471 1472 split = split_segment (p, a1, a2, p1, p2, out result); 1473 1474 if (split) { 1475 pl.append (result); 1476 pl.paths.remove (p); 1477 split_corner (pl); 1478 return true; 1479 } else { 1480 p1 = p.points.get ((index - 1) % p.points.size); 1481 p2 = p.points.get (index % p.points.size); 1482 a1 = p.points.get ((index + 2) % p.points.size); // one point ahead 1483 a2 = p.points.get ((index + 3) % p.points.size); 1484 1485 split = split_segment (p, a1, a2, p1, p2, out result); 1486 1487 if (split) { 1488 pl.append (result); 1489 pl.paths.remove (p); 1490 split_corner (pl); 1491 return true; 1492 } else { 1493 p1 = p.points.get ((index - 1) % p.points.size); 1494 p2 = p.points.get (index % p.points.size); 1495 a1 = p.points.get ((index + 3) % p.points.size); // two points ahead 1496 a2 = p.points.get ((index + 4) % p.points.size); 1497 1498 if ((p1.flags & EditPoint.STROKE_OFFSET) > 0 1499 && (a1.flags & EditPoint.STROKE_OFFSET) > 0) { 1500 p1.flags = EditPoint.COUNTER_TO_OUTLINE; 1501 a1.flags = EditPoint.COUNTER_TO_OUTLINE; 1502 } 1503 } 1504 } 1505 } 1506 } 1507 } 1508 1509 return false; 1510 } 1511 1512 static bool split_segment (Path p, EditPoint first, EditPoint next, EditPoint p1, EditPoint p2, out PathList result) { 1513 double ix, iy; 1514 bool intersection; 1515 int i; 1516 1517 result = new PathList (); 1518 intersection = segments_intersects (first, next, p1, p2, out ix, out iy, true); 1519 1520 if (intersection) { 1521 add_intersection (p, first, next, ix, iy); 1522 add_intersection (p, p1, p2, ix, iy); 1523 1524 i = mark_intersection_as_deleted (p); 1525 return_val_if_fail (i == 2, false); 1526 1527 result.append (get_remaining_points (p.copy ())); 1528 } 1529 1530 return intersection; 1531 } 1532 1533 static bool split_path (Path path1, Path path2, PathList result) { 1534 PathList pl1, pl2; 1535 Path a1, a2, b1, b2; 1536 Path m1, m2; 1537 int i; 1538 1539 if (path1 == path2) { 1540 return false; 1541 } 1542 1543 if (add_intersection_points (path1, path2, 2)) { 1544 i = mark_intersection_as_deleted (path1); 1545 if (i != 2) { 1546 warning (@"Expecting 2 points, $i points found."); 1547 return false; 1548 } 1549 1550 i = mark_intersection_as_deleted (path2); 1551 if (i != 2) { 1552 warning (@"Expecting 2 points, $i points found."); 1553 return false; 1554 } 1555 1556 1557 pl1 = get_remaining_points (path1.copy ()); 1558 pl2 = get_remaining_points (path2.copy ()); 1559 1560 return_if_fail (pl1.paths.size == 2); 1561 return_if_fail (pl2.paths.size == 2); 1562 1563 a1 = pl1.paths.get (0); 1564 a2 = pl1.paths.get (1); 1565 b1 = pl2.paths.get (0); 1566 b2 = pl2.paths.get (1); 1567 1568 m1 = PenTool.merge_open_paths (a1, b2); 1569 m2 = PenTool.merge_open_paths (b1, a2); 1570 1571 result.add (m1); 1572 result.add (m2); 1573 1574 return true; 1575 } 1576 1577 return false; 1578 } 1579 1580 static PathList split_paths (PathList pl) { 1581 PathList n = new PathList (); 1582 1583 n.append (pl); 1584 1585 foreach (Path p1 in pl.paths) { 1586 foreach (Path p2 in pl.paths) { 1587 if (p1 != p2) { 1588 if (split_path (p1, p2, n)) { 1589 n.paths.remove (p1); 1590 n.paths.remove (p2); 1591 return split_paths (n); 1592 } 1593 } 1594 } 1595 } 1596 1597 return n; 1598 } 1599 1600 static PathList get_parts_self (Path path, PathList? paths = null) { 1601 PathList pl; 1602 PathList r; 1603 1604 r = paths == null ? new PathList () : (!) paths; 1605 pl = split (path); 1606 1607 foreach (Path part in pl.paths) { 1608 if (!has_self_intersection (part)) { 1609 r.add (part); 1610 } else { 1611 get_parts_self (part, r); 1612 } 1613 } 1614 1615 if (r.paths.size == 0) { 1616 warning ("No parts in path"); 1617 } 1618 1619 return r; 1620 } 1621 1622 1623 static bool has_intersection_points (Path path) { 1624 foreach (EditPoint p in path.points) { 1625 if ((p.flags & EditPoint.INTERSECTION) > 0) { 1626 return true; 1627 } 1628 } 1629 return false; 1630 } 1631 1632 /** Split one path at intersection points in two parts. */ 1633 static PathList split (Path path) { 1634 Path new_path; 1635 PathList pl; 1636 int i; 1637 1638 if (!has_intersection_points (path)) { 1639 add_self_intersection_points (path); 1640 } else { 1641 warning ("points already created."); 1642 } 1643 1644 foreach (EditPoint p in path.points) { 1645 if (insides (p, path) == 1) { 1646 path.set_new_start (p); 1647 path.close (); 1648 break; 1649 } 1650 } 1651 1652 i = mark_intersection_as_deleted (path); 1653 1654 if (!(i == 0 || i == 2)) { 1655 warning (@"Split should only create two parts, $i points will be deleted."); 1656 } 1657 1658 new_path = path.copy (); 1659 pl = get_remaining_points (new_path); 1660 1661 return pl; 1662 } 1663 1664 static PathList process_deleted_control_points (Path path) { 1665 PathList paths, nl, pl, rl, result; 1666 1667 paths = new PathList (); 1668 rl = new PathList (); 1669 pl = new PathList (); 1670 nl = new PathList (); 1671 1672 if (!path.has_deleted_point ()) { 1673 print ("No deleted points\n"); 1674 return pl; 1675 } 1676 1677 pl.add (path); 1678 1679 foreach (Path p in pl.paths) { 1680 nl = p.process_deleted_points (); 1681 1682 if (nl.paths.size > 0) { 1683 rl.append (nl); 1684 rl.paths.remove (p); 1685 } 1686 } 1687 1688 result = new PathList (); 1689 foreach (Path p in rl.paths) { 1690 pl = process_deleted_control_points (p); 1691 1692 if (pl.paths.size > 0) { 1693 result.append (pl); 1694 } else { 1695 result.add (p); 1696 } 1697 } 1698 1699 for (int i = 1; i < result.paths.size; i++) { 1700 result.paths.get (i).reverse (); 1701 } 1702 1703 paths.append (result); 1704 1705 return paths; 1706 } 1707 1708 static PathList get_remaining_points (Path old_path) { 1709 PathList pl; 1710 1711 old_path.close (); 1712 pl = process_deleted_control_points (old_path); 1713 1714 if (pl.paths.size == 0) { 1715 print ("No paths.\n"); 1716 pl.add (old_path); 1717 } 1718 1719 foreach (Path pn in pl.paths) { 1720 pn.close (); 1721 } 1722 1723 return pl; 1724 } 1725 1726 static bool has_self_intersection (Path path) { 1727 bool intersection = false; 1728 1729 path.all_segments ((ep1, ep2) => { 1730 double ix, iy; 1731 EditPoint p1, p2; 1732 1733 if (segment_intersects (path, ep1, ep2, out ix, out iy, out p1, out p2, true)) { 1734 intersection = true; 1735 return false; 1736 } 1737 1738 return true; 1739 }); 1740 1741 return intersection; 1742 } 1743 1744 static bool add_self_intersection_points (Path path, bool only_offsets = false) { 1745 bool intersection = false; 1746 1747 path.all_segments ((ep1, ep2) => { 1748 double ix, iy; 1749 EditPoint p1, p2; 1750 1751 if (segment_intersects (path, ep1, ep2, out ix, out iy, out p1, out p2, true, only_offsets)) { 1752 add_intersection (path, ep1, ep2, ix, iy); 1753 add_intersection (path, p1, p2, ix, iy); 1754 1755 intersection = true; 1756 return false; 1757 } 1758 1759 return true; 1760 }); 1761 1762 return intersection; 1763 } 1764 1765 static EditPoint add_intersection (Path path, EditPoint prev, EditPoint next, double px, double py, Color? c = null) { 1766 Gee.ArrayList<EditPoint> n = new Gee.ArrayList<EditPoint> (); 1767 EditPoint ep1 = new EditPoint (); 1768 EditPoint ep2 = new EditPoint (); 1769 EditPoint ep3 = new EditPoint (); 1770 double d; 1771 1772 if (next == path.get_first_point ()) { 1773 ep1.prev = null; 1774 } else { 1775 ep1.prev = prev; 1776 } 1777 1778 ep1.prev = prev; 1779 ep1.next = ep2; 1780 ep1.flags |= EditPoint.NEW_CORNER | EditPoint.SPLIT_POINT; 1781 ep1.type = prev.type; 1782 ep1.x = px; 1783 ep1.y = py; 1784 ep1.color = c; 1785 n.add (ep1); 1786 1787 ep2.prev = ep1; 1788 ep2.next = ep3; 1789 ep2.flags |= EditPoint.INTERSECTION | EditPoint.SPLIT_POINT; 1790 ep2.type = prev.type; 1791 ep2.x = px; 1792 ep2.y = py; 1793 ep2.color = c; 1794 n.add (ep2); 1795 1796 ep3.prev = ep2; 1797 ep3.next = next; 1798 ep3.flags |= EditPoint.NEW_CORNER | EditPoint.SPLIT_POINT; 1799 ep3.type = prev.type; 1800 ep3.x = px; 1801 ep3.y = py; 1802 ep3.color = c; 1803 n.add (ep3); 1804 1805 next.get_left_handle ().convert_to_line (); 1806 1807 foreach (EditPoint np in n) { 1808 np = path.add_point_after (np, np.prev); 1809 path.create_list (); 1810 } 1811 1812 PenTool.convert_point_to_line (ep1, true); 1813 PenTool.convert_point_to_line (ep2, true); 1814 PenTool.convert_point_to_line (ep3, true); 1815 1816 ep1.recalculate_linear_handles (); 1817 ep2.recalculate_linear_handles (); 1818 ep3.recalculate_linear_handles (); 1819 1820 d = Path.distance_to_point (prev, next); 1821 prev.get_right_handle ().length *= Path.distance_to_point (prev, ep1) / d; 1822 next.get_left_handle ().length *= Path.distance_to_point (ep3, next) / d; 1823 1824 next.recalculate_linear_handles (); 1825 1826 return ep2; 1827 } 1828 1829 static bool segments_intersects (EditPoint p1, EditPoint p2, EditPoint ep, EditPoint next, 1830 out double ix, out double iy, 1831 bool skip_points_on_points = false) { 1832 double cross_x, cross_y; 1833 1834 ix = 0; 1835 iy = 0; 1836 1837 if (is_line (ep.x, ep.y, p1.x, p1.y, next.x, next.y)) { 1838 ix = p1.x; 1839 iy = p1.y; 1840 return true; 1841 } 1842 1843 if (is_line (ep.x, ep.y, p2.x, p2.y, next.x, next.y)) { 1844 ix = p2.x; 1845 iy = p2.y; 1846 return true; 1847 } 1848 1849 if (is_line (p1.x, p1.y, ep.x, ep.y, p2.x, p2.y)) { 1850 ix = ep.x; 1851 iy = ep.y; 1852 return true; 1853 } 1854 1855 if (is_line (p1.x, p1.y, next.x, next.y, p2.x, p2.y)) { 1856 ix = next.x; 1857 iy = next.y; 1858 return true; 1859 } 1860 1861 Path.find_intersection_point (ep, next, p1, p2, out cross_x, out cross_y); 1862 1863 if (fmin (ep.x, next.x) <= cross_x <= fmax (ep.x, next.x) 1864 && fmin (ep.y, next.y) <= cross_y <= fmax (ep.y, next.y)) { 1865 // iterate to find intersection. 1866 if (is_line (ep.x, ep.y, cross_x, cross_y, next.x, next.y) 1867 && is_line (p1.x, p1.y, cross_x, cross_y, p2.x, p2.y)) { 1868 1869 ix = cross_x; 1870 iy = cross_y; 1871 1872 return true; 1873 } 1874 } 1875 1876 return false; 1877 } 1878 1879 static bool segment_intersects (Path path, EditPoint ep, EditPoint next, 1880 out double ix, out double iy, 1881 out EditPoint ia, out EditPoint ib, 1882 bool skip_points_on_points = false, 1883 bool only_offsets = false) { 1884 1885 EditPoint p1, p2; 1886 bool intersection, inter; 1887 double iix, iiy; 1888 1889 double distance, min_distance; 1890 1891 intersection = false; 1892 1893 ix = 0; 1894 iy = 0; 1895 1896 iix = 0; 1897 iiy = 0; 1898 1899 ia = new EditPoint (); 1900 ib = new EditPoint (); 1901 1902 if (path.points.size == 0) { 1903 return false; 1904 } 1905 1906 min_distance = double.MAX; 1907 p1 = path.points.get (path.points.size - 1); 1908 for (int i = 0; i < path.points.size; i++) { 1909 p2 = path.points.get (i); 1910 1911 bool is_offset = ((p1.flags & EditPoint.STROKE_OFFSET) > 0) 1912 && ((p2.flags & EditPoint.STROKE_OFFSET) > 0) 1913 && ((ep.flags & EditPoint.STROKE_OFFSET) > 0) 1914 && ((next.flags & EditPoint.STROKE_OFFSET) > 0); 1915 1916 if (!only_offsets || is_offset) { 1917 if (p1 != ep && p2 != next) { 1918 inter = segments_intersects (p1, p2, ep, next, out iix, out iiy, 1919 skip_points_on_points); 1920 1921 if (inter) { 1922 distance = Path.distance (ep.x, iix, ep.y, iiy); 1923 if (distance < min_distance) { 1924 ia = p1; 1925 ib = p2; 1926 ix = iix; 1927 iy = iiy; 1928 intersection = true; 1929 min_distance = distance; 1930 } 1931 } 1932 } 1933 } 1934 1935 p1 = p2; 1936 } 1937 1938 return intersection; 1939 } 1940 1941 /** @return true if p2 is on the line p1 to p3 */ 1942 static bool is_line (double x1, double y1, double x2, double y2, double x3, double y3, double tolerance = 0.01) { 1943 return fmin (x1, x3) <= x2 && x2 <= fmax (x1, x3) 1944 && fmin (y1, y3) <= y2 && y2 <= fmax (y1, y3) 1945 && is_flat (x1, y1, x2, y2, x3, y3, tolerance); 1946 } 1947 1948 public static bool is_flat (double x1, double y1, double x2, double y2, double x3, double y3, double tolerance = 0.001) { 1949 double ds = Path.distance (x1, x3, y1, y3); 1950 double d1 = Path.distance (x1, x2, y1, y2); 1951 double d2 = Path.distance (x2, x3, y2, y3); 1952 double p = d1 / ds; 1953 double x = fabs ((x3 - x1) * p - (x2 - x1)) / ds; 1954 double y = fabs ((y3 - y1) * p - (y2 - y1)) / ds; 1955 double d = fabs (ds - (d1 + d2)) / ds; 1956 1957 return ds > 0.001 && d1 > 0.001 && d2 > 0.001 1958 && d < tolerance && x < tolerance && y < tolerance; 1959 } 1960 1961 // indside becomes outside in some paths 1962 static void remove_points_in_stroke (PathList pl) { 1963 PathList r; 1964 1965 foreach (Path p in pl.paths) { 1966 if (remove_points_in_stroke_for_path (p, pl, out r)) { 1967 pl.append (r); 1968 remove_points_in_stroke (pl); 1969 return; 1970 } 1971 } 1972 } 1973 1974 static bool remove_points_in_stroke_for_path (Path p, PathList pl, out PathList result) { 1975 EditPoint start_ep; 1976 EditPoint start_next; 1977 EditPoint start_prev; 1978 EditPoint end_ep = new EditPoint (); 1979 EditPoint end_next; 1980 EditPoint end_prev; 1981 1982 result = new PathList (); 1983 1984 for (int i = 1; i < p.points.size + 1; i++) { 1985 start_prev = p.points.get ((i - 1) % p.points.size); 1986 start_ep = p.points.get (i % p.points.size); 1987 start_next = p.points.get ((i + 1) % p.points.size); 1988 1989 if ((start_ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) { 1990 for (int j = i; j < p.points.size + i; j++) { 1991 end_prev = p.points.get ((j - 1) % p.points.size); 1992 end_ep = p.points.get (j % p.points.size); 1993 end_next = p.points.get ((j + 1) % p.points.size); 1994 1995 1996 if ((end_ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) { 1997 start_ep.flags = EditPoint.NONE; 1998 end_ep.flags = EditPoint.NONE; 1999 2000 if (merge_segments (pl, p, start_prev, start_ep, end_ep, end_next, out result)) { 2001 return true; 2002 } 2003 } 2004 } 2005 } 2006 2007 start_ep.flags = EditPoint.NONE; 2008 end_ep.flags = EditPoint.NONE; 2009 } 2010 2011 return false; 2012 } 2013 2014 static bool merge_segments (PathList pl, 2015 Path path1, EditPoint start1, EditPoint stop1, 2016 EditPoint start2, EditPoint stop2, 2017 out PathList result) { 2018 2019 result = new PathList (); 2020 2021 PathList r1; 2022 PathList r2; 2023 2024 foreach (Path path2 in pl.paths) { 2025 if (path2 != path1) { 2026 reset_intersections (path1); 2027 reset_intersections (path2); 2028 2029 if (add_merge_intersection_point (path1, path2, start1, stop1)) { 2030 if (add_merge_intersection_point (path1, path2, start2, stop2)) { 2031 2032 r1 = get_remaining_points (path1.copy ()); 2033 r2 = get_remaining_points (path2.copy ()); 2034 2035 if (r1.paths.size != 2) { 2036 warning (@"Expecting two paths in r1 found $(r1.paths.size)\n"); 2037 reset_intersections (path1); 2038 reset_intersections (path2); 2039 return true; 2040 } 2041 2042 if (r2.paths.size != 2) { 2043 warning (@"Expecting two paths in r2 found $(r2.paths.size)\n"); 2044 reset_intersections (path1); 2045 reset_intersections (path2); 2046 return true; 2047 } 2048 2049 pl.paths.remove (path1); 2050 pl.paths.remove (path2); 2051 2052 double d1 = Path.point_distance (r1.paths.get (0).get_first_point (), 2053 r2.paths.get (0).get_first_point ()); 2054 2055 double d2 = Path.point_distance (r1.paths.get (0).get_first_point (), 2056 r2.paths.get (1).get_first_point ()); 2057 2058 Path m1, m2; 2059 2060 if (d1 > d2) { 2061 m1 = PenTool.merge_open_paths (r1.paths.get (0), r2.paths.get (0)); 2062 m2 = PenTool.merge_open_paths (r1.paths.get (1), r2.paths.get (1)); 2063 } else { 2064 m1 = PenTool.merge_open_paths (r1.paths.get (1), r2.paths.get (0)); 2065 m2 = PenTool.merge_open_paths (r1.paths.get (0), r2.paths.get (1)); 2066 } 2067 2068 result.add (m1); 2069 result.add (m2); 2070 2071 return true; 2072 } else { 2073 reset_intersections (path1); 2074 reset_intersections (path2); 2075 } 2076 } else { 2077 reset_intersections (path1); 2078 reset_intersections (path2); 2079 } 2080 } 2081 } 2082 2083 return false; 2084 } 2085 2086 static void reset_intersections (Path p) { 2087 foreach (EditPoint ep in p.points) { 2088 ep.flags &= uint.MAX ^ EditPoint.INTERSECTION; 2089 ep.flags &= uint.MAX ^ EditPoint.COPIED; 2090 ep.deleted = false; 2091 } 2092 p.remove_points_on_points (); 2093 } 2094 2095 static bool add_merge_intersection_point (Path path1, Path path2, EditPoint first, EditPoint next) { 2096 double ix, iy; 2097 bool intersection; 2098 2099 intersection = false; 2100 ix = 0; 2101 iy = 0; 2102 path2.all_segments ((p1, p2) => { 2103 int i; 2104 2105 intersection = segments_intersects (first, next, p1, p2, out ix, out iy); 2106 2107 if (intersection) { 2108 add_intersection (path1, first, next, ix, iy); 2109 add_intersection (path2, p1, p2, ix, iy); 2110 2111 i = mark_intersection_as_deleted (path1); 2112 i = mark_intersection_as_deleted (path2); 2113 } 2114 2115 return !intersection; 2116 }); 2117 2118 return intersection; 2119 } 2120 2121 static bool is_inside_of_path (PointSelection ps, PathList pl, out Path outline) { 2122 outline = new Path (); 2123 foreach (Path p in pl.paths) { 2124 if (p != ps.path) { 2125 if (is_inside (ps.point, p)) { 2126 outline = p; 2127 return true; 2128 } 2129 } 2130 } 2131 2132 return false; 2133 } 2134 2135 static PathList get_all_parts (PathList pl) { 2136 PathList m; 2137 bool intersects = false; 2138 PathList r = new PathList (); 2139 2140 foreach (Path p in pl.paths) { 2141 if (has_self_intersection (p)) { 2142 m = get_parts (p); 2143 r.append (m); 2144 intersects = true; 2145 } else { 2146 r.add (p); 2147 } 2148 } 2149 2150 foreach (Path p in r.paths) { 2151 p.close (); 2152 p.update_region_boundaries (); 2153 } 2154 2155 if (intersects) { 2156 return get_all_parts (r); 2157 } 2158 2159 return r; 2160 } 2161 2162 public static PathList merge (PathList pl) { 2163 bool error = false; 2164 PathList m; 2165 PathList r = pl; 2166 Path p1, p2; 2167 2168 r = get_all_parts (r); 2169 2170 while (paths_has_intersection (r, out p1, out p2)) { 2171 if (merge_path (p1, p2, out m, out error)) { 2172 r.paths.remove (p1); 2173 r.paths.remove (p2); 2174 2175 foreach (Path np in m.paths) { 2176 np.remove_points_on_points (); 2177 r.add (np); 2178 } 2179 2180 r = get_all_parts (r); 2181 } else { 2182 warning ("Not merged."); 2183 error = true; 2184 } 2185 2186 if (error) { 2187 warning ("Merge error"); 2188 break; 2189 } 2190 } 2191 2192 if (!error) { 2193 remove_merged_parts (r); 2194 } 2195 2196 foreach (Path p in r.paths) { 2197 p.close (); 2198 p.recalculate_linear_handles (); 2199 } 2200 2201 return r; 2202 } 2203 2204 static void remove_merged_parts (PathList r) { 2205 Gee.ArrayList<Path> remove = new Gee.ArrayList<Path> (); 2206 int c; 2207 2208 foreach (Path p in r.paths) { 2209 p.update_region_boundaries (); 2210 } 2211 2212 foreach (Path p in r.paths) { 2213 c = counters (r, p); 2214 2215 if (c % 2 == 0) { 2216 if (!p.is_clockwise ()) { 2217 remove.add (p); 2218 } 2219 } else { 2220 if (p.is_clockwise ()) { 2221 remove.add (p); 2222 } 2223 } 2224 } 2225 2226 foreach (Path p in remove) { 2227 r.paths.remove (p); 2228 } 2229 } 2230 2231 public static PathList get_insides (PathList pl, Path path) { 2232 bool inside = false; 2233 PathList insides = new PathList (); 2234 2235 foreach (Path p in pl.paths) { 2236 if (p.points.size > 1 2237 && p != path 2238 && path.boundaries_intersecting (p)) { 2239 2240 inside = true; 2241 foreach (EditPoint ep in path.points) { 2242 if (!is_inside (ep, p)) { 2243 inside = false; 2244 break; 2245 } 2246 } 2247 2248 if (inside) { 2249 insides.add (path); 2250 } 2251 } 2252 } 2253 2254 return insides; 2255 } 2256 2257 public static int counters (PathList pl, Path path) { 2258 int inside_count = 0; 2259 bool inside; 2260 2261 foreach (Path p in pl.paths) { 2262 inside = true; 2263 2264 if (p.points.size > 1 2265 && p != path 2266 && path.boundaries_intersecting (p)) { 2267 2268 foreach (EditPoint ep in path.points) { 2269 if (!is_inside (ep, p)) { 2270 inside = false; 2271 break; 2272 } 2273 } 2274 2275 if (inside) { 2276 inside_count++; 2277 } 2278 } 2279 } 2280 2281 return inside_count; 2282 } 2283 2284 public static bool is_inside (EditPoint point, Path path) { 2285 EditPoint prev; 2286 bool inside = false; 2287 2288 if (path.points.size <= 1) { 2289 return false; 2290 } 2291 2292 prev = path.points.get (path.points.size - 1); 2293 2294 foreach (EditPoint p in path.points) { 2295 // FIXME: double check stroke 2296 if ((fabs (p.x - point.x) < 0.1 && fabs (p.y - point.y) < 0.1) 2297 || (fabs (prev.x - point.x) < 0.1 && fabs (prev.y - point.y) < 0.1)) { 2298 return true; 2299 } else if ((p.y > point.y) != (prev.y > point.y) 2300 && point.x < (prev.x - p.x) * (point.y - p.y) / (prev.y - p.y) + p.x) { 2301 inside = !inside; 2302 } 2303 2304 prev = p; 2305 } 2306 2307 return inside; 2308 } 2309 2310 public static int insides (EditPoint point, Path path) { 2311 EditPoint prev; 2312 int inside = 0; 2313 2314 if (path.points.size <= 1) { 2315 return 0; 2316 } 2317 2318 prev = path.get_last_point (); 2319 2320 foreach (EditPoint start in path.points) { 2321 if (start.x == point.x && point.y == start.y) { 2322 inside++; 2323 } else if ((start.y > point.y) != (prev.y > point.y) 2324 && point.x < (prev.x - start.x) * (point.y - start.y) / (prev.y - start.y) + start.x) { 2325 inside++; 2326 } 2327 2328 prev = start; 2329 } 2330 2331 return inside; 2332 } 2333 2334 static bool merge_path (Path path1, Path path2, out PathList merged_paths, out bool error) { 2335 IntersectionList intersections; 2336 EditPoint ep1, next, p1, p2, pp1, pp2; 2337 Path path, other, merged; 2338 PathList r, other_paths, result; 2339 bool intersects; 2340 int s, i; 2341 double ix, iy, iix, iiy; 2342 bool merge = false; 2343 EditPoint intersection_point, other_intersection_point; 2344 bool path1_direction, path2_direction; 2345 2346 if (path1.points.size == 0) { 2347 return false; 2348 } 2349 2350 if (path2.points.size == 0) { 2351 return false; 2352 } 2353 2354 error = false; 2355 merged_paths = new PathList (); 2356 intersections = new IntersectionList (); 2357 2358 reset_intersections (path1); 2359 reset_intersections (path2); 2360 2361 iix = 0; 2362 iiy = 0; 2363 2364 result = new PathList (); 2365 2366 if (path1.points.size == 0 || path2.points.size == 0) { 2367 warning ("No points in path."); 2368 error = true; 2369 return false; 2370 } 2371 2372 if (!path1.boundaries_intersecting (path2)) { 2373 return false; 2374 } 2375 2376 Path original_path1 = path1.copy (); 2377 Path original_path2 = path2.copy (); 2378 2379 s = 0; 2380 foreach (EditPoint e in original_path1.points) { 2381 if (!is_inside (e, original_path2) 2382 && insides (e, original_path1) == 1) { // FIXME: later as well 2383 break; 2384 } 2385 s++; 2386 } 2387 2388 if (s >= path1.points.size) { 2389 Path t; 2390 t = original_path1; 2391 original_path1 = original_path2; 2392 original_path2 = t; 2393 s = 0; 2394 foreach (EditPoint e in original_path1.points) { 2395 if (!is_inside (e, original_path2)) { 2396 break; 2397 } 2398 s++; 2399 } 2400 } 2401 2402 if (s >= original_path1.points.size) { 2403 warning ("No start point found."); 2404 error = true; 2405 return false; 2406 } 2407 2408 path = original_path1; 2409 other = original_path2; 2410 2411 other_paths = new PathList (); 2412 r = new PathList (); 2413 other_paths.add (path); 2414 other_paths.add (other); 2415 intersects = false; 2416 p1 = new EditPoint (); 2417 p2 = new EditPoint (); 2418 pp1 = new EditPoint (); 2419 pp2 = new EditPoint (); 2420 2421 ix = 0; 2422 iy = 0; 2423 i = s; 2424 merged = new Path (); 2425 2426 path1_direction = is_clockwise (original_path1); 2427 path2_direction = is_clockwise (original_path1); 2428 2429 while (true) { 2430 ep1 = path.points.get (i % path.points.size); 2431 next = path.points.get ((i + 1) % path.points.size); 2432 2433 if ((ep1.flags & EditPoint.COPIED) > 0) { 2434 if (merge) { 2435 merged.close (); 2436 result.add (merged.copy ()); 2437 } 2438 2439 merged = new Path (); 2440 2441 bool find_parts = false; 2442 Intersection new_start = new Intersection.empty (); 2443 foreach (Intersection inter in intersections.points) { 2444 if (!inter.done && !find_parts) { 2445 find_parts = true; 2446 new_start = inter; 2447 break; 2448 } 2449 } 2450 2451 if (!find_parts) { 2452 break; // done, no more parts to merge 2453 } else { 2454 // set start point for next part 2455 path = new_start.other_path; 2456 2457 if (path.points.size == 0) { 2458 warning ("No points."); 2459 error = true; 2460 return false; 2461 } 2462 2463 i = index_of (path, new_start.get_point (path)); 2464 2465 if (i < 0) { 2466 warning ("Start point not found."); 2467 error = true; 2468 return false; 2469 } 2470 2471 ep1 = path.points.get (i % path.points.size); 2472 next = path.points.get ((i + 1) % path.points.size); 2473 2474 if ((ep1.flags & EditPoint.INTERSECTION) == 0) { 2475 warning ("Not starting on an intersection point."); 2476 error = true; 2477 return false; 2478 } 2479 2480 new_start.done = true; 2481 } 2482 } 2483 2484 intersects = false; 2485 2486 double dm; 2487 double d; 2488 2489 if ((ep1.flags & EditPoint.INTERSECTION) > 0) { 2490 Intersection current_intersection; 2491 bool continue_on_other_path; 2492 current_intersection = intersections.get_point (ep1, out continue_on_other_path); 2493 current_intersection.done = true; 2494 2495 // take the other part of an intersection 2496 if ((next.flags & EditPoint.COPIED) != 0) { 2497 bool next_is_intersection = false; 2498 bool next_continue_on_other_path; 2499 2500 next_is_intersection = ((next.flags & EditPoint.INTERSECTION) > 0); 2501 2502 if (next_is_intersection) { 2503 Intersection next_intersection = intersections.get_point (next, out next_continue_on_other_path); 2504 2505 ep1.flags |= EditPoint.COPIED; 2506 merged.add_point (ep1.copy ()); 2507 2508 if (!next_intersection.done) { 2509 EditPoint new_start_point; 2510 2511 path = next_continue_on_other_path 2512 ? next_intersection.other_path : next_intersection.path; 2513 2514 new_start_point = next_continue_on_other_path 2515 ? next_intersection.other_point : next_intersection.point; 2516 2517 i = index_of (path, new_start_point); 2518 2519 if (i < 0) { 2520 warning ("Point not found in path.\n"); 2521 error = true; 2522 return false; 2523 } 2524 2525 ep1 = path.points.get (i % path.points.size); 2526 next = path.points.get ((i + 1) % path.points.size); 2527 } else { 2528 warning ("Part is already created.\n"); 2529 error = true; 2530 return false; 2531 } 2532 } else { 2533 ep1.flags |= EditPoint.COPIED; 2534 merged.add_point (ep1.copy ()); 2535 2536 EditPoint new_start_point; 2537 2538 if ((next.flags & EditPoint.COPIED) > 0) { 2539 path = current_intersection.get_other_path (path); 2540 new_start_point = current_intersection.get_point (path); 2541 i = index_of (path, new_start_point); 2542 2543 if (i < 0) { 2544 warning ("Point not found in path."); 2545 error = true; 2546 return false; 2547 } 2548 2549 ep1 = path.points.get (i % path.points.size); 2550 next = path.points.get ((i + 1) % path.points.size); 2551 2552 if ((next.flags & EditPoint.INTERSECTION) > 0) { 2553 warning ("Wrong type."); 2554 error = true; 2555 return false; 2556 } 2557 2558 if ((next.flags & EditPoint.COPIED) > 0) { 2559 merged.add_point (ep1.copy ()); 2560 continue; 2561 } 2562 } else { 2563 ep1.flags |= EditPoint.COPIED; 2564 merged.add_point (ep1.copy ()); 2565 } 2566 } 2567 } else { 2568 ep1.flags |= EditPoint.COPIED; 2569 2570 if (path1_direction == path2_direction) { 2571 if (!is_inside (ep1, original_path1)) { 2572 merged.add_point (ep1.copy ()); 2573 } 2574 } else { 2575 merged.add_point (ep1.copy ()); 2576 } 2577 } 2578 2579 current_intersection.done = true; 2580 } else { 2581 // find new intersection 2582 dm = double.MAX; 2583 foreach (Path o in other_paths.paths) { 2584 bool inter = segment_intersects (o, ep1, next, out iix, out iiy, 2585 out pp1, out pp2, false, false); 2586 d = Path.distance (ep1.x, iix, ep1.y, iiy); 2587 if (d < dm && inter) { 2588 other = o; 2589 dm = d; 2590 intersects = true; 2591 p1 = pp1; 2592 p2 = pp2; 2593 ix = iix; 2594 iy = iiy; 2595 } 2596 2597 if (d < 0.0001) { 2598 intersects = false; 2599 } 2600 } 2601 2602 if (intersects) { 2603 merged.add_point (ep1.copy ()); 2604 ep1.flags |= EditPoint.COPIED; 2605 2606 intersection_point = add_intersection (path, ep1, next, ix, iy); 2607 other_intersection_point = add_intersection (other, p1, p2, ix, iy); 2608 2609 bool g = false; 2610 foreach (Intersection old_intersection in intersections.points) { 2611 if (old_intersection.point == intersection_point 2612 || old_intersection.other_point == other_intersection_point) { 2613 old_intersection.done = true; 2614 g = true; 2615 } 2616 } 2617 2618 if (!g) { 2619 Intersection ip = new Intersection (intersection_point, path, other_intersection_point, other); 2620 intersections.points.add (ip); 2621 } 2622 2623 merged.add_point (intersection_point.copy ()); 2624 intersection_point.flags |= EditPoint.COPIED; 2625 2626 i = index_of (other, other_intersection_point); 2627 2628 if (i < 0) { 2629 warning (@"Point not found ($i)."); 2630 break; 2631 } 2632 2633 path = other; 2634 merge = true; 2635 } else { 2636 ep1.flags |= EditPoint.COPIED; 2637 merged.add_point (ep1.copy ()); 2638 2639 PointSelection ps; 2640 Path outline; 2641 2642 // remove points inside of path 2643 if (is_clockwise (original_path2)) { 2644 ps = new PointSelection (ep1, merged); 2645 if (is_inside_of_path (ps, result, out outline)) { 2646 ep1.deleted = true; 2647 } 2648 } 2649 } 2650 } 2651 2652 i++; 2653 } 2654 2655 if (merge) { 2656 original_path1.remove_points_on_points (); 2657 original_path2.remove_points_on_points (); 2658 original_path1.remove_deleted_points (); 2659 original_path2.remove_deleted_points (); 2660 2661 foreach (Path np in result.paths) { 2662 Path p = np.copy (); 2663 bool has_direction = true; 2664 2665 p.remove_points_on_points (); 2666 2667 if (has_direction) { 2668 p.close (); 2669 reset_intersections (p); 2670 merged_paths.append (get_parts (p)); 2671 p.update_region_boundaries (); 2672 p.recalculate_linear_handles (); 2673 } 2674 } 2675 } 2676 2677 return merge && !error; 2678 } 2679 2680 static int index_of (Path p, EditPoint ep) { 2681 int i = 0; 2682 foreach (EditPoint e in p.points) { 2683 if (e == ep) { 2684 return i; 2685 } 2686 i++; 2687 } 2688 2689 return -1; 2690 } 2691 2692 public static int counters_in_point_in_path (Path p, EditPoint ep) { 2693 int inside_count = 0; 2694 bool inside; 2695 2696 if (p.points.size > 1) { 2697 inside = true; 2698 2699 if (!is_inside (ep, p)) { 2700 inside = false; 2701 } 2702 2703 if (inside) { 2704 inside_count++; 2705 } 2706 } 2707 2708 return inside_count; 2709 } 2710 2711 static int mark_intersection_as_deleted (Path path) { 2712 int i = 0; 2713 2714 foreach (EditPoint p in path.points) { 2715 if ((p.flags & EditPoint.INTERSECTION) > 0) { 2716 p.deleted = true; 2717 i++; 2718 } 2719 } 2720 2721 return i; 2722 } 2723 2724 /** @param n number of interrsections to find per path. */ 2725 static bool add_intersection_points (Path path1, Path path2, int n = 1) { 2726 bool intersection = false; 2727 int found = 0; 2728 2729 path1.all_segments ((ep1, ep2) => { 2730 double ix, iy; 2731 EditPoint p1, p2; 2732 bool i; 2733 2734 i = segment_intersects (path2, ep1, ep2, out ix, out iy, 2735 out p1, out p2, true); 2736 2737 if (i) { 2738 add_intersection (path1, ep1, ep2, ix, iy); 2739 add_intersection (path2, p1, p2, ix, iy); 2740 intersection = true; 2741 found++; 2742 return found < n; 2743 } 2744 2745 return true; 2746 }); 2747 2748 if (intersection && found != n) { 2749 warning (@"Wrong number of points, $found != $n"); 2750 } 2751 2752 return intersection; 2753 } 2754 2755 /** @param n number of interrsections to find per path. */ 2756 static bool has_intersection (Path path1, Path path2) { 2757 bool intersection = false; 2758 2759 if (!path1.boundaries_intersecting (path2)) { 2760 return false; 2761 } 2762 2763 path1.all_segments ((ep1, ep2) => { 2764 double ix, iy; 2765 EditPoint p1, p2; 2766 bool i; 2767 2768 i = segment_intersects (path2, ep1, ep2, out ix, out iy, 2769 out p1, out p2, true); 2770 2771 if (i) { 2772 intersection = true; 2773 } 2774 2775 return !intersection; 2776 }); 2777 2778 return intersection; 2779 } 2780 2781 static bool paths_has_intersection (PathList r, out Path path1, out Path path2) { 2782 int i, j; 2783 path1 = new Path (); 2784 path2 = new Path (); 2785 2786 i = 0; 2787 foreach (Path p1 in r.paths) { 2788 2789 j = 0; 2790 foreach (Path p2 in r.paths) { 2791 if (p1 != p2) { 2792 if (has_intersection (p1, p2)) { 2793 path1 = p1; 2794 path2 = p2; 2795 return true; 2796 } 2797 } 2798 j++; 2799 } 2800 i++; 2801 } 2802 return false; 2803 } 2804 2805 public static bool has_points_outside (PathList pl, Path p) { 2806 if (pl.paths.size != 2) { 2807 warning ("Stroke should only create two parts."); 2808 return false; 2809 } 2810 2811 foreach (Path path in pl.paths) { 2812 if (path != p) { 2813 foreach (EditPoint ep in p.points) { 2814 if (!is_inside (ep, path)) { 2815 return true; 2816 } 2817 } 2818 } 2819 } 2820 2821 return false; 2822 } 2823 2824 static bool is_clockwise (Path p) { 2825 double sum = 0; 2826 EditPoint p1, p2; 2827 2828 EditPointHandle l, r; 2829 2830 p.recalculate_linear_handles (); 2831 2832 if (p.points.size < 3) { 2833 return true; 2834 } 2835 2836 for (int i = 0; i < p.points.size; i++) { 2837 p1 = p.points.get (i); 2838 p2 = p.points.get ((i + 1) % p.points.size); 2839 2840 l = p1.get_left_handle (); 2841 r = p1.get_right_handle (); 2842 if (!(fabs (l.angle - r.angle) < 0.0001 && l.length > 0.01 && r.length > 0.01)) { 2843 sum += (p2.x - p1.x) * (p2.y + p1.y); 2844 } 2845 } 2846 2847 return sum > 0; 2848 } 2849 2850 public static PathList create_stroke (Path original_path, 2851 double thickness, bool fast) { 2852 2853 PathList pl; 2854 EditPoint p1, p2, p3; 2855 EditPoint previous, previous_inside, start, start_inside; 2856 2857 Path side1, side2; 2858 2859 double x, y, x2, y2, x3, y3; 2860 int size; 2861 bool flat, f_next, f_bigger; 2862 int i; 2863 2864 double tolerance; 2865 double step_increment; 2866 double step_size; 2867 EditPoint corner1, corner1_inside; 2868 double step; 2869 double min_increment; 2870 2871 EditPointHandle l, r; 2872 2873 Path path = original_path.copy (); 2874 2875 int keep; 2876 bool on_curve; 2877 2878 pl = new PathList (); 2879 size = path.is_open () ? path.points.size - 1 : path.points.size; 2880 2881 side1 = new Path (); 2882 side2 = new Path (); 2883 2884 foreach (EditPoint ph in path.points) { 2885 if (ph.type == PointType.HIDDEN) { 2886 ph.type = PointType.CUBIC; 2887 } 2888 } 2889 path.remove_deleted_points (); 2890 2891 if (path.points.size < 2) { 2892 return pl; 2893 } 2894 2895 previous = new EditPoint (); 2896 previous_inside = new EditPoint (); 2897 corner1 = new EditPoint (); 2898 corner1_inside = new EditPoint (); 2899 2900 if (path.is_open () || fast) { 2901 p1 = path.points.get (0); 2902 p2 = path.points.get (1 % path.points.size); 2903 2904 get_segment (thickness, 0, 0.00001, p1, p2, out start); 2905 get_segment (-thickness, 0, 0.00001, p1, p2, out start_inside); 2906 2907 previous = start.copy (); 2908 previous_inside = start_inside.copy (); 2909 2910 previous.flags |= EditPoint.CURVE_KEEP; 2911 previous_inside.flags |= EditPoint.CURVE_KEEP; 2912 2913 side1.add_point (previous); 2914 side2.add_point (previous_inside); 2915 } 2916 2917 min_increment = 0.013; 2918 2919 for (i = 0; i < size; i++) { 2920 p1 = path.points.get (i % path.points.size); 2921 p2 = path.points.get ((i + 1) % path.points.size); 2922 p3 = path.points.get ((i + 2) % path.points.size); 2923 2924 tolerance = 0.01; // 0.13 / sqrt (stroke_width) 2925 step_increment = 1.05; 2926 step_size = 0.039; // / stroke_width; 2927 2928 corner1 = new EditPoint (); 2929 2930 if (p1.type == PointType.HIDDEN 2931 || p2.type == PointType.HIDDEN) { 2932 continue; 2933 } 2934 2935 get_segment (thickness, 0, 0.00001, p1, p2, out start); 2936 get_segment (-thickness, 0, 0.00001, p1, p2, out start_inside); 2937 2938 previous = start.copy (); 2939 previous_inside = start_inside.copy (); 2940 2941 previous.flags |= EditPoint.CURVE | EditPoint.SEGMENT_END; 2942 previous_inside.flags |= EditPoint.CURVE | EditPoint.SEGMENT_END; 2943 2944 side1.add_point (previous); 2945 side2.add_point (previous_inside); 2946 2947 step = step_size; 2948 keep = 0; 2949 step_size = 0.01; 2950 2951 while (step < 1 - 2 * step_size) { 2952 Path.get_point_for_step (p1, p2, step, out x, out y); 2953 Path.get_point_for_step (p1, p2, step + step_size, out x2, out y2); 2954 Path.get_point_for_step (p1, p2, step + 2 * step_size, out x3, out y3); 2955 2956 flat = is_flat (x, y, x2, y2, x3, y3, tolerance); 2957 2958 Path.get_point_for_step (p1, p2, step, out x, out y); 2959 Path.get_point_for_step (p1, p2, step + step_size / step_increment, out x2, out y2); 2960 Path.get_point_for_step (p1, p2, step + 2 * step_size / step_increment, out x3, out y3); 2961 2962 f_next = is_flat (x, y, x2, y2, x3, y3, tolerance); 2963 2964 Path.get_point_for_step (p1, p2, step, out x, out y); 2965 Path.get_point_for_step (p1, p2, step + step_size * step_increment, out x2, out y2); 2966 Path.get_point_for_step (p1, p2, step + 2 * step_size * step_increment, out x3, out y3); 2967 2968 f_bigger = is_flat (x, y, x2, y2, x3, y3, tolerance); 2969 2970 if (!flat && !f_next && step_size > min_increment) { 2971 step_size /= step_increment; 2972 continue; 2973 } 2974 2975 if (flat && f_bigger && step_size < 0.1) { 2976 step_size *= step_increment; 2977 continue; 2978 } 2979 2980 get_segment (thickness, step, step_size, p1, p2, out corner1); 2981 get_segment (-thickness, step, step_size, p1, p2, out corner1_inside); 2982 2983 previous.get_right_handle ().length *= step_size; 2984 corner1.get_left_handle ().length *= step_size; 2985 previous_inside.get_right_handle ().length *= step_size; 2986 corner1_inside.get_left_handle ().length *= step_size; 2987 2988 previous = corner1.copy (); 2989 previous_inside = corner1_inside.copy (); 2990 2991 if (keep == 0 && step > 0.3) { // keep two points per segment 2992 on_curve = true; 2993 keep++; 2994 } else if (keep == 1 && step > 0.6) { 2995 on_curve = true; 2996 keep++; 2997 } else { 2998 on_curve = false; 2999 } 3000 3001 if (!on_curve) { 3002 previous.flags |= EditPoint.CURVE; 3003 previous_inside.flags |= EditPoint.CURVE; 3004 } else { 3005 previous.flags |= EditPoint.CURVE_KEEP; 3006 previous_inside.flags |= EditPoint.CURVE_KEEP; 3007 } 3008 3009 side1.add_point (previous); 3010 side2.add_point (previous_inside); 3011 3012 step += step_size; 3013 } 3014 3015 previous.get_right_handle ().length *= step_size; 3016 corner1.get_left_handle ().length *= step_size; 3017 previous_inside.get_right_handle ().length *= step_size; 3018 corner1_inside.get_left_handle ().length *= step_size; 3019 3020 get_segment (thickness, 1 - 0.00001, 0.00001, p1, p2, out corner1); 3021 get_segment (-thickness, 1 - 0.00001, 0.00001, p1, p2, out corner1_inside); 3022 3023 previous = corner1.copy (); 3024 previous_inside = corner1_inside.copy (); 3025 3026 previous.get_right_handle ().length *= step_size; 3027 previous.get_left_handle ().length *= step_size; 3028 previous_inside.get_right_handle ().length *= step_size; 3029 previous_inside.get_left_handle ().length *= step_size; 3030 3031 previous.flags |= EditPoint.CURVE | EditPoint.SEGMENT_END; 3032 previous_inside.flags |= EditPoint.CURVE | EditPoint.SEGMENT_END; 3033 3034 side1.add_point (previous); 3035 side2.add_point (previous_inside); 3036 3037 l = p2.get_left_handle (); 3038 r = p2.get_right_handle (); 3039 3040 if (fabs ((l.angle + r.angle + PI) % (2 * PI) - PI) > 0.005) { // FIXME: 0.01 3041 if (!path.is_open () || i < size - 1) { 3042 get_segment (thickness, 0, 0.00001, p2, p3, out start); 3043 add_corner (side1, previous, start, p2.copy (), thickness); 3044 3045 get_segment (-thickness, 0, 0.00001, p2, p3, out start); 3046 add_corner (side2, previous_inside, start, p2.copy (), thickness); 3047 } 3048 } 3049 3050 if (fast) { 3051 EditPoint s1, s2; 3052 bool open; 3053 3054 convert_to_curve (side1); 3055 convert_to_curve (side2); 3056 3057 side2.reverse (); 3058 s1 = side1.get_last_point ().copy (); 3059 s2 = side2.get_first_point ().copy (); 3060 3061 s1.flags &= EditPoint.CURVE ^ EditPoint.ALL; 3062 s2.flags &= EditPoint.CURVE ^ EditPoint.ALL; 3063 3064 s1.convert_to_line (); 3065 s2.convert_to_line (); 3066 3067 open = path.is_open (); 3068 3069 if (!open) { 3070 path.reopen (); 3071 } 3072 3073 pl.append (merge_stroke_parts (path, side1, side2)); 3074 3075 if (!open) { 3076 path.close (); 3077 } 3078 3079 side1 = new Path (); 3080 side2 = new Path (); 3081 3082 get_segment (thickness, 0, 0.00001, p2, p3, out start); 3083 get_segment (-thickness, 0, 0.00001, p2, p3, out start_inside); 3084 3085 previous = start.copy (); 3086 previous_inside = start_inside.copy (); 3087 3088 previous.flags |= EditPoint.CURVE; 3089 previous_inside.flags |= EditPoint.CURVE; 3090 3091 side1.add_point (previous); 3092 side2.add_point (previous_inside); 3093 } 3094 } 3095 3096 if (!fast) { 3097 side1.remove_points_on_points (); 3098 side2.remove_points_on_points (); 3099 3100 convert_to_curve (side1); 3101 convert_to_curve (side2); 3102 3103 side2.reverse (); 3104 3105 pl = merge_stroke_parts (path, side1, side2); 3106 } 3107 3108 if (fast) { 3109 foreach (Path p in pl.paths) { 3110 p.close (); 3111 convert_to_curve (p); 3112 } 3113 } 3114 3115 return pl; 3116 } 3117 3118 static void convert_to_curve (Path path) { 3119 if (path.is_open ()) { 3120 path.get_first_point ().flags &= EditPoint.ALL ^ EditPoint.CURVE; 3121 path.get_last_point ().flags &= EditPoint.ALL ^ EditPoint.CURVE; 3122 } 3123 3124 path.recalculate_linear_handles (); 3125 path.remove_points_on_points (); 3126 3127 foreach (EditPoint ep in path.points) { 3128 if ((ep.flags & EditPoint.SEGMENT_END) == 0) { 3129 if ((ep.flags & EditPoint.CURVE) > 0 || (ep.flags & EditPoint.CURVE_KEEP) > 0) { 3130 ep.convert_to_curve (); 3131 } 3132 } 3133 } 3134 3135 foreach (EditPoint ep in path.points) { 3136 if ((ep.flags & EditPoint.SEGMENT_END) == 0) { 3137 if ((ep.flags & EditPoint.CURVE) > 0 || (ep.flags & EditPoint.CURVE_KEEP) > 0) { 3138 ep.set_tie_handle (true); 3139 } 3140 } 3141 } 3142 3143 foreach (EditPoint ep in path.points) { 3144 if ((ep.flags & EditPoint.SEGMENT_END) == 0) { 3145 if ((ep.flags & EditPoint.CURVE) > 0 || (ep.flags & EditPoint.CURVE_KEEP) > 0) { 3146 ep.process_tied_handle (); 3147 } 3148 } 3149 } 3150 } 3151 3152 public static void get_segment (double stroke_thickness, double step, double step_size, 3153 EditPoint p1, EditPoint p2, out EditPoint ep1) { 3154 3155 double thickness = stroke_thickness / 2; 3156 Path overlay; 3157 double x, y, x2, y2, x3, y3; 3158 EditPoint corner1, corner2, corner3; 3159 PointType type; 3160 3161 Path.get_point_for_step (p1, p2, step, out x, out y); 3162 Path.get_point_for_step (p1, p2, step + step_size, out x2, out y2); 3163 Path.get_point_for_step (p1, p2, step + 2 * step_size, out x3, out y3); 3164 3165 overlay = new Path (); 3166 3167 type = p1.get_right_handle ().type; 3168 corner1 = new EditPoint (x, y, type); 3169 corner2 = new EditPoint (x2, y2, type); 3170 corner3 = new EditPoint (x3, y3, type); 3171 3172 corner2.convert_to_line (); 3173 3174 overlay.add_point (corner1); 3175 overlay.add_point (corner2); 3176 overlay.add_point (corner3); 3177 3178 overlay.close (); 3179 overlay.recalculate_linear_handles (); 3180 3181 move_segment (corner1, corner2, thickness); 3182 3183 ep1 = corner2; 3184 } 3185 3186 public static PathList merge_stroke_parts (Path p, Path side1, Path side2) { 3187 Path merged = new Path (); 3188 PathList paths = new PathList (); 3189 3190 if (!p.is_open ()) { 3191 side1.update_region_boundaries (); 3192 paths.add (side1); 3193 side2.update_region_boundaries (); 3194 paths.add (side2); 3195 side1.close (); 3196 side2.close (); 3197 } else if (p.is_open ()) { 3198 side2.reverse (); 3199 merged = merge_strokes (p, side1, side2); 3200 merged.close (); 3201 merged.update_region_boundaries (); 3202 paths.add (merged); 3203 merged.reverse (); 3204 } else { 3205 warning ("Can not create stroke."); 3206 paths.add (p); 3207 } 3208 3209 return paths; 3210 } 3211 } 3212 3213 } 3214