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