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