The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

StrokeTool.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

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

Revisions

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