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