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