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.
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 static bool stroke_selected = false; 23 static int iterations = 0; 24 25 public StrokeTool (string tooltip) { 26 iterations = 10; 27 select_action.connect((self) => { 28 stroke_selected = true; 29 iterations++; 30 stroke_selected_paths (); 31 stroke_selected = false; 32 }); 33 } 34 35 public static void set_stroke_for_selected_paths (double width) { 36 Glyph g = MainWindow.get_current_glyph (); 37 38 foreach (Path p in g.active_paths) { 39 p.set_stroke (width); 40 } 41 42 GlyphCanvas.redraw (); 43 } 44 45 /** Create strokes for the selected outlines. */ 46 void stroke_selected_paths () { 47 Glyph g = MainWindow.get_current_glyph (); 48 PathList paths = new PathList (); 49 50 foreach (Path p in g.active_paths) { 51 paths.append (get_stroke (p, p.stroke)); 52 } 53 54 foreach (Path np in paths.paths) { 55 g.add_path (np); 56 } 57 } 58 59 public static PathList get_stroke (Path path, double thickness) { 60 PathList n; 61 PathList o = new PathList (); 62 Path original = path.copy (); 63 64 original.remove_points_on_points (); 65 66 n = get_stroke_outline (original, thickness); 67 68 o = split_corners (n); 69 remove_self_intersecting_corners (o); 70 71 o = merge (o); 72 73 PathList parts = new PathList (); 74 foreach (Path p in o.paths) { 75 parts.append (split (p)); 76 } 77 78 return parts; 79 } 80 81 static bool is_corner_self_intersection (Path p) { 82 EditPointHandle l, r; 83 bool corner, i, remove; 84 85 remove = false; 86 i = false; 87 p.remove_points_on_points (); 88 foreach (EditPoint ep in p.points) { 89 90 corner = (ep.flags & EditPoint.NEW_CORNER) > 0; 91 92 if ((ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) { 93 return false; 94 } 95 96 if (corner || i) { 97 l = ep.get_left_handle (); 98 r = ep.get_right_handle (); 99 100 if (fabs (l.angle - r.angle) < 0.005) { 101 ep.color = new Color (1,0,0,1); 102 remove = true; 103 } else ep.color = new Color (0,0,1,1); 104 } 105 106 i = corner && p.points.size == 4; 107 } 108 109 return remove; 110 } 111 112 static void remove_self_intersecting_corners (PathList pl) { 113 PathList parts; 114 foreach (Path p in pl.paths) { 115 116 if (is_corner_self_intersection (p)) { 117 parts = get_parts (p); 118 if (parts.paths.size > 1) { 119 pl.append (parts); 120 remove_self_intersecting_corners (pl); 121 return; 122 } else { 123 124 if (has_self_intersection (p)) { // FIXME: DELETE 125 warning ("Path ha self intersection."); 126 } 127 128 pl.paths.remove (p); 129 remove_self_intersecting_corners (pl); 130 return; 131 } 132 } 133 } 134 } 135 136 public static PathList get_stroke_outline (Path path, double thickness) { 137 return get_strokes (path, thickness); 138 } 139 140 public static PathList get_strokes (Path p, double thickness) { 141 Path counter = new Path (); 142 Path outline = new Path (); 143 Path merged = new Path (); 144 PathList paths = new PathList (); 145 146 if (!p.is_open () && p.is_filled ()) { 147 outline = create_stroke (p, thickness); 148 outline.close (); 149 150 outline.update_region_boundaries (); 151 paths.add (outline); 152 } else if (!p.is_open () && !p.is_filled ()) { 153 outline = create_stroke (p, thickness); 154 counter = create_stroke (p, -1 * thickness); 155 156 if (p.is_clockwise ()) { 157 outline.force_direction (Direction.CLOCKWISE); 158 } else { 159 outline.force_direction (Direction.COUNTER_CLOCKWISE); 160 } 161 162 if (outline.is_clockwise ()) { 163 counter.force_direction (Direction.COUNTER_CLOCKWISE); 164 } else { 165 counter.force_direction (Direction.CLOCKWISE); 166 } 167 168 outline.update_region_boundaries (); 169 paths.add (outline); 170 171 counter.update_region_boundaries (); 172 paths.add (counter); 173 } else if (p.is_open ()) { // FIXME: this can create many parts 174 outline = create_stroke (p, thickness); 175 counter = create_stroke (p, -1 * thickness); 176 merged = merge_strokes (p, outline, counter, thickness); 177 178 if (p.is_clockwise ()) { 179 merged.force_direction (Direction.CLOCKWISE); 180 } else { 181 merged.force_direction (Direction.COUNTER_CLOCKWISE); 182 } 183 184 merged.update_region_boundaries (); 185 paths.add (merged); 186 } else { 187 warning ("Can not create stroke."); 188 paths.add (p); 189 } 190 191 return paths; 192 } 193 194 /** Create one stroke from the outline and counter stroke and close the 195 * open endings. 196 * 197 * @param path the path to create stroke for 198 * @param stroke for the outline of path 199 * @param stroke for the counter path 200 */ 201 static Path merge_strokes (Path path, Path stroke, Path counter, double thickness) { 202 Path merged; 203 204 counter.reverse (); 205 merged = stroke.copy (); 206 207 if (path.is_open ()) { 208 merged.delete_last_point (); 209 counter.delete_first_point (); 210 merged.delete_last_point (); 211 counter.delete_first_point (); 212 } 213 214 merged.append_path (counter); 215 216 merged.close (); 217 merged.create_list (); 218 merged.recalculate_linear_handles (); 219 220 return merged; 221 } 222 223 static Path create_stroke (Path p, double thickness) { 224 Path stroked; 225 Path path; 226 227 if (p.points.size >= 2) { 228 path = p.copy (); 229 //FIXME: DELETE path.remove_points_on_points (); 230 stroked = generate_stroke (path, thickness); 231 232 if (!p.is_open ()) { 233 stroked.reverse (); 234 stroked.close (); 235 } 236 } else { 237 // TODO: create stroke for a path with one point 238 warning ("One point."); 239 stroked = new Path (); 240 } 241 242 return stroked; 243 } 244 245 static Path generate_stroke (Path p, double thickness) { 246 Path stroked = new Path (); 247 EditPoint start = new EditPoint (); 248 EditPoint end; 249 EditPoint previous; 250 int i; 251 252 previous = p.get_first_point ().copy (); 253 move_segment (start, previous, thickness); 254 255 i = 0; 256 foreach (EditPoint ep in p.points) { 257 start = ep.copy (); 258 end = ep.get_next ().copy (); 259 260 move_segment (start, end, thickness); 261 262 if (start == p.get_last_point ()) { 263 end = p.get_first_point (); 264 } 265 266 if (!p.is_open () || (i != 0 && i != p.points.size - 1)) { 267 add_corner (stroked, previous, start, ep.copy (), thickness); 268 } 269 270 stroked.add_point (start); 271 272 if (end.get_left_handle ().length > 0) { 273 stroked.add_point (end); 274 } 275 276 // open ends around corner 277 start.get_left_handle ().convert_to_line (); 278 end.get_right_handle ().convert_to_line (); 279 280 start.flags |= EditPoint.STROKE_OFFSET; 281 end.flags |= EditPoint.STROKE_OFFSET; 282 283 previous = end; 284 285 i++; 286 } 287 288 stroked.recalculate_linear_handles (); 289 290 return stroked; 291 } 292 293 static void move_segment (EditPoint stroke_start, EditPoint stroke_stop, double thickness) { 294 EditPointHandle r, l; 295 double m, n; 296 double qx, qy; 297 298 stroke_start.set_tie_handle (false); 299 stroke_stop.set_tie_handle (false); 300 301 r = stroke_start.get_right_handle (); 302 l = stroke_stop.get_left_handle (); 303 304 m = cos (r.angle + PI / 2) * thickness; 305 n = sin (r.angle + PI / 2) * thickness; 306 307 stroke_start.get_right_handle ().move_to_coordinate_delta (m, n); 308 stroke_start.get_left_handle ().move_to_coordinate_delta (m, n); 309 310 stroke_start.independent_x += m; 311 stroke_start.independent_y += n; 312 313 qx = cos (l.angle - PI / 2) * thickness; 314 qy = sin (l.angle - PI / 2) * thickness; 315 316 stroke_stop.get_right_handle ().move_to_coordinate_delta (qx, qy); 317 stroke_stop.get_left_handle ().move_to_coordinate_delta (qx, qy); 318 319 stroke_stop.independent_x += qx; 320 stroke_stop.independent_y += qy; 321 } 322 323 static void add_corner (Path stroked, EditPoint previous, EditPoint next, 324 EditPoint original, double stroke_width) { 325 326 double ratio; 327 double distance; 328 EditPoint corner; 329 double corner_x, corner_y; 330 EditPointHandle previous_handle; 331 EditPointHandle next_handle; 332 EditPoint cutoff1, cutoff2; 333 334 previous_handle = previous.get_left_handle (); 335 next_handle = next.get_right_handle (); 336 337 previous_handle.angle += PI; 338 next_handle.angle += PI; 339 340 Path.find_intersection_handle (previous_handle, next_handle, out corner_x, out corner_y); 341 corner = new EditPoint (corner_x, corner_y, previous.type); 342 corner.convert_to_line (); 343 344 previous_handle.angle -= PI; 345 next_handle.angle -= PI; 346 347 distance = Path.distance_to_point (corner, original); 348 ratio = 1.5 * fabs (stroke_width) / distance; 349 350 if (distance < 0.1) { 351 previous.flags |= EditPoint.NEW_CORNER; 352 next.flags |= EditPoint.NEW_CORNER; 353 } else { 354 if (ratio > 1) { 355 stroked.add_point (corner); 356 } else { 357 cutoff1 = new EditPoint (); 358 cutoff1.set_point_type (previous.type); 359 cutoff1.convert_to_line (); 360 361 cutoff2 = new EditPoint (); 362 cutoff2.set_point_type (previous.type); 363 cutoff2.convert_to_line (); 364 365 cutoff1.x = previous.x + (corner.x - previous.x) * ratio; 366 cutoff1.y = previous.y + (corner.y - previous.y) * ratio; 367 368 cutoff2.x = next.x + (corner.x - next.x) * ratio; 369 cutoff2.y = next.y + (corner.y - next.y) * ratio; 370 371 cutoff1 = stroked.add_point (cutoff1); 372 cutoff2 = stroked.add_point (cutoff2); 373 374 cutoff1.recalculate_linear_handles (); 375 cutoff2.recalculate_linear_handles (); 376 377 previous.flags |= EditPoint.NEW_CORNER; 378 next.flags |= EditPoint.NEW_CORNER; 379 } 380 } 381 } 382 383 static PathList get_parts (Path path) { 384 PathList intersections; 385 PathList r; 386 387 r = get_parts_self (path); 388 intersections = new PathList (); 389 390 foreach (Path p in r.paths) { 391 intersections.add (p); 392 } 393 394 // return split_paths (intersections); 395 return intersections; 396 } 397 398 static PathList split_corners (PathList result) { 399 split_corner (result); 400 return result; 401 } 402 403 static bool split_corner (PathList pl) { 404 EditPoint p1, p2; 405 EditPoint a1, a2; 406 PathList r; 407 bool split; 408 409 foreach (Path p in pl.paths) { 410 if (p.points.size == 0) { 411 continue; 412 } 413 414 for (int index = 1; index < p.points.size + 2; index++) { 415 p1 = p.points.get ((index - 1) % p.points.size); 416 p2 = p.points.get (index % p.points.size); 417 a1 = p.points.get ((index + 3) % p.points.size); // two points ahead 418 a2 = p.points.get ((index + 4) % p.points.size); 419 420 if ((p1.flags & EditPoint.STROKE_OFFSET) > 0 421 || (p2.flags & EditPoint.STROKE_OFFSET) > 0 422 || (a1.flags & EditPoint.STROKE_OFFSET) > 0 423 || (a2.flags & EditPoint.STROKE_OFFSET) > 0) { // FIXME: safe? 424 425 split = split_segment (p, a1, a2, p1, p2, out r); 426 427 if (split) { 428 pl.append (r); 429 pl.paths.remove (p); 430 split_corner (pl); 431 return true; 432 } else { 433 p1 = p.points.get ((index - 1) % p.points.size); 434 p2 = p.points.get (index % p.points.size); 435 a1 = p.points.get ((index + 2) % p.points.size); // one point ahead 436 a2 = p.points.get ((index + 3) % p.points.size); 437 438 split = split_segment (p, a1, a2, p1, p2, out r); 439 440 if (split) { 441 pl.append (r); 442 pl.paths.remove (p); 443 split_corner (pl); 444 return true; 445 } else { 446 447 // FIXME: the special case, merge counter path with outline here 448 p1 = p.points.get ((index - 1) % p.points.size); 449 p2 = p.points.get (index % p.points.size); 450 a1 = p.points.get ((index + 3) % p.points.size); // two points ahead 451 a2 = p.points.get ((index + 4) % p.points.size); 452 453 if ((p1.flags & EditPoint.STROKE_OFFSET) > 0 454 && (a1.flags & EditPoint.STROKE_OFFSET) > 0) { 455 p1.color = new Color (1,0,1,0.7); 456 a1.color = new Color (1,0,1,0.7); 457 p1.flags = EditPoint.COUNTER_TO_OUTLINE; 458 a1.flags = EditPoint.COUNTER_TO_OUTLINE; 459 460 p1.counter_to_outline = true; 461 a1.counter_to_outline = true; 462 } 463 } 464 } 465 } 466 } 467 } 468 469 return false; 470 } 471 472 static bool split_segment (Path p, EditPoint first, EditPoint next, EditPoint p1, EditPoint p2, out PathList result) { 473 double ix, iy; 474 bool intersection; 475 int i; 476 477 result = new PathList (); 478 intersection = segments_intersects (first, next, p1, p2, out ix, out iy, true); 479 480 if (intersection) { 481 add_intersection (p, first, next, ix, iy); 482 add_intersection (p, p1, p2, ix, iy); 483 484 i = mark_intersection_as_deleted (p); 485 return_val_if_fail (i == 2, false); 486 487 result.append (get_remaining_points (p.copy ())); 488 } 489 490 return intersection; 491 } 492 493 static bool split_path (Path path1, Path path2, PathList result) { 494 PathList pl1, pl2; 495 Path a1, a2, b1, b2; 496 Path m1, m2; 497 int i; 498 499 if (path1 == path2) { 500 return false; 501 } 502 503 if (add_intersection_points (path1, path2, 2)) { 504 i = mark_intersection_as_deleted (path1); 505 return_if_fail (i == 2); 506 507 i = mark_intersection_as_deleted (path2); 508 return_if_fail (i == 2); 509 510 pl1 = get_remaining_points (path1.copy ()); 511 pl2 = get_remaining_points (path2.copy ()); 512 513 return_if_fail (pl1.paths.size == 2); 514 return_if_fail (pl2.paths.size == 2); 515 516 a1 = pl1.paths.get (0); 517 a2 = pl1.paths.get (1); 518 b1 = pl2.paths.get (0); 519 b2 = pl2.paths.get (1); 520 521 m1 = PenTool.merge_open_paths (a1, b2); 522 m2 = PenTool.merge_open_paths (b1, a2); 523 524 result.add (m1); 525 result.add (m2); 526 527 return true; 528 } 529 530 return false; 531 } 532 533 static PathList split_paths (PathList pl) { 534 PathList n = new PathList (); 535 536 n.append (pl); 537 538 foreach (Path p1 in pl.paths) { 539 foreach (Path p2 in pl.paths) { 540 if (p1 != p2) { 541 if (split_path (p1, p2, n)) { 542 n.paths.remove (p1); 543 n.paths.remove (p2); 544 return split_paths (n); 545 } 546 } 547 } 548 } 549 550 return n; 551 } 552 553 static PathList get_parts_self (Path path, PathList? paths = null) { 554 PathList pl; 555 PathList r; 556 557 r = paths == null ? new PathList () : (!) paths; 558 pl = split (path); 559 560 foreach (Path part in pl.paths) { 561 if (!has_self_intersection (part)) { 562 r.add (part); 563 } else { 564 get_parts_self (part, r); 565 } 566 } 567 568 if (r.paths.size == 0) { 569 warning ("No parts in path"); 570 } 571 572 return r; 573 } 574 575 static bool has_intersection_points (Path path) { 576 foreach (EditPoint p in path.points) { 577 if ((p.flags & EditPoint.INTERSECTION) > 0) { 578 return true; 579 } 580 } 581 return false; 582 } 583 584 /** Split one path at intersection points in two parts. */ 585 static PathList split (Path path) { 586 PathList pl; 587 int i; 588 589 if (!has_intersection_points (path)) { 590 add_self_intersection_points (path); 591 } else { 592 warning ("points already created."); 593 } 594 595 i = mark_intersection_as_deleted (path); 596 597 if (!(i == 0 || i == 2)) { 598 warning (@"Split should only create two parts, $i points will be deleted."); 599 } 600 601 pl = get_remaining_points (path.copy ()); 602 603 return pl; 604 } 605 606 static PathList process_deleted_control_points (Path path) { 607 PathList paths, nl, pl, rl; 608 609 paths = new PathList (); 610 rl = new PathList (); 611 pl = new PathList (); 612 nl = new PathList (); 613 614 if (!path.has_deleted_point ()) { 615 return pl; 616 } 617 618 pl.add (path); 619 620 foreach (Path p in pl.paths) { 621 nl = p.process_deleted_points (); 622 623 if (nl.paths.size > 0) { 624 rl.append (nl); 625 rl.paths.remove (p); 626 } 627 } 628 629 foreach (Path p in rl.paths) { 630 pl = process_deleted_control_points (p); 631 632 if (pl.paths.size > 0) { 633 paths.append (pl); 634 } else { 635 paths.add (p); 636 } 637 } 638 639 return paths; 640 } 641 642 static PathList get_remaining_points (Path old_path) { 643 PathList pl; 644 645 old_path.close (); 646 pl = process_deleted_control_points (old_path); 647 648 if (pl.paths.size == 0) { 649 pl.add (old_path); 650 } 651 652 foreach (Path pn in pl.paths) { 653 pn.close (); 654 } 655 656 return pl; 657 } 658 659 static bool has_self_intersection (Path path) { 660 bool intersection = false; 661 662 path.all_segments ((ep1, ep2) => { 663 double ix, iy; 664 EditPoint p1, p2; 665 666 if (segment_intersects (path, ep1, ep2, out ix, out iy, out p1, out p2)) { 667 intersection = true; 668 return false; 669 } 670 671 return true; 672 }); 673 674 return intersection; 675 } 676 677 static bool add_self_intersection_points (Path path, bool only_offsets = false) { 678 bool intersection = false; 679 680 path.all_segments ((ep1, ep2) => { 681 double ix, iy; 682 EditPoint p1, p2; 683 684 if (segment_intersects (path, ep1, ep2, out ix, out iy, out p1, out p2, true, only_offsets)) { 685 add_intersection (path, ep1, ep2, ix, iy); 686 add_intersection (path, p1, p2, ix, iy); 687 688 intersection = true; 689 return false; 690 } 691 692 return true; 693 }); 694 695 if (intersection) { 696 // FIXME: path.create_list (); 697 } 698 699 return intersection; 700 } 701 702 static void add_intersection (Path path, EditPoint prev, EditPoint next, double px, double py, Color? c = null) { 703 Gee.ArrayList<EditPoint> n = new Gee.ArrayList<EditPoint> (); 704 EditPoint ep1 = new EditPoint (); 705 EditPoint ep2 = new EditPoint (); 706 EditPoint ep3 = new EditPoint (); 707 708 if (next == path.get_first_point ()) { // FIXME: double check 709 ep1.prev = null; 710 } else { 711 ep1.prev = prev; 712 } 713 714 ep1.prev = prev; 715 ep1.next = ep2; 716 ep1.flags |= EditPoint.NEW_CORNER; 717 ep1.type = PointType.CUBIC; 718 ep1.x = px; 719 ep1.y = py; 720 ep1.color = c; 721 n.add (ep1); 722 723 ep2.prev = ep1; 724 ep2.next = ep3; 725 ep2.flags |= EditPoint.INTERSECTION; 726 ep2.type = PointType.QUADRATIC; 727 ep2.x = px; 728 ep2.y = py; 729 ep2.color = c; 730 n.add (ep2); 731 732 ep3.prev = ep2; 733 ep3.next = next; 734 ep3.flags |= EditPoint.NEW_CORNER; 735 ep3.type = PointType.CUBIC; 736 ep3.x = px; 737 ep3.y = py; 738 ep3.color = c; 739 n.add (ep3); 740 741 foreach (EditPoint np in n) { 742 np = path.add_point_after (np, np.prev); 743 path.create_list (); 744 } 745 746 path.recalculate_linear_handles (); 747 } 748 749 static bool segments_intersects (EditPoint p1, EditPoint p2, EditPoint ep, EditPoint next, 750 out double ix, out double iy, 751 bool skip_points_on_points = false) { 752 double cross_x, cross_y; 753 754 ix = 0; 755 iy = 0; 756 757 Path.find_intersection_point (ep, next, p1, p2, out cross_x, out cross_y); 758 759 if (Glyph.CANVAS_MIN < cross_x < Glyph.CANVAS_MAX 760 && Glyph.CANVAS_MIN < cross_y < Glyph.CANVAS_MAX) { 761 // iterate to find intersection. 762 763 if (skip_points_on_points || 764 !((ep.x == cross_x && ep.y == cross_y) 765 || (next.x == cross_x && next.y == cross_y) 766 || (p1.x == cross_x && p1.y == cross_y) 767 || (p2.x == cross_x && p2.y == cross_y))) { 768 769 if (is_line (ep.x, ep.y, cross_x, cross_y, next.x, next.y) 770 && is_line (p1.x, p1.y, cross_x, cross_y, p2.x, p2.y)) { 771 772 ix = cross_x; 773 iy = cross_y; 774 775 return true; 776 } 777 } 778 } 779 780 return false; 781 } 782 783 static bool segment_intersects (Path path, EditPoint ep, EditPoint next, 784 out double ix, out double iy, 785 out EditPoint ia, out EditPoint ib, 786 bool skip_points_on_points = false, 787 bool only_offsets = false) { 788 789 EditPoint p1, p2; 790 bool intersection; 791 792 ix = 0; 793 iy = 0; 794 795 ia = new EditPoint (); 796 ib = new EditPoint (); 797 798 if (path.points.size == 0) { 799 return false; 800 } 801 802 p1 = path.points.get (path.points.size - 1); 803 for (int i = 0; i < path.points.size; i++) { 804 p2 = path.points.get (i); 805 806 bool is_offset = ((p1.flags & EditPoint.STROKE_OFFSET) > 0) 807 && ((p2.flags & EditPoint.STROKE_OFFSET) > 0) 808 && ((ep.flags & EditPoint.STROKE_OFFSET) > 0) 809 && ((next.flags & EditPoint.STROKE_OFFSET) > 0); 810 811 if (!only_offsets || is_offset) { 812 intersection = segments_intersects (p1, p2, ep, next, out ix, out iy, 813 skip_points_on_points); 814 815 if (intersection) { 816 ia = p1; 817 ib = p2; 818 return true; 819 } 820 } 821 822 p1 = p2; 823 } 824 825 return false; 826 } 827 828 /** @return true if p2 is on the line p1 to p3 */ 829 static bool is_line (double x1, double y1, double x2, double y2, double x3, double y3) { 830 double ds = Path.distance (x1, x3, y1, y3); 831 double d1 = Path.distance (x1, x2, y1, y2); 832 double d2 = Path.distance (x2, x3, y2, y3); 833 double p = d1 / ds; 834 double x = fabs ((x3 - x1) * p - (x2 - x1)); 835 double y = fabs ((y3 - y1) * p - (y2 - y1)); 836 double d = fabs (ds - (d1 + d2)); 837 838 // FIXME: delete print (@"$(fmin (x1, x3)) < $x2 && $x2 < $(fmax (x1, x3))\n"); 839 // FIXME: delete print (@"$(fmin (y1, y3)) < $y2 && $y2 < $(fmax (y1, y3))\n"); 840 841 return ds > 0.01 && d1 > 0.01 && d2 > 0.01 842 && d < 0.01 && x < 0.01 && y < 0.01 843 && fmin (x1, x3) <= x2 && x2 <= fmax (x1, x3) 844 && fmin (y1, y3) <= y2 && y2 <= fmax (y1, y3); 845 } 846 847 static Path get_outline (Path path) { 848 PathList pl = get_parts (path); 849 Path outline = new Path (); 850 int inside; 851 int min_inside = int.MAX; 852 int points = 0; 853 int i = 0; 854 855 foreach (Path p in pl.paths) { 856 inside = Path.counters (pl, p); 857 858 if (inside < min_inside) { 859 outline = p; 860 min_inside = inside; 861 } 862 863 i++; 864 } 865 866 if (min_inside == 0) { 867 foreach (Path p in pl.paths) { 868 if (p.points.size > points) { 869 outline = p; 870 points = p.points.size; 871 } 872 } 873 } 874 875 return outline; 876 } 877 878 static void remove_points_in_stroke (PathList pl) { 879 PathList result; 880 PathList r; 881 PathList parts; 882 883 foreach (Path p in pl.paths) { 884 if (remove_points_in_stroke_for_path (p, pl, out r)) { 885 pl.append (r); 886 remove_points_in_stroke (pl); 887 return; 888 } 889 } 890 } 891 892 static bool remove_points_in_stroke_for_path (Path p, PathList pl, out PathList result) { 893 bool remove = false; 894 EditPoint start_ep; 895 EditPoint start_next; 896 EditPoint start_prev; 897 EditPoint end_ep = new EditPoint (); 898 EditPoint end_next; 899 EditPoint end_prev; 900 Path path2; 901 EditPoint found = new EditPoint (); 902 903 result = new PathList (); 904 905 for (int i = 1; i < p.points.size + 1; i++) { 906 start_prev = p.points.get ((i - 1) % p.points.size); 907 start_ep = p.points.get (i % p.points.size); 908 start_next = p.points.get ((i + 1) % p.points.size); 909 910 if ((start_ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) { 911 for (int j = i; j < p.points.size + i; j++) { 912 end_prev = p.points.get ((j - 1) % p.points.size); 913 end_ep = p.points.get (j % p.points.size); 914 end_next = p.points.get ((j + 1) % p.points.size); 915 916 // FIXME: if (!is_inside_of_path 917 918 if ((end_ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) { 919 start_ep.flags = EditPoint.NONE; 920 end_ep.flags = EditPoint.NONE; 921 922 //start_ep.color = new Color (0,0,1,1); 923 //end_ep.color = new Color (0,0,1,1); 924 925 if (merge_segments (pl, p, start_prev, start_ep, end_ep, end_next, out result)) { 926 return true; 927 } 928 } 929 } 930 } 931 932 start_ep.flags = EditPoint.NONE; 933 end_ep.flags = EditPoint.NONE; 934 } 935 936 return false; 937 } 938 939 static bool merge_segments (PathList pl, 940 Path path1, EditPoint start1, EditPoint stop1, 941 EditPoint start2, EditPoint stop2, 942 out PathList result) { 943 944 result = new PathList (); 945 946 PathList r1; 947 PathList r2; 948 949 foreach (Path path2 in pl.paths) { 950 if (path2 != path1) { 951 reset_intersections (path1); 952 reset_intersections (path2); 953 954 if (add_merge_intersection_point (path1, path2, start1, stop1)) { 955 if (add_merge_intersection_point (path1, path2, start2, stop2)) { 956 957 r1 = get_remaining_points (path1.copy ()); 958 r2 = get_remaining_points (path2.copy ()); 959 960 if (r1.paths.size != 2) { 961 warning (@"Expecting two paths in r1 found $(r1.paths.size)\n"); 962 reset_intersections (path1); 963 reset_intersections (path2); 964 return true; 965 } 966 967 if (r2.paths.size != 2) { 968 warning (@"Expecting two paths in r2 found $(r2.paths.size)\n"); 969 reset_intersections (path1); 970 reset_intersections (path2); 971 return true; 972 } 973 974 pl.paths.remove (path1); 975 pl.paths.remove (path2); 976 977 // FIXME: find criteria 978 979 start1.color = new Color (1,0,0,1); 980 stop1.color = new Color (1,0,0,1); 981 start2.color = new Color (1,0,0,1); 982 stop2.color = new Color (1,0,0,1); 983 984 double d1 = Path.point_distance (r1.paths.get (0).get_first_point (), 985 r2.paths.get (0).get_first_point ()); 986 987 double d2 = Path.point_distance (r1.paths.get (0).get_first_point (), 988 r2.paths.get (1).get_first_point ()); 989 990 Path m1, m2; 991 992 if (d1 > d2) { 993 m1 = PenTool.merge_open_paths (r1.paths.get (0), r2.paths.get (0)); 994 m2 = PenTool.merge_open_paths (r1.paths.get (1), r2.paths.get (1)); 995 } else { 996 m1 = PenTool.merge_open_paths (r1.paths.get (1), r2.paths.get (0)); 997 m2 = PenTool.merge_open_paths (r1.paths.get (0), r2.paths.get (1)); 998 } 999 1000 result.add (m1); 1001 result.add (m2); 1002 1003 return true; 1004 } else { 1005 reset_intersections (path1); 1006 reset_intersections (path2); 1007 } 1008 } else { 1009 reset_intersections (path1); 1010 reset_intersections (path2); 1011 } 1012 } 1013 } 1014 1015 return false; 1016 } 1017 1018 static void reset_intersections (Path p) { 1019 foreach (EditPoint ep in p.points) { 1020 ep.flags &= uint.MAX ^ EditPoint.INTERSECTION; 1021 ep.deleted = false; 1022 } 1023 p.remove_points_on_points (); 1024 } 1025 1026 static bool has_counter_to_outline (Path p) { 1027 foreach (EditPoint ep in p.points) { 1028 if ((ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) { 1029 return true; 1030 } 1031 } 1032 1033 return false; 1034 } 1035 1036 static bool add_merge_intersection_point (Path path1, Path path2, EditPoint first, EditPoint next) { 1037 double ix, iy; 1038 bool intersection; 1039 1040 intersection = false; 1041 ix = 0; 1042 iy = 0; 1043 path2.all_segments ((p1, p2) => { 1044 int i; 1045 1046 intersection = segments_intersects (first, next, p1, p2, out ix, out iy); 1047 1048 if (intersection) { 1049 add_intersection (path1, first, next, ix, iy); 1050 add_intersection (path2, p1, p2, ix, iy); 1051 1052 i = mark_intersection_as_deleted (path1); 1053 i = mark_intersection_as_deleted (path2); 1054 } 1055 1056 return !intersection; 1057 }); 1058 1059 return intersection; 1060 } 1061 1062 static bool is_inside_of_path (PointSelection ps, PathList pl, out Path outline) { 1063 outline = new Path (); 1064 foreach (Path p in pl.paths) { 1065 if (p != ps.path) { 1066 if (SvgParser.is_inside (ps.point, p)) { 1067 outline = p; 1068 return true; 1069 } 1070 } 1071 } 1072 1073 return false; 1074 } 1075 1076 static PathList merge (PathList pl) { 1077 remove_points_in_stroke (pl); 1078 1079 PathList r = pl; 1080 foreach (Path p in pl.paths) { 1081 if (stroke_selected) { // FIXME: DELETE 1082 ((!) BirdFont.get_current_font ().get_glyph ("a")).add_path (p); 1083 } 1084 } 1085 1086 PathList result = new PathList (); 1087 PathList m; 1088 1089 foreach (Path p1 in r.paths) { 1090 foreach (Path p2 in r.paths) { 1091 if (p1 != p2) { 1092 if (p1.is_clockwise ()) { // FIXME 1093 m = merge_path (p1, p2); 1094 if (m.paths.size > 1) { 1095 result.append (m); 1096 } 1097 } 1098 } 1099 } 1100 result.add (p1); 1101 } 1102 1103 return result; 1104 } 1105 1106 static PathList merge_path (Path path1, Path path2) { 1107 EditPoint ep1, next, p1, p2, start_point; 1108 Path path, other; 1109 PathList pl1, pl2, r, other_paths; 1110 bool intersects; 1111 int s = 0; 1112 int i; 1113 double ix, iy; 1114 1115 foreach (EditPoint e in path1.points) { 1116 if (SvgParser.is_inside (e, path2)) { 1117 break; 1118 } 1119 s++; 1120 } 1121 1122 other_paths = new PathList (); 1123 r = new PathList (); 1124 path = path1; 1125 other = path2; 1126 other_paths.add (other); 1127 intersects = false; 1128 p1 = new EditPoint (); 1129 p2 = new EditPoint (); 1130 ix = 0; 1131 iy = 0; 1132 i = s; 1133 path.points.get (i % path.points.size).color = new Color (0,1,0,1); 1134 while (i < path.points.size + s) { 1135 ep1 = path.points.get (i % path.points.size); 1136 next = path.points.get ((i + 1) % path.points.size); 1137 1138 foreach (Path o in other_paths.paths) { 1139 other = o; 1140 intersects = segment_intersects (other, ep1, next, out ix, out iy, 1141 out p1, out p2, true); 1142 } 1143 1144 if (intersects) { 1145 add_intersection (path, ep1, next, ix, iy); 1146 add_intersection (other, p1, p2, ix, iy); 1147 1148 pl1 = get_remaining_points (path.copy ()); 1149 pl2 = get_remaining_points (other.copy ()); 1150 1151 return_val_if_fail (0 < pl1.paths.size < 3, r); 1152 return_val_if_fail (0 < pl2.paths.size < 3, r); 1153 1154 r.paths.remove (path); 1155 r.paths.remove (other); 1156 1157 path = get_next_part (pl2, path.get_last_point ()); 1158 other_paths = pl1; 1159 other_paths.append (pl2); 1160 other_paths.paths.remove (path); 1161 1162 r.append (pl1); 1163 r.append (pl2); 1164 1165 i = 0; 1166 } 1167 1168 i++; 1169 } 1170 1171 return r; 1172 } 1173 1174 static Path get_next_part (PathList pl, EditPoint ep) { 1175 double d, m; 1176 Path r; 1177 1178 r = new Path (); 1179 m = double.MAX; 1180 foreach (Path p in pl.paths) { 1181 d = Path.distance_to_point (p.get_last_point (), ep); 1182 if (d < m) { 1183 m = d; 1184 r = p; 1185 } 1186 } 1187 1188 return r; 1189 } 1190 1191 public static int counters_in_point_in_path (Path p, EditPoint ep) { 1192 int inside_count = 0; 1193 bool inside; 1194 1195 if (p.points.size > 1) { 1196 inside = true; 1197 1198 if (!SvgParser.is_inside (ep, p)) { 1199 inside = false; 1200 } 1201 1202 if (inside) { 1203 inside_count++; 1204 } 1205 } 1206 1207 return inside_count; 1208 } 1209 1210 static int mark_intersection_as_deleted (Path path) { 1211 int i = 0; 1212 1213 foreach (EditPoint p in path.points) { 1214 1215 if ((p.flags & EditPoint.INTERSECTION) > 0) { 1216 p.deleted = true; 1217 i++; 1218 } 1219 } 1220 1221 return i; 1222 } 1223 1224 /** @return true if the two paths can be merged. */ 1225 static bool merge_path_delete (Path path1, Path path2, out Path merged) { 1226 PathList pl1, pl2; 1227 Path p1, p2; 1228 int i; 1229 1230 merged = new Path (); 1231 1232 if (path1 == path2) { 1233 return false; 1234 } 1235 1236 if (add_intersection_points (path1, path2)) { 1237 i = mark_intersection_as_deleted (path1); 1238 return_if_fail (i == 1); 1239 1240 i = mark_intersection_as_deleted (path2); 1241 return_if_fail (i == 1); 1242 1243 pl1 = get_remaining_points (path1.copy ()); 1244 pl2 = get_remaining_points (path2.copy ()); 1245 1246 return_if_fail (pl1.paths.size == 1); 1247 return_if_fail (pl2.paths.size == 1); 1248 1249 p1 = pl1.paths.get (0); 1250 p2 = pl2.paths.get (0); 1251 1252 merged = PenTool.merge_open_paths (p1, p2); 1253 1254 return true; 1255 } 1256 1257 return false; 1258 } 1259 1260 /** @param n number of interrsections to find per path. */ 1261 static bool add_intersection_points (Path path1, Path path2, int n = 1) { 1262 bool intersection = false; 1263 int found = 0; 1264 1265 path1.all_segments ((ep1, ep2) => { 1266 double ix, iy; 1267 EditPoint p1, p2; 1268 bool i; 1269 1270 i = segment_intersects (path2, ep1, ep2, out ix, out iy, 1271 out p1, out p2, true); 1272 1273 if (i) { 1274 add_intersection (path1, ep1, ep2, ix, iy); 1275 add_intersection (path2, p1, p2, ix, iy); 1276 intersection = true; 1277 found++; 1278 return found < n; 1279 } 1280 1281 return true; 1282 }); 1283 1284 return intersection; 1285 } 1286 1287 } 1288 1289 } 1290