The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

Path.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

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

Revisions

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