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