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