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 if (stroke == 0) { 1120 all_segments ((a, b) => { 1121 update_region_boundaries_for_segment (a, b); 1122 return true; 1123 }); 1124 } else { 1125 s = get_stroke_fast (); 1126 foreach (Path p in s.paths) { 1127 p.all_segments ((a, b) => { 1128 update_region_boundaries_for_segment (a, b); 1129 return true; 1130 }); 1131 } 1132 } 1133 1134 if (points.size == 1) { 1135 EditPoint e = points.get (0); 1136 xmax = e.x; 1137 xmin = e.x; 1138 ymax = e.y; 1139 ymin = e.y; 1140 } 1141 } 1142 1143 /** Test if @param path is a valid outline for this object. */ 1144 public bool test_is_outline (Path path) { 1145 assert (false); 1146 return this.test_is_outline_of_path (path) && path.test_is_outline_of_path (this); 1147 } 1148 1149 private bool test_is_outline_of_path (Path outline) 1150 requires (outline.points.size >= 2 || points.size >= 2) 1151 { 1152 // rather slow use it for testing, only 1153 unowned EditPoint i = outline.points.get (0).get_link_item (); 1154 unowned EditPoint prev = outline.points.get (outline.points.size - 1).get_link_item (); 1155 1156 double tolerance = 1; 1157 bool g = false; 1158 1159 EditPoint ep = new EditPoint (0, 0); 1160 double min = double.MAX; 1161 1162 while (true) { 1163 min = 10000; 1164 1165 all_of (prev, i, (cx, cy) => { 1166 get_closest_point_on_path (ep, cx, cy); 1167 1168 double n = pow (ep.x - cx, 2) + pow (cy - ep.y, 2); 1169 1170 if (n < min) min = n; 1171 1172 if (n < tolerance) { 1173 g = true; 1174 return false; 1175 } 1176 1177 return true; 1178 }); 1179 1180 if (!g) { 1181 critical (@"this path does not seem to be the outline. (min $min)"); 1182 } 1183 1184 g = false; 1185 1186 if (i == outline.points.get (outline.points.size - 1)) { 1187 break; 1188 } 1189 1190 i = i.get_next (); 1191 } 1192 1193 return true; 1194 } 1195 1196 /** Add the extra point between line handles for double curve. */ 1197 public void add_hidden_double_points () requires (points.size > 1) { 1198 EditPoint hidden; 1199 EditPoint prev; 1200 EditPoint first; 1201 PointType left; 1202 PointType right; 1203 double x, y; 1204 Gee.ArrayList<EditPoint> middle_points = new Gee.ArrayList<EditPoint> (); 1205 Gee.ArrayList<EditPoint> first_points = new Gee.ArrayList<EditPoint> (); 1206 1207 first = is_open () ? points.get (0) : points.get (points.size - 1); 1208 1209 foreach (EditPoint next in points) { 1210 left = first.get_right_handle ().type; 1211 right = next.get_left_handle ().type; 1212 1213 if (next != first && (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE)) { 1214 1215 first.get_right_handle ().type = PointType.QUADRATIC; 1216 1217 // half way between handles 1218 x = first.get_right_handle ().x + (next.get_left_handle ().x - first.get_right_handle ().x) / 2; 1219 y = first.get_right_handle ().y + (next.get_left_handle ().y - first.get_right_handle ().y) / 2; 1220 1221 hidden = new EditPoint (x, y, PointType.QUADRATIC); 1222 hidden.get_right_handle ().type = PointType.QUADRATIC; 1223 hidden.get_left_handle ().type = PointType.QUADRATIC; 1224 hidden.type = PointType.QUADRATIC; 1225 1226 hidden.right_handle.move_to_coordinate_internal (next.get_left_handle ().x, next.get_left_handle ().y); 1227 1228 first.get_right_handle ().type = PointType.QUADRATIC; 1229 first.type = PointType.QUADRATIC; 1230 1231 next.get_left_handle ().type = PointType.QUADRATIC; 1232 next.type = PointType.QUADRATIC; 1233 1234 middle_points.add (hidden); 1235 first_points.add (first); 1236 } 1237 first = next; 1238 } 1239 1240 for (int i = 0; i < middle_points.size; i++) { 1241 hidden = middle_points.get (i); 1242 add_point_after (middle_points.get (i), first_points.get (i)); 1243 } 1244 1245 create_list (); 1246 1247 prev = get_last_point (); 1248 foreach (EditPoint ep in points) { 1249 if (ep.type == PointType.QUADRATIC) { 1250 x = prev.get_right_handle ().x; 1251 y = prev.get_right_handle ().y; 1252 ep.get_left_handle ().move_to_coordinate (x, y); 1253 } 1254 1255 prev = ep; 1256 } 1257 } 1258 1259 /** Convert quadratic bezier points to cubic representation of the glyph 1260 * for ttf-export. 1261 */ 1262 public Path get_quadratic_points () { 1263 PointConverter converter; 1264 converter = new PointConverter (this); 1265 return converter.get_quadratic_path (); 1266 } 1267 1268 public void insert_new_point_on_path (EditPoint ep, double t = -1, bool move_point_to_path = false) { 1269 EditPoint start, stop; 1270 double x0, x1, y0, y1; 1271 double position, min; 1272 PointType left, right; 1273 double closest_x = 0; 1274 double closest_y = 0; 1275 1276 if (ep.next == null || ep.prev == null) { 1277 warning ("missing point"); 1278 return; 1279 } 1280 1281 start = ep.get_prev (); 1282 stop = ep.get_next (); 1283 1284 right = start.get_right_handle ().type; 1285 left = stop.get_left_handle ().type; 1286 1287 if (right == PointType.CUBIC || left == PointType.CUBIC) { 1288 start.get_right_handle ().type = PointType.CUBIC; 1289 stop.get_left_handle ().type = PointType.CUBIC; 1290 } 1291 1292 add_point_after (ep, ep.get_prev ()); 1293 1294 min = double.MAX; 1295 1296 position = 0.5; 1297 1298 if (t < 0) { 1299 all_of (start, stop, (cx, cy, t) => { 1300 double n = pow (ep.x - cx, 2) + pow (ep.y - cy, 2); 1301 1302 if (n < min) { 1303 min = n; 1304 position = t; 1305 closest_x = cx; 1306 closest_y = cy; 1307 } 1308 1309 return true; 1310 }); 1311 1312 if (move_point_to_path) { 1313 ep.x = closest_x; 1314 ep.y = closest_y; 1315 } 1316 } else { 1317 position = t; 1318 } 1319 1320 if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) { 1321 double_bezier_vector (position, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x0, out x1); 1322 double_bezier_vector (position, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y0, out y1); 1323 1324 ep.get_left_handle ().set_point_type (PointType.DOUBLE_CURVE); 1325 ep.get_right_handle ().set_point_type (PointType.DOUBLE_CURVE); 1326 1327 ep.get_left_handle ().move_to_coordinate (x0, y0); 1328 ep.get_right_handle ().move_to_coordinate (x1, y1); 1329 1330 ep.type = PointType.DOUBLE_CURVE; 1331 } else if (right == PointType.QUADRATIC) { 1332 x0 = quadratic_bezier_vector (1 - position, stop.x, start.get_right_handle ().x, start.x); 1333 y0 = quadratic_bezier_vector (1 - position, stop.y, start.get_right_handle ().y, start.y); 1334 ep.get_right_handle ().move_to_coordinate (x0, y0); 1335 1336 ep.get_left_handle ().set_point_type (PointType.QUADRATIC); 1337 ep.get_right_handle ().set_point_type (PointType.QUADRATIC); 1338 1339 ep.get_left_handle ().move_to_coordinate_internal (0, 0); 1340 1341 ep.type = PointType.QUADRATIC; 1342 } else if (right == PointType.CUBIC || left == PointType.CUBIC) { 1343 bezier_vector (position, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x0, out x1); 1344 bezier_vector (position, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y0, out y1); 1345 1346 ep.get_left_handle ().set_point_type (PointType.CUBIC); 1347 ep.get_left_handle ().move_to_coordinate (x0, y0); 1348 1349 ep.get_right_handle ().set_point_type (PointType.CUBIC); 1350 ep.get_right_handle ().move_to_coordinate (x1, y1); 1351 1352 ep.type = PointType.LINE_CUBIC; 1353 } else if (right == PointType.LINE_QUADRATIC && left == PointType.LINE_QUADRATIC) { 1354 ep.get_right_handle ().set_point_type (PointType.LINE_QUADRATIC); 1355 ep.get_left_handle ().set_point_type (PointType.LINE_QUADRATIC); 1356 ep.type = PointType.QUADRATIC; 1357 } else if (right == PointType.LINE_CUBIC && left == PointType.LINE_CUBIC) { 1358 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1359 ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); 1360 ep.type = PointType.LINE_CUBIC; 1361 } else if (right == PointType.LINE_DOUBLE_CURVE && left == PointType.LINE_DOUBLE_CURVE) { 1362 ep.get_right_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); 1363 ep.get_left_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); 1364 ep.type = PointType.DOUBLE_CURVE; 1365 } else { 1366 warning (@"Point types: $right and $left in insert_new_point_on_path"); 1367 } 1368 1369 ep.get_left_handle ().parent = ep; 1370 ep.get_right_handle ().parent = ep; 1371 1372 stop.get_left_handle ().length *= 1 - position; 1373 start.get_right_handle ().length *= position; 1374 1375 if (right == PointType.QUADRATIC) { // update connected handle 1376 if (ep.prev != null) { 1377 ep.get_left_handle ().move_to_coordinate_internal ( 1378 ep.get_prev ().right_handle.x, 1379 ep.get_prev ().right_handle.y); 1380 1381 } else { 1382 warning ("ep.prev is null for quadratic point"); 1383 } 1384 } 1385 1386 create_list (); 1387 recalculate_linear_handles (); 1388 } 1389 1390 /** Get a point on the this path closest to x and y coordinates. 1391 * Don't look for a point in the segment skip_previous to skip_next. 1392 */ 1393 public void get_closest_point_on_path (EditPoint edit_point, double x, double y, 1394 EditPoint? skip_previous = null, EditPoint? skip_next = null, 1395 int steps = -1) { 1396 return_if_fail (points.size >= 1); 1397 1398 double min = double.MAX; 1399 double n = 0; 1400 bool g = false; 1401 1402 double ox = 0; 1403 double oy = 0; 1404 1405 EditPoint prev = points.get (points.size - 1); 1406 EditPoint i = points.get (0); 1407 1408 bool done = false; 1409 bool exit = false; 1410 bool first = true; 1411 1412 EditPoint? previous_point = null; 1413 EditPoint? next_point = null; 1414 1415 EditPoint previous; 1416 EditPoint next; 1417 double step = 0; 1418 1419 if (points.size == 0) { 1420 warning ("Empty path."); 1421 return; 1422 } 1423 1424 if (points.size == 1) { 1425 edit_point.x = i.x; 1426 edit_point.y = i.y; 1427 1428 edit_point.prev = i; 1429 edit_point.next = i; 1430 return; 1431 } 1432 1433 edit_point.x = i.x; 1434 edit_point.y = i.y; 1435 1436 create_list (); 1437 1438 while (!exit) { 1439 if (!first && i == points.get (points.size - 1)) { 1440 done = true; 1441 } 1442 1443 if (!done) { 1444 i = i.get_next (); 1445 prev = i.get_prev (); 1446 } else if (done && !is_open ()) { 1447 i = points.get (0); 1448 prev = points.get (points.size - 1); 1449 exit = true; 1450 } else { 1451 break; 1452 } 1453 1454 if (skip_previous == prev) { 1455 continue; 1456 } 1457 1458 if (prev.prev != null && skip_previous == prev.get_prev ()) { 1459 continue; 1460 } 1461 1462 if (skip_next == i) { 1463 continue; 1464 } 1465 1466 if (prev.next != null && skip_next == prev.get_next ()) { 1467 continue; 1468 } 1469 1470 all_of (prev, i, (cx, cy, t) => { 1471 n = pow (x - cx, 2) + pow (y - cy, 2); 1472 1473 if (n < min) { 1474 min = n; 1475 1476 ox = cx; 1477 oy = cy; 1478 1479 previous_point = i.prev; 1480 next_point = i; 1481 1482 step = t; 1483 1484 g = true; 1485 } 1486 1487 return true; 1488 }, steps); 1489 1490 first = false; 1491 } 1492 1493 if (previous_point == null && is_open ()) { 1494 previous_point = points.get (points.size - 1).get_link_item (); 1495 } 1496 1497 if (previous_point == null) { 1498 warning (@"previous_point == null, points.size: $(points.size)"); 1499 return; 1500 } 1501 1502 if (next_point == null) { 1503 warning ("next_point != null"); 1504 return; 1505 } 1506 1507 previous = (!) previous_point; 1508 next = (!) next_point; 1509 1510 edit_point.prev = previous_point; 1511 edit_point.next = next_point; 1512 1513 edit_point.set_position (ox, oy); 1514 1515 edit_point.type = previous.type; 1516 edit_point.get_left_handle ().type = previous.get_right_handle ().type; 1517 edit_point.get_right_handle ().type = next.get_left_handle ().type; 1518 } 1519 1520 public static bool all_of (EditPoint start, EditPoint stop, 1521 RasterIterator iter, int steps = -1, 1522 double min_t = 0, double max_t = 1) { 1523 1524 PointType right = PenTool.to_curve (start.get_right_handle ().type); 1525 PointType left = PenTool.to_curve (stop.get_left_handle ().type); 1526 1527 if (steps == -1) { 1528 steps = (int) (10 * get_length_from (start, stop)); 1529 } 1530 1531 if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) { 1532 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); 1533 } else if (right == PointType.QUADRATIC && left == PointType.QUADRATIC) { 1534 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); 1535 } else if (right == PointType.CUBIC && left == PointType.CUBIC) { 1536 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); 1537 } 1538 1539 if (start.x == stop.x && start.y == stop.y) { 1540 warning ("Zero length."); 1541 return true; 1542 } 1543 1544 // 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))"); 1545 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); 1546 } 1547 1548 public static void get_point_for_step (EditPoint start, EditPoint stop, double step, 1549 out double x, out double y) { 1550 1551 PointType right = PenTool.to_curve (start.get_right_handle ().type); 1552 PointType left = PenTool.to_curve (stop.get_left_handle ().type); 1553 1554 if (right == PointType.DOUBLE_CURVE && left == PointType.DOUBLE_CURVE) { 1555 x = double_bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x); 1556 y = double_bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y); 1557 } else if (right == PointType.QUADRATIC && left == PointType.QUADRATIC) { 1558 x = quadratic_bezier_path (step, start.x, start.get_right_handle ().x, stop.x); 1559 y = quadratic_bezier_path (step, start.y, start.get_right_handle ().y, stop.y); 1560 } else if (right == PointType.CUBIC && left == PointType.CUBIC) { 1561 x = bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x); 1562 y = bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y); 1563 } else if (right == PointType.HIDDEN && left == PointType.HIDDEN) { 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 } else { 1567 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))"); 1568 x = bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x); 1569 y = bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y); 1570 } 1571 } 1572 1573 private static bool all_of_double (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, 1574 RasterIterator iter, double steps = 400, double min_t = 0, double max_t = 1) { 1575 1576 double px = x1; 1577 double py = y1; 1578 1579 double t; 1580 double middle_x, middle_y; 1581 double double_step; 1582 1583 middle_x = x1 + (x2 - x1) / 2; 1584 middle_y = y1 + (y2 - y1) / 2; 1585 1586 for (int i = 0; i < steps; i++) { 1587 t = i / steps + min_t; 1588 1589 px = quadratic_bezier_path (t, x0, x1, middle_x); 1590 py = quadratic_bezier_path (t, y0, y1, middle_y); 1591 1592 double_step = t / 2; 1593 1594 if (double_step > max_t) { 1595 return false; 1596 } 1597 1598 if (!iter (px, py, double_step)) { 1599 return false; 1600 } 1601 } 1602 1603 for (int i = 0; i < steps; i++) { 1604 t = i / steps + min_t; 1605 1606 px = quadratic_bezier_path (t, middle_x, x2, x3); 1607 py = quadratic_bezier_path (t, middle_y, y2, y3); 1608 1609 double_step = 0.5 + t / 2; 1610 1611 if (double_step > max_t) { 1612 return false; 1613 } 1614 1615 if (!iter (px, py, double_step)) { 1616 return false; 1617 } 1618 } 1619 1620 return true; 1621 } 1622 1623 private static bool all_of_quadratic_curve (double x0, double y0, double x1, double y1, double x2, double y2, 1624 RasterIterator iter, double steps = 400, double min_t = 0, double max_t = 1) { 1625 double px = x1; 1626 double py = y1; 1627 1628 double t; 1629 1630 for (int i = 0; i < steps; i++) { 1631 t = i / steps + min_t; 1632 1633 px = quadratic_bezier_path (t, x0, x1, x2); 1634 py = quadratic_bezier_path (t, y0, y1, y2); 1635 1636 if (t > max_t) { 1637 return false; 1638 } 1639 1640 if (!iter (px, py, t)) { 1641 return false; 1642 } 1643 } 1644 1645 return true; 1646 } 1647 1648 private static bool all_of_curve (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, 1649 RasterIterator iter, double steps = 400, double min_t = 0, double max_t = 1) { 1650 double px = x1; 1651 double py = y1; 1652 1653 double t; 1654 1655 for (int i = 0; i < steps; i++) { 1656 t = i / steps + min_t; 1657 1658 px = bezier_path (t, x0, x1, x2, x3); 1659 py = bezier_path (t, y0, y1, y2, y3); 1660 1661 if (t > max_t) { 1662 return false; 1663 } 1664 1665 if (!iter (px, py, t)) { 1666 return false; 1667 } 1668 } 1669 1670 return true; 1671 } 1672 1673 public bool all_segments (SegmentIterator iter) { 1674 unowned EditPoint i, next; 1675 1676 if (points.size < 2) { 1677 return false; 1678 } 1679 1680 for (int j = 0; j < points.size - 1; j++) { 1681 i = points.get (j).get_link_item (); 1682 next = i.get_next (); 1683 if (!iter (i, next)) { 1684 return false; 1685 } 1686 } 1687 1688 if (!is_open ()) { 1689 return iter (points.get (points.size - 1), points.get (0)); 1690 } 1691 1692 return true; 1693 } 1694 1695 public void all_of_path (RasterIterator iter, int steps = -1) { 1696 all_segments ((start, stop) => { 1697 return all_of (start, stop, iter, steps); 1698 }); 1699 } 1700 1701 public static double bezier_path (double step, double p0, double p1, double p2, double p3) { 1702 double q0, q1, q2; 1703 double r0, r1; 1704 1705 q0 = step * (p1 - p0) + p0; 1706 q1 = step * (p2 - p1) + p1; 1707 q2 = step * (p3 - p2) + p2; 1708 1709 r0 = step * (q1 - q0) + q0; 1710 r1 = step * (q2 - q1) + q1; 1711 1712 return step * (r1 - r0) + r0; 1713 } 1714 1715 public static void bezier_vector (double step, double p0, double p1, double p2, double p3, out double a0, out double a1) { 1716 double q0, q1, q2; 1717 1718 q0 = step * (p1 - p0) + p0; 1719 q1 = step * (p2 - p1) + p1; 1720 q2 = step * (p3 - p2) + p2; 1721 1722 a0 = step * (q1 - q0) + q0; 1723 a1 = step * (q2 - q1) + q1; 1724 } 1725 1726 public static double quadratic_bezier_vector (double step, double p0, double p1, double p2) { 1727 return step * (p1 - p0) + p0; 1728 } 1729 1730 public static double quadratic_bezier_path (double step, double p0, double p1, double p2) { 1731 double q0 = step * (p1 - p0) + p0; 1732 double q1 = step * (p2 - p1) + p1; 1733 1734 return step * (q1 - q0) + q0; 1735 } 1736 1737 public static double double_bezier_path (double step, double p0, double p1, double p2, double p3) { 1738 double middle = p1 + (p2 - p1) / 2; 1739 1740 if (step == 0.5) { 1741 // FIXME: return the middle point 1742 warning ("Middle"); 1743 } 1744 1745 if (step < 0.5) { 1746 return quadratic_bezier_path (2 * step, p0, p1, middle); 1747 } 1748 1749 return quadratic_bezier_path (2 * (step - 0.5), middle, p2, p3); 1750 } 1751 1752 public static void double_bezier_vector (double step, double p0, double p1, double p2, double p3, out double a0, out double a1) { 1753 double b0, b1, c0, c1, d0, d1; 1754 1755 if (unlikely (step <= 0 || step >= 1)) { 1756 warning (@"Bad step: $step"); 1757 step += 0.00004; 1758 } 1759 1760 // set angle 1761 b0 = double_bezier_path (step + 0.00001, p0, p1, p2, p3); 1762 c0 = double_bezier_path (step + 0.00002, p0, p1, p2, p3); 1763 1764 b1 = double_bezier_path (step - 0.00001, p0, p1, p2, p3); 1765 c1 = double_bezier_path (step - 0.00002, p0, p1, p2, p3); 1766 1767 // adjust length 1768 d0 = b0 + (b0 - c0) * 25000 * (step); 1769 d1 = b1 + (b1 - c1) * 25000 * (1 - step); 1770 1771 a0 = d0; 1772 a1 = d1; 1773 } 1774 1775 public static void get_handles_for_step (EditPoint start, EditPoint stop, double step, 1776 out double x1, out double y1, out double x2, out double y2) { 1777 1778 PointType right = PenTool.to_curve (start.type); 1779 PointType left = PenTool.to_curve (stop.type); 1780 1781 if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) { 1782 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? 1783 double_bezier_vector (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y1, out y2); 1784 } else if (right == PointType.QUADRATIC && left == PointType.QUADRATIC) { 1785 x1 = quadratic_bezier_vector (step, start.x, start.get_right_handle ().x, stop.x); 1786 y1 = quadratic_bezier_vector (step, start.y, start.get_right_handle ().y, stop.y); 1787 x2 = x1; 1788 y2 = y1; 1789 } else if (right == PointType.CUBIC && left == PointType.CUBIC) { 1790 bezier_vector (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x1, out x2); 1791 bezier_vector (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y1, out y2); 1792 } else if (right == PointType.HIDDEN && left == PointType.HIDDEN) { 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 } else { 1796 // 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))"); 1797 bezier_vector (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x1, out x2); 1798 bezier_vector (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y1, out y2); 1799 } 1800 } 1801 1802 public void plot (Context cr, WidgetAllocation allocation, double view_zoom) { 1803 double px = 0, py = 0; 1804 double xc = allocation.width / 2.0; 1805 double yc = allocation.height / 2.0; 1806 1807 cr.save (); 1808 1809 all_of_path ((x, y) => { 1810 cr.move_to (px + xc, -py + yc); 1811 cr.line_to (x + xc, -y + yc); 1812 1813 px = x; 1814 py = y; 1815 1816 return true; 1817 }); 1818 1819 cr.stroke (); 1820 cr.restore (); 1821 } 1822 1823 public void print_boundaries () { 1824 stderr.printf (@"xmax $xmax \n"); 1825 stderr.printf (@"xmin $xmin \n"); 1826 stderr.printf (@"ymax $ymax \n"); 1827 stderr.printf (@"ymin $ymin \n"); 1828 } 1829 1830 public bool has_region_boundaries () { 1831 return !(xmax == -10000 || xmin == 10000 || ymax == -10000 || ymin == 10000); 1832 } 1833 1834 public void create_list () { 1835 EditPoint ep; 1836 1837 if (points.size == 0) { 1838 return; 1839 } 1840 1841 if (points.size == 1) { 1842 ep = points.get (0); 1843 ep.next = null; 1844 ep.prev = null; 1845 return; 1846 } 1847 1848 ep = points.get (0); 1849 ep.next = points.get (1).get_link_item (); 1850 ep.prev = points.get (points.size - 1).get_link_item (); 1851 1852 for (int i = 1; i < points.size - 1; i++) { 1853 ep = points.get (i); 1854 ep.prev = points.get (i - 1).get_link_item (); 1855 ep.next = points.get (i + 1).get_link_item (); 1856 } 1857 1858 ep = points.get (points.size - 1); 1859 ep.next = points.get (0).get_link_item (); 1860 ep.prev = points.get (points.size - 2).get_link_item (); 1861 } 1862 1863 public bool has_point (EditPoint ep) { 1864 return points.contains (ep); 1865 } 1866 1867 public bool has_deleted_point () { 1868 foreach (EditPoint p in points) { 1869 if (p.deleted) { 1870 return true; 1871 } 1872 } 1873 return false; 1874 } 1875 1876 /** @return the remaining parts as a new path. */ 1877 public PathList process_deleted_points () 1878 requires (points.size > 0) 1879 { 1880 EditPoint p; 1881 EditPoint ep; 1882 Path current_path = new Path (); 1883 Path remaining_points = new Path (); 1884 PathList path_list = new PathList (); 1885 int i; 1886 int index = 0; 1887 1888 remaining_points.stroke = stroke; 1889 current_path.stroke = stroke; 1890 1891 if (!has_deleted_point ()) { 1892 return path_list; 1893 } 1894 1895 if (points.size == 1) { 1896 points.remove_at (0); 1897 return path_list; 1898 } 1899 1900 // set start position to a point that will be removed 1901 for (i = 0; i < points.size; i++) { 1902 p = points.get (i); 1903 1904 if (p.deleted) { 1905 index = i; 1906 i++; 1907 ep = p; 1908 break; 1909 } 1910 } 1911 1912 // don't tie end points on the open path 1913 if (points.size > 1) { 1914 p = points.get (1); 1915 p.convert_to_curve (); 1916 p.set_reflective_handles (false); 1917 p.set_tie_handle (false); 1918 } 1919 1920 if (points.size > 0) { 1921 p = points.get (points.size - 1); 1922 p.convert_to_curve (); 1923 p.set_reflective_handles (false); 1924 p.set_tie_handle (false); 1925 } 1926 1927 // copy points after the deleted point 1928 while (i < points.size) { 1929 p = points.get (i); 1930 current_path.add_point (p); 1931 i++; 1932 } 1933 1934 // copy points before the deleted point 1935 for (i = 0; i < index; i++) { 1936 p = points.get (i); 1937 remaining_points.add_point (p); 1938 } 1939 1940 // merge if we still only have one path 1941 if (!is_open ()) { 1942 foreach (EditPoint point in remaining_points.points) { 1943 current_path.add_point (point.copy ()); 1944 } 1945 1946 if (current_path.points.size > 0) { 1947 ep = current_path.points.get (0); 1948 ep.set_tie_handle (false); 1949 ep.set_reflective_handles (false); 1950 ep.get_left_handle ().type = PenTool.to_line (ep.type); 1951 ep.type = PenTool.to_curve (ep.type); 1952 path_list.add (current_path); 1953 1954 ep = current_path.points.get (current_path.points.size - 1); 1955 ep.get_right_handle ().type = PenTool.to_line (ep.type); 1956 ep.type = PenTool.to_curve (ep.get_right_handle ().type); 1957 } 1958 } else { 1959 if (current_path.points.size > 0) { 1960 ep = current_path.points.get (0); 1961 ep.set_tie_handle (false); 1962 ep.set_reflective_handles (false); 1963 ep.get_left_handle ().type = PenTool.to_line (ep.type); 1964 ep.type = PenTool.to_curve (ep.type); 1965 set_new_start (current_path.points.get (0)); 1966 path_list.add (current_path); 1967 ep = current_path.points.get (current_path.points.size - 1); 1968 ep.get_right_handle ().type = PenTool.to_line (ep.type); 1969 ep.type = PenTool.to_curve (ep.get_right_handle ().type); 1970 } 1971 1972 if (remaining_points.points.size > 0) { 1973 remaining_points.points.get (0).set_tie_handle (false); 1974 remaining_points.points.get (0).set_reflective_handles (false); 1975 remaining_points.points.get (0).type = remaining_points.points.get (0).type; 1976 set_new_start (remaining_points.points.get (0)); 1977 path_list.add (remaining_points); 1978 1979 if (current_path.points.size > 0) { 1980 ep = current_path.points.get (current_path.points.size - 1); 1981 ep.get_right_handle ().type = PenTool.to_line (ep.type); 1982 ep.type = PenTool.to_curve (ep.get_right_handle ().type); 1983 } 1984 } 1985 } 1986 1987 foreach (Path path in path_list.paths) { 1988 path.update_region_boundaries (); 1989 } 1990 1991 return path_list; 1992 } 1993 1994 public void set_new_start (EditPoint ep) 1995 requires (points.size > 0) { 1996 Gee.ArrayList<EditPoint> list = new Gee.ArrayList<EditPoint> (); 1997 int start = 0; 1998 1999 for (int i = 0; i < points.size; i++) { 2000 if (ep == points.get (i)) { 2001 start = i; 2002 } 2003 } 2004 2005 for (int i = start; i < points.size; i++) { 2006 list.add (points.get (i)); 2007 } 2008 2009 for (int i = 0; i < start; i++) { 2010 list.add (points.get (i)); 2011 } 2012 2013 control_points = list; 2014 } 2015 2016 public void append_path (Path path) { 2017 if (points.size == 0 || path.points.size == 0) { 2018 warning ("No points"); 2019 return; 2020 } 2021 2022 // copy remaining points 2023 foreach (EditPoint p in path.points) { 2024 add_point (p.copy ()); 2025 } 2026 2027 path.points.clear (); 2028 } 2029 2030 /** Roatate around coordinate xc, xc. */ 2031 public void rotate (double theta, double xc, double yc) { 2032 double a, radius; 2033 2034 foreach (EditPoint ep in points) { 2035 radius = sqrt (pow (xc - ep.x, 2) + pow (yc + ep.y, 2)); 2036 2037 if (yc + ep.y < 0) { 2038 radius = -radius; 2039 } 2040 2041 a = acos ((ep.x - xc) / radius); 2042 2043 ep.x = xc + cos (a - theta) * radius; 2044 ep.y = yc + sin (a - theta) * radius; 2045 2046 ep.get_right_handle ().angle -= theta; 2047 ep.get_left_handle ().angle -= theta; 2048 2049 while (ep.get_right_handle ().angle < 0) { 2050 ep.get_right_handle ().angle += 2 * PI; 2051 } 2052 2053 while (ep.get_left_handle ().angle < 0) { 2054 ep.get_left_handle ().angle += 2 * PI; 2055 } 2056 } 2057 2058 rotation += theta; 2059 rotation %= 2 * PI; 2060 2061 update_region_boundaries (); 2062 } 2063 2064 public void flip_vertical () { 2065 EditPointHandle hl, hr; 2066 double lx, ly, rx, ry; 2067 2068 foreach (EditPoint e in points) { 2069 hl = e.get_left_handle (); 2070 hr = e.get_right_handle (); 2071 2072 lx = hl.x; 2073 ly = hl.y; 2074 rx = hr.x; 2075 ry = hr.y; 2076 2077 e.y *= -1; 2078 2079 hr.move_to_coordinate_internal (rx, -1 * ry); 2080 hl.move_to_coordinate_internal (lx, -1 * ly); 2081 } 2082 2083 update_region_boundaries (); 2084 } 2085 2086 public void flip_horizontal () { 2087 EditPointHandle hl, hr; 2088 double lx, ly, rx, ry; 2089 foreach (EditPoint e in points) { 2090 hl = e.get_left_handle (); 2091 hr = e.get_right_handle (); 2092 2093 lx = hl.x; 2094 ly = hl.y; 2095 rx = hr.x; 2096 ry = hr.y; 2097 2098 e.x *= -1; 2099 2100 hr.move_to_coordinate_internal (-1 * rx, ry); 2101 hl.move_to_coordinate_internal (-1 * lx, ly); 2102 } 2103 2104 update_region_boundaries (); 2105 } 2106 2107 public void init_point_type (PointType pt = PointType.NONE) { 2108 PointType type; 2109 2110 if (pt == PointType.NONE) { 2111 pt = DrawingTools.point_type; 2112 } 2113 2114 switch (pt) { 2115 case PointType.QUADRATIC: 2116 type = PointType.LINE_QUADRATIC; 2117 break; 2118 case PointType.DOUBLE_CURVE: 2119 type = PointType.LINE_DOUBLE_CURVE; 2120 break; 2121 case PointType.CUBIC: 2122 type = PointType.LINE_CUBIC; 2123 break; 2124 default: 2125 warning ("No type is set"); 2126 type = PointType.LINE_CUBIC; 2127 break; 2128 } 2129 2130 foreach (EditPoint ep in points) { 2131 ep.type = type; 2132 ep.get_right_handle ().type = type; 2133 ep.get_left_handle ().type = type; 2134 } 2135 } 2136 2137 public void convert_path_ending_to_line () { 2138 if (points.size < 2) { 2139 return; 2140 } 2141 2142 get_first_point ().get_left_handle ().convert_to_line (); 2143 get_last_point ().get_right_handle ().convert_to_line (); 2144 } 2145 2146 public void print_all_types () { 2147 print (@"Control points:\n"); 2148 foreach (EditPoint ep in points) { 2149 print (@"$(ep.type) L: $(ep.get_left_handle ().type) R: L: $(ep.get_right_handle ().type)\n"); 2150 } 2151 } 2152 2153 /** Find the point where two lines intersect. */ 2154 public static void find_intersection (double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, 2155 out double point_x, out double point_y) { 2156 point_x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)); 2157 point_y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)); 2158 } 2159 2160 public static void find_intersection_handle (EditPointHandle h1, EditPointHandle h2, out double point_x, out double point_y) { 2161 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); 2162 } 2163 2164 /** Finx intersection point for two straight lines. */ 2165 public static void find_intersection_point (EditPoint p1, EditPoint p2, EditPoint q1, EditPoint q2, out double point_x, out double point_y) { 2166 find_intersection (p1.x, p1.y, p2.x, p2.y, q1.x, q1.y, q2.x, q2.y, out point_x, out point_y); 2167 } 2168 2169 public void add_extrema () { 2170 double x0, y0, x1, y1, x2, y2, x3, y3; 2171 double minx, maxx, miny, maxy; 2172 2173 if (unlikely (points.size < 2)) { 2174 warning (@"Missing points, $(points.size) points in path."); 2175 return; 2176 } 2177 2178 minx = Glyph.CANVAS_MAX; 2179 miny = Glyph.CANVAS_MAX; 2180 maxx = Glyph.CANVAS_MIN; 2181 maxy = Glyph.CANVAS_MIN; 2182 2183 x0 = 0; 2184 y0 = 0; 2185 x1 = 0; 2186 y1 = 0; 2187 x2 = 0; 2188 y2 = 0; 2189 x3 = 0; 2190 y3 = 0; 2191 2192 all_of_path ((x, y) => { 2193 if (x < minx) { 2194 x0 = x; 2195 y0 = y; 2196 minx = x; 2197 } 2198 2199 if (x > maxx) { 2200 x1 = x; 2201 y1 = y; 2202 maxx = x; 2203 } 2204 2205 if (y < miny) { 2206 x2 = x; 2207 y2 = y; 2208 miny = y; 2209 } 2210 2211 if (y > maxy) { 2212 x3 = x; 2213 y3 = y; 2214 maxy = y; 2215 } 2216 2217 return true; 2218 }); 2219 2220 insert_new_point_on_path_at (x0 - 0.001, y0); 2221 insert_new_point_on_path_at (x1 + 0.001, y1); 2222 insert_new_point_on_path_at (x2, y2 - 0.001); 2223 insert_new_point_on_path_at (x3, y3 + 0.001); 2224 } 2225 2226 public EditPoint insert_new_point_on_path_at (double x, double y) { 2227 EditPoint ep = new EditPoint (); 2228 EditPoint prev, next; 2229 bool exists; 2230 2231 if (points.size < 2) { 2232 warning ("Can't add extrema to just one point."); 2233 return ep; 2234 } 2235 2236 get_closest_point_on_path (ep, x, y); 2237 2238 next = (ep.next == null) ? points.get (0) : ep.get_next (); 2239 prev = (ep.prev == null) ? points.get (points.size - 1) : ep.get_prev (); 2240 2241 exists = prev.x == ep.x && prev.y == ep.y; 2242 exists |= next.x == ep.x && next.y == ep.y; 2243 2244 if (!exists) { 2245 insert_new_point_on_path (ep); 2246 } 2247 2248 return ep; 2249 } 2250 2251 public static bool is_counter (PathList pl, Path path) { 2252 return counters (pl, path) % 2 != 0; 2253 } 2254 2255 public static int counters (PathList pl, Path path) { 2256 int inside_count = 0; 2257 bool inside; 2258 PathList lines = new PathList (); 2259 2260 lines = pl; 2261 2262 /** // FIXME: Check automatic orientation. 2263 foreach (Path p in pl.paths) { 2264 lines.add (SvgParser.get_lines (p)); 2265 } 2266 */ 2267 2268 foreach (Path p in lines.paths) { 2269 if (p.points.size > 1 && p != path 2270 && path.boundaries_intersecting (p)) { 2271 2272 inside = false; 2273 foreach (EditPoint ep in path.points) { 2274 if (SvgParser.is_inside (ep, p)) { 2275 inside = true; 2276 } 2277 } 2278 2279 if (inside) { 2280 inside_count++; 2281 } 2282 } 2283 } 2284 2285 return inside_count; 2286 } 2287 2288 public bool boundaries_intersecting (Path p) { 2289 return in_boundaries (p.xmin, p.xmax, p.ymin, p.ymax); 2290 } 2291 2292 public bool in_boundaries (double other_xmin, double other_xmax, double other_ymin, double other_ymax) { 2293 return ((xmin <= other_xmin <= xmax) || (xmin <= other_xmax <= xmax) 2294 || (other_xmin <= xmin <= other_xmax) || (other_xmin <= xmax <= other_xmax)) 2295 && ((ymin <= other_ymin <= ymax) || (ymin <= other_ymax <= ymax) 2296 || (other_ymin <= ymin <= other_ymax) || (other_ymin <= ymax <= other_ymax)); 2297 } 2298 2299 /** @param t smallest distance to other points. */ 2300 public void remove_points_on_points (double t = 0.00001) { 2301 Gee.ArrayList<EditPoint> remove = new Gee.ArrayList<EditPoint> (); 2302 EditPoint n; 2303 EditPointHandle hr, h; 2304 double t3 = t / 3; 2305 2306 if (points.size == 0) { 2307 return; 2308 } 2309 2310 for (int i = 0; i < points.size + 1; i++) { 2311 EditPoint ep = points.get (i % points.size); 2312 if (ep.get_right_handle ().length < t3 2313 && ep.get_left_handle ().length < t3 2314 && !is_endpoint (ep) 2315 && (ep.flags & EditPoint.CURVE_KEEP) == 0 2316 && (ep.flags & EditPoint.SEGMENT_END) == 0) { 2317 ep.deleted = true; 2318 } 2319 } 2320 2321 remove_deleted_points (); 2322 2323 for (int i = 0; i < points.size + 1; i++) { 2324 EditPoint ep = points.get (i % points.size); 2325 n = points.get ((i + 1) % points.size); 2326 2327 if (Path.distance_to_point (n, ep) < t) { 2328 remove.add (ep); 2329 } 2330 } 2331 2332 create_list (); 2333 2334 foreach (EditPoint r in remove) { 2335 if (points.size == 0) { 2336 return; 2337 } 2338 2339 if (r.next != null) { 2340 n = r.get_next (); 2341 } else { 2342 n = points.get (0); 2343 } 2344 2345 points.remove (r); 2346 h = n.get_left_handle (); 2347 hr = r.get_left_handle (); 2348 h.length = hr.length; 2349 h.angle = hr.angle; 2350 h.type = hr.type; 2351 2352 if (h.length < t) { 2353 h.length = t; 2354 h.angle = n.get_right_handle ().angle - PI; 2355 } 2356 2357 create_list (); 2358 } 2359 2360 recalculate_linear_handles (); 2361 } 2362 2363 public bool is_endpoint (EditPoint ep) { 2364 if (points.size == 0) { 2365 return false; 2366 } 2367 2368 return ep == points.get (0) || ep == points.get (points.size - 1); 2369 } 2370 2371 public void remove_deleted_points () { 2372 Gee.ArrayList<EditPoint> p = new Gee.ArrayList<EditPoint> (); 2373 2374 foreach (EditPoint ep in points) { 2375 if (ep.deleted) { 2376 p.add (ep); 2377 } 2378 } 2379 2380 foreach (EditPoint e in p) { 2381 points.remove (e); 2382 } 2383 2384 create_list (); 2385 } 2386 2387 public static void find_closes_point_in_segment (EditPoint ep0, EditPoint ep1, 2388 double px, double py, 2389 out double nx, out double ny, 2390 double max_step = 200) { 2391 2392 double min_distance = double.MAX; 2393 double npx, npy; 2394 double min_t, max_t; 2395 double rmin_t, rmax_t; 2396 bool found; 2397 int step; 2398 2399 npx = 0; 2400 npy = 0; 2401 2402 min_t = 0; 2403 max_t = 1; 2404 2405 rmin_t = 0; 2406 rmax_t = 1; 2407 2408 for (step = 3; step <= max_step; step *= 2) { 2409 found = false; 2410 min_distance = double.MAX; 2411 Path.all_of (ep0, ep1, (xa, ya, ta) => { 2412 double d = Path.distance (px, xa, py, ya); 2413 2414 if (d < min_distance) { 2415 min_distance = d; 2416 npx = xa; 2417 npy = ya; 2418 rmin_t = ta - 1.0 / step; 2419 rmax_t = ta + 1.0 / step; 2420 found = true; 2421 } 2422 2423 return true; 2424 }, step, min_t, max_t); 2425 2426 if (!found) { 2427 rmin_t = 1 - (1.0 / step); 2428 rmax_t = 1; 2429 } 2430 2431 min_t = (rmin_t > 0) ? rmin_t : 0; 2432 max_t = (rmax_t < 1) ? rmax_t : 1; 2433 } 2434 2435 nx = npx; 2436 ny = npy; 2437 } 2438 2439 public void reset_stroke () { 2440 full_stroke = null; 2441 fast_stroke = null; 2442 } 2443 2444 public void create_full_stroke () { 2445 if (stroke <= 0) { 2446 return; 2447 } 2448 2449 StrokeTask task = new StrokeTask (this); 2450 2451 // Create idle task in order ignore repeted calls to this method 2452 // during one main loop iteration. 2453 IdleSource idle = new IdleSource (); 2454 idle.set_callback (() => { 2455 MainWindow.native_window.run_non_blocking_background_thread (task); 2456 return false; 2457 }); 2458 idle.attach (null); 2459 2460 stop_stroke_creator (); 2461 stroke_creator = task; 2462 } 2463 2464 public void stop_stroke_creator () { 2465 if (stroke_creator != null) { 2466 ((!) stroke_creator).cancel (); 2467 } 2468 } 2469 2470 public PathList get_completed_stroke () { 2471 if (full_stroke == null) { 2472 StrokeTool s = new StrokeTool (); 2473 full_stroke = s.get_stroke (this, stroke); 2474 } 2475 2476 return (!) full_stroke; 2477 } 2478 2479 public PathList get_stroke_fast () { 2480 if (full_stroke != null) { 2481 return (!) full_stroke; 2482 } 2483 2484 if (fast_stroke != null) { 2485 return (!) fast_stroke; 2486 } 2487 2488 StrokeTool s = new StrokeTool (); 2489 fast_stroke = s.get_stroke_fast (this, stroke); 2490 2491 return (!) fast_stroke; 2492 } 2493 2494 // Callback for path simplifier 2495 public void add_cubic_bezier_points (double x0, double y0, double x1, double y1, 2496 double x2, double y2, double x3, double y3) { 2497 2498 EditPoint start; 2499 EditPoint end; 2500 2501 if (points.size > 0) { 2502 start = get_last_point (); 2503 } else { 2504 start = add (x0, y0); 2505 } 2506 2507 end = add (x3, y3); 2508 2509 start.set_point_type (PointType.CUBIC); 2510 end.set_point_type (PointType.CUBIC); 2511 2512 start.convert_to_curve (); 2513 end.convert_to_curve (); 2514 2515 start.get_right_handle ().move_to_coordinate (x1, y1); 2516 end.get_left_handle ().move_to_coordinate (x2, y2); 2517 } 2518 2519 public void recalculate_linear_handles () { 2520 foreach (EditPoint e in points) { 2521 recalculate_linear_handles_for_point (e); 2522 } 2523 } 2524 2525 /** Set bezier points for linear paths. */ 2526 public void recalculate_linear_handles_for_point (EditPoint ep) { 2527 EditPointHandle h; 2528 EditPoint n; 2529 double nx, ny; 2530 2531 return_if_fail (!is_null (ep.right_handle) && !is_null (ep.left_handle)); 2532 2533 // left handle 2534 if (ep.prev != null) { 2535 n = ep.get_prev (); 2536 h = ep.get_left_handle (); 2537 } else { 2538 n = get_last_point (); 2539 h = ep.get_left_handle (); 2540 } 2541 2542 return_if_fail (!is_null (n) && !is_null (h)); 2543 2544 if (h.type == PointType.LINE_CUBIC) { 2545 nx = ep.x + ((n.x - ep.x) / 3); 2546 ny = ep.y + ((n.y - ep.y) / 3); 2547 h.move_to_coordinate (nx, ny); 2548 } 2549 2550 if (h.type == PointType.LINE_DOUBLE_CURVE) { 2551 nx = ep.x + ((n.x - ep.x) / 4); 2552 ny = ep.y + ((n.y - ep.y) / 4); 2553 h.move_to_coordinate (nx, ny); 2554 } 2555 2556 if (h.type == PointType.LINE_QUADRATIC) { 2557 nx = ep.x + ((n.x - ep.x) / 2); 2558 ny = ep.y + ((n.y - ep.y) / 2); 2559 h.move_to_coordinate (nx, ny); 2560 } 2561 2562 // the other side 2563 h = n.get_right_handle (); 2564 return_if_fail (!is_null (h) && !is_null (h)); 2565 2566 if (h.type == PointType.LINE_DOUBLE_CURVE) { 2567 nx = n.x + ((ep.x - n.x) / 4); 2568 ny = n.y + ((ep.y - n.y) / 4); 2569 h.move_to_coordinate (nx, ny); 2570 } 2571 2572 if (h.type == PointType.LINE_CUBIC) { 2573 nx = n.x + ((ep.x - n.x) / 3); 2574 ny = n.y + ((ep.y - n.y) / 3); 2575 h.move_to_coordinate (nx, ny); 2576 } 2577 2578 if (h.type == PointType.LINE_QUADRATIC) { 2579 nx = n.x + ((ep.x - n.x) / 2); 2580 ny = n.y + ((ep.y - n.y) / 2); 2581 h.move_to_coordinate (nx, ny); 2582 } 2583 2584 // right handle 2585 if (ep.next != null) { 2586 n = ep.get_next (); 2587 h = ep.get_right_handle (); 2588 } else { 2589 n = get_first_point (); 2590 h = ep.get_right_handle (); 2591 } 2592 2593 return_if_fail (!is_null (n) && !is_null (h)); 2594 2595 if (h.type == PointType.LINE_CUBIC) { 2596 nx = ep.x + ((n.x - ep.x) / 3); 2597 ny = ep.y + ((n.y - ep.y) / 3); 2598 2599 h.move_to_coordinate (nx, ny); 2600 } 2601 2602 if (h.type == PointType.LINE_DOUBLE_CURVE) { 2603 nx = ep.x + ((n.x - ep.x) / 4); 2604 ny = ep.y + ((n.y - ep.y) / 4); 2605 2606 h.move_to_coordinate (nx, ny); 2607 } 2608 2609 if (h.type == PointType.LINE_QUADRATIC) { 2610 nx = ep.x + ((n.x - ep.x) / 2); 2611 ny = ep.y + ((n.y - ep.y) / 2); 2612 2613 h.move_to_coordinate (nx, ny); 2614 } 2615 2616 h = n.get_left_handle (); 2617 return_if_fail (!is_null (h)); 2618 2619 if (h.type == PointType.LINE_CUBIC) { 2620 nx = n.x + ((ep.x - n.x) / 3); 2621 ny = n.y + ((ep.y - n.y) / 3); 2622 2623 h.move_to_coordinate (nx, ny); 2624 } 2625 2626 if (h.type == PointType.LINE_DOUBLE_CURVE) { 2627 nx = n.x + ((ep.x - n.x) / 4); 2628 ny = n.y + ((ep.y - n.y) / 4); 2629 2630 h.move_to_coordinate (nx, ny); 2631 } 2632 2633 if (h.type == PointType.LINE_QUADRATIC) { 2634 nx = n.x + ((ep.x - n.x) / 2); 2635 ny = n.y + ((ep.y - n.y) / 2); 2636 2637 h.move_to_coordinate (nx, ny); 2638 } 2639 } 2640 } 2641 2642 } 2643