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