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