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