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