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