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