The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

StrokeTool.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

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

Revisions

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