The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

StrokeTool.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

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

Revisions

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