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