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