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