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