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