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