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