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.
Adjust handle position in stroke
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 class StrokeTool : Tool { 21 22 public static double stroke_width = 0; 23 public static bool add_stroke = false; 24 25 public static bool show_stroke_tools = false; 26 public static bool stroke_selected = false; 27 28 public StrokeTool (string tooltip) { 29 } 30 31 /** Create strokes for the selected outlines. */ 32 public static void stroke_selected_paths () { 33 Glyph g = MainWindow.get_current_glyph (); 34 PathList paths = new PathList (); 35 36 stroke_selected = true; // FIXME: delete 37 38 g.store_undo_state (); 39 40 foreach (Path p in g.active_paths) { 41 if (p.stroke > 0) { 42 paths.append (p.get_stroke ()); 43 } 44 } 45 46 // FIXME: delete 47 bool h = false; 48 foreach (Path p in g.active_paths) { 49 if (p.stroke == 0) { 50 h = true; 51 } 52 } 53 54 if (h) { 55 PathList n = new PathList (); 56 57 foreach (Path p in g.active_paths) { 58 if (p.stroke == 0) { 59 n.add (p); 60 } 61 } 62 63 n = merge (n); 64 paths.append (n); 65 } 66 67 if (paths.paths.size > 0) { 68 foreach (Path p in g.active_paths) { 69 g.path_list.remove (p); 70 } 71 72 g.active_paths.clear (); 73 74 foreach (Path np in paths.paths) { 75 g.add_path (np); 76 g.active_paths.add (np); 77 } 78 79 PenTool.update_orientation (); 80 81 GlyphCanvas.redraw (); 82 } 83 84 PenTool.update_orientation (); 85 stroke_selected = false; // FIXME: delete 86 } 87 88 public static PathList get_stroke_fast (Path path, double thickness) { 89 PathList o; 90 Path stroke; 91 92 stroke = path.copy (); 93 stroke.remove_points_on_points (0.3); 94 o = create_stroke (stroke, thickness); 95 96 return o; 97 } 98 99 public static PathList get_stroke (Path path, double thickness) { 100 PathList o; 101 102 o = get_stroke_fast (path, thickness); 103 o = get_all_parts (o); 104 o = merge (o); 105 106 return o; 107 } 108 109 /** Create one stroke from the outline and counter stroke and close the 110 * open endings. 111 * 112 * @param path the path to create stroke for 113 * @param stroke for the outline of path 114 * @param stroke for the counter path 115 */ 116 static Path merge_strokes (Path path, Path stroke, Path counter) { 117 Path merged; 118 EditPoint last_counter, first; 119 120 merged = stroke.copy (); 121 counter.reverse (); 122 123 counter.reverse (); 124 merged.reverse (); 125 126 last_counter = new EditPoint (); 127 first = new EditPoint (); 128 129 merged.append_path (counter); 130 131 merged.close (); 132 merged.create_list (); 133 merged.recalculate_linear_handles (); 134 135 if (path.is_open ()) { 136 first = merged.get_first_point (); 137 last_counter = merged.get_last_point (); 138 139 first.get_left_handle ().convert_to_line (); 140 first.recalculate_linear_handles (); 141 142 last_counter.get_right_handle ().convert_to_line (); 143 last_counter.recalculate_linear_handles (); 144 } 145 146 return merged; 147 } 148 149 static void adjust_handles (EditPoint stroke_start, EditPoint stroke_end, EditPoint stroke_next, 150 EditPoint start, EditPoint end, EditPoint next) { 151 double px, py; 152 double dp = Path.distance_to_point (stroke_start, stroke_end); 153 double dn = Path.distance_to_point (stroke_end, stroke_next); 154 double op = Path.distance_to_point (start, end); 155 double on = Path.distance_to_point (end, next); 156 double rp = dp / op; 157 double rn = on / dn; 158 EditPointHandle r = stroke_start.get_right_handle (); 159 EditPointHandle l = stroke_end.get_left_handle (); 160 161 if (!r.is_line ()) { 162 if (r.type == PointType.CUBIC) { 163 r.length *= rp; 164 } else { 165 Path.find_intersection_handle (r, l, out px, out py); 166 167 if (EditPoint.is_valid_position (px, py)) { 168 l.move_to_coordinate (px, py); 169 r.move_to_coordinate (px, py); 170 } else { 171 warning ("Invalid position."); 172 } 173 } 174 175 if (l.type == PointType.CUBIC) { 176 l.length *= rn; 177 } 178 } 179 } 180 181 static void move_segment (EditPoint stroke_start, EditPoint stroke_stop, double thickness) { 182 EditPointHandle r, l; 183 double m, n; 184 double qx, qy; 185 186 stroke_start.set_tie_handle (false); 187 stroke_stop.set_tie_handle (false); 188 189 r = stroke_start.get_right_handle (); 190 l = stroke_stop.get_left_handle (); 191 192 m = cos (r.angle + PI / 2) * thickness; 193 n = sin (r.angle + PI / 2) * thickness; 194 195 stroke_start.get_right_handle ().move_to_coordinate_delta (m, n); 196 stroke_start.get_left_handle ().move_to_coordinate_delta (m, n); 197 198 stroke_start.independent_x += m; 199 stroke_start.independent_y += n; 200 201 qx = cos (l.angle - PI / 2) * thickness; 202 qy = sin (l.angle - PI / 2) * thickness; 203 204 stroke_stop.get_right_handle ().move_to_coordinate_delta (qx, qy); 205 stroke_stop.get_left_handle ().move_to_coordinate_delta (qx, qy); 206 207 stroke_stop.independent_x += qx; 208 stroke_stop.independent_y += qy; 209 } 210 211 static void add_corner (Path stroked, EditPoint previous, EditPoint next, 212 EditPoint original, double stroke_width) { 213 214 double ratio; 215 double distance; 216 EditPoint corner; 217 double corner_x, corner_y; 218 EditPointHandle previous_handle; 219 EditPointHandle next_handle; 220 EditPoint cutoff1, cutoff2; 221 double adjusted_stroke = (stroke_width * 0.999999) / 2.0; 222 223 previous_handle = previous.get_left_handle (); 224 next_handle = next.get_right_handle (); 225 226 previous_handle.angle += PI; 227 next_handle.angle += PI; 228 229 Path.find_intersection_handle (previous_handle, next_handle, out corner_x, out corner_y); 230 corner = new EditPoint (corner_x, corner_y, previous.type); 231 corner.convert_to_line (); 232 233 previous_handle.angle -= PI; 234 next_handle.angle -= PI; 235 236 distance = Path.distance_to_point (corner, original); 237 ratio = 1.5 * fabs (adjusted_stroke) / distance; 238 239 if (ratio > 1) { 240 stroked.add_point (corner); 241 } else { 242 cutoff1 = new EditPoint (); 243 cutoff1.set_point_type (previous.type); 244 cutoff1.convert_to_line (); 245 246 cutoff2 = new EditPoint (); 247 cutoff2.set_point_type (previous.type); 248 cutoff2.convert_to_line (); 249 250 cutoff1.x = previous.x + (corner.x - previous.x) * ratio; 251 cutoff1.y = previous.y + (corner.y - previous.y) * ratio; 252 253 cutoff2.x = next.x + (corner.x - next.x) * ratio; 254 cutoff2.y = next.y + (corner.y - next.y) * ratio; 255 256 if (!cutoff1.is_valid () || cutoff2.is_valid ()) { 257 cutoff1 = stroked.add_point (cutoff1); 258 cutoff2 = stroked.add_point (cutoff2); 259 } 260 261 cutoff1.recalculate_linear_handles (); 262 cutoff2.recalculate_linear_handles (); 263 264 if (distance > 4 * stroke_width) { 265 previous.flags = EditPoint.NONE; 266 next.flags = EditPoint.NONE; 267 } else { 268 previous.flags |= EditPoint.NEW_CORNER; 269 next.flags |= EditPoint.NEW_CORNER; 270 } 271 } 272 } 273 274 static PathList get_parts (Path path) { 275 PathList intersections; 276 PathList r; 277 278 r = get_parts_self (path); 279 intersections = new PathList (); 280 281 foreach (Path p in r.paths) { 282 intersections.add (p); 283 } 284 285 return intersections; 286 } 287 288 static bool split_corner (PathList pl) { 289 EditPoint p1, p2; 290 EditPoint a1, a2; 291 PathList result; 292 bool split; 293 294 foreach (Path p in pl.paths) { 295 if (p.points.size == 0) { 296 continue; 297 } 298 299 for (int index = 1; index < p.points.size + 2; index++) { 300 p1 = p.points.get ((index - 1) % p.points.size); 301 p2 = p.points.get (index % p.points.size); 302 a1 = p.points.get ((index + 3) % p.points.size); // two points ahead 303 a2 = p.points.get ((index + 4) % p.points.size); 304 305 if ((p1.flags & EditPoint.STROKE_OFFSET) > 0 306 || (p2.flags & EditPoint.STROKE_OFFSET) > 0 307 || (a1.flags & EditPoint.STROKE_OFFSET) > 0 308 || (a2.flags & EditPoint.STROKE_OFFSET) > 0) { 309 310 split = split_segment (p, a1, a2, p1, p2, out result); 311 312 if (split) { 313 pl.append (result); 314 pl.paths.remove (p); 315 split_corner (pl); 316 return true; 317 } else { 318 p1 = p.points.get ((index - 1) % p.points.size); 319 p2 = p.points.get (index % p.points.size); 320 a1 = p.points.get ((index + 2) % p.points.size); // one point ahead 321 a2 = p.points.get ((index + 3) % p.points.size); 322 323 split = split_segment (p, a1, a2, p1, p2, out result); 324 325 if (split) { 326 pl.append (result); 327 pl.paths.remove (p); 328 split_corner (pl); 329 return true; 330 } else { 331 p1 = p.points.get ((index - 1) % p.points.size); 332 p2 = p.points.get (index % p.points.size); 333 a1 = p.points.get ((index + 3) % p.points.size); // two points ahead 334 a2 = p.points.get ((index + 4) % p.points.size); 335 336 if ((p1.flags & EditPoint.STROKE_OFFSET) > 0 337 && (a1.flags & EditPoint.STROKE_OFFSET) > 0) { 338 p1.flags = EditPoint.COUNTER_TO_OUTLINE; 339 a1.flags = EditPoint.COUNTER_TO_OUTLINE; 340 } 341 } 342 } 343 } 344 } 345 } 346 347 return false; 348 } 349 350 static bool split_segment (Path p, EditPoint first, EditPoint next, EditPoint p1, EditPoint p2, out PathList result) { 351 double ix, iy; 352 bool intersection; 353 int i; 354 355 result = new PathList (); 356 intersection = segments_intersects (first, next, p1, p2, out ix, out iy, true); 357 358 if (intersection) { 359 add_intersection (p, first, next, ix, iy); 360 add_intersection (p, p1, p2, ix, iy); 361 362 i = mark_intersection_as_deleted (p); 363 return_val_if_fail (i == 2, false); 364 365 result.append (get_remaining_points (p.copy ())); 366 } 367 368 return intersection; 369 } 370 371 static bool split_path (Path path1, Path path2, PathList result) { 372 PathList pl1, pl2; 373 Path a1, a2, b1, b2; 374 Path m1, m2; 375 int i; 376 377 if (path1 == path2) { 378 return false; 379 } 380 381 if (add_intersection_points (path1, path2, 2)) { 382 i = mark_intersection_as_deleted (path1); 383 return_if_fail (i == 2); 384 385 i = mark_intersection_as_deleted (path2); 386 return_if_fail (i == 2); 387 388 pl1 = get_remaining_points (path1.copy ()); 389 pl2 = get_remaining_points (path2.copy ()); 390 391 return_if_fail (pl1.paths.size == 2); 392 return_if_fail (pl2.paths.size == 2); 393 394 a1 = pl1.paths.get (0); 395 a2 = pl1.paths.get (1); 396 b1 = pl2.paths.get (0); 397 b2 = pl2.paths.get (1); 398 399 m1 = PenTool.merge_open_paths (a1, b2); 400 m2 = PenTool.merge_open_paths (b1, a2); 401 402 result.add (m1); 403 result.add (m2); 404 405 return true; 406 } 407 408 return false; 409 } 410 411 static PathList split_paths (PathList pl) { 412 PathList n = new PathList (); 413 414 n.append (pl); 415 416 foreach (Path p1 in pl.paths) { 417 foreach (Path p2 in pl.paths) { 418 if (p1 != p2) { 419 if (split_path (p1, p2, n)) { 420 n.paths.remove (p1); 421 n.paths.remove (p2); 422 return split_paths (n); 423 } 424 } 425 } 426 } 427 428 return n; 429 } 430 431 static PathList get_parts_self (Path path, PathList? paths = null) { 432 PathList pl; 433 PathList r; 434 435 r = paths == null ? new PathList () : (!) paths; 436 pl = split (path); 437 438 foreach (Path part in pl.paths) { 439 if (!has_self_intersection (part)) { 440 r.add (part); 441 } else { 442 get_parts_self (part, r); 443 } 444 } 445 446 if (r.paths.size == 0) { 447 warning ("No parts in path"); 448 } 449 450 return r; 451 } 452 453 454 static bool has_intersection_points (Path path) { 455 foreach (EditPoint p in path.points) { 456 if ((p.flags & EditPoint.INTERSECTION) > 0) { 457 return true; 458 } 459 } 460 return false; 461 } 462 463 /** Split one path at intersection points in two parts. */ 464 static PathList split (Path path) { 465 Path new_path; 466 PathList pl; 467 int i; 468 469 if (!has_intersection_points (path)) { 470 add_self_intersection_points (path); 471 } else { 472 warning ("points already created."); 473 } 474 475 foreach (EditPoint p in path.points) { 476 if (insides (p, path) == 1) { 477 path.set_new_start (p); 478 path.close (); 479 break; 480 } 481 } 482 483 i = mark_intersection_as_deleted (path); 484 485 if (!(i == 0 || i == 2)) { 486 warning (@"Split should only create two parts, $i points will be deleted."); 487 } 488 489 new_path = path.copy (); 490 pl = get_remaining_points (new_path); 491 492 return pl; 493 } 494 495 static PathList process_deleted_control_points (Path path) { 496 PathList paths, nl, pl, rl, result; 497 498 paths = new PathList (); 499 rl = new PathList (); 500 pl = new PathList (); 501 nl = new PathList (); 502 503 if (!path.has_deleted_point ()) { 504 return pl; 505 } 506 507 pl.add (path); 508 509 foreach (Path p in pl.paths) { 510 nl = p.process_deleted_points (); 511 512 if (nl.paths.size > 0) { 513 rl.append (nl); 514 rl.paths.remove (p); 515 } 516 } 517 518 result = new PathList (); 519 foreach (Path p in rl.paths) { 520 pl = process_deleted_control_points (p); 521 522 if (pl.paths.size > 0) { 523 result.append (pl); 524 } else { 525 result.add (p); 526 } 527 } 528 529 for (int i = 1; i < result.paths.size; i++) { 530 result.paths.get (i).reverse (); 531 } 532 533 paths.append (result); 534 535 return paths; 536 } 537 538 static PathList get_remaining_points (Path old_path) { 539 PathList pl; 540 541 old_path.close (); 542 pl = process_deleted_control_points (old_path); 543 544 if (pl.paths.size == 0) { 545 pl.add (old_path); 546 } 547 548 foreach (Path pn in pl.paths) { 549 pn.close (); 550 } 551 552 return pl; 553 } 554 555 static bool has_self_intersection (Path path) { 556 bool intersection = false; 557 558 path.all_segments ((ep1, ep2) => { 559 double ix, iy; 560 EditPoint p1, p2; 561 562 if (segment_intersects (path, ep1, ep2, out ix, out iy, out p1, out p2, true)) { 563 intersection = true; 564 return false; 565 } 566 567 return true; 568 }); 569 570 return intersection; 571 } 572 573 static bool add_self_intersection_points (Path path, bool only_offsets = false) { 574 bool intersection = false; 575 576 path.all_segments ((ep1, ep2) => { 577 double ix, iy; 578 EditPoint p1, p2; 579 580 if (segment_intersects (path, ep1, ep2, out ix, out iy, out p1, out p2, true, only_offsets)) { 581 add_intersection (path, ep1, ep2, ix, iy); 582 add_intersection (path, p1, p2, ix, iy); 583 584 intersection = true; 585 return false; 586 } 587 588 return true; 589 }); 590 591 return intersection; 592 } 593 594 static EditPoint add_intersection (Path path, EditPoint prev, EditPoint next, double px, double py, Color? c = null) { 595 Gee.ArrayList<EditPoint> n = new Gee.ArrayList<EditPoint> (); 596 EditPoint ep1 = new EditPoint (); 597 EditPoint ep2 = new EditPoint (); 598 EditPoint ep3 = new EditPoint (); 599 600 if (next == path.get_first_point ()) { 601 ep1.prev = null; 602 } else { 603 ep1.prev = prev; 604 } 605 606 ep1.prev = prev; 607 ep1.next = ep2; 608 ep1.flags |= EditPoint.NEW_CORNER; 609 ep1.type = prev.type; 610 ep1.x = px; 611 ep1.y = py; 612 ep1.color = c; 613 n.add (ep1); 614 615 ep2.prev = ep1; 616 ep2.next = ep3; 617 ep2.flags |= EditPoint.INTERSECTION; 618 ep2.type = prev.type; 619 ep2.x = px; 620 ep2.y = py; 621 ep2.color = c; 622 n.add (ep2); 623 624 ep3.prev = ep2; 625 ep3.next = next; 626 ep3.flags |= EditPoint.NEW_CORNER; 627 ep3.type = prev.type; 628 ep3.x = px; 629 ep3.y = py; 630 ep3.color = c; 631 n.add (ep3); 632 633 foreach (EditPoint np in n) { 634 np = path.add_point_after (np, np.prev); 635 path.create_list (); 636 } 637 638 PenTool.convert_point_to_line (ep1, true); 639 PenTool.convert_point_to_line (ep2, true); 640 PenTool.convert_point_to_line (ep3, true); 641 642 ep1.recalculate_linear_handles (); 643 ep2.recalculate_linear_handles (); 644 ep3.recalculate_linear_handles (); 645 646 return ep2; 647 } 648 649 static bool segments_intersects (EditPoint p1, EditPoint p2, EditPoint ep, EditPoint next, 650 out double ix, out double iy, 651 bool skip_points_on_points = false) { 652 double cross_x, cross_y; 653 654 ix = 0; 655 iy = 0; 656 657 if (is_line (ep.x, ep.y, p1.x, p1.y, next.x, next.y)) { 658 ix = p1.x; 659 iy = p1.y; 660 return true; 661 } 662 663 if (is_line (ep.x, ep.y, p2.x, p2.y, next.x, next.y)) { 664 ix = p2.x; 665 iy = p2.y; 666 return true; 667 } 668 669 if (is_line (p1.x, p1.y, ep.x, ep.y, p2.x, p2.y)) { 670 ix = ep.x; 671 iy = ep.y; 672 return true; 673 } 674 675 if (is_line (p1.x, p1.y, next.x, next.y, p2.x, p2.y)) { 676 ix = next.x; 677 iy = next.y; 678 return true; 679 } 680 681 Path.find_intersection_point (ep, next, p1, p2, out cross_x, out cross_y); 682 683 if (Glyph.CANVAS_MIN < cross_x < Glyph.CANVAS_MAX 684 && Glyph.CANVAS_MIN < cross_y < Glyph.CANVAS_MAX) { 685 // iterate to find intersection. 686 if (is_line (ep.x, ep.y, cross_x, cross_y, next.x, next.y) 687 && is_line (p1.x, p1.y, cross_x, cross_y, p2.x, p2.y)) { 688 689 ix = cross_x; 690 iy = cross_y; 691 692 return true; 693 } 694 } 695 696 return false; 697 } 698 699 static bool segment_intersects (Path path, EditPoint ep, EditPoint next, 700 out double ix, out double iy, 701 out EditPoint ia, out EditPoint ib, 702 bool skip_points_on_points = false, 703 bool only_offsets = false) { 704 705 EditPoint p1, p2; 706 bool intersection, inter; 707 double iix, iiy; 708 709 double distance, min_distance; 710 711 intersection = false; 712 713 ix = 0; 714 iy = 0; 715 716 iix = 0; 717 iiy = 0; 718 719 ia = new EditPoint (); 720 ib = new EditPoint (); 721 722 if (path.points.size == 0) { 723 return false; 724 } 725 726 min_distance = double.MAX; 727 p1 = path.points.get (path.points.size - 1); 728 for (int i = 0; i < path.points.size; i++) { 729 p2 = path.points.get (i); 730 731 bool is_offset = ((p1.flags & EditPoint.STROKE_OFFSET) > 0) 732 && ((p2.flags & EditPoint.STROKE_OFFSET) > 0) 733 && ((ep.flags & EditPoint.STROKE_OFFSET) > 0) 734 && ((next.flags & EditPoint.STROKE_OFFSET) > 0); 735 736 if (!only_offsets || is_offset) { 737 if (p1 != ep && p2 != next) { 738 inter = segments_intersects (p1, p2, ep, next, out iix, out iiy, 739 skip_points_on_points); 740 741 if (inter) { 742 distance = Path.distance (ep.x, iix, ep.y, iiy); 743 if (distance < min_distance) { 744 ia = p1; 745 ib = p2; 746 ix = iix; 747 iy = iiy; 748 intersection = true; 749 min_distance = distance; 750 } 751 } 752 } 753 } 754 755 p1 = p2; 756 } 757 758 return intersection; 759 } 760 761 /** @return true if p2 is on the line p1 to p3 */ 762 static bool is_line (double x1, double y1, double x2, double y2, double x3, double y3, double tolerance = 0.01) { 763 return fmin (x1, x3) <= x2 && x2 <= fmax (x1, x3) 764 && fmin (y1, y3) <= y2 && y2 <= fmax (y1, y3) 765 && is_flat (x1, y1, x2, y2, x3, y3, tolerance); 766 } 767 768 public static bool is_flat (double x1, double y1, double x2, double y2, double x3, double y3, double tolerance = 0.001) { 769 double ds = Path.distance (x1, x3, y1, y3); 770 double d1 = Path.distance (x1, x2, y1, y2); 771 double d2 = Path.distance (x2, x3, y2, y3); 772 double p = d1 / ds; 773 double x = fabs ((x3 - x1) * p - (x2 - x1)); 774 double y = fabs ((y3 - y1) * p - (y2 - y1)); 775 double d = fabs (ds - (d1 + d2)); 776 777 return ds > 0.001 && d1 > 0.001 && d2 > 0.001 778 && d < tolerance && x < tolerance && y < tolerance; 779 } 780 781 // indside becomes outside in some paths 782 static void remove_points_in_stroke (PathList pl) { 783 PathList r; 784 785 foreach (Path p in pl.paths) { 786 if (remove_points_in_stroke_for_path (p, pl, out r)) { 787 pl.append (r); 788 remove_points_in_stroke (pl); 789 return; 790 } 791 } 792 } 793 794 static bool remove_points_in_stroke_for_path (Path p, PathList pl, out PathList result) { 795 EditPoint start_ep; 796 EditPoint start_next; 797 EditPoint start_prev; 798 EditPoint end_ep = new EditPoint (); 799 EditPoint end_next; 800 EditPoint end_prev; 801 802 result = new PathList (); 803 804 for (int i = 1; i < p.points.size + 1; i++) { 805 start_prev = p.points.get ((i - 1) % p.points.size); 806 start_ep = p.points.get (i % p.points.size); 807 start_next = p.points.get ((i + 1) % p.points.size); 808 809 if ((start_ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) { 810 for (int j = i; j < p.points.size + i; j++) { 811 end_prev = p.points.get ((j - 1) % p.points.size); 812 end_ep = p.points.get (j % p.points.size); 813 end_next = p.points.get ((j + 1) % p.points.size); 814 815 816 if ((end_ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) { 817 start_ep.flags = EditPoint.NONE; 818 end_ep.flags = EditPoint.NONE; 819 820 if (merge_segments (pl, p, start_prev, start_ep, end_ep, end_next, out result)) { 821 return true; 822 } 823 } 824 } 825 } 826 827 start_ep.flags = EditPoint.NONE; 828 end_ep.flags = EditPoint.NONE; 829 } 830 831 return false; 832 } 833 834 static bool merge_segments (PathList pl, 835 Path path1, EditPoint start1, EditPoint stop1, 836 EditPoint start2, EditPoint stop2, 837 out PathList result) { 838 839 result = new PathList (); 840 841 PathList r1; 842 PathList r2; 843 844 foreach (Path path2 in pl.paths) { 845 if (path2 != path1) { 846 reset_intersections (path1); 847 reset_intersections (path2); 848 849 if (add_merge_intersection_point (path1, path2, start1, stop1)) { 850 if (add_merge_intersection_point (path1, path2, start2, stop2)) { 851 852 r1 = get_remaining_points (path1.copy ()); 853 r2 = get_remaining_points (path2.copy ()); 854 855 if (r1.paths.size != 2) { 856 warning (@"Expecting two paths in r1 found $(r1.paths.size)\n"); 857 reset_intersections (path1); 858 reset_intersections (path2); 859 return true; 860 } 861 862 if (r2.paths.size != 2) { 863 warning (@"Expecting two paths in r2 found $(r2.paths.size)\n"); 864 reset_intersections (path1); 865 reset_intersections (path2); 866 return true; 867 } 868 869 pl.paths.remove (path1); 870 pl.paths.remove (path2); 871 872 double d1 = Path.point_distance (r1.paths.get (0).get_first_point (), 873 r2.paths.get (0).get_first_point ()); 874 875 double d2 = Path.point_distance (r1.paths.get (0).get_first_point (), 876 r2.paths.get (1).get_first_point ()); 877 878 Path m1, m2; 879 880 if (d1 > d2) { 881 m1 = PenTool.merge_open_paths (r1.paths.get (0), r2.paths.get (0)); 882 m2 = PenTool.merge_open_paths (r1.paths.get (1), r2.paths.get (1)); 883 } else { 884 m1 = PenTool.merge_open_paths (r1.paths.get (1), r2.paths.get (0)); 885 m2 = PenTool.merge_open_paths (r1.paths.get (0), r2.paths.get (1)); 886 } 887 888 result.add (m1); 889 result.add (m2); 890 891 return true; 892 } else { 893 reset_intersections (path1); 894 reset_intersections (path2); 895 } 896 } else { 897 reset_intersections (path1); 898 reset_intersections (path2); 899 } 900 } 901 } 902 903 return false; 904 } 905 906 static void reset_intersections (Path p) { 907 foreach (EditPoint ep in p.points) { 908 ep.flags &= uint.MAX ^ EditPoint.INTERSECTION; 909 ep.flags &= uint.MAX ^ EditPoint.COPIED; 910 ep.deleted = false; 911 } 912 p.remove_points_on_points (); 913 } 914 915 static bool add_merge_intersection_point (Path path1, Path path2, EditPoint first, EditPoint next) { 916 double ix, iy; 917 bool intersection; 918 919 intersection = false; 920 ix = 0; 921 iy = 0; 922 path2.all_segments ((p1, p2) => { 923 int i; 924 925 intersection = segments_intersects (first, next, p1, p2, out ix, out iy); 926 927 if (intersection) { 928 add_intersection (path1, first, next, ix, iy); 929 add_intersection (path2, p1, p2, ix, iy); 930 931 i = mark_intersection_as_deleted (path1); 932 i = mark_intersection_as_deleted (path2); 933 } 934 935 return !intersection; 936 }); 937 938 return intersection; 939 } 940 941 static bool is_inside_of_path (PointSelection ps, PathList pl, out Path outline) { 942 outline = new Path (); 943 foreach (Path p in pl.paths) { 944 if (p != ps.path) { 945 if (is_inside (ps.point, p)) { 946 outline = p; 947 return true; 948 } 949 } 950 } 951 952 return false; 953 } 954 955 static PathList get_all_parts (PathList pl) { 956 PathList m; 957 bool intersects = false; 958 PathList r = new PathList (); 959 960 foreach (Path p in pl.paths) { 961 if (has_self_intersection (p)) { 962 m = get_parts (p); 963 r.append (m); 964 intersects = true; 965 } else { 966 r.add (p); 967 } 968 } 969 970 foreach (Path p in r.paths) { 971 p.close (); 972 p.update_region_boundaries (); 973 } 974 975 if (intersects) { 976 return get_all_parts (r); 977 } 978 979 return r; 980 } 981 982 static PathList merge (PathList pl) { 983 bool error = false; 984 PathList m; 985 PathList r = pl; 986 Path p1, p2; 987 988 r = get_all_parts (r); 989 990 while (paths_has_intersection (r, out p1, out p2)) { 991 if (merge_path (p1, p2, out m, out error)) { 992 r.paths.remove (p1); 993 r.paths.remove (p2); 994 995 foreach (Path np in m.paths) { 996 np.remove_points_on_points (); 997 r.add (np); 998 } 999 1000 r = get_all_parts (r); 1001 } else { 1002 warning ("Not merged."); 1003 } 1004 1005 if (error) { 1006 warning ("Merge error"); 1007 break; 1008 } 1009 } 1010 1011 if (!error) { 1012 remove_merged_parts (r); 1013 } 1014 1015 foreach (Path p in r.paths) { 1016 p.close (); 1017 p.recalculate_linear_handles (); 1018 } 1019 1020 return r; 1021 } 1022 1023 static bool has_zero_area_segment (Path p) { 1024 EditPointHandle l, r; 1025 1026 p.recalculate_linear_handles (); 1027 1028 foreach (EditPoint ep in p.points) { 1029 l = ep.get_left_handle (); 1030 r = ep.get_right_handle (); 1031 1032 if (l.length < 0.01 || r.length < 0.01) { 1033 continue; 1034 } 1035 1036 if (!(fabs ((l.angle - r.angle + PI) % 2 * PI) < 0.0001) 1037 && !(fabs (l.angle - r.angle) < 0.0001)) { 1038 return false; 1039 } 1040 } 1041 return true; 1042 } 1043 1044 static void remove_merged_parts (PathList r) { 1045 Gee.ArrayList<Path> remove = new Gee.ArrayList<Path> (); 1046 int c; 1047 1048 foreach (Path p in r.paths) { 1049 p.update_region_boundaries (); 1050 } 1051 1052 foreach (Path p in r.paths) { 1053 c = counters (r, p); 1054 1055 if (has_zero_area_segment (p)) { 1056 remove.add (p); 1057 } 1058 1059 if (c % 2 == 0) { 1060 1061 if (!is_clockwise (p)) { 1062 remove.add (p); 1063 } 1064 1065 if (stroke_selected) 1066 ((!) BirdFont.get_current_font ().get_glyph ("m")).add_path (p); 1067 1068 } else { 1069 if (stroke_selected) 1070 ((!) BirdFont.get_current_font ().get_glyph ("n")).add_path (p); 1071 1072 if (is_clockwise (p)) { 1073 remove.add (p); 1074 } 1075 } 1076 } 1077 1078 if (stroke_selected) { // FIXME: DELETE 1079 foreach (Path mm in r.paths) 1080 ((!) BirdFont.get_current_font ().get_glyph ("i")).add_path (mm); 1081 } 1082 1083 foreach (Path p in remove) { 1084 r.paths.remove (p); 1085 } 1086 1087 if (stroke_selected) { // FIXME: DELETE 1088 foreach (Path mm in r.paths) 1089 ((!) BirdFont.get_current_font ().get_glyph ("j")).add_path (mm); 1090 } 1091 } 1092 1093 public static int counters (PathList pl, Path path) { 1094 int inside_count = 0; 1095 bool inside; 1096 1097 foreach (Path p in pl.paths) { 1098 inside = true; 1099 1100 if (p.points.size > 1 1101 && p != path 1102 && path.boundaries_intersecting (p)) { 1103 1104 // FIXME: all points can be corners in counter paths 1105 foreach (EditPoint ep in path.points) { 1106 if (!is_inside (ep, p)) { 1107 inside = false; 1108 } 1109 } 1110 1111 if (inside) { 1112 inside_count++; 1113 } 1114 } 1115 } 1116 1117 return inside_count; 1118 } 1119 1120 public static bool is_inside (EditPoint point, Path path) { 1121 EditPoint prev; 1122 bool inside = false; 1123 1124 if (path.points.size <= 1) { 1125 return false; 1126 } 1127 1128 prev = path.points.get (path.points.size - 1); 1129 1130 foreach (EditPoint p in path.points) { 1131 if ((p.x == point.x && p.y == point.y) || (prev.x == point.x && prev.y == point.y)) { 1132 return true; 1133 } else if ((p.y > point.y) != (prev.y > point.y) 1134 && point.x < (prev.x - p.x) * (point.y - p.y) / (prev.y - p.y) + p.x) { 1135 inside = !inside; 1136 } 1137 1138 prev = p; 1139 } 1140 1141 return inside; 1142 } 1143 1144 public static int insides (EditPoint point, Path path) { 1145 EditPoint prev; 1146 int inside = 0; 1147 1148 if (path.points.size <= 1) { 1149 return 0; 1150 } 1151 1152 prev = path.get_last_point (); 1153 1154 foreach (EditPoint start in path.points) { 1155 if (start.x == point.x && point.y == start.y) { 1156 inside++; 1157 } else if ((start.y > point.y) != (prev.y > point.y) 1158 && point.x < (prev.x - start.x) * (point.y - start.y) / (prev.y - start.y) + start.x) { 1159 inside++; 1160 } 1161 1162 prev = start; 1163 } 1164 1165 return inside; 1166 } 1167 1168 static bool merge_path (Path path1, Path path2, out PathList merged_paths, out bool error) { 1169 IntersectionList intersections; 1170 EditPoint ep1, next, p1, p2, pp1, pp2; 1171 Path path, other, merged; 1172 PathList r, other_paths, result; 1173 bool intersects; 1174 int s, i; 1175 double ix, iy, iix, iiy; 1176 bool merge = false; 1177 EditPoint intersection_point, other_intersection_point; 1178 bool path1_direction, path2_direction; 1179 1180 error = false; 1181 merged_paths = new PathList (); 1182 intersections = new IntersectionList (); 1183 1184 reset_intersections (path1); 1185 reset_intersections (path2); 1186 1187 iix = 0; 1188 iiy = 0; 1189 1190 result = new PathList (); 1191 1192 if (path1.points.size == 0 || path2.points.size == 0) { 1193 warning ("No points in path."); 1194 error = true; 1195 return false; 1196 } 1197 1198 if (!path1.boundaries_intersecting (path2)) { 1199 return false; 1200 } 1201 1202 Path original_path1 = path1.copy (); 1203 Path original_path2 = path2.copy (); 1204 1205 s = 0; 1206 foreach (EditPoint e in original_path1.points) { 1207 if (!is_inside (e, original_path2) 1208 && insides (e, original_path1) == 1) { // FIXME: later as well 1209 break; 1210 } 1211 s++; 1212 } 1213 1214 if (s >= path1.points.size) { 1215 Path t; 1216 t = original_path1; 1217 original_path1 = original_path2; 1218 original_path2 = t; 1219 s = 0; 1220 foreach (EditPoint e in original_path1.points) { 1221 if (!is_inside (e, original_path2)) { 1222 break; 1223 } 1224 s++; 1225 } 1226 } 1227 1228 if (s >= original_path1.points.size) { 1229 warning ("No start point found."); 1230 error = true; 1231 return false; 1232 } 1233 1234 path = original_path1; 1235 other = original_path2; 1236 1237 other_paths = new PathList (); 1238 r = new PathList (); 1239 other_paths.add (path); 1240 other_paths.add (other); 1241 intersects = false; 1242 p1 = new EditPoint (); 1243 p2 = new EditPoint (); 1244 pp1 = new EditPoint (); 1245 pp2 = new EditPoint (); 1246 1247 ix = 0; 1248 iy = 0; 1249 i = s; 1250 merged = new Path (); 1251 1252 path1_direction = is_clockwise (original_path1); 1253 path2_direction = is_clockwise (original_path1); 1254 1255 while (true) { 1256 ep1 = path.points.get (i % path.points.size); 1257 next = path.points.get ((i + 1) % path.points.size); 1258 1259 if ((ep1.flags & EditPoint.COPIED) > 0) { 1260 if (merge) { 1261 merged.close (); 1262 result.add (merged.copy ()); 1263 } 1264 1265 merged = new Path (); 1266 1267 bool find_parts = false; 1268 Intersection new_start = new Intersection.empty (); 1269 foreach (Intersection inter in intersections.points) { 1270 if (!inter.done && !find_parts) { 1271 find_parts = true; 1272 new_start = inter; 1273 break; 1274 } 1275 } 1276 1277 if (!find_parts) { 1278 break; // done, no more parts to merge 1279 } else { 1280 // set start point for next part 1281 path = new_start.other_path; 1282 1283 if (path.points.size == 0) { 1284 warning ("No points."); 1285 error = true; 1286 return false; 1287 } 1288 1289 i = index_of (path, new_start.get_point (path)); 1290 1291 if (i < 0) { 1292 warning ("Start point not found."); 1293 error = true; 1294 return false; 1295 } 1296 1297 ep1 = path.points.get (i % path.points.size); 1298 next = path.points.get ((i + 1) % path.points.size); 1299 1300 if ((ep1.flags & EditPoint.INTERSECTION) == 0) { 1301 warning ("Not starting on an intersection point."); 1302 error = true; 1303 return false; 1304 } 1305 1306 new_start.done = true; 1307 } 1308 } 1309 1310 intersects = false; 1311 1312 double dm; 1313 double d; 1314 1315 if ((ep1.flags & EditPoint.INTERSECTION) > 0) { 1316 Intersection current_intersection; 1317 bool continue_on_other_path; 1318 current_intersection = intersections.get_point (ep1, out continue_on_other_path); 1319 current_intersection.done = true; 1320 1321 // take the other part of an intersection 1322 if ((next.flags & EditPoint.COPIED) != 0) { 1323 bool next_is_intersection = false; 1324 bool next_continue_on_other_path; 1325 1326 next_is_intersection = ((next.flags & EditPoint.INTERSECTION) > 0); 1327 1328 if (next_is_intersection) { 1329 Intersection next_intersection = intersections.get_point (next, out next_continue_on_other_path); 1330 1331 ep1.flags |= EditPoint.COPIED; 1332 merged.add_point (ep1.copy ()); 1333 1334 if (!next_intersection.done) { 1335 EditPoint new_start_point; 1336 1337 path = next_continue_on_other_path 1338 ? next_intersection.other_path : next_intersection.path; 1339 1340 new_start_point = next_continue_on_other_path 1341 ? next_intersection.other_point : next_intersection.point; 1342 1343 i = index_of (path, new_start_point); 1344 1345 if (i < 0) { 1346 warning ("Point not found in path.\n"); 1347 error = true; 1348 return false; 1349 } 1350 1351 ep1 = path.points.get (i % path.points.size); 1352 next = path.points.get ((i + 1) % path.points.size); 1353 } else { 1354 warning ("Part is already created.\n"); 1355 error = true; 1356 return false; 1357 } 1358 } else { 1359 ep1.flags |= EditPoint.COPIED; 1360 merged.add_point (ep1.copy ()); 1361 1362 EditPoint new_start_point; 1363 1364 if ((next.flags & EditPoint.COPIED) > 0) { 1365 path = current_intersection.get_other_path (path); 1366 new_start_point = current_intersection.get_point (path); 1367 i = index_of (path, new_start_point); 1368 1369 if (i < 0) { 1370 warning ("Point not found in path."); 1371 error = true; 1372 return false; 1373 } 1374 1375 ep1 = path.points.get (i % path.points.size); 1376 next = path.points.get ((i + 1) % path.points.size); 1377 1378 if ((next.flags & EditPoint.INTERSECTION) > 0) { 1379 warning ("Wrong type."); 1380 error = true; 1381 return false; 1382 } 1383 1384 if ((next.flags & EditPoint.COPIED) > 0) { 1385 merged.add_point (ep1.copy ()); 1386 continue; 1387 } 1388 } else { 1389 ep1.flags |= EditPoint.COPIED; 1390 merged.add_point (ep1.copy ()); 1391 } 1392 } 1393 } else { 1394 ep1.flags |= EditPoint.COPIED; 1395 1396 if (path1_direction == path2_direction) { 1397 if (!is_inside (ep1, original_path1)) { 1398 merged.add_point (ep1.copy ()); 1399 } 1400 } else { 1401 merged.add_point (ep1.copy ()); 1402 } 1403 } 1404 1405 current_intersection.done = true; 1406 } else { 1407 // find new intersection 1408 dm = double.MAX; 1409 foreach (Path o in other_paths.paths) { 1410 bool inter = segment_intersects (o, ep1, next, out iix, out iiy, 1411 out pp1, out pp2); 1412 d = Path.distance (ep1.x, iix, ep1.y, iiy); 1413 if (d < dm && inter) { 1414 other = o; 1415 dm = d; 1416 intersects = true; 1417 p1 = pp1; 1418 p2 = pp2; 1419 ix = iix; 1420 iy = iiy; 1421 } 1422 1423 if (d < 0.0001) { 1424 intersects = false; 1425 } 1426 } 1427 1428 if (intersects) { 1429 merged.add_point (ep1.copy ()); 1430 ep1.flags |= EditPoint.COPIED; 1431 1432 intersection_point = add_intersection (path, ep1, next, ix, iy); 1433 other_intersection_point = add_intersection (other, p1, p2, ix, iy); 1434 1435 bool g = false; 1436 foreach (Intersection old_intersection in intersections.points) { 1437 if (old_intersection.point == intersection_point 1438 || old_intersection.other_point == other_intersection_point) { 1439 old_intersection.done = true; 1440 g = true; 1441 } 1442 } 1443 1444 if (!g) { 1445 Intersection ip = new Intersection (intersection_point, path, other_intersection_point, other); 1446 intersections.points.add (ip); 1447 } 1448 1449 merged.add_point (intersection_point.copy ()); 1450 intersection_point.flags |= EditPoint.COPIED; 1451 1452 i = index_of (other, other_intersection_point); 1453 1454 if (i < 0) { 1455 warning (@"Point not found ($i)."); 1456 break; 1457 } 1458 1459 path = other; 1460 merge = true; 1461 } else { 1462 ep1.flags |= EditPoint.COPIED; 1463 merged.add_point (ep1.copy ()); 1464 1465 PointSelection ps; 1466 Path outline; 1467 1468 // remove points inside of path 1469 if (is_clockwise (original_path2)) { 1470 ps = new PointSelection (ep1, merged); 1471 if (is_inside_of_path (ps, result, out outline)) { 1472 ep1.deleted = true; 1473 ep1.color = Color.red (); 1474 } 1475 } 1476 } 1477 } 1478 1479 i++; 1480 } 1481 1482 if (merge) { 1483 original_path1.remove_points_on_points (); 1484 original_path2.remove_points_on_points (); 1485 original_path1.remove_deleted_points (); 1486 original_path2.remove_deleted_points (); 1487 1488 foreach (Path np in result.paths) { 1489 Path p = np.copy (); 1490 bool has_direction = true; 1491 1492 p.remove_points_on_points (); 1493 1494 if (has_direction) { 1495 p.close (); 1496 reset_intersections (p); 1497 merged_paths.append (get_parts (p)); 1498 p.update_region_boundaries (); 1499 p.recalculate_linear_handles (); 1500 } 1501 } 1502 } 1503 1504 return merge && !error; 1505 } 1506 1507 static int index_of (Path p, EditPoint ep) { 1508 int i = 0; 1509 foreach (EditPoint e in p.points) { 1510 if (e == ep) { 1511 return i; 1512 } 1513 i++; 1514 } 1515 1516 return -1; 1517 } 1518 1519 public static int counters_in_point_in_path (Path p, EditPoint ep) { 1520 int inside_count = 0; 1521 bool inside; 1522 1523 if (p.points.size > 1) { 1524 inside = true; 1525 1526 if (!is_inside (ep, p)) { 1527 inside = false; 1528 } 1529 1530 if (inside) { 1531 inside_count++; 1532 } 1533 } 1534 1535 return inside_count; 1536 } 1537 1538 static int mark_intersection_as_deleted (Path path) { 1539 int i = 0; 1540 1541 foreach (EditPoint p in path.points) { 1542 if ((p.flags & EditPoint.INTERSECTION) > 0) { 1543 p.deleted = true; 1544 i++; 1545 } 1546 } 1547 1548 return i; 1549 } 1550 1551 /** @param n number of interrsections to find per path. */ 1552 static bool add_intersection_points (Path path1, Path path2, int n = 1) { 1553 bool intersection = false; 1554 int found = 0; 1555 1556 path1.all_segments ((ep1, ep2) => { 1557 double ix, iy; 1558 EditPoint p1, p2; 1559 bool i; 1560 1561 i = segment_intersects (path2, ep1, ep2, out ix, out iy, 1562 out p1, out p2, true); 1563 1564 if (i) { 1565 add_intersection (path1, ep1, ep2, ix, iy); 1566 add_intersection (path2, p1, p2, ix, iy); 1567 intersection = true; 1568 found++; 1569 return found < n; 1570 } 1571 1572 return true; 1573 }); 1574 1575 return intersection; 1576 } 1577 1578 /** @param n number of interrsections to find per path. */ 1579 static bool has_intersection (Path path1, Path path2) { 1580 bool intersection = false; 1581 1582 if (!path1.boundaries_intersecting (path2)) { 1583 return false; 1584 } 1585 1586 path1.all_segments ((ep1, ep2) => { 1587 double ix, iy; 1588 EditPoint p1, p2; 1589 bool i; 1590 1591 i = segment_intersects (path2, ep1, ep2, out ix, out iy, 1592 out p1, out p2, true); 1593 1594 if (i) { 1595 intersection = true; 1596 } 1597 1598 return !intersection; 1599 }); 1600 1601 return intersection; 1602 } 1603 1604 static bool paths_has_intersection (PathList r, out Path path1, out Path path2) { 1605 int i, j; 1606 path1 = new Path (); 1607 path2 = new Path (); 1608 1609 i = 0; 1610 foreach (Path p1 in r.paths) { 1611 1612 j = 0; 1613 foreach (Path p2 in r.paths) { 1614 if (p1 != p2) { 1615 if (has_intersection (p1, p2)) { 1616 path1 = p1; 1617 path2 = p2; 1618 return true; 1619 } 1620 } 1621 j++; 1622 } 1623 i++; 1624 } 1625 return false; 1626 } 1627 1628 public static bool has_points_outside (PathList pl, Path p) { 1629 if (pl.paths.size != 2) { 1630 warning ("Stroke should only create two parts."); 1631 return false; 1632 } 1633 1634 foreach (Path path in pl.paths) { 1635 if (path != p) { 1636 foreach (EditPoint ep in p.points) { 1637 if (!is_inside (ep, path)) { 1638 return true; 1639 } 1640 } 1641 } 1642 } 1643 1644 return false; 1645 } 1646 1647 static bool is_clockwise (Path p) { 1648 double sum = 0; 1649 EditPoint p1, p2; 1650 1651 EditPointHandle l, r; 1652 1653 p.recalculate_linear_handles (); 1654 1655 if (p.points.size < 3) { 1656 return true; 1657 } 1658 1659 for (int i = 0; i < p.points.size; i++) { 1660 p1 = p.points.get (i); 1661 p2 = p.points.get ((i + 1) % p.points.size); 1662 1663 l = p1.get_left_handle (); 1664 r = p1.get_right_handle (); 1665 if (!(fabs (l.angle - r.angle) < 0.0001 && l.length > 0.01 && r.length > 0.01)) { 1666 sum += (p2.x - p1.x) * (p2.y + p1.y); 1667 } 1668 } 1669 1670 return sum > 0; 1671 } 1672 1673 public static PathList create_stroke (Path path, double thickness) { 1674 PathList pl; 1675 EditPoint p1, p2, p3; 1676 EditPoint previous, previous_inside, start; 1677 1678 Path side1, side2; 1679 1680 double x, y, x2, y2, x3, y3; 1681 int size; 1682 bool flat, f_next, f_bigger; 1683 int i; 1684 1685 double tolerance; 1686 double step_increment; 1687 double step_size; 1688 EditPoint corner1, corner1_inside; 1689 double step; 1690 1691 EditPointHandle l, r; 1692 1693 pl = new PathList (); 1694 size = path.is_open () ? path.points.size - 1 : path.points.size; 1695 1696 side1 = new Path (); 1697 side2 = new Path (); 1698 1699 if (path.points.size < 2) { 1700 return pl; 1701 } 1702 1703 previous = new EditPoint (); 1704 previous_inside = new EditPoint (); 1705 corner1 = new EditPoint (); 1706 corner1_inside = new EditPoint (); 1707 1708 if (path.is_open ()) { 1709 p1 = path.points.get (0); 1710 p2 = path.points.get (1 % path.points.size); 1711 1712 get_segment (thickness, 0, 0.00001, p1, p2, out start); 1713 get_segment (-thickness, 0, 0.00001, p1, p2, out start); 1714 1715 previous = start.copy (); 1716 previous_inside = start.copy (); 1717 1718 side1.add_point (previous); 1719 side2.add_point (previous_inside); 1720 } 1721 1722 for (i = 0; i < size; i++) { 1723 p1 = path.points.get (i % path.points.size); 1724 p2 = path.points.get ((i + 1) % path.points.size); 1725 p3 = path.points.get ((i + 2) % path.points.size); 1726 1727 // tolerance = 0.13 / sqrt (stroke_width); 1728 tolerance = 2 / sqrt (stroke_width); 1729 step_increment = 1.05; 1730 step_size = 0.039 / stroke_width; 1731 1732 corner1 = new EditPoint (); 1733 1734 if (p1.type == PointType.HIDDEN 1735 || p2.type == PointType.HIDDEN) { 1736 continue; 1737 } 1738 1739 step = 0; 1740 while (step < 1 - 2 * step_size) { 1741 Path.get_point_for_step (p1, p2, step, out x, out y); 1742 Path.get_point_for_step (p1, p2, step + step_size, out x2, out y2); 1743 Path.get_point_for_step (p1, p2, step + 2 * step_size, out x3, out y3); 1744 1745 flat = is_flat (x, y, x2, y2, x3, y3, tolerance); 1746 1747 Path.get_point_for_step (p1, p2, step, out x, out y); 1748 Path.get_point_for_step (p1, p2, step + step_size / step_increment, out x2, out y2); 1749 Path.get_point_for_step (p1, p2, step + 2 * step_size / step_increment, out x3, out y3); 1750 1751 f_next = is_flat (x, y, x2, y2, x3, y3, tolerance); 1752 1753 Path.get_point_for_step (p1, p2, step, out x, out y); 1754 Path.get_point_for_step (p1, p2, step + step_size * step_increment, out x2, out y2); 1755 Path.get_point_for_step (p1, p2, step + 2 * step_size * step_increment, out x3, out y3); 1756 1757 f_bigger = is_flat (x, y, x2, y2, x3, y3, tolerance); 1758 1759 if (!flat && !f_next && step_size > 0.013) { 1760 step_size /= step_increment; 1761 continue; 1762 } 1763 1764 if (flat && f_bigger && step_size < 0.5) { 1765 step_size *= step_increment; 1766 continue; 1767 } 1768 1769 get_segment (thickness, step, step_size, p1, p2, out corner1); 1770 get_segment (-thickness, step, step_size, p1, p2, out corner1_inside); 1771 1772 previous.get_right_handle ().length *= step_size; 1773 corner1.get_left_handle ().length *= step_size; 1774 previous_inside.get_right_handle ().length *= step_size; 1775 corner1_inside.get_left_handle ().length *= step_size; 1776 1777 previous = corner1.copy (); 1778 previous_inside = corner1_inside.copy (); 1779 1780 side1.add_point (previous); 1781 side2.add_point (previous_inside); 1782 1783 step += step_size; 1784 } 1785 1786 get_segment (thickness, 1 - 0.00001, 0.00001, p1, p2, out corner1); 1787 get_segment (-thickness, 1 - 0.00001, 0.00001, p1, p2, out corner1_inside); 1788 1789 previous = corner1.copy (); 1790 previous_inside = corner1_inside.copy (); 1791 1792 previous_inside.color = Color.pink (); 1793 1794 previous.get_right_handle ().length *= step_size; 1795 previous.get_left_handle ().length *= step_size; 1796 previous_inside.get_right_handle ().length *= step_size; 1797 previous_inside.get_left_handle ().length *= step_size; 1798 1799 side1.add_point (previous); 1800 side2.add_point (previous_inside); 1801 1802 l = p2.get_left_handle (); 1803 r = p2.get_right_handle (); 1804 1805 if (fabs (l.angle + r.angle - PI) % 2 * PI > 0.01) { 1806 if (!path.is_open () || i < size - 1) { 1807 get_segment (thickness, 0, 0.00001, p2, p3, out start); 1808 add_corner (side1, previous, start, p2.copy (), thickness); 1809 1810 get_segment (-thickness, 0, 0.00001, p2, p3, out start); 1811 add_corner (side2, previous_inside, start, p2.copy (), thickness); 1812 } 1813 } 1814 } 1815 1816 side1.recalculate_linear_handles (); 1817 side2.recalculate_linear_handles (); 1818 1819 side2.reverse (); 1820 1821 pl = merge_stroke_parts (path, side1, side2); 1822 1823 return pl; 1824 } 1825 1826 public static void get_segment (double stroke_thickness, double step, double step_size, 1827 EditPoint p1, EditPoint p2, out EditPoint ep1) { 1828 1829 double thickness = stroke_thickness / 2; 1830 Path overlay; 1831 double x, y, x2, y2, x3, y3; 1832 EditPoint corner1, corner2, corner3; 1833 PointType type; 1834 double handle1_x, handle1_y, handle2_x, handle2_y; 1835 1836 Path.get_point_for_step (p1, p2, step, out x, out y); 1837 Path.get_point_for_step (p1, p2, step + step_size, out x2, out y2); 1838 Path.get_point_for_step (p1, p2, step + 2 * step_size, out x3, out y3); 1839 1840 overlay = new Path (); 1841 1842 type = p1.get_right_handle ().type; 1843 corner1 = new EditPoint (x, y, type); 1844 corner2 = new EditPoint (x2, y2, type); 1845 corner3 = new EditPoint (x3, y3, type); 1846 1847 overlay.add_point (corner1); 1848 overlay.add_point (corner2); 1849 overlay.add_point (corner3); 1850 1851 overlay.close (); 1852 overlay.recalculate_linear_handles (); 1853 1854 Path.get_handles_for_step (p1, p2, step + step_size, 1855 out handle1_x, out handle1_y, out handle2_x, out handle2_y); 1856 1857 corner2.get_left_handle ().move_to_coordinate (handle1_x, handle1_y); 1858 corner2.get_right_handle ().move_to_coordinate (handle2_x, handle2_y); 1859 1860 corner2.convert_to_curve (); 1861 1862 move_segment (corner1, corner2, thickness); 1863 1864 ep1 = corner2; 1865 } 1866 1867 public static PathList merge_stroke_parts (Path p, Path side1, Path side2) { 1868 Path merged = new Path (); 1869 PathList paths = new PathList (); 1870 1871 if (!p.is_open () && p.is_filled ()) { 1872 side1.close (); 1873 side1.update_region_boundaries (); 1874 paths.add (side1); 1875 } else if (!p.is_open () && !p.is_filled ()) { 1876 side1.update_region_boundaries (); 1877 paths.add (side1); 1878 side2.update_region_boundaries (); 1879 paths.add (side2); 1880 side1.close (); 1881 side2.close (); 1882 } else if (p.is_open ()) { 1883 side2.reverse (); 1884 merged = merge_strokes (p, side1, side2); 1885 merged.close (); 1886 merged.update_region_boundaries (); 1887 paths.add (merged); 1888 merged.reverse (); 1889 } else { 1890 warning ("Can not create stroke."); 1891 paths.add (p); 1892 } 1893 1894 return paths; 1895 } 1896 } 1897 1898 } 1899