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