The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

Path.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

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

Revisions

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