The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

Path.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/Path.vala.
Fix self intersecting stroke
1 /* 2 Copyright (C) 2012, 2013, 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 enum Direction { 21 CLOCKWISE, 22 COUNTER_CLOCKWISE 23 } 24 25 public class Path { 26 27 public Gee.ArrayList<EditPoint> points { 28 get { 29 if (control_points == null) { 30 control_points = new Gee.ArrayList<EditPoint> (); 31 BirdFontFile.parse_path_data (point_data, this); 32 point_data = ""; 33 } 34 35 return (!) control_points; 36 } 37 } 38 39 public Gee.ArrayList<EditPoint>? control_points = null; 40 41 EditPoint? last_point = null; 42 43 /** Path boundaries */ 44 public double xmax = Glyph.CANVAS_MIN; 45 public double xmin = Glyph.CANVAS_MAX; 46 public double ymax = Glyph.CANVAS_MIN; 47 public double ymin = Glyph.CANVAS_MAX; 48 49 /** Stroke width */ 50 public double stroke = 0; 51 PathList? full_stroke = null; 52 PathList? fast_stroke = null; 53 54 /** Fill property for closed paths with stroke. */ 55 public bool fill = false; 56 57 bool edit = true; 58 bool open = true; 59 60 public bool direction_is_set = false; 61 bool no_derived_direction = false; 62 bool clockwise_direction = true; 63 64 // Iterate over each pixel in a path 65 public delegate bool RasterIterator (double x, double y, double step); 66 67 public delegate bool SegmentIterator (EditPoint start, EditPoint stop); 68 69 /** The stroke of an outline when the path is not filled. */ 70 public static double stroke_width = 0; 71 public static bool show_all_line_handles = true; 72 public static bool fill_open_path = false; 73 74 public double rotation = 0; 75 public double skew = 0; 76 77 public bool hide_end_handle = true; 78 public bool highlight_last_segment = false; 79 80 public string point_data = ""; 81 82 public Path () { 83 string width; 84 85 if (unlikely (stroke_width < 1)) { 86 width = Preferences.get ("stroke_width"); 87 if (width != "") { 88 stroke_width = double.parse (width); 89 } 90 } 91 92 if (stroke_width < 1) { 93 stroke_width = 1; 94 } 95 } 96 97 public bool is_filled () { 98 return fill; 99 } 100 101 public void set_fill (bool f) { 102 fill = f; 103 } 104 105 public EditPoint get_first_point () { 106 if (unlikely (points.size == 0)) { 107 warning ("No point"); 108 return new EditPoint (); 109 } 110 111 return points.get (0); 112 } 113 114 public EditPoint get_last_visible_point () { 115 EditPoint e; 116 117 if (unlikely (points.size == 0)) { 118 warning ("No point"); 119 return new EditPoint (); 120 } 121 122 for (int i = points.size - 1; i >= 0; i--) { 123 e = points.get (i); 124 if (e.type != PointType.HIDDEN) { 125 return e; 126 } 127 } 128 129 warning ("Only hidden points"); 130 return new EditPoint (); 131 } 132 133 public EditPoint get_last_point () { 134 if (unlikely (points.size == 0)) { 135 warning ("No point"); 136 return new EditPoint (); 137 } 138 139 return points.get (points.size - 1); 140 } 141 142 public bool has_direction () { 143 return direction_is_set; 144 } 145 146 public bool empty () { 147 return points.size == 0; 148 } 149 150 public void set_stroke (double width) { 151 stroke = width; 152 } 153 154 public void draw_boundaries (Context cr) { 155 double x = Glyph.reverse_path_coordinate_x (xmin); 156 double y = Glyph.reverse_path_coordinate_y (ymin); 157 double x2 = Glyph.reverse_path_coordinate_x (xmax); 158 double y2 = Glyph.reverse_path_coordinate_y (ymax); 159 160 cr.save (); 161 162 Theme.color (cr, "Default Background"); 163 cr.set_line_width (2); 164 cr.rectangle (x, y, x2 - x, y2 - y); 165 cr.stroke (); 166 167 cr.restore (); 168 } 169 170 public void draw_outline (Context cr) { 171 unowned EditPoint? n = null; 172 unowned EditPoint en; 173 unowned EditPoint em; 174 int i; 175 176 if (points.size < 2) { 177 return; 178 } 179 180 cr.new_path (); 181 182 // draw lines 183 i = 0; 184 foreach (EditPoint e in points) { 185 if (n != null) { 186 en = (!) n; 187 if (!highlight_last_segment || i != points.size - 1) { 188 draw_next (en, e, cr); 189 } 190 } 191 192 n = e; 193 i++; 194 } 195 196 // close path 197 if (!is_open () && n != null) { 198 if (highlight_last_segment) { 199 cr.stroke (); 200 en = points.get (points.size - 1).get_link_item (); 201 em = points.get (0).get_link_item (); 202 draw_next (en, em, cr); 203 cr.stroke (); 204 } else { 205 en = (!) n; 206 em = points.get (0).get_link_item (); 207 draw_next (en, em, cr); 208 cr.stroke (); 209 } 210 } else { 211 cr.stroke (); 212 } 213 214 // draw highlighted segment 215 if (highlight_last_segment && points.size >= 2) { 216 draw_next (points.get (points.size - 2), points.get (points.size - 1), cr, true); 217 cr.stroke (); 218 } 219 } 220 221 public void draw_edit_points (Context cr) { 222 if (is_editable ()) { 223 // control points for curvature 224 foreach (EditPoint e in points) { 225 if (show_all_line_handles || e.selected_point || e.selected_handle > 0) { 226 draw_edit_point_handles (e, cr); 227 } 228 } 229 230 // control points 231 foreach (EditPoint e in points) { 232 draw_edit_point (e, cr); 233 } 234 } 235 } 236 237 /** Add all control points for a path to the cairo context. 238 * Call Context.new_path (); before this method and Context.fill () 239 * to show the path. 240 */ 241 public void draw_path (Context cr, Glyph glyph, Color? color = null) { 242 unowned EditPoint? n = null; 243 unowned EditPoint en; 244 unowned EditPoint em; 245 Color c; 246 double center_x, center_y; 247 double ex, ey; 248 249 if (points.size == 0){ 250 return; 251 } 252 253 center_x = glyph.allocation.width / 2.0; 254 center_y = glyph.allocation.height / 2.0; 255 256 ex = center_x + points.get (0).x; 257 ey = center_y - points.get (0).y; 258 259 cr.move_to (ex, ey); 260 261 // draw lines 262 foreach (EditPoint e in points) { 263 if (n != null) { 264 en = (!) n; 265 draw_next (en, e, cr); 266 } 267 268 n = e; 269 } 270 271 // close path 272 if (!is_open () && points.size >= 2 && n != null) { 273 en = (!) n; 274 em = points.get (0).get_link_item (); 275 draw_next (en, em, cr); 276 } 277 278 // fill path 279 cr.close_path (); 280 281 if (color != null) { 282 c = (!) color; 283 cr.set_source_rgba (c.r, c.g, c.b, c.a); 284 } else { 285 if (is_clockwise ()) { 286 Theme.color_opacity (cr, "Selected Objects", 0.4); 287 } else { 288 Theme.color_opacity (cr, "Selected Objects", 0.8); 289 } 290 } 291 } 292 293 public void draw_orientation_arrow (Context cr, double opacity) { 294 EditPoint top = new EditPoint (); 295 double max = Glyph.CANVAS_MIN; 296 Text arrow; 297 double x, y, angle; 298 double size = 50 * Glyph.ivz (); 299 300 foreach (EditPoint e in points) { 301 if (e.y > max) { 302 max = e.y; 303 top = e; 304 } 305 } 306 307 arrow = new Text ("orientation_arrow", size); 308 arrow.load_font ("icons.bf"); 309 arrow.use_cache (false); 310 311 Theme.text_color_opacity (arrow, "Highlighted 1", opacity); 312 313 angle = top.get_right_handle ().angle; 314 x = Glyph.xc () + top.x + cos (angle + PI / 2) * 10 * Glyph.ivz (); 315 y = Glyph.yc () - top.y - sin (angle + PI / 2) * 10 * Glyph.ivz (); 316 317 if (points.size > 0) { 318 cr.save (); 319 cr.translate (x, y); 320 cr.rotate (-angle); 321 cr.translate (-x, -y); 322 323 arrow.draw_at_baseline (cr, x, y); 324 325 cr.restore (); 326 } 327 } 328 329 private void draw_next (EditPoint e, EditPoint en, Context cr, bool highlighted = false) { 330 PointType r = e.get_right_handle ().type; 331 PointType l = en.get_left_handle ().type; 332 333 if (r == PointType.DOUBLE_CURVE || l == PointType.DOUBLE_CURVE) { 334 draw_double_curve (e, en, cr, highlighted); 335 } else { 336 draw_curve (e, en, cr, highlighted); 337 } 338 } 339 340 private static void draw_double_curve (EditPoint e, EditPoint en, Context cr, bool highlighted) { 341 EditPoint middle; 342 double x, y; 343 344 x = e.get_right_handle ().x + (en.get_left_handle ().x - e.get_right_handle ().x) / 2; 345 y = e.get_right_handle ().y + (en.get_left_handle ().y - e.get_right_handle ().y) / 2; 346 347 middle = new EditPoint (x, y, PointType.DOUBLE_CURVE); 348 middle.right_handle = en.get_left_handle ().copy (); 349 350 middle.right_handle.type = PointType.DOUBLE_CURVE; 351 middle.left_handle.type = PointType.DOUBLE_CURVE; 352 353 draw_curve (e, middle, cr, highlighted); 354 draw_curve (middle, en, cr, highlighted); 355 } 356 357 private static void draw_curve (EditPoint e, EditPoint en, Context cr, bool highlighted = false, double alpha = 1) { 358 Glyph g = MainWindow.get_current_glyph (); 359 double xa, ya, xb, yb, xc, yc, xd, yd; 360 PointType t = e.get_right_handle ().type; 361 PointType u = en.get_left_handle ().type; 362 363 get_bezier_points (e, en, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd); 364 365 if (!highlighted) { 366 Theme.color (cr, "Stroke Color"); 367 } else { 368 Theme.color (cr, "Highlighted Guide"); 369 } 370 371 cr.set_line_width (stroke_width / g.view_zoom); 372 373 cr.line_to (xa, ya); // this point makes sense only if it is in the first or last position 374 375 if (t == PointType.QUADRATIC || t == PointType.LINE_QUADRATIC || t == PointType.DOUBLE_CURVE || u == PointType.QUADRATIC || u == PointType.LINE_QUADRATIC || u == PointType.DOUBLE_CURVE) { 376 cr.curve_to ((xa + 2 * xb) / 3, (ya + 2 * yb) / 3, (xd + 2 * xb) / 3, (yd + 2 * yb) / 3, xd, yd); 377 } else { 378 cr.curve_to (xb, yb, xc, yc, xd, yd); 379 } 380 } 381 382 /** Curve relative to window center. */ 383 public static void get_bezier_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb, out double xc, out double yc, out double xd, out double yd) { 384 Glyph g = MainWindow.get_current_glyph (); 385 386 double center_x, center_y; 387 388 center_x = g.allocation.width / 2.0; 389 center_y = g.allocation.height / 2.0; 390 391 xa = center_x + e.x; 392 ya = center_y - e.y; 393 394 xb = center_x + e.get_right_handle ().x; 395 yb = center_y - e.get_right_handle ().y; 396 397 xc = center_x + en.get_left_handle ().x; 398 yc = center_y - en.get_left_handle ().y; 399 400 xd = center_x + en.x; 401 yd = center_y - en.y; 402 } 403 404 /** Curve absolute glyph data. */ 405 public static void get_abs_bezier_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb, out double xc, out double yc, out double xd, out double yd) { 406 xa = + e.x; 407 ya = - e.y; 408 409 xb = + e.get_right_handle ().x; 410 yb = - e.get_right_handle ().y; 411 412 xc = + en.get_left_handle ().x; 413 yc = - en.get_left_handle ().y; 414 415 xd = + en.x; 416 yd = - en.y; 417 } 418 419 /** Line points relative to centrum. */ 420 public static void get_line_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb) { 421 double xc = Glyph.xc (); 422 double yc = Glyph.yc (); 423 424 xa = xc + e.x; 425 ya = yc - e.y; 426 427 xb = xc + en.x; 428 yb = yc - en.y; 429 } 430 431 public void draw_line (EditPoint e, EditPoint en, Context cr, double alpha = 1) { 432 Glyph g = MainWindow.get_current_glyph (); 433 double ax, ay, bx, by; 434 435 get_line_points (e, en, out ax, out ay, out bx, out by); 436 437 Theme.color (cr, "Handle Color"); 438 cr.set_line_width (1.7 * (stroke_width / g.view_zoom)); 439 440 cr.line_to (ax, ay); 441 cr.line_to (bx, by); 442 443 cr.stroke (); 444 } 445 446 public void draw_edit_point (EditPoint e, Context cr) { 447 draw_edit_point_center (e, cr); 448 } 449 450 public void draw_edit_point_handles (EditPoint e, Context cr) { 451 Color color_left = Theme.get_color ("Control Point Handle"); 452 Color color_right = Theme.get_color ("Control Point Handle"); 453 EditPoint handle_right = e.get_right_handle ().get_point (); 454 EditPoint handle_left = e.get_left_handle ().get_point (); 455 456 cr.stroke (); 457 458 if (e.type != PointType.HIDDEN) { 459 if (e.get_right_handle ().selected) { 460 color_right = Theme.get_color ("Selected Control Point Handle"); 461 } else if (e.get_right_handle ().active) { 462 color_right = Theme.get_color ("Active Control Point Handle"); 463 } else { 464 color_right = Theme.get_color ("Control Point Handle"); 465 } 466 467 if (e.get_left_handle ().selected) { 468 color_left = Theme.get_color ("Selected Control Point Handle"); 469 } else if (e.get_left_handle ().active) { 470 color_left = Theme.get_color ("Active Control Point Handle"); 471 } else { 472 color_left = Theme.get_color ("Control Point Handle"); 473 } 474 475 if (!hide_end_handle || !(is_open () && e == points.get (points.size - 1))) { 476 draw_line (handle_right, e, cr, 0.15); 477 draw_control_point (cr, e.get_right_handle ().x, e.get_right_handle ().y, color_right); 478 } 479 480 if (!(is_open () && e == points.get (0))) { 481 draw_line (handle_left, e, cr, 0.15); 482 draw_control_point (cr, e.get_left_handle ().x, e.get_left_handle ().y, color_left); 483 } 484 } 485 } 486 487 public static void draw_edit_point_center (EditPoint e, Context cr) { 488 Color c; 489 490 if (e.type != PointType.HIDDEN) { 491 if (e.type == PointType.CUBIC || e.type == PointType.LINE_CUBIC) { 492 if (e.is_selected ()) { 493 if (e.active_point) { 494 if (e.color != null) { 495 c = (!) e.color; 496 } else { 497 c = Theme.get_color ("Selected Active Cubic Control Point"); 498 } 499 } else { 500 if (e.color != null) { 501 c = (!) e.color; 502 } else { 503 c = Theme.get_color ("Selected Cubic Control Point"); 504 } 505 } 506 } else { 507 if (e.active_point) { 508 if (e.color != null) { 509 c = (!) e.color; 510 } else { 511 c = Theme.get_color ("Active Cubic Control Point"); 512 } 513 } else { 514 if (e.color != null) { 515 c = (!) e.color; 516 } else { 517 c = Theme.get_color ("Cubic Control Point"); 518 } 519 } 520 } 521 } else { 522 if (e.is_selected ()) { 523 if (e.active_point) { 524 if (e.color != null) { 525 c = (!) e.color; 526 } else { 527 c = Theme.get_color ("Selected Active Quadratic Control Point"); 528 } 529 } else { 530 if (e.color != null) { 531 c = (!) e.color; 532 } else { 533 c = Theme.get_color ("Selected Quadratic Control Point"); 534 } 535 } 536 } else { 537 if (e.active_point) { 538 if (e.color != null) { 539 c = (!) e.color; 540 } else { 541 c = Theme.get_color ("Active Quadratic Control Point"); 542 } 543 } else { 544 if (e.color != null) { 545 c = (!) e.color; 546 } else { 547 c = Theme.get_color ("Quadratic Control Point"); 548 } 549 } 550 } 551 } 552 553 draw_control_point (cr, e.x, e.y, c); 554 } 555 } 556 557 public static void draw_control_point (Context cr, double x, double y, Color color, double size = 3.5) { 558 Glyph g = MainWindow.get_current_glyph (); 559 double ivz = 1 / g.view_zoom; 560 double width = size * Math.sqrt (stroke_width) * ivz; 561 double xc = g.allocation.width / 2.0; 562 double yc = g.allocation.height / 2.0; 563 564 cr.save (); 565 566 x = xc + x; 567 y = yc - y; 568 569 cr.set_source_rgba (color.r, color.g, color.b, color.a); 570 571 cr.move_to (x, y); 572 cr.arc (x, y, width, 0, 2 * Math.PI); 573 cr.close_path (); 574 cr.fill (); 575 576 cr.restore (); 577 } 578 579 /** Set direction for this path to clockwise for outline and 580 * counter clockwise for inline paths. 581 */ 582 public bool force_direction (Direction direction) { 583 bool c = (direction == Direction.CLOCKWISE); 584 bool d = is_clockwise (); 585 direction_is_set = true; 586 587 if (c != d) { 588 this.reverse (); 589 } 590 591 d = is_clockwise (); 592 if (unlikely (d != c)) { 593 warning ("Failed to set direction for path in force_direction."); 594 return true; 595 } 596 597 return false; 598 } 599 600 /** Switch direction from clockwise path to counter clockwise path or vise versa. */ 601 public bool reverse () { 602 bool direction = is_clockwise (); 603 604 if (no_derived_direction) { 605 clockwise_direction = !clockwise_direction; 606 } 607 608 reverse_points (); 609 610 if (unlikely (direction == is_clockwise ())) { 611 return false; 612 } 613 614 return true; 615 } 616 617 private void reverse_points () requires (points.size > 0) { 618 EditPointHandle t; 619 Path p = copy (); 620 EditPoint e; 621 622 create_list (); 623 624 points.clear (); 625 626 for (int i = p.points.size - 1; i >= 0 ; i--) { 627 e = p.points.get (i); 628 629 t = e.right_handle; 630 e.right_handle = e.left_handle; 631 e.left_handle = t; 632 633 add_point (e); 634 } 635 636 create_list (); 637 } 638 639 public void print_all_points () { 640 int i = 0; 641 foreach (EditPoint p in points) { 642 ++i; 643 string t = (p.type == PointType.END) ? " endpoint" : ""; 644 stdout.printf (@"Point $i at ($(p.x), $(p.y)) $t \n"); 645 } 646 } 647 648 private double clockwise_sum () { 649 double sum = 0; 650 651 return_val_if_fail (points.size >= 3, 0); 652 653 foreach (EditPoint e in points) { 654 sum += e.get_direction (); 655 } 656 657 return sum; 658 } 659 660 public bool is_clockwise () { 661 double s; 662 Path p; 663 664 if (unlikely (points.size <= 2)) { 665 no_derived_direction = true; 666 return clockwise_direction; 667 } 668 669 if (unlikely (points.size == 2)) { 670 p = copy (); 671 all_segments ((a, b) => { 672 double px, py; 673 double step; 674 EditPoint new_point; 675 676 step = 0.3; 677 678 Path.get_point_for_step (a, b, step, out px, out py); 679 680 new_point = new EditPoint (px, py); 681 new_point.prev = a; 682 new_point.next = b; 683 684 p.insert_new_point_on_path (new_point, step); 685 686 return true; 687 }); 688 689 return p.is_clockwise (); 690 } 691 692 s = clockwise_sum (); 693 694 if (s == 0) { 695 no_derived_direction = true; 696 return clockwise_direction; 697 } 698 699 return s > 0; 700 } 701 702 public bool is_editable () { 703 return edit; 704 } 705 706 /** Show control points on outline path. */ 707 public void set_editable (bool e) { 708 edit = e; 709 } 710 711 public bool is_open () { 712 return open; 713 } 714 715 /** Resize path relative to bottom left coordinates. */ 716 public void resize (double ratio) { 717 foreach (EditPoint p in points) { 718 p.x *= ratio; 719 p.y *= ratio; 720 p.right_handle.length *= ratio; 721 p.left_handle.length *= ratio; 722 } 723 724 xmin *= ratio; 725 xmax *= ratio; 726 ymin *= ratio; 727 ymax *= ratio; 728 } 729 730 public void scale (double scale_x, double scale_y) { 731 foreach (EditPoint p in points) { 732 p.right_handle.length *= scale_x * scale_y; 733 p.left_handle.length *= scale_x * scale_y; 734 } 735 736 foreach (EditPoint p in points) { 737 p.x *= scale_x; 738 p.y *= scale_y; 739 } 740 741 xmin *= scale_x; 742 xmax *= scale_x; 743 ymin *= scale_y; 744 ymax *= scale_y; 745 } 746 747 public Path copy () { 748 Path new_path = new Path (); 749 EditPoint p; 750 751 foreach (EditPoint ep in points) { 752 p = ep.copy (); 753 new_path.add_point (p); 754 } 755 756 new_path.edit = edit; 757 new_path.open = open; 758 new_path.stroke = stroke; 759 new_path.skew = skew; 760 new_path.fill = fill; 761 new_path.direction_is_set = direction_is_set; 762 new_path.create_list (); 763 764 new_path.hide_end_handle = hide_end_handle; 765 new_path.highlight_last_segment = highlight_last_segment; 766 767 return new_path; 768 } 769 770 public bool is_over (double x, double y) { 771 Glyph g = MainWindow.get_current_glyph (); 772 773 x = x * Glyph.ivz () + g.view_offset_x - Glyph.xc (); 774 y = y * Glyph.ivz () + g.view_offset_y - Glyph.yc (); 775 776 y *= -1; 777 778 return is_over_coordinate (x, y); 779 } 780 781 public bool is_over_coordinate (double x, double y) { 782 return is_over_coordinate_var (x, y); 783 } 784 785 public static double point_distance (EditPoint p1, EditPoint p2) { 786 return distance (p1.x, p2.x, p1.y, p2.y); 787 } 788 789 public static double distance (double ax, double bx, double ay, double by) { 790 return Math.fabs (Math.sqrt (Math.pow (ax - bx, 2) + Math.pow (ay - by, 2))); 791 } 792 793 public static double distance_to_point (EditPoint a, EditPoint b) { 794 return distance (a.x, b.x, a.y, b.y); 795 } 796 797 public static double distance_pixels (double x1, double y1, double x2, double y2) { 798 return distance (Glyph.path_coordinate_x (x1), 799 Glyph.path_coordinate_x (x2), 800 Glyph.path_coordinate_x (y1), 801 Glyph.path_coordinate_x (y2)); 802 } 803 804 public static double get_length_from (EditPoint a, EditPoint b) { 805 double x, y; 806 807 x = Math.fabs (a.x - a.get_right_handle ().x); 808 x += Math.fabs (a.get_right_handle ().x - b.get_left_handle ().x ); 809 x += Math.fabs (b.get_left_handle ().x - b.x); 810 811 y = Math.fabs (a.y - a.get_right_handle ().y); 812 y += Math.fabs (a.get_right_handle ().y - b.get_left_handle ().y); 813 y += Math.fabs (b.get_left_handle ().y - b.y); 814 815 return Math.fabs (Math.sqrt (x * x + y * y)); 816 } 817 818 public Path flatten () { 819 Path flat = new Path (); 820 821 all_of_path ((x, y, t) => { 822 flat.add (x, y); 823 return true; 824 }); 825 826 return flat; 827 } 828 829 /** Variable precision */ 830 public bool is_over_coordinate_var (double x, double y) { 831 int insides = 0; 832 Path path; 833 834 if (stroke > 0) { 835 foreach (Path p in get_stroke_fast ().paths) { 836 path = p.flatten (); 837 if (StrokeTool.is_inside (new EditPoint (x, y), path)) { 838 insides++; 839 } 840 } 841 842 if (insides % 2 == 1) { 843 return true; 844 } 845 } else if (is_over_boundry (x, y)) { 846 path = flatten (); 847 return StrokeTool.is_inside (new EditPoint (x, y), path); 848 } 849 850 return false; 851 } 852 853 public bool is_over_boundry (double x, double y) { 854 if (unlikely (ymin == double.MAX || ymin == 10000)) { 855 warning ("bounding box is not calculated, run update_region_boundaries first."); 856 update_region_boundaries (); 857 } 858 859 return (ymin <= y <= ymax) && (xmin <= x <= xmax); 860 } 861 862 public bool has_overlapping_boundry (Path p) { 863 return !(xmax <= p.xmin || ymax <= p.ymin) || (xmin >= p.xmax || ymin >= p.ymax); 864 } 865 866 public EditPoint delete_first_point () { 867 EditPoint r; 868 int size; 869 870 size = points.size; 871 if (unlikely (size == 0)) { 872 warning ("No points in path."); 873 return new EditPoint (); 874 } 875 876 r = points.get (0); 877 points.remove_at (0); 878 879 if (size > 1) { 880 r.get_next ().prev = null; 881 } 882 883 return r; 884 } 885 886 public EditPoint delete_last_point () { 887 EditPoint r; 888 int size; 889 890 size = points.size; 891 if (unlikely (size == 0)) { 892 warning ("No points in path."); 893 return new EditPoint (); 894 } 895 896 r = points.get (size - 1); 897 points.remove_at (size - 1); 898 899 if (size > 1) { 900 r.get_prev ().next = null; 901 902 if (r.next != null) { 903 r.get_next ().prev = null; 904 } 905 } 906 907 return r; 908 } 909 910 public EditPoint add (double x, double y) { 911 if (points.size > 0) { 912 return add_after (x, y, points.get (points.size - 1)); 913 } 914 915 return add_after (x, y, null); 916 } 917 918 public EditPoint add_point (EditPoint p) { 919 if (points.size > 0) { 920 return add_point_after (p, points.get (points.size - 1)); 921 } 922 923 return add_point_after (p, null); 924 } 925 926 /** Insert a new point after @param previous_point and return a reference 927 * to the new item in list. 928 */ 929 public EditPoint add_after (double x, double y, EditPoint? previous_point) { 930 EditPoint p = new EditPoint (x, y, PointType.NONE); 931 return add_point_after (p, previous_point); 932 } 933 934 /** @return a list item pointing to the new point */ 935 public EditPoint add_point_after (EditPoint p, EditPoint? previous_point) { 936 int prev_index; 937 938 if (unlikely (previous_point == null && points.size != 0)) { 939 warning ("previous_point == null"); 940 previous_point = points.get (points.size - 1).get_link_item (); 941 } 942 943 if (points.size == 0) { 944 points.add (p); 945 p.prev = points.get (0).get_link_item (); 946 p.next = points.get (0).get_link_item (); 947 } else { 948 p.prev = (!) previous_point; 949 p.next = ((!) previous_point).next; 950 951 prev_index = points.index_of ((!) previous_point); 952 953 if (unlikely (!(0 <= prev_index < points.size))) { 954 warning ("no previous point"); 955 } 956 957 points.insert (prev_index + 1, p); 958 } 959 960 last_point = p; 961 962 return p; 963 } 964 965 public void recalculate_linear_handles () { 966 foreach (EditPoint e in points) { 967 e.recalculate_linear_handles (); 968 } 969 } 970 971 public void close () { 972 open = false; 973 edit = false; 974 975 create_list (); 976 977 if (points.size > 2) { 978 points.get (0).recalculate_linear_handles (); 979 points.get (points.size - 1).recalculate_linear_handles (); 980 } 981 } 982 983 public void reopen () { 984 open = true; 985 edit = true; 986 } 987 988 /** Move path. */ 989 public void move (double delta_x, double delta_y) { 990 foreach (EditPoint ep in points) { 991 ep.x += delta_x; 992 ep.y += delta_y; 993 } 994 995 update_region_boundaries (); 996 } 997 998 private void update_region_boundaries_for_point (EditPoint p) { 999 EditPointHandle left_handle; 1000 EditPointHandle right_handle; 1001 1002 left_handle = p.get_left_handle (); 1003 right_handle = p.get_right_handle (); 1004 1005 if (p.x > xmax) { 1006 xmax = p.x; 1007 } 1008 1009 if (p.x < xmin) { 1010 xmin = p.x; 1011 } 1012 1013 if (p.y > ymax) { 1014 ymax = p.y; 1015 } 1016 1017 if (p.y < ymin) { 1018 ymin = p.y; 1019 } 1020 1021 update_region_boundaries_for_handle (left_handle); 1022 update_region_boundaries_for_handle (right_handle); 1023 } 1024 1025 private void update_region_boundaries_for_handle (EditPointHandle h) { 1026 if (h.x > xmax) { 1027 xmax = h.x; 1028 } 1029 1030 if (h.x < xmin) { 1031 xmin = h.x; 1032 } 1033 1034 if (h.y > ymax) { 1035 ymax = h.y; 1036 } 1037 1038 if (h.y < ymin) { 1039 ymin = h.y; 1040 } 1041 } 1042 1043 public void update_region_boundaries () { 1044 xmax = Glyph.CANVAS_MIN; 1045 xmin = Glyph.CANVAS_MAX; 1046 ymax = Glyph.CANVAS_MIN; 1047 ymin = Glyph.CANVAS_MAX; 1048 1049 if (points.size == 0) { 1050 xmax = 0; 1051 xmin = 0; 1052 ymax = 0; 1053 ymin = 0; 1054 } 1055 1056 foreach (EditPoint p in points) { 1057 update_region_boundaries_for_point (p); 1058 } 1059 1060 if (stroke > 0) { 1061 xmax += stroke; 1062 ymax += stroke; 1063 xmin -= stroke; 1064 ymin -= stroke; 1065 } 1066 } 1067 1068 /** Test if @param path is a valid outline for this object. */ 1069 public bool test_is_outline (Path path) { 1070 assert (false); 1071 return this.test_is_outline_of_path (path) && path.test_is_outline_of_path (this); 1072 } 1073 1074 private bool test_is_outline_of_path (Path outline) 1075 requires (outline.points.size >= 2 || points.size >= 2) 1076 { 1077 // rather slow use it for testing, only 1078 unowned EditPoint i = outline.points.get (0).get_link_item (); 1079 unowned EditPoint prev = outline.points.get (outline.points.size - 1).get_link_item (); 1080 1081 double tolerance = 1; 1082 bool g = false; 1083 1084 EditPoint ep = new EditPoint (0, 0); 1085 double min = double.MAX; 1086 1087 while (true) { 1088 min = 10000; 1089 1090 all_of (prev, i, (cx, cy) => { 1091 get_closest_point_on_path (ep, cx, cy); 1092 1093 double n = pow (ep.x - cx, 2) + pow (cy - ep.y, 2); 1094 1095 if (n < min) min = n; 1096 1097 if (n < tolerance) { 1098 g = true; 1099 return false; 1100 } 1101 1102 return true; 1103 }); 1104 1105 if (!g) { 1106 critical (@"this path does not seem to be the outline. (min $min)"); 1107 } 1108 1109 g = false; 1110 1111 if (i == outline.points.get (outline.points.size - 1)) { 1112 break; 1113 } 1114 1115 i = i.get_next (); 1116 } 1117 1118 return true; 1119 } 1120 1121 /** Add the extra point between line handles for double curve. */ 1122 public void add_hidden_double_points () requires (points.size > 1) { 1123 EditPoint hidden; 1124 EditPoint prev; 1125 EditPoint first; 1126 PointType left; 1127 PointType right; 1128 double x, y; 1129 Gee.ArrayList<EditPoint> middle_points = new Gee.ArrayList<EditPoint> (); 1130 Gee.ArrayList<EditPoint> first_points = new Gee.ArrayList<EditPoint> (); 1131 1132 first = is_open () ? points.get (0) : points.get (points.size - 1); 1133 1134 foreach (EditPoint next in points) { 1135 left = first.get_right_handle ().type; 1136 right = next.get_left_handle ().type; 1137 1138 if (next != first && (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE)) { 1139 1140 first.get_right_handle ().type = PointType.QUADRATIC; 1141 1142 // half way between handles 1143 x = first.get_right_handle ().x + (next.get_left_handle ().x - first.get_right_handle ().x) / 2; 1144 y = first.get_right_handle ().y + (next.get_left_handle ().y - first.get_right_handle ().y) / 2; 1145 1146 hidden = new EditPoint (x, y, PointType.QUADRATIC); 1147 hidden.get_right_handle ().type = PointType.QUADRATIC; 1148 hidden.get_left_handle ().type = PointType.QUADRATIC; 1149 hidden.type = PointType.QUADRATIC; 1150 1151 hidden.right_handle.move_to_coordinate_internal (next.get_left_handle ().x, next.get_left_handle ().y); 1152 1153 first.get_right_handle ().type = PointType.QUADRATIC; 1154 first.type = PointType.QUADRATIC; 1155 1156 next.get_left_handle ().type = PointType.QUADRATIC; 1157 next.type = PointType.QUADRATIC; 1158 1159 middle_points.add (hidden); 1160 first_points.add (first); 1161 } 1162 first = next; 1163 } 1164 1165 for (int i = 0; i < middle_points.size; i++) { 1166 hidden = middle_points.get (i); 1167 add_point_after (middle_points.get (i), first_points.get (i)); 1168 } 1169 1170 create_list (); 1171 1172 prev = get_last_point (); 1173 foreach (EditPoint ep in points) { 1174 if (ep.type == PointType.QUADRATIC) { 1175 x = prev.get_right_handle ().x; 1176 y = prev.get_right_handle ().y; 1177 ep.get_left_handle ().move_to_coordinate (x, y); 1178 } 1179 1180 prev = ep; 1181 } 1182 } 1183 1184 /** Convert quadratic bezier points to cubic representation of the glyph 1185 * for ttf-export. 1186 */ 1187 public Path get_quadratic_points () { 1188 PointConverter converter; 1189 converter = new PointConverter (this); 1190 return converter.get_quadratic_path (); 1191 } 1192 1193 public void insert_new_point_on_path (EditPoint ep, double t = -1, bool move_point_to_path = false) { 1194 EditPoint start, stop; 1195 double x0, x1, y0, y1; 1196 double position, min; 1197 PointType left, right; 1198 double closest_x = 0; 1199 double closest_y = 0; 1200 1201 if (ep.next == null || ep.prev == null) { 1202 warning ("missing point"); 1203 return; 1204 } 1205 1206 start = ep.get_prev (); 1207 stop = ep.get_next (); 1208 1209 right = start.get_right_handle ().type; 1210 left = stop.get_left_handle ().type; 1211 1212 if (right == PointType.CUBIC || left == PointType.CUBIC) { 1213 start.get_right_handle ().type = PointType.CUBIC; 1214 stop.get_left_handle ().type = PointType.CUBIC; 1215 } 1216 1217 add_point_after (ep, ep.get_prev ()); 1218 1219 min = double.MAX; 1220 1221 position = 0.5; 1222 1223 if (t < 0) { 1224 all_of (start, stop, (cx, cy, t) => { 1225 double n = pow (ep.x - cx, 2) + pow (ep.y - cy, 2); 1226 1227 if (n < min) { 1228 min = n; 1229 position = t; 1230 closest_x = cx; 1231 closest_y = cy; 1232 } 1233 1234 return true; 1235 }); 1236 1237 if (move_point_to_path) { 1238 ep.x = closest_x; 1239 ep.y = closest_y; 1240 } 1241 } else { 1242 position = t; 1243 } 1244 1245 if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) { 1246 double_bezier_vector (position, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x0, out x1); 1247 double_bezier_vector (position, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y0, out y1); 1248 1249 ep.get_left_handle ().set_point_type (PointType.DOUBLE_CURVE); 1250 ep.get_right_handle ().set_point_type (PointType.DOUBLE_CURVE); 1251 1252 ep.get_left_handle ().move_to_coordinate (x0, y0); 1253 ep.get_right_handle ().move_to_coordinate (x1, y1); 1254 1255 ep.type = PointType.DOUBLE_CURVE; 1256 } else if (right == PointType.QUADRATIC) { 1257 x0 = quadratic_bezier_vector (1 - position, stop.x, start.get_right_handle ().x, start.x); 1258 y0 = quadratic_bezier_vector (1 - position, stop.y, start.get_right_handle ().y, start.y); 1259 ep.get_right_handle ().move_to_coordinate (x0, y0); 1260 1261 ep.get_left_handle ().set_point_type (PointType.QUADRATIC); 1262 ep.get_right_handle ().set_point_type (PointType.QUADRATIC); 1263 1264 ep.get_left_handle ().move_to_coordinate_internal (0, 0); 1265 1266 ep.type = PointType.QUADRATIC; 1267 } else if (right == PointType.CUBIC || left == PointType.CUBIC) { 1268 bezier_vector (position, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x0, out x1); 1269 bezier_vector (position, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y0, out y1); 1270 1271 ep.get_left_handle ().set_point_type (PointType.CUBIC); 1272 ep.get_left_handle ().move_to_coordinate (x0, y0); 1273 1274 ep.get_right_handle ().set_point_type (PointType.CUBIC); 1275 ep.get_right_handle ().move_to_coordinate (x1, y1); 1276 1277 ep.type = PointType.LINE_CUBIC; 1278 } else if (right == PointType.LINE_QUADRATIC && left == PointType.LINE_QUADRATIC) { 1279 ep.get_right_handle ().set_point_type (PointType.LINE_QUADRATIC); 1280 ep.get_left_handle ().set_point_type (PointType.LINE_QUADRATIC); 1281 ep.type = PointType.QUADRATIC; 1282 } else if (right == PointType.LINE_CUBIC && left == PointType.LINE_CUBIC) { 1283 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1284 ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); 1285 ep.type = PointType.LINE_CUBIC; 1286 } else if (right == PointType.LINE_DOUBLE_CURVE && left == PointType.LINE_DOUBLE_CURVE) { 1287 ep.get_right_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); 1288 ep.get_left_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); 1289 ep.type = PointType.DOUBLE_CURVE; 1290 } else { 1291 warning ("Point types: $right and $left in insert_new_point_on_path"); 1292 } 1293 1294 ep.get_left_handle ().parent = ep; 1295 ep.get_right_handle ().parent = ep; 1296 1297 stop.get_left_handle ().length *= 1 - position; 1298 start.get_right_handle ().length *= position; 1299 1300 if (right == PointType.QUADRATIC) { // update connected handle 1301 if (ep.prev != null) { 1302 ep.get_left_handle ().move_to_coordinate_internal ( 1303 ep.get_prev ().right_handle.x, 1304 ep.get_prev ().right_handle.y); 1305 1306 } else { 1307 warning ("ep.prev is null for quadratic point"); 1308 } 1309 } 1310 1311 create_list (); 1312 foreach (EditPoint p in points) { 1313 p.recalculate_linear_handles (); 1314 } 1315 } 1316 1317 /** Get a point on the this path closest to x and y coordinates. */ 1318 public void get_closest_point_on_path (EditPoint edit_point, double x, double y) { 1319 return_if_fail (points.size >= 1); 1320 1321 double min = double.MAX; 1322 double n = 0; 1323 bool g = false; 1324 1325 double ox = 0; 1326 double oy = 0; 1327 1328 EditPoint prev = points.get (points.size - 1).get_link_item (); 1329 EditPoint i = points.get (0).get_link_item (); 1330 1331 bool done = false; 1332 bool exit = false; 1333 bool first = true; 1334 1335 EditPoint? previous_point = null; 1336 EditPoint? next_point = null; 1337 1338 EditPoint previous; 1339 EditPoint next; 1340 double step = 0; 1341 1342 if (points.size == 0) { 1343 warning ("Empty path."); 1344 return; 1345 } 1346 1347 if (points.size == 1) { 1348 edit_point.x = i.x; 1349 edit_point.y = i.y; 1350 1351 edit_point.prev = i; 1352 edit_point.next = i; 1353 return; 1354 } 1355 1356 edit_point.x = i.x; 1357 edit_point.y = i.y; 1358 1359 create_list (); 1360 1361 while (!exit) { 1362 if (!first && i == points.get (points.size - 1)) { 1363 done = true; 1364 } 1365 1366 if (!done) { 1367 i = i.get_next (); 1368 prev = i.get_prev (); 1369 } else if (done && !is_open ()) { 1370 i = points.get (0).get_link_item (); 1371 prev = points.get (points.size - 1).get_link_item (); 1372 exit = true; 1373 } else { 1374 break; 1375 } 1376 1377 all_of (prev, i, (cx, cy, t) => { 1378 n = pow (x - cx, 2) + pow (y - cy, 2); 1379 1380 if (n < min) { 1381 min = n; 1382 1383 ox = cx; 1384 oy = cy; 1385 1386 previous_point = i.prev; 1387 next_point = i; 1388 1389 step = t; 1390 1391 g = true; 1392 } 1393 1394 return true; 1395 }); 1396 1397 first = false; 1398 } 1399 1400 if (previous_point == null && is_open ()) { 1401 previous_point = points.get (points.size - 1).get_link_item (); 1402 } 1403 1404 if (previous_point == null) { 1405 warning (@"previous_point == null, points.size: $(points.size)"); 1406 return; 1407 } 1408 1409 if (next_point == null) { 1410 warning ("next_point != null"); 1411 return; 1412 } 1413 1414 previous = (!) previous_point; 1415 next = (!) next_point; 1416 1417 edit_point.prev = previous_point; 1418 edit_point.next = next_point; 1419 1420 edit_point.set_position (ox, oy); 1421 } 1422 1423 public static bool all_of (EditPoint start, EditPoint stop, 1424 RasterIterator iter, int steps = -1, 1425 double min_t = 0, double max_t = 1) { 1426 1427 PointType right = PenTool.to_curve (start.get_right_handle ().type); 1428 PointType left = PenTool.to_curve (stop.get_left_handle ().type); 1429 1430 if (steps == -1) { 1431 steps = (int) (10 * get_length_from (start, stop)); 1432 } 1433 1434 if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) { 1435 return all_of_double (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().y, stop.get_left_handle ().x, stop.get_left_handle ().y, stop.x, stop.y, iter, steps, min_t, max_t); 1436 } else if (right == PointType.QUADRATIC && left == PointType.QUADRATIC) { 1437 return all_of_quadratic_curve (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().y, stop.x, stop.y, iter, steps, min_t, max_t); 1438 } else if (right == PointType.CUBIC && left == PointType.CUBIC) { 1439 return all_of_curve (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().y, stop.get_left_handle ().x, stop.get_left_handle ().y, stop.x, stop.y, iter, steps, min_t, max_t); 1440 } 1441 1442 if (start.x == stop.x && start.y == stop.y) { 1443 warning ("Zero length."); 1444 return true; 1445 } 1446 1447 warning (@"Mixed point types in segment $(start.x),$(start.y) to $(stop.x),$(stop.y) right: $(right), left: $(left) (start: $(start.type), stop: $(stop.type))"); 1448 return all_of_quadratic_curve (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().x, stop.x, stop.y, iter, steps); 1449 } 1450 1451 public static void get_point_for_step (EditPoint start, EditPoint stop, double step, 1452 out double x, out double y) { 1453 1454 PointType right = PenTool.to_curve (start.type); 1455 PointType left = PenTool.to_curve (stop.type); 1456 1457 if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) { 1458 x = double_bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x); 1459 y = double_bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y); 1460 } else if (right == PointType.QUADRATIC && left == PointType.QUADRATIC) { 1461 x = quadratic_bezier_path (step, start.x, start.get_right_handle ().x, stop.x); 1462 y = quadratic_bezier_path (step, start.y, start.get_right_handle ().y, stop.y); 1463 } else if (right == PointType.CUBIC && left == PointType.CUBIC) { 1464 x = bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x); 1465 y = bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y); 1466 } else if (right == PointType.HIDDEN && left == PointType.HIDDEN) { 1467 x = bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x); 1468 y = bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y); 1469 } else { 1470 warning (@"Mixed point types in segment $(start.x),$(start.y) to $(stop.x),$(stop.y) right: $(right), left: $(left) (start: $(start.type), stop: $(stop.type))"); 1471 x = bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x); 1472 y = bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y); 1473 } 1474 } 1475 1476 private static bool all_of_double (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, 1477 RasterIterator iter, double steps = 400, double min_t = 0, double max_t = 1) { 1478 1479 double px = x1; 1480 double py = y1; 1481 1482 double t; 1483 double middle_x, middle_y; 1484 double double_step; 1485 1486 middle_x = x1 + (x2 - x1) / 2; 1487 middle_y = y1 + (y2 - y1) / 2; 1488 1489 for (int i = 0; i < steps; i++) { 1490 t = i / steps + min_t; 1491 1492 px = quadratic_bezier_path (t, x0, x1, middle_x); 1493 py = quadratic_bezier_path (t, y0, y1, middle_y); 1494 1495 double_step = t / 2; 1496 1497 if (double_step > max_t) { 1498 return false; 1499 } 1500 1501 if (!iter (px, py, double_step)) { 1502 return false; 1503 } 1504 } 1505 1506 for (int i = 0; i < steps; i++) { 1507 t = i / steps + min_t; 1508 1509 px = quadratic_bezier_path (t, middle_x, x2, x3); 1510 py = quadratic_bezier_path (t, middle_y, y2, y3); 1511 1512 double_step = 0.5 + t / 2; 1513 1514 if (double_step > max_t) { 1515 return false; 1516 } 1517 1518 if (!iter (px, py, double_step)) { 1519 return false; 1520 } 1521 } 1522 1523 return true; 1524 } 1525 1526 private static bool all_of_quadratic_curve (double x0, double y0, double x1, double y1, double x2, double y2, 1527 RasterIterator iter, double steps = 400, double min_t = 0, double max_t = 1) { 1528 double px = x1; 1529 double py = y1; 1530 1531 double t; 1532 1533 for (int i = 0; i < steps; i++) { 1534 t = i / steps + min_t; 1535 1536 px = quadratic_bezier_path (t, x0, x1, x2); 1537 py = quadratic_bezier_path (t, y0, y1, y2); 1538 1539 if (t > max_t) { 1540 return false; 1541 } 1542 1543 if (!iter (px, py, t)) { 1544 return false; 1545 } 1546 } 1547 1548 return true; 1549 } 1550 1551 private static bool all_of_curve (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, 1552 RasterIterator iter, double steps = 400, double min_t = 0, double max_t = 1) { 1553 double px = x1; 1554 double py = y1; 1555 1556 double t; 1557 1558 for (int i = 0; i < steps; i++) { 1559 t = i / steps + min_t; 1560 1561 px = bezier_path (t, x0, x1, x2, x3); 1562 py = bezier_path (t, y0, y1, y2, y3); 1563 1564 if (t > max_t) { 1565 return false; 1566 } 1567 1568 if (!iter (px, py, t)) { 1569 return false; 1570 } 1571 } 1572 1573 return true; 1574 } 1575 1576 public bool all_segments (SegmentIterator iter) { 1577 unowned EditPoint i, next; 1578 1579 if (points.size < 2) { 1580 return false; 1581 } 1582 1583 for (int j = 0; j < points.size - 1; j++) { 1584 i = points.get (j).get_link_item (); 1585 next = i.get_next (); 1586 if (!iter (i, next)) { 1587 return false; 1588 } 1589 } 1590 1591 if (!is_open ()) { 1592 return iter (points.get (points.size - 1), points.get (0)); 1593 } 1594 1595 return true; 1596 } 1597 1598 public void all_of_path (RasterIterator iter, int steps = -1) { 1599 all_segments ((start, stop) => { 1600 return all_of (start, stop, iter, steps); 1601 }); 1602 } 1603 1604 public static double bezier_path (double step, double p0, double p1, double p2, double p3) { 1605 double q0, q1, q2; 1606 double r0, r1; 1607 1608 q0 = step * (p1 - p0) + p0; 1609 q1 = step * (p2 - p1) + p1; 1610 q2 = step * (p3 - p2) + p2; 1611 1612 r0 = step * (q1 - q0) + q0; 1613 r1 = step * (q2 - q1) + q1; 1614 1615 return step * (r1 - r0) + r0; 1616 } 1617 1618 public static void bezier_vector (double step, double p0, double p1, double p2, double p3, out double a0, out double a1) { 1619 double q0, q1, q2; 1620 1621 q0 = step * (p1 - p0) + p0; 1622 q1 = step * (p2 - p1) + p1; 1623 q2 = step * (p3 - p2) + p2; 1624 1625 a0 = step * (q1 - q0) + q0; 1626 a1 = step * (q2 - q1) + q1; 1627 } 1628 1629 public static double quadratic_bezier_vector (double step, double p0, double p1, double p2) { 1630 return step * (p1 - p0) + p0; 1631 } 1632 1633 public static double quadratic_bezier_path (double step, double p0, double p1, double p2) { 1634 double q0 = step * (p1 - p0) + p0; 1635 double q1 = step * (p2 - p1) + p1; 1636 1637 return step * (q1 - q0) + q0; 1638 } 1639 1640 public static double double_bezier_path (double step, double p0, double p1, double p2, double p3) { 1641 double middle = p1 + (p2 - p1) / 2; 1642 1643 if (step == 0.5) { 1644 // FIXME: return the middle point 1645 warning ("Middle"); 1646 } 1647 1648 if (step < 0.5) { 1649 return quadratic_bezier_path (2 * step, p0, p1, middle); 1650 } 1651 1652 return quadratic_bezier_path (2 * (step - 0.5), middle, p2, p3); 1653 } 1654 1655 public static void double_bezier_vector (double step, double p0, double p1, double p2, double p3, out double a0, out double a1) { 1656 double b0, b1, c0, c1, d0, d1; 1657 1658 if (unlikely (step <= 0 || step >= 1)) { 1659 warning (@"Bad step: $step"); 1660 step += 0.00004; 1661 } 1662 1663 // set angle 1664 b0 = double_bezier_path (step + 0.00001, p0, p1, p2, p3); 1665 c0 = double_bezier_path (step + 0.00002, p0, p1, p2, p3); 1666 1667 b1 = double_bezier_path (step - 0.00001, p0, p1, p2, p3); 1668 c1 = double_bezier_path (step - 0.00002, p0, p1, p2, p3); 1669 1670 // adjust length 1671 d0 = b0 + (b0 - c0) * 25000 * (step); 1672 d1 = b1 + (b1 - c1) * 25000 * (1 - step); 1673 1674 a0 = d0; 1675 a1 = d1; 1676 } 1677 1678 public static void get_handles_for_step (EditPoint start, EditPoint stop, double step, 1679 out double x1, out double y1, out double x2, out double y2) { 1680 1681 PointType right = PenTool.to_curve (start.type); 1682 PointType left = PenTool.to_curve (stop.type); 1683 1684 if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) { 1685 double_bezier_vector (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x1, out x2); // FIXME: swap parameter? 1686 double_bezier_vector (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y1, out y2); 1687 } else if (right == PointType.QUADRATIC && left == PointType.QUADRATIC) { 1688 x1 = quadratic_bezier_vector (step, start.x, start.get_right_handle ().x, stop.x); 1689 y1 = quadratic_bezier_vector (step, start.y, start.get_right_handle ().y, stop.y); 1690 x2 = x1; 1691 y2 = y1; 1692 } else if (right == PointType.CUBIC && left == PointType.CUBIC) { 1693 bezier_vector (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x1, out x2); 1694 bezier_vector (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y1, out y2); 1695 } else if (right == PointType.HIDDEN && left == PointType.HIDDEN) { 1696 bezier_vector (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x1, out x2); 1697 bezier_vector (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y1, out y2); 1698 } else { 1699 warning (@"Mixed point types in segment $(start.x),$(start.y) to $(stop.x),$(stop.y) right: $(right), left: $(left) (start: $(start.type), stop: $(stop.type))"); 1700 bezier_vector (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x1, out x2); 1701 bezier_vector (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y1, out y2); 1702 } 1703 } 1704 1705 public void plot (Context cr, WidgetAllocation allocation, double view_zoom) { 1706 double px = 0, py = 0; 1707 double xc = allocation.width / 2.0; 1708 double yc = allocation.height / 2.0; 1709 1710 cr.save (); 1711 1712 all_of_path ((x, y) => { 1713 cr.move_to (px + xc, -py + yc); 1714 cr.line_to (x + xc, -y + yc); 1715 1716 px = x; 1717 py = y; 1718 1719 return true; 1720 }); 1721 1722 cr.stroke (); 1723 cr.restore (); 1724 } 1725 1726 public void print_boundaries () { 1727 stderr.printf (@"xmax $xmax \n"); 1728 stderr.printf (@"xmin $xmin \n"); 1729 stderr.printf (@"ymax $ymax \n"); 1730 stderr.printf (@"ymin $ymin \n"); 1731 } 1732 1733 public bool has_region_boundaries () { 1734 return !(xmax == -10000 || xmin == 10000 || ymax == -10000 || ymin == 10000); 1735 } 1736 1737 public void create_list () { 1738 EditPoint ep; 1739 1740 if (points.size == 0) { 1741 return; 1742 } 1743 1744 if (points.size == 1) { 1745 ep = points.get (0); 1746 ep.next = null; 1747 ep.prev = null; 1748 return; 1749 } 1750 1751 ep = points.get (0); 1752 ep.next = points.get (1).get_link_item (); 1753 ep.prev = points.get (points.size - 1).get_link_item (); 1754 1755 for (int i = 1; i < points.size - 1; i++) { 1756 ep = points.get (i); 1757 ep.prev = points.get (i - 1).get_link_item (); 1758 ep.next = points.get (i + 1).get_link_item (); 1759 } 1760 1761 ep = points.get (points.size - 1); 1762 ep.next = points.get (0).get_link_item (); 1763 ep.prev = points.get (points.size - 2).get_link_item (); 1764 } 1765 1766 public bool has_point (EditPoint ep) { 1767 return points.contains (ep); 1768 } 1769 1770 public bool has_deleted_point () { 1771 foreach (EditPoint p in points) { 1772 if (p.deleted) { 1773 return true; 1774 } 1775 } 1776 return false; 1777 } 1778 1779 /** @return the remaining parts as a new path. */ 1780 public PathList process_deleted_points () 1781 requires (points.size > 0) 1782 { 1783 EditPoint p; 1784 EditPoint ep; 1785 Path current_path = new Path (); 1786 Path remaining_points = new Path (); 1787 PathList path_list = new PathList (); 1788 int i; 1789 int index = 0; 1790 1791 if (!has_deleted_point ()) { 1792 return path_list; 1793 } 1794 1795 if (points.size == 1) { 1796 points.remove_at (0); 1797 return path_list; 1798 } 1799 1800 // set start position to a point that will be removed 1801 for (i = 0; i < points.size; i++) { 1802 p = points.get (i); 1803 1804 if (p.deleted) { 1805 index = i; 1806 i++; 1807 ep = p; 1808 break; 1809 } 1810 } 1811 1812 // don't tie end points on the open path 1813 if (points.size > 1) { 1814 p = points.get (1); 1815 p.convert_to_curve (); 1816 p.set_reflective_handles (false); 1817 p.set_tie_handle (false); 1818 } 1819 1820 if (points.size > 0) { 1821 p = points.get (points.size - 1); 1822 p.convert_to_curve (); 1823 p.set_reflective_handles (false); 1824 p.set_tie_handle (false); 1825 } 1826 1827 // copy points after the deleted point 1828 while (i < points.size) { 1829 p = points.get (i); 1830 current_path.add_point (p); 1831 i++; 1832 } 1833 1834 // copy points before the deleted point 1835 for (i = 0; i < index; i++) { 1836 p = points.get (i); 1837 remaining_points.add_point (p); 1838 } 1839 1840 // merge if we still only have one path 1841 if (!is_open ()) { 1842 foreach (EditPoint point in remaining_points.points) { 1843 current_path.add_point (point.copy ()); 1844 } 1845 1846 if (current_path.points.size > 0) { 1847 ep = current_path.points.get (0); 1848 ep.set_tie_handle (false); 1849 ep.set_reflective_handles (false); 1850 ep.get_left_handle ().type = PenTool.to_line (ep.type); 1851 ep.type = PenTool.to_curve (ep.type); 1852 path_list.add (current_path); 1853 1854 ep = current_path.points.get (current_path.points.size - 1); 1855 ep.get_right_handle ().type = PenTool.to_line (ep.type); 1856 ep.type = PenTool.to_curve (ep.get_right_handle ().type); 1857 } 1858 } else { 1859 if (current_path.points.size > 0) { 1860 ep = current_path.points.get (0); 1861 ep.set_tie_handle (false); 1862 ep.set_reflective_handles (false); 1863 ep.get_left_handle ().type = PenTool.to_line (ep.type); 1864 ep.type = PenTool.to_curve (ep.type); 1865 set_new_start (current_path.points.get (0)); 1866 path_list.add (current_path); 1867 ep = current_path.points.get (current_path.points.size - 1); 1868 ep.get_right_handle ().type = PenTool.to_line (ep.type); 1869 ep.type = PenTool.to_curve (ep.get_right_handle ().type); 1870 } 1871 1872 if (remaining_points.points.size > 0) { 1873 remaining_points.points.get (0).set_tie_handle (false); 1874 remaining_points.points.get (0).set_reflective_handles (false); 1875 remaining_points.points.get (0).type = remaining_points.points.get (0).type; 1876 set_new_start (remaining_points.points.get (0)); 1877 path_list.add (remaining_points); 1878 1879 if (current_path.points.size > 0) { 1880 ep = current_path.points.get (current_path.points.size - 1); 1881 ep.get_right_handle ().type = PenTool.to_line (ep.type); 1882 ep.type = PenTool.to_curve (ep.get_right_handle ().type); 1883 } 1884 } 1885 } 1886 1887 foreach (Path path in path_list.paths) { 1888 path.update_region_boundaries (); 1889 } 1890 1891 return path_list; 1892 } 1893 1894 public void set_new_start (EditPoint ep) 1895 requires (points.size > 0) { 1896 Gee.ArrayList<EditPoint> list = new Gee.ArrayList<EditPoint> (); 1897 int start = 0; 1898 1899 for (int i = 0; i < points.size; i++) { 1900 if (ep == points.get (i)) { 1901 start = i; 1902 } 1903 } 1904 1905 for (int i = start; i < points.size; i++) { 1906 list.add (points.get (i)); 1907 } 1908 1909 for (int i = 0; i < start; i++) { 1910 list.add (points.get (i)); 1911 } 1912 1913 control_points = list; 1914 } 1915 1916 public void append_path (Path path) { 1917 if (points.size == 0 || path.points.size == 0) { 1918 warning ("No points"); 1919 return; 1920 } 1921 1922 // copy remaining points 1923 foreach (EditPoint p in path.points) { 1924 add_point (p.copy ()); 1925 } 1926 1927 path.points.clear (); 1928 } 1929 1930 /** Roatate around coordinate xc, xc. */ 1931 public void rotate (double theta, double xc, double yc) { 1932 double a, radius; 1933 1934 foreach (EditPoint ep in points) { 1935 radius = sqrt (pow (xc - ep.x, 2) + pow (yc + ep.y, 2)); 1936 1937 if (yc + ep.y < 0) { 1938 radius = -radius; 1939 } 1940 1941 a = acos ((ep.x - xc) / radius); 1942 1943 ep.x = xc + cos (a - theta) * radius; 1944 ep.y = yc + sin (a - theta) * radius; 1945 1946 ep.get_right_handle ().angle -= theta; 1947 ep.get_left_handle ().angle -= theta; 1948 1949 while (ep.get_right_handle ().angle < 0) { 1950 ep.get_right_handle ().angle += 2 * PI; 1951 } 1952 1953 while (ep.get_left_handle ().angle < 0) { 1954 ep.get_left_handle ().angle += 2 * PI; 1955 } 1956 } 1957 1958 rotation += theta; 1959 rotation %= 2 * PI; 1960 1961 update_region_boundaries (); 1962 } 1963 1964 public void flip_vertical () { 1965 EditPointHandle hl, hr; 1966 double lx, ly, rx, ry; 1967 1968 foreach (EditPoint e in points) { 1969 hl = e.get_left_handle (); 1970 hr = e.get_right_handle (); 1971 1972 lx = hl.x; 1973 ly = hl.y; 1974 rx = hr.x; 1975 ry = hr.y; 1976 1977 e.y *= -1; 1978 1979 hr.move_to_coordinate_internal (rx, -1 * ry); 1980 hl.move_to_coordinate_internal (lx, -1 * ly); 1981 } 1982 1983 update_region_boundaries (); 1984 } 1985 1986 public void flip_horizontal () { 1987 EditPointHandle hl, hr; 1988 double lx, ly, rx, ry; 1989 foreach (EditPoint e in points) { 1990 hl = e.get_left_handle (); 1991 hr = e.get_right_handle (); 1992 1993 lx = hl.x; 1994 ly = hl.y; 1995 rx = hr.x; 1996 ry = hr.y; 1997 1998 e.x *= -1; 1999 2000 hr.move_to_coordinate_internal (-1 * rx, ry); 2001 hl.move_to_coordinate_internal (-1 * lx, ly); 2002 } 2003 2004 update_region_boundaries (); 2005 } 2006 2007 public void init_point_type () { 2008 PointType type; 2009 2010 switch (DrawingTools.point_type) { 2011 case PointType.QUADRATIC: 2012 type = PointType.LINE_QUADRATIC; 2013 break; 2014 case PointType.DOUBLE_CURVE: 2015 type = PointType.LINE_DOUBLE_CURVE; 2016 break; 2017 case PointType.CUBIC: 2018 type = PointType.LINE_CUBIC; 2019 break; 2020 default: 2021 warning ("No type is set"); 2022 type = PointType.LINE_CUBIC; 2023 break; 2024 } 2025 2026 foreach (EditPoint ep in points) { 2027 ep.type = type; 2028 ep.get_right_handle ().type = type; 2029 ep.get_left_handle ().type = type; 2030 } 2031 } 2032 2033 public void convert_path_ending_to_line () { 2034 if (points.size < 2) { 2035 return; 2036 } 2037 2038 get_first_point ().get_left_handle ().convert_to_line (); 2039 get_last_point ().get_right_handle ().convert_to_line (); 2040 } 2041 2042 public void print_all_types () { 2043 print (@"Control points:\n"); 2044 foreach (EditPoint ep in points) { 2045 print (@"$(ep.type) L: $(ep.get_left_handle ().type) R: L: $(ep.get_right_handle ().type)\n"); 2046 } 2047 } 2048 2049 /** Find the point where two lines intersect. */ 2050 public static void find_intersection (double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, 2051 out double point_x, out double point_y) { 2052 point_x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)); 2053 point_y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)); 2054 } 2055 2056 public static void find_intersection_handle (EditPointHandle h1, EditPointHandle h2, out double point_x, out double point_y) { 2057 find_intersection (h1.parent.x, h1.parent.y, h1.x, h1.y, h2.parent.x, h2.parent.y, h2.x, h2.y, out point_x, out point_y); 2058 } 2059 2060 /** Finx intersection point for two straight lines. */ 2061 public static void find_intersection_point (EditPoint p1, EditPoint p2, EditPoint q1, EditPoint q2, out double point_x, out double point_y) { 2062 find_intersection (p1.x, p1.y, p2.x, p2.y, q1.x, q1.y, q2.x, q2.y, out point_x, out point_y); 2063 } 2064 2065 public void add_extrema () { 2066 double x0, y0, x1, y1, x2, y2, x3, y3; 2067 double minx, maxx, miny, maxy; 2068 2069 if (unlikely (points.size < 2)) { 2070 warning (@"Missing points, $(points.size) points in path."); 2071 return; 2072 } 2073 2074 minx = Glyph.CANVAS_MAX; 2075 miny = Glyph.CANVAS_MAX; 2076 maxx = Glyph.CANVAS_MIN; 2077 maxy = Glyph.CANVAS_MIN; 2078 2079 x0 = 0; 2080 y0 = 0; 2081 x1 = 0; 2082 y1 = 0; 2083 x2 = 0; 2084 y2 = 0; 2085 x3 = 0; 2086 y3 = 0; 2087 2088 all_of_path ((x, y) => { 2089 if (x < minx) { 2090 x0 = x; 2091 y0 = y; 2092 minx = x; 2093 } 2094 2095 if (x > maxx) { 2096 x1 = x; 2097 y1 = y; 2098 maxx = x; 2099 } 2100 2101 if (y < miny) { 2102 x2 = x; 2103 y2 = y; 2104 miny = y; 2105 } 2106 2107 if (y > maxy) { 2108 x3 = x; 2109 y3 = y; 2110 maxy = y; 2111 } 2112 2113 return true; 2114 }); 2115 2116 insert_new_point_on_path_at (x0 - 0.001, y0); 2117 insert_new_point_on_path_at (x1 + 0.001, y1); 2118 insert_new_point_on_path_at (x2, y2 - 0.001); 2119 insert_new_point_on_path_at (x3, y3 + 0.001); 2120 } 2121 2122 public EditPoint insert_new_point_on_path_at (double x, double y) { 2123 EditPoint ep = new EditPoint (); 2124 EditPoint prev, next; 2125 bool exists; 2126 2127 if (points.size < 2) { 2128 warning ("Can't add extrema to just one point."); 2129 return ep; 2130 } 2131 2132 get_closest_point_on_path (ep, x, y); 2133 2134 next = (ep.next == null) ? points.get (0) : ep.get_next (); 2135 prev = (ep.prev == null) ? points.get (points.size - 1) : ep.get_prev (); 2136 2137 exists = prev.x == ep.x && prev.y == ep.y; 2138 exists |= next.x == ep.x && next.y == ep.y; 2139 2140 if (!exists) { 2141 insert_new_point_on_path (ep); 2142 } 2143 2144 return ep; 2145 } 2146 2147 public static bool is_counter (PathList pl, Path path) { 2148 return counters (pl, path) % 2 != 0; 2149 } 2150 2151 public static int counters (PathList pl, Path path) { 2152 int inside_count = 0; 2153 bool inside; 2154 PathList lines = new PathList (); 2155 2156 lines = pl; 2157 2158 /** // FIXME: Check automatic orientation. 2159 foreach (Path p in pl.paths) { 2160 lines.add (SvgParser.get_lines (p)); 2161 } 2162 */ 2163 2164 foreach (Path p in lines.paths) { 2165 if (p.points.size > 1 && p != path 2166 && path.boundaries_intersecting (p)) { 2167 2168 inside = false; 2169 foreach (EditPoint ep in path.points) { 2170 if (SvgParser.is_inside (ep, p)) { 2171 inside = true; 2172 } 2173 } 2174 2175 if (inside) { 2176 inside_count++; 2177 } 2178 } 2179 } 2180 2181 return inside_count; 2182 } 2183 2184 public bool boundaries_intersecting (Path p) { 2185 return in_boundaries (p.xmin, p.xmax, p.ymin, p.ymax); 2186 } 2187 2188 public bool in_boundaries (double other_xmin, double other_xmax, double other_ymin, double other_ymax) { 2189 return ((xmin <= other_xmin <= xmax) || (xmin <= other_xmax <= xmax) 2190 || (other_xmin <= xmin <= other_xmax) || (other_xmin <= xmax <= other_xmax)) 2191 && ((ymin <= other_ymin <= ymax) || (ymin <= other_ymax <= ymax) 2192 || (other_ymin <= ymin <= other_ymax) || (other_ymin <= ymax <= other_ymax)); 2193 } 2194 2195 /** @param t smallest distance to other points. */ 2196 public void remove_points_on_points (double t = 0.00001) { 2197 Gee.ArrayList<EditPoint> remove = new Gee.ArrayList<EditPoint> (); 2198 EditPoint n; 2199 EditPointHandle hr, h; 2200 2201 if (points.size == 0) { 2202 return; 2203 } 2204 2205 create_list (); 2206 2207 foreach (EditPoint ep in points) { 2208 if (ep.next != null) { 2209 n = ep.get_next (); 2210 } else { 2211 n = points.get (0); 2212 } 2213 2214 if (fabs (n.x - ep.x) < t && fabs (n.y - ep.y) < t) { 2215 if ((ep.flags & EditPoint.NEW_CORNER) == 0) { 2216 remove.add (ep); 2217 } 2218 } 2219 } 2220 2221 foreach (EditPoint r in remove) { 2222 if (r.next != null) { 2223 n = r.get_next (); 2224 } else { 2225 n = points.get (0); 2226 } 2227 2228 points.remove (r); 2229 h = n.get_left_handle (); 2230 hr = r.get_left_handle (); 2231 h.length = hr.length; 2232 h.angle = hr.angle; 2233 h.type = hr.type; 2234 2235 if (h.length < t) { 2236 h.length = t; 2237 h.angle = n.get_right_handle ().angle - PI; 2238 } 2239 2240 create_list (); 2241 } 2242 2243 recalculate_linear_handles (); 2244 } 2245 2246 public void remove_deleted_points () { 2247 Gee.ArrayList<EditPoint> p = new Gee.ArrayList<EditPoint> (); 2248 2249 foreach (EditPoint ep in points) { 2250 if (ep.deleted) { 2251 p.add (ep); 2252 } 2253 } 2254 2255 foreach (EditPoint e in p) { 2256 points.remove (e); 2257 } 2258 2259 create_list (); 2260 } 2261 2262 public static void find_closes_point_in_segment (EditPoint ep0, EditPoint ep1, 2263 double px, double py, 2264 out double nx, out double ny, 2265 double max_step = 200) { 2266 2267 double min_distance = double.MAX; 2268 double npx, npy; 2269 double min_t, max_t; 2270 double rmin_t, rmax_t; 2271 bool found; 2272 int step; 2273 2274 npx = 0; 2275 npy = 0; 2276 2277 min_t = 0; 2278 max_t = 1; 2279 2280 rmin_t = 0; 2281 rmax_t = 1; 2282 2283 for (step = 3; step <= max_step; step *= 2) { 2284 found = false; 2285 min_distance = double.MAX; 2286 Path.all_of (ep0, ep1, (xa, ya, ta) => { 2287 double d = Path.distance (px, xa, py, ya); 2288 2289 if (d < min_distance) { 2290 min_distance = d; 2291 npx = xa; 2292 npy = ya; 2293 rmin_t = ta - 1.0 / step; 2294 rmax_t = ta + 1.0 / step; 2295 found = true; 2296 } 2297 2298 return true; 2299 }, step, min_t, max_t); 2300 2301 if (!found) { 2302 rmin_t = 1 - (1.0 / step); 2303 rmax_t = 1; 2304 } 2305 2306 min_t = (rmin_t > 0) ? rmin_t : 0; 2307 max_t = (rmax_t < 1) ? rmax_t : 1; 2308 } 2309 2310 nx = npx; 2311 ny = npy; 2312 } 2313 2314 public void reset_stroke () { 2315 full_stroke = null; 2316 fast_stroke = null; 2317 } 2318 2319 public void create_full_stroke () { 2320 full_stroke = StrokeTool.get_stroke (this, stroke); 2321 } 2322 2323 public PathList get_stroke () { 2324 if (full_stroke == null) { 2325 full_stroke = StrokeTool.get_stroke (this, stroke); 2326 } 2327 2328 return (!) full_stroke; 2329 } 2330 2331 public PathList get_stroke_fast () { 2332 PathList s; 2333 2334 if (full_stroke != null) { 2335 return (!) full_stroke; 2336 } 2337 2338 if (fast_stroke != null) { 2339 return (!) fast_stroke; 2340 } 2341 2342 fast_stroke = StrokeTool.get_stroke_fast (this, stroke); 2343 return (!) fast_stroke; 2344 } 2345 2346 // Callback for path simplifier 2347 public void add_cubic_bezier_points (double x0, double y0, double x1, double y1, 2348 double x2, double y2, double x3, double y3) { 2349 2350 EditPoint start; 2351 EditPoint end; 2352 2353 if (points.size > 0) { 2354 start = get_last_point (); 2355 } else { 2356 start = add (x0, y0); 2357 } 2358 2359 end = add (x3, y3); 2360 2361 start.set_point_type (PointType.CUBIC); 2362 end.set_point_type (PointType.CUBIC); 2363 2364 start.convert_to_curve (); 2365 end.convert_to_curve (); 2366 2367 start.get_right_handle ().move_to_coordinate (x1, y1); 2368 end.get_left_handle ().move_to_coordinate (x2, y2); 2369 } 2370 } 2371 2372 } 2373