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