The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

Path.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

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