The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

Path.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

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

Revisions

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