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