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