The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

PenTool.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/PenTool.vala.
Fix self intersecting stroke
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 Math; 16 using Cairo; 17 18 namespace BirdFont { 19 20 /** Create new points. */ 21 public class PenTool : Tool { 22 23 private static double contact_surface { 24 get { 25 return MainWindow.units * 20; 26 } 27 } 28 29 public static bool move_selected = false; 30 public static bool move_selected_handle = false; 31 32 public static bool move_point_on_path = false; 33 34 public static bool edit_active_corner = false; 35 36 public static bool move_point_independent_of_handle = false; 37 38 public static Gee.ArrayList<PointSelection> selected_points; 39 40 public static EditPointHandle active_handle; 41 public static EditPointHandle selected_handle; 42 public static PointSelection handle_selection; 43 44 public static EditPoint? active_edit_point; 45 public static Path active_path; 46 47 public static EditPoint selected_point; 48 49 public static double last_point_x = 0; 50 public static double last_point_y = 0; 51 52 public static bool show_selection_box = false; 53 private static double selection_box_x = 0; 54 private static double selection_box_y = 0; 55 private static double selection_box_last_x = 0; 56 private static double selection_box_last_y = 0; 57 58 private static bool point_selection_image = false; 59 60 public static double precision = 1; 61 62 // The pixel where the user pressed the mouse button 63 public static int begin_action_x = 0; 64 public static int begin_action_y = 0; 65 66 /* First move action must move the current point in to the grid. */ 67 bool first_move_action = false; 68 69 /** Move curve handle instead of control point. */ 70 private bool last_selected_is_handle = false; 71 72 static Gee.ArrayList<Path> clockwise; 73 static Gee.ArrayList<Path> counter_clockwise; 74 75 public static double path_stroke_width = 0; 76 public static double simplification_threshold = 0.5; 77 78 public static bool retain_angle = false; 79 80 static int press_counter = 0; 81 82 public PenTool (string name) { 83 base (name, t_("Add new points")); 84 85 selected_points = new Gee.ArrayList<PointSelection> (); 86 87 active_handle = new EditPointHandle.empty (); 88 selected_handle = new EditPointHandle.empty (); 89 handle_selection = new PointSelection.empty (); 90 91 active_edit_point = new EditPoint (); 92 active_path = new Path (); 93 94 selected_point = new EditPoint (); 95 clockwise = new Gee.ArrayList<Path> (); 96 counter_clockwise = new Gee.ArrayList<Path> (); 97 98 select_action.connect ((self) => { 99 MainWindow.get_current_glyph ().clear_active_paths (); 100 }); 101 102 deselect_action.connect ((self) => { 103 MainWindow.get_current_glyph ().clear_active_paths (); 104 }); 105 106 press_action.connect ((self, b, x, y) => { 107 // retain path direction 108 clockwise = new Gee.ArrayList<Path> (); 109 counter_clockwise = new Gee.ArrayList<Path> (); 110 111 begin_action_x = x; 112 begin_action_y = y; 113 114 update_orientation (); 115 116 first_move_action = true; 117 118 last_point_x = Glyph.path_coordinate_x (x); 119 last_point_y = Glyph.path_coordinate_y (y); 120 121 move_action (this, x, y); 122 123 press (b, x, y, false); 124 125 if (BirdFont.android) { 126 point_selection_image = true; 127 } 128 129 selection_box_x = x; 130 selection_box_y = y; 131 132 last_point_x = Glyph.path_coordinate_x (x); 133 last_point_y = Glyph.path_coordinate_y (y); 134 135 BirdFont.get_current_font ().touch (); 136 137 // move new points on to grid 138 if (b == 1 && (GridTool.has_ttf_grid () || GridTool.is_visible ())) { 139 move (x, y); 140 } 141 142 reset_stroke (); 143 }); 144 145 double_click_action.connect ((self, b, x, y) => { 146 last_point_x = Glyph.path_coordinate_x (x); 147 last_point_y = Glyph.path_coordinate_y (y); 148 149 if (!move_selected_handle && !move_selected) { 150 press (b, x, y, true); 151 } else { 152 warning ("double click suppressed"); 153 } 154 }); 155 156 release_action.connect ((self, b, ix, iy) => { 157 double x, y; 158 Glyph g; 159 160 g = MainWindow.get_current_glyph (); 161 x = Glyph.path_coordinate_x (ix); 162 y = Glyph.path_coordinate_y (iy); 163 164 if (has_join_icon () && active_edit_point != null) { 165 join_paths ((!) active_edit_point); 166 } 167 168 active_handle = new EditPointHandle.empty (); 169 170 if (show_selection_box) { 171 select_points_in_box (); 172 } 173 174 move_selected = false; 175 move_selected_handle = false; 176 edit_active_corner = false; 177 show_selection_box = false; 178 179 set_orientation (); 180 181 MainWindow.set_cursor (NativeWindow.VISIBLE); 182 183 point_selection_image = false; 184 BirdFont.get_current_font ().touch (); 185 reset_stroke (); 186 187 foreach (Path p in g.active_paths) { 188 if (p.is_open () && p.points.size > 0) { 189 p.get_first_point ().set_tie_handle (false); 190 p.get_first_point ().set_reflective_handles (false); 191 p.get_last_point ().set_tie_handle (false); 192 p.get_last_point ().set_reflective_handles (false); 193 } 194 } 195 }); 196 197 move_action.connect ((self, x, y) => { 198 selection_box_last_x = x; 199 selection_box_last_y = y; 200 201 if (Path.distance (begin_action_x, x, begin_action_y, y) > 10 * MainWindow.units) { 202 point_selection_image = false; 203 } 204 205 move (x, y); 206 }); 207 208 key_press_action.connect ((self, keyval) => { 209 reset_stroke (); 210 211 if (keyval == Key.DEL || keyval == Key.BACK_SPACE) { 212 if (KeyBindings.has_shift ()) { 213 delete_selected_points (); 214 } else { 215 delete_simplify (); 216 } 217 } 218 219 if (is_arrow_key (keyval)) { 220 if (KeyBindings.modifier != CTRL) { 221 move_selected_points (keyval); 222 active_edit_point = selected_point; 223 } else { 224 move_select_next_point (keyval); 225 } 226 } 227 228 if (KeyBindings.has_shift ()) { 229 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) { 230 last_point_x = selected_point.x; 231 last_point_y = selected_point.y; 232 } 233 } 234 235 GlyphCanvas.redraw (); 236 BirdFont.get_current_font ().touch (); 237 }); 238 239 key_release_action.connect ((self, keyval) => { 240 double x, y; 241 if (is_arrow_key (keyval)) { 242 if (KeyBindings.modifier != CTRL && active_edit_point != null) { 243 x = Glyph.reverse_path_coordinate_x (selected_point.x); 244 y = Glyph.reverse_path_coordinate_y (selected_point.y); 245 join_paths ((!) active_edit_point); 246 reset_stroke (); 247 } 248 } 249 }); 250 251 draw_action.connect ((tool, cairo_context, glyph) => { 252 draw_on_canvas (cairo_context, glyph); 253 }); 254 } 255 256 public static void update_orientation () { 257 Glyph glyph = MainWindow.get_current_glyph (); 258 259 clockwise.clear (); 260 counter_clockwise.clear (); 261 foreach (Path p in glyph.path_list) { 262 if (p.is_clockwise ()) { 263 clockwise.add (p); 264 } else { 265 counter_clockwise.add (p); 266 } 267 } 268 } 269 270 public static void set_orientation () { 271 // update path direction if it has changed 272 foreach (Path p in clockwise) { 273 if (!p.is_open () && !p.is_clockwise ()) { 274 p.reverse (); 275 update_selection (); 276 } 277 } 278 279 foreach (Path p in counter_clockwise) { 280 if (!p.is_open () && p.is_clockwise ()) { 281 p.reverse (); 282 update_selection (); 283 } 284 } 285 } 286 287 public bool has_join_icon () { 288 if (active_edit_point != null) { 289 return can_join ((!) active_edit_point); 290 } 291 292 return false; 293 } 294 295 public static bool can_join (EditPoint ep) { 296 double mx, my; 297 get_tie_position (ep, out mx, out my); 298 return (mx > -10 * MainWindow.units && my > -10 * MainWindow.units); 299 } 300 301 public static void select_points_in_box () { 302 double x1, y1, x2, y2; 303 Glyph g; 304 305 g = MainWindow.get_current_glyph (); 306 307 x1 = Glyph.path_coordinate_x (fmin (selection_box_x, selection_box_last_x)); 308 y1 = Glyph.path_coordinate_y (fmin (selection_box_y, selection_box_last_y)); 309 x2 = Glyph.path_coordinate_x (fmax (selection_box_x, selection_box_last_x)); 310 y2 = Glyph.path_coordinate_y (fmax (selection_box_y, selection_box_last_y)); 311 312 remove_all_selected_points (); 313 314 foreach (Path p in g.path_list) { 315 // TODO: Select path only of bounding box is in selection box 316 foreach (EditPoint ep in p.points) { 317 if (x1 <= ep.x <= x2 && y2 <= ep.y <= y1) { 318 add_selected_point (ep, p); 319 ep.set_selected (true); 320 } 321 } 322 } 323 } 324 325 public static void delete_selected_points () { 326 Glyph g = MainWindow.get_current_glyph (); 327 328 foreach (PointSelection p in selected_points) { 329 p.point.deleted = true; 330 } 331 332 process_deleted (); 333 334 foreach (Path p in g.path_list) { 335 if (p.has_deleted_point ()) { 336 process_deleted (); 337 } 338 } 339 340 g.update_view (); 341 342 selected_points.clear (); 343 selected_handle.selected = false; 344 345 active_handle = new EditPointHandle.empty (); 346 selected_handle = new EditPointHandle.empty (); 347 348 active_edit_point = null; 349 selected_point = new EditPoint (); 350 } 351 352 static void get_closes_point_in_segment (EditPoint ep0, EditPoint ep1, EditPoint ep2, 353 double px, double py, 354 out double nx, out double ny) { 355 double npx0, npy0; 356 double npx1, npy1; 357 358 Path.find_closes_point_in_segment (ep0, ep1, px, py, out npx0, out npy0, 50); 359 Path.find_closes_point_in_segment (ep1, ep2, px, py, out npx1, out npy1, 50); 360 361 if (Path.distance (px, npx0, py, npy0) < Path.distance (px, npx1, py, npy1)) { 362 nx = npx0; 363 ny = npy0; 364 } else { 365 nx = npx1; 366 ny = npy1; 367 } 368 } 369 370 public static void get_path_distortion (EditPoint oe0, EditPoint oe1, EditPoint oe2, 371 EditPoint ep1, EditPoint ep2, 372 out double distortion_first, out double distortion_next) { 373 double nx, ny; 374 double df, dn; 375 int step; 376 377 df = 0; 378 dn = 0; 379 nx = 0; 380 ny = 0; 381 382 step = 4; 383 384 Path.all_of (ep1, ep2, (xa, ya, ta) => { 385 double f, n; 386 387 get_closes_point_in_segment (oe0, oe1, oe2, xa, ya, out nx, out ny); 388 389 if (ta < 0.5) { 390 f = Path.distance (nx, xa, ny, ya); 391 if (f > df) { 392 df += f; 393 } 394 } else { 395 n = Path.distance (nx, xa, ny, ya); 396 if (n > dn) { 397 dn += n; 398 } 399 } 400 401 return true; 402 }, step); 403 404 distortion_first = df; 405 distortion_next = dn; 406 } 407 408 public static void delete_simplify () { 409 Glyph g = MainWindow.get_current_glyph (); 410 411 foreach (PointSelection p in selected_points) { 412 remove_point_simplify (p); 413 } 414 415 g.update_view (); 416 417 selected_points.clear (); 418 selected_handle.selected = false; 419 420 active_handle = new EditPointHandle.empty (); 421 selected_handle = new EditPointHandle.empty (); 422 423 active_edit_point = null; 424 selected_point = new EditPoint (); 425 } 426 427 /** @return path distortion. */ 428 public static double remove_point_simplify (PointSelection p, double tolerance = 0.6) { 429 double e; 430 431 e = remove_point_simplify_path (p, tolerance, Glyph.CANVAS_MAX); 432 p.path.update_region_boundaries (); 433 434 return e; 435 } 436 437 /** @return path distortion. */ 438 public static double remove_point_simplify_path (PointSelection p, 439 double tolerance = 0.6, double keep_tolerance = 10000) { 440 441 double start_length, stop_length; 442 double start_distortion, start_min_distortion, start_previous_length; 443 double stop_distortion, stop_min_distortion, stop_previous_length; 444 double distortion, min_distortion; 445 double prev_length_adjustment, next_length_adjustment; 446 double prev_length_adjustment_reverse, next_length_adjustment_reverse; 447 EditPoint ep1, ep2; 448 EditPoint next, prev; 449 double step, distance; 450 451 return_if_fail (p.path.points.size > 0); 452 453 if (p.path.points.size <= 2) { 454 p.point.deleted = true; 455 p.path.remove_deleted_points (); 456 return 0; 457 } 458 459 p.point.deleted = true; 460 461 if (p.point.next != null) { 462 next = p.point.get_next (); 463 } else { 464 next = p.path.points.get (0); 465 } 466 467 if (p.point.prev != null) { 468 prev = p.point.get_prev (); 469 } else { 470 prev = p.path.points.get (p.path.points.size - 1); 471 } 472 473 prev.get_right_handle ().convert_to_curve (); 474 next.get_left_handle ().convert_to_curve (); 475 476 if (prev.get_right_handle ().type == PointType.QUADRATIC 477 && next.get_left_handle ().type != PointType.QUADRATIC) { 478 convert_point_type (prev, next.get_left_handle ().type); 479 } 480 481 if (prev.get_right_handle ().type != PointType.QUADRATIC 482 && next.get_left_handle ().type == PointType.QUADRATIC) { 483 convert_point_type (next, prev.get_right_handle ().type); 484 } 485 486 ep1 = prev.copy (); 487 ep2 = next.copy (); 488 489 start_length = ep1.get_right_handle ().length; 490 stop_length = ep2.get_left_handle ().length; 491 492 stop_previous_length = start_length; 493 start_previous_length = stop_length; 494 495 stop_min_distortion = double.MAX; 496 ep1.get_right_handle ().length = start_length; 497 498 start_min_distortion = double.MAX; 499 ep2.get_left_handle ().length = stop_length; 500 501 prev_length_adjustment = 0; 502 next_length_adjustment = 0; 503 prev_length_adjustment_reverse = 0; 504 next_length_adjustment_reverse = 0; 505 506 min_distortion = double.MAX; 507 distance = Path.distance (ep1.x, ep2.x, ep1.y, ep2.y); 508 509 for (double m = 50.0; m >= tolerance / 2.0; m /= 10.0) { 510 step = m / 10.0; 511 min_distortion = double.MAX; 512 513 double first = (m == 50.0) ? 0 : -m; 514 515 for (double a = first; a < m; a += step) { 516 for (double b = first; b < m; b += step) { 517 518 if (start_length + a + stop_length + b > distance) { 519 break; 520 } 521 522 ep1.get_right_handle ().length = start_length + a; 523 ep2.get_left_handle ().length = stop_length + b; 524 525 get_path_distortion (prev, p.point, next, 526 ep1, ep2, 527 out start_distortion, out stop_distortion); 528 529 distortion = Math.fmax (start_distortion, stop_distortion); 530 531 if (distortion < min_distortion 532 && start_length + a > 0 533 && stop_length + b > 0) { 534 min_distortion = distortion; 535 prev_length_adjustment_reverse = a; 536 next_length_adjustment = b; 537 } 538 } 539 } 540 541 start_length += prev_length_adjustment_reverse; 542 stop_length += next_length_adjustment; 543 } 544 545 546 if (min_distortion < keep_tolerance || keep_tolerance >= Glyph.CANVAS_MAX) { 547 prev.get_right_handle ().length = start_length; 548 549 if (prev.get_right_handle ().type != PointType.QUADRATIC) { 550 next.get_left_handle ().length = stop_length; 551 } else { 552 next.get_left_handle ().move_to_coordinate ( 553 prev.get_right_handle ().x, prev.get_right_handle ().y); 554 } 555 556 p.point.deleted = true; 557 p.path.remove_deleted_points (); 558 } 559 560 return min_distortion; 561 } 562 563 /** @return path distortion. */ 564 public static double remove_point_simplify_path_fast (PointSelection p, 565 double tolerance = 0.6, double keep_tolerance = 10000) { 566 567 double start_length, stop_length; 568 double start_distortion, start_min_distortion, start_previous_length; 569 double stop_distortion, stop_min_distortion, stop_previous_length; 570 double distortion, min_distortion; 571 double prev_length_adjustment, next_length_adjustment; 572 double prev_length_adjustment_reverse, next_length_adjustment_reverse; 573 EditPoint ep1, ep2; 574 EditPoint next, prev; 575 double step, distance; 576 577 return_if_fail (p.path.points.size > 0); 578 579 if (p.path.points.size <= 2) { 580 p.point.deleted = true; 581 p.path.remove_deleted_points (); 582 return 0; 583 } 584 585 p.point.deleted = true; 586 587 if (p.point.next != null) { 588 next = p.point.get_next (); 589 } else { 590 next = p.path.points.get (0); 591 } 592 593 if (p.point.prev != null) { 594 prev = p.point.get_prev (); 595 } else { 596 prev = p.path.points.get (p.path.points.size - 1); 597 } 598 599 prev.get_right_handle ().convert_to_curve (); 600 next.get_left_handle ().convert_to_curve (); 601 602 if (prev.get_right_handle ().type == PointType.QUADRATIC 603 && next.get_left_handle ().type != PointType.QUADRATIC) { 604 convert_point_type (prev, next.get_left_handle ().type); 605 } 606 607 if (prev.get_right_handle ().type != PointType.QUADRATIC 608 && next.get_left_handle ().type == PointType.QUADRATIC) { 609 convert_point_type (next, prev.get_right_handle ().type); 610 } 611 612 ep1 = prev.copy (); 613 ep2 = next.copy (); 614 615 start_length = ep1.get_right_handle ().length; 616 stop_length = ep2.get_left_handle ().length; 617 618 stop_previous_length = start_length; 619 start_previous_length = stop_length; 620 621 stop_min_distortion = double.MAX; 622 ep1.get_right_handle ().length = start_length; 623 624 start_min_distortion = double.MAX; 625 ep2.get_left_handle ().length = stop_length; 626 627 prev_length_adjustment = 0; 628 next_length_adjustment = 0; 629 prev_length_adjustment_reverse = 0; 630 next_length_adjustment_reverse = 0; 631 632 min_distortion = double.MAX; 633 distance = Path.distance (ep1.x, ep2.x, ep1.y, ep2.y); 634 635 for (double m = 50.0; m >= tolerance / 2.0; m /= 10.0) { 636 step = m / 10.0; 637 min_distortion = double.MAX; 638 639 double first = (m == 50.0) ? 0 : -m; 640 641 for (double a = first; a < m; a += step) { 642 for (double b = first; b < m; b += step) { 643 644 if (start_length + a + stop_length + b > distance) { 645 break; 646 } 647 648 ep1.get_right_handle ().length = start_length + a; 649 ep2.get_left_handle ().length = stop_length + b; 650 651 get_path_distortion (prev, p.point, next, 652 ep1, ep2, 653 out start_distortion, out stop_distortion); 654 655 distortion = Math.fmax (start_distortion, stop_distortion); 656 657 if (distortion < min_distortion 658 && start_length + a > 0 659 && stop_length + b > 0) { 660 min_distortion = distortion; 661 prev_length_adjustment_reverse = a; 662 next_length_adjustment = b; 663 } 664 } 665 } 666 667 start_length += prev_length_adjustment_reverse; 668 stop_length += next_length_adjustment; 669 } 670 671 672 if (min_distortion < keep_tolerance || keep_tolerance >= Glyph.CANVAS_MAX) { 673 prev.get_right_handle ().length = start_length; 674 675 if (prev.get_right_handle ().type != PointType.QUADRATIC) { 676 next.get_left_handle ().length = stop_length; 677 } else { 678 next.get_left_handle ().move_to_coordinate ( 679 prev.get_right_handle ().x, prev.get_right_handle ().y); 680 } 681 682 p.point.deleted = true; 683 p.path.remove_deleted_points (); 684 } 685 686 return min_distortion; 687 } 688 689 /** Retain selected points even if path is copied after running reverse. */ 690 public static void update_selection () { 691 Glyph g = MainWindow.get_current_glyph (); 692 693 selected_points.clear (); 694 695 foreach (Path p in g.path_list) { 696 foreach (EditPoint e in p.points) { 697 if (e.is_selected ()) { 698 selected_points.add (new PointSelection (e, p)); 699 } 700 } 701 } 702 } 703 704 static void process_deleted () { 705 Glyph g = MainWindow.get_current_glyph (); 706 while (g.process_deleted ()); 707 } 708 709 public static void close_all_paths () { 710 Glyph g = MainWindow.get_current_glyph (); 711 foreach (Path p in g.path_list) { 712 if (p.stroke == 0) { 713 p.close (); 714 } 715 } 716 g.close_path (); 717 GlyphCanvas.redraw (); 718 } 719 720 public void set_precision (double p) { 721 precision = p; 722 SettingsDisplay.precision.set_value_round (p, false, false); 723 } 724 725 public static void reset_stroke () { 726 Glyph g = MainWindow.get_current_glyph (); 727 728 foreach (PointSelection p in selected_points) { 729 p.path.reset_stroke (); 730 } 731 732 foreach (Path path in g.active_paths) { 733 path.reset_stroke (); 734 } 735 } 736 737 public void move (int x, int y) { 738 double coordinate_x, coordinate_y; 739 double delta_coordinate_x, delta_coordinate_y; 740 double angle = 0; 741 bool tied; 742 Glyph g; 743 744 g = MainWindow.get_current_glyph (); 745 746 control_point_event (x, y); 747 curve_active_corner_event (x, y); 748 set_default_handle_positions (); 749 750 if (move_selected_handle && move_selected) { 751 warning ("move_selected_handle && move_selected"); 752 move_selected = false; 753 move_selected_handle = false; 754 } 755 756 if (move_selected_handle || move_selected) { 757 MainWindow.set_cursor (NativeWindow.HIDDEN); 758 reset_stroke (); 759 } else { 760 MainWindow.set_cursor (NativeWindow.VISIBLE); 761 } 762 763 // move control point handles 764 if (move_selected_handle) { 765 set_type_for_moving_handle (); 766 767 // don't update angle if the user is holding down shift 768 if (KeyBindings.modifier == SHIFT || PenTool.retain_angle) { 769 angle = selected_handle.angle; 770 } 771 772 if (GridTool.is_visible ()) { 773 coordinate_x = Glyph.path_coordinate_x (x); 774 coordinate_y = Glyph.path_coordinate_y (y); 775 GridTool.tie_coordinate (ref coordinate_x, ref coordinate_y); 776 delta_coordinate_x = coordinate_x - last_point_x; 777 delta_coordinate_y = coordinate_y - last_point_y; 778 selected_handle.move_to_coordinate (selected_handle.x + delta_coordinate_x, selected_handle.y + delta_coordinate_y); 779 } else if (GridTool.has_ttf_grid ()) { 780 coordinate_x = Glyph.path_coordinate_x (x); 781 coordinate_y = Glyph.path_coordinate_y (y); 782 GridTool.ttf_grid_coordinate (ref coordinate_x, ref coordinate_y); 783 delta_coordinate_x = coordinate_x - last_point_x; 784 delta_coordinate_y = coordinate_y - last_point_y; 785 selected_handle.move_delta_coordinate (delta_coordinate_x, delta_coordinate_y); 786 } else { 787 coordinate_x = Glyph.path_coordinate_x (x); 788 coordinate_y = Glyph.path_coordinate_y (y); 789 delta_coordinate_x = coordinate_x - last_point_x; 790 delta_coordinate_y = coordinate_y - last_point_y; 791 selected_handle.move_delta_coordinate (delta_coordinate_x, delta_coordinate_y); 792 } 793 794 if (KeyBindings.modifier == SHIFT || PenTool.retain_angle) { 795 selected_handle.angle = angle; 796 selected_handle.process_connected_handle (); 797 798 if (selected_handle.parent.tie_handles) { 799 if (selected_handle.is_left_handle ()) { 800 selected_handle.parent.get_right_handle ().angle = angle - PI; 801 } else { 802 selected_handle.parent.get_left_handle ().angle = angle + PI; 803 } 804 } 805 } 806 807 handle_selection.path.update_region_boundaries (); 808 809 // FIXME: redraw line only 810 GlyphCanvas.redraw (); 811 812 if (GridTool.is_visible ()) { 813 last_point_x = selected_handle.x; 814 last_point_y = selected_handle.y; 815 } else if (GridTool.has_ttf_grid ()) { 816 last_point_x = selected_handle.x; 817 last_point_y = selected_handle.y; 818 } else { 819 last_point_x = Glyph.path_coordinate_x (x); 820 last_point_y = Glyph.path_coordinate_y (y); 821 } 822 } 823 824 // move edit point 825 if (move_selected) { 826 827 if (GridTool.is_visible ()) { 828 coordinate_x = Glyph.path_coordinate_x (x); 829 coordinate_y = Glyph.path_coordinate_y (y); 830 831 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) { 832 833 if (first_move_action) { 834 last_point_x = selected_point.x; 835 last_point_y = selected_point.y; 836 } 837 838 move_point_on_handles (coordinate_x, coordinate_y, out coordinate_x, out coordinate_y); 839 } else { 840 GridTool.tie_coordinate (ref coordinate_x, ref coordinate_y); 841 } 842 843 delta_coordinate_x = coordinate_x - last_point_x; 844 delta_coordinate_y = coordinate_y - last_point_y; 845 846 foreach (PointSelection selected in selected_points) { 847 if (move_point_independent_of_handle || KeyBindings.modifier == SHIFT) { 848 selected.point.set_independet_position (selected.point.x + delta_coordinate_x, 849 selected.point.y + delta_coordinate_y); 850 } else { 851 selected.point.set_position (selected.point.x + delta_coordinate_x, 852 selected.point.y + delta_coordinate_y); 853 } 854 855 selected.point.recalculate_linear_handles (); 856 selected.path.update_region_boundaries (); 857 } 858 } else if (GridTool.has_ttf_grid ()) { 859 coordinate_x = Glyph.path_coordinate_x (x); 860 coordinate_y = Glyph.path_coordinate_y (y); 861 862 GridTool.ttf_grid_coordinate (ref coordinate_x, ref coordinate_y); 863 864 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) { 865 866 if (first_move_action) { 867 last_point_x = selected_point.x; 868 last_point_y = selected_point.y; 869 } 870 871 move_point_on_handles (coordinate_x, coordinate_y, out coordinate_x, out coordinate_y); 872 } 873 874 delta_coordinate_x = coordinate_x - last_point_x; 875 delta_coordinate_y = coordinate_y - last_point_y; 876 877 foreach (PointSelection selected in selected_points) { 878 if (move_point_independent_of_handle || KeyBindings.modifier == SHIFT) { 879 tied = selected.point.tie_handles; 880 selected.point.tie_handles = false; 881 selected.point.set_independet_position (selected.point.x + delta_coordinate_x, 882 selected.point.y + delta_coordinate_y); 883 selected.point.tie_handles = tied; 884 } else { 885 selected.point.set_position (selected.point.x + delta_coordinate_x, 886 selected.point.y + delta_coordinate_y); 887 } 888 889 selected.point.recalculate_linear_handles (); 890 selected.path.update_region_boundaries (); 891 } 892 } else { 893 894 coordinate_x = Glyph.path_coordinate_x (x); 895 coordinate_y = Glyph.path_coordinate_y (y); 896 897 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) { 898 899 if (first_move_action) { 900 last_point_x = selected_point.x; 901 last_point_y = selected_point.y; 902 } 903 904 move_point_on_handles (coordinate_x, coordinate_y, out coordinate_x, out coordinate_y); 905 delta_coordinate_x = coordinate_x - last_point_x; 906 delta_coordinate_y = coordinate_y - last_point_y; 907 } else { 908 delta_coordinate_x = coordinate_x - last_point_x; 909 delta_coordinate_y = coordinate_y - last_point_y; 910 } 911 912 foreach (PointSelection selected in selected_points) { 913 914 if (move_point_independent_of_handle || KeyBindings.modifier == SHIFT) { 915 selected.point.set_independet_position (selected.point.x + delta_coordinate_x, 916 selected.point.y + delta_coordinate_y); 917 } else { 918 selected.point.set_position (selected.point.x + delta_coordinate_x, 919 selected.point.y + delta_coordinate_y); 920 } 921 922 selected.point.recalculate_linear_handles (); 923 selected.path.update_region_boundaries (); 924 } 925 } 926 927 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) { 928 last_point_x = selected_point.x; 929 last_point_y = selected_point.y; 930 } else if (GridTool.is_visible ()) { 931 last_point_x = selected_point.x; 932 last_point_y = selected_point.y; 933 } else if (GridTool.has_ttf_grid ()) { 934 last_point_x = selected_point.x; 935 last_point_y = selected_point.y; 936 } else { 937 last_point_x = Glyph.path_coordinate_x (x); 938 last_point_y = Glyph.path_coordinate_y (y); 939 } 940 941 GlyphCanvas.redraw (); 942 } 943 944 if (show_selection_box) { 945 GlyphCanvas.redraw (); 946 } 947 } 948 949 private void move_point_on_handles (double px, double py, out double cx, out double cy) { 950 EditPoint ep; 951 ep = selected_point.copy (); 952 ep.tie_handles = false; 953 ep.reflective_point = false; 954 ep.get_right_handle ().angle += PI / 2; 955 ep.x = px; 956 ep.y = py; 957 958 Path.find_intersection_handle (ep.get_right_handle (), selected_point.get_right_handle (), out cx, out cy); 959 } 960 961 public void press (int button, int x, int y, bool double_click) { 962 Glyph? g = MainWindow.get_current_glyph (); 963 Glyph glyph = (!) g; 964 bool reflective; 965 966 return_if_fail (g != null); 967 968 if ((double_click && !BirdFont.android) 969 || Toolbox.drawing_tools.insert_point_on_path_tool.is_selected ()) { 970 glyph.insert_new_point_on_path (x, y); 971 return; 972 } 973 974 if (button == 1) { 975 add_point_event (x, y); 976 return; 977 } 978 979 if (button == 2) { 980 if (glyph.is_open ()) { 981 force_direction (); 982 glyph.close_path (); 983 } else { 984 glyph.open_path (); 985 } 986 987 glyph.clear_active_paths (); 988 989 return; 990 } 991 992 if (button == 3) { 993 move_point_event (x, y); 994 995 // alt+click on a handle ends the symmetrical editing 996 if ((KeyBindings.has_alt () || KeyBindings.has_ctrl ()) 997 && is_over_handle (x, y)) { 998 999 // don't use set point to reflective to on open ends 1000 reflective = true; 1001 foreach (Path path in MainWindow.get_current_glyph ().active_paths) { 1002 if (path.is_open () && path.points.size > 0) { 1003 if (selected_handle.parent == path.get_first_point () 1004 || selected_handle.parent == path.get_last_point ()) { 1005 reflective = false; 1006 } 1007 } 1008 } 1009 1010 if (reflective) { 1011 selected_handle.parent.set_reflective_handles (false); 1012 selected_handle.parent.set_tie_handle (false); 1013 GlyphCanvas.redraw (); 1014 } 1015 } 1016 1017 return; 1018 } 1019 } 1020 1021 public void add_point_event (int x, int y) { 1022 Glyph? g = MainWindow.get_current_glyph (); 1023 Glyph glyph = (!) g; 1024 PointSelection ps; 1025 1026 if (move_selected_handle) { 1027 warning ("moving handle"); 1028 return; 1029 } 1030 1031 return_if_fail (g != null); 1032 1033 remove_all_selected_points (); 1034 ps = new_point_action (x, y); 1035 active_path = ps.path; 1036 glyph.store_undo_state (); 1037 } 1038 1039 public void move_point_event (int x, int y) { 1040 Glyph? g = MainWindow.get_current_glyph (); 1041 Glyph glyph = (!) g; 1042 1043 return_if_fail (g != null); 1044 1045 control_point_event (x, y); 1046 curve_corner_event (x, y); 1047 1048 if (!move_selected_handle) { 1049 select_active_point (x, y); 1050 last_selected_is_handle = false; 1051 } 1052 1053 if (!KeyBindings.has_shift () 1054 && selected_points.size == 0 1055 && !active_handle.active) { 1056 show_selection_box = true; 1057 } 1058 1059 glyph.store_undo_state (); 1060 } 1061 1062 void set_type_for_moving_handle () { 1063 if (selected_handle.type == PointType.LINE_CUBIC) { 1064 selected_handle.set_point_type (PointType.CUBIC); 1065 } 1066 1067 if (selected_handle.type == PointType.LINE_QUADRATIC) { 1068 selected_handle.set_point_type (PointType.QUADRATIC); 1069 } 1070 1071 if (selected_handle.type == PointType.LINE_DOUBLE_CURVE) { 1072 selected_handle.set_point_type (PointType.DOUBLE_CURVE); 1073 } 1074 } 1075 1076 /** Set fill property to transparend for counter paths. */ 1077 public static void force_direction () { 1078 Glyph g = MainWindow.get_current_glyph (); 1079 1080 clear_directions (); 1081 1082 foreach (Path p in g.path_list) { 1083 if (!p.has_direction ()) { 1084 if (is_counter_path (p)) { 1085 p.force_direction (Direction.COUNTER_CLOCKWISE); 1086 } else { 1087 p.force_direction (Direction.CLOCKWISE); 1088 } 1089 } 1090 } 1091 1092 update_selected_points (); 1093 } 1094 1095 public static void clear_directions () { 1096 clockwise.clear (); 1097 counter_clockwise.clear (); 1098 } 1099 1100 public static bool is_counter_path (Path path) { 1101 Glyph g = MainWindow.get_current_glyph (); 1102 PathList pl = new PathList (); 1103 1104 foreach (Path p in g.path_list) { 1105 pl.add (p); 1106 } 1107 1108 return Path.is_counter (pl, path); 1109 } 1110 1111 public void remove_from_selected (EditPoint ep) 1112 requires (selected_points.size > 0) { 1113 1114 Gee.ArrayList<PointSelection> remove = new Gee.ArrayList<PointSelection> (); 1115 1116 foreach (PointSelection e in selected_points) { 1117 if (e.point.equals (e.point)) { 1118 remove.add (e); 1119 } 1120 } 1121 1122 foreach (PointSelection e in remove) { 1123 selected_points.remove (e); 1124 } 1125 } 1126 1127 public void select_active_point (double x, double y) { 1128 Glyph glyph = MainWindow.get_current_glyph (); 1129 bool reverse; 1130 1131 control_point_event (x, y); 1132 1133 // continue adding points from the other end of the selected path 1134 reverse = false; 1135 1136 foreach (Path p in glyph.path_list) { 1137 if (p.is_open () && p.points.size >= 1 1138 && (active_edit_point == p.points.get (0) 1139 || active_edit_point == p.points.get (p.points.size - 1))) { 1140 update_selection (); 1141 reverse = true; 1142 control_point_event (x, y); 1143 break; 1144 } 1145 } 1146 1147 foreach (Path p in glyph.path_list) { 1148 if (p.is_open () && p.points.size > 1 && active_edit_point == p.points.get (0)) { 1149 p.reverse (); 1150 update_selection (); 1151 reverse = true; 1152 control_point_event (x, y); 1153 break; 1154 } 1155 } 1156 1157 if (active_edit_point == null) { 1158 if (KeyBindings.modifier != SHIFT) { 1159 remove_all_selected_points (); 1160 return; 1161 } 1162 } 1163 1164 move_selected = true; 1165 move_point_on_path = true; 1166 1167 if (active_edit_point != null) { 1168 glyph.clear_active_paths (); 1169 glyph.add_active_path (active_path); 1170 DrawingTools.update_stroke_settings (); 1171 1172 if (KeyBindings.modifier == SHIFT) { 1173 if (((!)active_edit_point).is_selected () && selected_points.size > 1) { 1174 ((!)active_edit_point).set_selected (false); 1175 remove_from_selected ((!)active_edit_point); 1176 selected_point = new EditPoint (); 1177 last_selected_is_handle = false; 1178 } else { 1179 ((!)active_edit_point).set_selected (true); 1180 selected_point = (!)active_edit_point; 1181 add_selected_point (selected_point, active_path); 1182 last_selected_is_handle = false; 1183 } 1184 } else { 1185 selected_point = (!)active_edit_point; 1186 1187 if (!((!)active_edit_point).is_selected ()) { 1188 remove_all_selected_points (); 1189 ((!)active_edit_point).set_selected (true); 1190 selected_point = (!)active_edit_point; 1191 add_selected_point (selected_point, active_path); // FIXME: double check active path 1192 last_selected_is_handle = false; 1193 } 1194 1195 // alt+click creates a point with symmetrical handles 1196 if (KeyBindings.has_alt () || KeyBindings.has_ctrl ()) { 1197 selected_point.set_reflective_handles (true); 1198 selected_point.process_symmetrical_handles (); 1199 GlyphCanvas.redraw (); 1200 } 1201 } 1202 } 1203 1204 if (reverse) { 1205 clockwise.clear (); 1206 counter_clockwise.clear (); 1207 } 1208 } 1209 1210 public static Path? find_path_to_join (EditPoint end_point) { 1211 Path? m = null; 1212 Glyph glyph = MainWindow.get_current_glyph (); 1213 EditPoint ep_last, ep_first; 1214 1215 foreach (Path path in glyph.path_list) { 1216 if (path.points.size == 0) { 1217 continue; 1218 } 1219 1220 ep_last = path.points.get (path.points.size - 1); 1221 ep_first = path.points.get (0); 1222 1223 if (end_point == ep_last) { 1224 m = path; 1225 break; 1226 } 1227 1228 if (end_point == ep_first) { 1229 m = path; 1230 break; 1231 } 1232 } 1233 1234 return m; 1235 } 1236 1237 public static Path merge_open_paths (Path path1, Path path2) { 1238 Path union, merge; 1239 EditPoint first_point; 1240 1241 union = path1.copy (); 1242 merge = path2.copy (); 1243 1244 return_val_if_fail (path1.points.size >= 1, merge); 1245 return_val_if_fail (path2.points.size >= 1, union); 1246 1247 merge.points.get (0).set_tie_handle (false); 1248 merge.points.get (0).set_reflective_handles (false); 1249 1250 merge.points.get (merge.points.size - 1).set_tie_handle (false); 1251 merge.points.get (merge.points.size - 1).set_reflective_handles (false); 1252 1253 union.points.get (union.points.size - 1).set_tie_handle (false); 1254 union.points.get (union.points.size - 1).set_reflective_handles (false); 1255 1256 union.points.get (0).set_tie_handle (false); 1257 union.points.get (0).set_reflective_handles (false); 1258 1259 first_point = merge.get_first_point (); 1260 1261 if (union.get_last_point ().get_left_handle ().is_curve ()) { 1262 first_point.get_left_handle ().convert_to_curve (); 1263 } else { 1264 first_point.get_left_handle ().convert_to_line (); 1265 } 1266 1267 first_point.get_left_handle ().move_to_coordinate_internal ( 1268 union.get_last_point ().get_left_handle ().x, 1269 union.get_last_point ().get_left_handle ().y); 1270 1271 union.delete_last_point (); 1272 1273 union.append_path (merge); 1274 1275 return union; 1276 } 1277 1278 public static void close_path (Path path) { 1279 bool last_segment_is_line; 1280 bool first_segment_is_line; 1281 1282 return_if_fail (path.points.size > 1); 1283 1284 last_segment_is_line = path.get_last_point ().get_left_handle ().is_line (); 1285 first_segment_is_line = path.get_first_point ().get_right_handle ().is_line (); 1286 1287 // TODO: set point type 1288 path.points.get (0).left_handle.move_to_coordinate ( 1289 path.points.get (path.points.size - 1).left_handle.x, 1290 path.points.get (path.points.size - 1).left_handle.y); 1291 1292 path.points.get (0).left_handle.type = 1293 path.points.get (path.points.size - 1).left_handle.type; 1294 1295 path.points.get (0).recalculate_linear_handles (); 1296 path.points.get (path.points.size - 1).recalculate_linear_handles (); 1297 1298 // force the connected handle to move 1299 path.points.get (0).set_position ( 1300 path.points.get (0).x, path.points.get (0).y); 1301 1302 path.points.remove_at (path.points.size - 1); 1303 1304 path.close (); 1305 1306 if (last_segment_is_line) { 1307 path.get_first_point ().get_left_handle ().convert_to_line (); 1308 path.get_first_point ().recalculate_linear_handles (); 1309 } 1310 1311 if (first_segment_is_line) { 1312 path.get_first_point ().get_right_handle ().convert_to_line (); 1313 path.get_first_point ().recalculate_linear_handles (); 1314 } 1315 } 1316 1317 /** @return the new path or null if no path could be merged with the end point. */ 1318 public static Path? join_paths (EditPoint end_point) { 1319 Glyph glyph = MainWindow.get_current_glyph (); 1320 Path? p; 1321 Path path; 1322 Path union; 1323 bool direction_changed = false; 1324 int px, py; 1325 1326 if (glyph.path_list.size == 0) { 1327 warning ("No paths."); 1328 return null; 1329 } 1330 1331 p = find_path_to_join (end_point); 1332 if (p == null) { 1333 warning ("No path to join."); 1334 return null; 1335 } 1336 1337 path = (!) p; 1338 if (!path.is_open ()) { 1339 warning ("Path is closed."); 1340 return null; 1341 } 1342 1343 return_if_fail (path.points.size > 0); 1344 1345 px = Glyph.reverse_path_coordinate_x (end_point.x); 1346 py = Glyph.reverse_path_coordinate_y (end_point.y); 1347 1348 if (path.points.size == 1) { 1349 glyph.delete_path (path); 1350 glyph.clear_active_paths (); 1351 1352 foreach (Path merge in glyph.path_list) { 1353 if (merge.points.size > 0) { 1354 if (is_close_to_point (merge.points.get (merge.points.size - 1), px, py)) { 1355 glyph.add_active_path (merge); 1356 active_path = merge; 1357 merge.reopen (); 1358 glyph.open_path (); 1359 return merge; 1360 } 1361 1362 if (is_close_to_point (merge.points.get (0), px, py)) { 1363 glyph.add_active_path (merge); 1364 active_path = merge; 1365 clear_directions (); 1366 merge.reopen (); 1367 glyph.open_path (); 1368 merge.reverse (); 1369 return merge; 1370 } 1371 } 1372 } 1373 1374 return null; 1375 } 1376 1377 if (active_edit_point == path.points.get (0)) { 1378 path.reverse (); 1379 update_selection (); 1380 path.recalculate_linear_handles (); 1381 direction_changed = true; 1382 active_edit_point = path.points.get (path.points.size - 1); 1383 active_path = path; 1384 } 1385 1386 if (path.points.get (0) == active_edit_point) { 1387 warning ("Wrong direction."); 1388 return null; 1389 } 1390 1391 // join path with it self 1392 if (is_endpoint (end_point) 1393 && is_close_to_point (path.points.get (0), px, py)) { 1394 1395 close_path (path); 1396 glyph.close_path (); 1397 force_direction (); 1398 1399 glyph.clear_active_paths (); 1400 glyph.add_active_path (path); 1401 1402 if (direction_changed) { 1403 path.reverse (); 1404 update_selection (); 1405 } 1406 1407 remove_all_selected_points (); 1408 1409 return path; 1410 } 1411 1412 foreach (Path merge in glyph.path_list) { 1413 // don't join path with itself here 1414 if (path == merge) { 1415 continue; 1416 } 1417 1418 // we need both start and end points 1419 if (merge.points.size <= 1 || path.points.size <= 1) { 1420 continue; 1421 } 1422 1423 if (is_close_to_point (merge.points.get (merge.points.size - 1), px, py)) { 1424 merge.reverse (); 1425 update_selection (); 1426 direction_changed = !direction_changed; 1427 } 1428 1429 return_if_fail (merge.points.size > 0); 1430 1431 if (is_close_to_point (merge.points.get (0), px, py)) { 1432 if (path.points.size == 1) { 1433 warning ("path.points.size == 1\n"); 1434 } else if (merge.points.size == 1) { 1435 warning ("merge.points.size == 1\n"); 1436 } else { 1437 union = merge_open_paths (path, merge); 1438 1439 glyph.add_path (union); 1440 glyph.delete_path (path); 1441 glyph.delete_path (merge); 1442 glyph.clear_active_paths (); 1443 glyph.add_active_path (union); 1444 1445 union.reopen (); 1446 union.create_list (); 1447 1448 force_direction (); 1449 1450 if (direction_changed) { 1451 path.reverse (); 1452 update_selection (); 1453 } 1454 1455 union.update_region_boundaries (); 1456 1457 return union; 1458 } 1459 } 1460 } 1461 1462 if (direction_changed) { 1463 path.reverse (); 1464 update_selection (); 1465 } 1466 1467 return null; 1468 } 1469 1470 /** Merge paths if ends are close. */ 1471 public static bool is_close_to_point (EditPoint ep, double x, double y) { 1472 double px, py, distance; 1473 1474 px = Glyph.reverse_path_coordinate_x (ep.x); 1475 py = Glyph.reverse_path_coordinate_y (ep.y); 1476 1477 distance = sqrt (fabs (pow (px - x, 2)) + fabs (pow (py - y, 2))); 1478 1479 return (distance < 7 * MainWindow.units); 1480 } 1481 1482 /** Show the user that curves will be merged on release. */ 1483 public void draw_on_canvas (Context cr, Glyph glyph) { 1484 if (show_selection_box) { 1485 draw_selection_box (cr); 1486 } 1487 1488 if (point_selection_image) { 1489 draw_point_selection_circle (cr); 1490 } 1491 1492 draw_merge_icon (cr); 1493 } 1494 1495 /** Higlight the selected point on Android. */ 1496 void draw_point_selection_circle (Context cr) { 1497 PointSelection ps; 1498 1499 if (active_handle.active) { 1500 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x), 1501 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Control Point Handle")); 1502 } else if (selected_points.size > 0) { 1503 ps = selected_points.get (selected_points.size - 1); 1504 1505 if (ps.point.type == PointType.CUBIC) { 1506 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x), 1507 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Selected Cubic Control Point")); 1508 } else { 1509 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x), 1510 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Selected Quadratic Control Point")); 1511 } 1512 } 1513 } 1514 1515 void draw_selection_box (Context cr) { 1516 double x, y, w, h; 1517 1518 x = fmin (selection_box_x, selection_box_last_x); 1519 y = fmin (selection_box_y, selection_box_last_y); 1520 w = fmax (selection_box_x, selection_box_last_x) - x; 1521 h = fmax (selection_box_y, selection_box_last_y) - y; 1522 1523 cr.save (); 1524 Theme.color (cr, "Foreground 1"); 1525 cr.set_line_width (2); 1526 cr.rectangle (x, y, w, h); 1527 cr.stroke (); 1528 cr.restore (); 1529 } 1530 1531 public static void draw_join_icon (Context cr, double x, double y) { 1532 cr.save (); 1533 Theme.color (cr, "Merge"); 1534 cr.move_to (x, y); 1535 cr.arc (x, y, 15, 0, 2 * Math.PI); 1536 cr.close_path (); 1537 cr.fill (); 1538 cr.restore (); 1539 } 1540 1541 void draw_merge_icon (Context cr) { 1542 double x, y; 1543 if (active_edit_point != null) { 1544 get_tie_position ((!) active_edit_point, out x, out y); 1545 draw_join_icon (cr, x, y); 1546 } 1547 } 1548 1549 /** Obtain the position where to ends meet. */ 1550 static void get_tie_position (EditPoint current_point, out double x, out double y) { 1551 Glyph glyph; 1552 EditPoint active; 1553 double px, py; 1554 1555 x = -100; 1556 y = -100; 1557 1558 if (!is_endpoint (current_point)) { 1559 return; 1560 } 1561 1562 glyph = MainWindow.get_current_glyph (); 1563 active = current_point; 1564 1565 return_if_fail (!is_null (glyph)); 1566 1567 px = Glyph.reverse_path_coordinate_x (active.x); 1568 py = Glyph.reverse_path_coordinate_y (active.y); 1569 1570 foreach (Path path in glyph.path_list) { 1571 1572 if (!path.is_open ()) { 1573 continue; 1574 } 1575 1576 if (path.points.size == 0) { 1577 continue; 1578 } 1579 1580 foreach (EditPoint ep in path.points) { 1581 if (ep == active || !is_endpoint (ep)) { 1582 continue; 1583 } 1584 1585 if (is_close_to_point (ep, px, py)) { 1586 x = Glyph.reverse_path_coordinate_x (ep.x); 1587 y = Glyph.reverse_path_coordinate_y (ep.y); 1588 return; 1589 } 1590 } 1591 } 1592 } 1593 1594 public static bool is_endpoint (EditPoint ep) { 1595 EditPoint start; 1596 EditPoint end; 1597 Glyph glyph = MainWindow.get_current_glyph (); 1598 1599 foreach (Path path in glyph.path_list) { 1600 if (path.points.size < 1) { 1601 continue; 1602 } 1603 1604 start = path.points.get (0); 1605 end = path.points.get (path.points.size - 1); 1606 1607 if (ep == start || ep == end) { 1608 return true; 1609 } 1610 } 1611 1612 return false; 1613 } 1614 1615 public static void set_active_edit_point (EditPoint? e, Path path) { 1616 bool redraw; 1617 Glyph g = MainWindow.get_current_glyph (); 1618 1619 foreach (Path p in g.path_list) { 1620 foreach (var ep in p.points) { 1621 ep.set_active (false); 1622 } 1623 } 1624 1625 redraw = active_edit_point != e; 1626 active_edit_point = e; 1627 1628 if (e != null) { 1629 ((!)e).set_active (true); 1630 } 1631 1632 if (redraw) { 1633 GlyphCanvas.redraw (); 1634 } 1635 } 1636 1637 PointSelection? get_closest_point (double ex, double ey, out Path? path) { 1638 double x = Glyph.path_coordinate_x (ex); 1639 double y = Glyph.path_coordinate_y (ey); 1640 double d = double.MAX; 1641 double nd; 1642 PointSelection? ep = null; 1643 Glyph g = MainWindow.get_current_glyph (); 1644 1645 path = null; 1646 1647 foreach (Path current_path in g.path_list) { 1648 if (is_close_to_path (current_path, ex, ey)) { 1649 foreach (EditPoint e in current_path.points) { 1650 nd = e.get_distance (x, y); 1651 1652 if (nd < d) { 1653 d = nd; 1654 ep = new PointSelection (e, current_path); 1655 path = current_path; 1656 } 1657 } 1658 } 1659 } 1660 1661 return ep; 1662 } 1663 1664 public double get_distance_to_closest_edit_point (double event_x, double event_y) { 1665 Path? p; 1666 PointSelection e; 1667 PointSelection? ep = get_closest_point (event_x, event_y, out p); 1668 1669 double x = Glyph.path_coordinate_x (event_x); 1670 double y = Glyph.path_coordinate_y (event_y); 1671 1672 if (ep == null) { 1673 return double.MAX; 1674 } 1675 1676 e = (!) ep; 1677 1678 return e.point.get_distance (x, y); 1679 } 1680 1681 public void control_point_event (double event_x, double event_y) { 1682 Path? p; 1683 PointSelection? ep = get_closest_point (event_x, event_y, out p); 1684 Glyph g = MainWindow.get_current_glyph (); 1685 double x = Glyph.path_coordinate_x (event_x); 1686 double y = Glyph.path_coordinate_y (event_y); 1687 double distance; 1688 PointSelection e; 1689 set_active_edit_point (null, new Path ()); 1690 1691 if (ep == null) { 1692 return; 1693 } 1694 1695 e = (!) ep; 1696 distance = e.point.get_distance (x, y) * g.view_zoom; 1697 1698 if (distance < contact_surface) { 1699 set_active_edit_point (e.point, e.path); 1700 1701 active_path = e.path; 1702 g.add_active_path (active_path); 1703 } 1704 } 1705 1706 public PointSelection new_point_action (int x, int y) { 1707 Glyph glyph; 1708 PointSelection new_point; 1709 1710 glyph = MainWindow.get_current_glyph (); 1711 glyph.open_path (); 1712 1713 remove_all_selected_points (); 1714 1715 new_point = add_new_edit_point (x, y); 1716 new_point.point.set_selected (true); 1717 1718 selected_point = new_point.point; 1719 active_edit_point = new_point.point; 1720 1721 return_if_fail (glyph.active_paths.size > 0); 1722 add_selected_point (selected_point, glyph.active_paths.get (glyph.active_paths.size - 1)); 1723 1724 active_path = new_point.path; 1725 glyph.clear_active_paths (); 1726 glyph.add_active_path (new_point.path); 1727 1728 move_selected = true; 1729 1730 return new_point; 1731 } 1732 1733 public static PointSelection add_new_edit_point (int x, int y) { 1734 Glyph glyph; 1735 PointSelection new_point; 1736 1737 glyph = MainWindow.get_current_glyph (); 1738 1739 new_point = glyph.add_new_edit_point (x, y); 1740 new_point.path.update_region_boundaries (); 1741 1742 if (new_point.path.is_open () && new_point.path.points.size > 0) { 1743 new_point.path.get_first_point ().set_reflective_handles (false); 1744 new_point.path.get_first_point ().set_tie_handle (false); 1745 new_point.path.get_last_point ().set_reflective_handles (false); 1746 new_point.path.get_last_point ().set_tie_handle (false); 1747 } 1748 1749 selected_point = new_point.point; 1750 active_edit_point = new_point.point; 1751 1752 set_point_type (selected_point); 1753 set_default_handle_positions (); 1754 1755 selected_points.clear (); 1756 add_selected_point (new_point.point, new_point.path); 1757 1758 return new_point; 1759 } 1760 1761 static void set_point_type (EditPoint p) { 1762 if (p.prev != null && p.get_prev ().right_handle.type == PointType.QUADRATIC) { 1763 p.left_handle.type = PointType.QUADRATIC; 1764 p.right_handle.type = PointType.LINE_QUADRATIC; 1765 p.type = PointType.QUADRATIC; 1766 } else if (DrawingTools.get_selected_point_type () == PointType.QUADRATIC) { 1767 p.left_handle.type = PointType.LINE_QUADRATIC; 1768 p.right_handle.type = PointType.LINE_QUADRATIC; 1769 p.type = PointType.LINE_QUADRATIC; 1770 } else if (DrawingTools.get_selected_point_type () == PointType.DOUBLE_CURVE) { 1771 p.left_handle.type = PointType.LINE_DOUBLE_CURVE; 1772 p.right_handle.type = PointType.LINE_DOUBLE_CURVE; 1773 p.type = PointType.DOUBLE_CURVE; 1774 } else { 1775 p.left_handle.type = PointType.LINE_CUBIC; 1776 p.right_handle.type = PointType.LINE_CUBIC; 1777 p.type = PointType.CUBIC; 1778 } 1779 } 1780 1781 public static void set_default_handle_positions () { 1782 Glyph g = MainWindow.get_current_glyph (); 1783 foreach (var p in g.path_list) { 1784 if (p.is_editable ()) { 1785 p.create_list (); 1786 set_default_handle_positions_on_path (p); 1787 } 1788 } 1789 } 1790 1791 static void set_default_handle_positions_on_path (Path path) { 1792 foreach (EditPoint e in path.points) { 1793 if (!e.tie_handles && !e.reflective_point) { 1794 e.recalculate_linear_handles (); 1795 } 1796 } 1797 } 1798 1799 private bool is_over_handle (double event_x, double event_y) { 1800 Glyph g = MainWindow.get_current_glyph (); 1801 double distance_to_edit_point = g.view_zoom * get_distance_to_closest_edit_point (event_x, event_y); 1802 1803 if (!Path.show_all_line_handles) { 1804 foreach (PointSelection selected_corner in selected_points) { 1805 if (is_close_to_handle (selected_corner.point, event_x, event_y, distance_to_edit_point)) { 1806 return true; 1807 } 1808 } 1809 } else { 1810 foreach (Path p in g.path_list) { 1811 if (is_close_to_path (p, event_x, event_y)) { 1812 foreach (EditPoint ep in p.points) { 1813 if (is_close_to_handle (ep, event_x, event_y, distance_to_edit_point)) { 1814 return true; 1815 } 1816 } 1817 } 1818 } 1819 } 1820 1821 return false; 1822 } 1823 1824 bool is_close_to_path (Path p, double event_x, double event_y) { 1825 double c = contact_surface * Glyph.ivz (); 1826 double x = Glyph.path_coordinate_x (event_x); 1827 double y = Glyph.path_coordinate_y (event_y); 1828 1829 if (unlikely (!p.has_region_boundaries ())) { 1830 if (p.points.size > 0) { 1831 warning (@"No bounding box. $(p.points.size)"); 1832 p.update_region_boundaries (); 1833 } 1834 } 1835 1836 return p.xmin - c <= x <= p.xmax + c && p.ymin - c <= y <= p.ymax + c; 1837 } 1838 1839 private bool is_close_to_handle (EditPoint selected_corner, double event_x, double event_y, double distance_to_edit_point) { 1840 double x = Glyph.path_coordinate_x (event_x); 1841 double y = Glyph.path_coordinate_y (event_y); 1842 Glyph g = MainWindow.get_current_glyph (); 1843 double d_point = distance_to_edit_point; 1844 double dl, dr; 1845 1846 dl = g.view_zoom * selected_corner.get_left_handle ().get_point ().get_distance (x, y); 1847 dr = g.view_zoom * selected_corner.get_right_handle ().get_point ().get_distance (x, y); 1848 1849 if (dl < contact_surface && dl < d_point) { 1850 return true; 1851 } 1852 1853 if (dr < contact_surface && dr < d_point) { 1854 return true; 1855 } 1856 1857 return false; 1858 } 1859 1860 PointSelection get_closest_handle (double event_x, double event_y) { 1861 EditPointHandle left, right; 1862 double x = Glyph.path_coordinate_x (event_x); 1863 double y = Glyph.path_coordinate_y (event_y); 1864 EditPointHandle eh = new EditPointHandle.empty(); 1865 Glyph g = MainWindow.get_current_glyph (); 1866 double d = double.MAX; 1867 double dn; 1868 Path path = new Path (); 1869 bool left_handle = false; 1870 EditPoint parent_point; 1871 EditPoint tied_point; 1872 1873 foreach (Path p in g.path_list) { 1874 if (is_close_to_path (p, event_x, event_y) || p == active_path) { 1875 foreach (EditPoint ep in p.points) { 1876 if (ep.is_selected () || Path.show_all_line_handles) { 1877 left = ep.get_left_handle (); 1878 right = ep.get_right_handle (); 1879 1880 dn = left.get_point ().get_distance (x, y); 1881 1882 if (dn < d) { 1883 eh = left; 1884 d = dn; 1885 path = p; 1886 left_handle = true; 1887 } 1888 1889 dn = right.get_point ().get_distance (x, y); 1890 1891 if (dn < d) { 1892 eh = right; 1893 d = dn; 1894 path = p; 1895 left_handle = false; 1896 } 1897 } 1898 } 1899 } 1900 } 1901 1902 // Make sure the selected handle belongs to the selected point if 1903 // the current segment is quadratic. 1904 if (eh.type == PointType.QUADRATIC) { 1905 parent_point = eh.get_parent (); 1906 1907 if (left_handle) { 1908 if (parent_point.prev != null) { 1909 tied_point = parent_point.get_prev (); 1910 if (tied_point.selected_point) { 1911 eh = tied_point.get_right_handle (); 1912 } 1913 } 1914 } else { 1915 if (parent_point.next != null) { 1916 tied_point = parent_point.get_next (); 1917 if (tied_point.selected_point) { 1918 eh = tied_point.get_left_handle (); 1919 } 1920 } 1921 } 1922 } 1923 1924 return new PointSelection.handle_selection (eh, path); 1925 } 1926 1927 private void curve_active_corner_event (double event_x, double event_y) { 1928 PointSelection eh; 1929 Glyph glyph; 1930 1931 glyph = MainWindow.get_current_glyph (); 1932 active_handle.active = false; 1933 1934 if (!is_over_handle (event_x, event_y)) { 1935 return; 1936 } 1937 1938 eh = get_closest_handle (event_x, event_y); 1939 eh.handle.active = true; 1940 active_handle = eh.handle; 1941 } 1942 1943 private void curve_corner_event (double event_x, double event_y) { 1944 Glyph g = MainWindow.get_current_glyph (); 1945 g.open_path (); 1946 PointSelection p; 1947 1948 if (!is_over_handle (event_x, event_y)) { 1949 return; 1950 } 1951 1952 move_selected_handle = true; 1953 last_selected_is_handle = true; 1954 selected_handle.selected = false; 1955 p = get_closest_handle (event_x, event_y); 1956 selected_handle = p.handle; 1957 handle_selection = p; 1958 selected_handle.selected = true; 1959 1960 active_path = p.path; 1961 g.add_active_path (active_path); 1962 } 1963 1964 public static void add_selected_point (EditPoint p, Path path) { 1965 foreach (PointSelection ep in selected_points) { 1966 if (p == ep.point) { 1967 return; 1968 } 1969 } 1970 1971 selected_points.add (new PointSelection (p, path)); 1972 } 1973 1974 public static void remove_all_selected_points () { 1975 Glyph g = MainWindow.get_current_glyph (); 1976 1977 foreach (PointSelection ep in selected_points) { 1978 ep.point.set_active (false); 1979 ep.point.set_selected (false); 1980 } 1981 1982 selected_points.clear (); 1983 1984 foreach (Path p in g.path_list) { 1985 foreach (EditPoint e in p.points) { 1986 e.set_active (false); 1987 e.set_selected (false); 1988 } 1989 } 1990 } 1991 1992 static void move_select_next_point (uint keyval) { 1993 PointSelection next = new PointSelection.empty (); 1994 1995 if (selected_points.size == 0) { 1996 return; 1997 } 1998 1999 switch (keyval) { 2000 case Key.UP: 2001 next = get_next_point_up (); 2002 break; 2003 case Key.DOWN: 2004 next = get_next_point_down (); 2005 break; 2006 case Key.LEFT: 2007 next = get_next_point_left (); 2008 break; 2009 case Key.RIGHT: 2010 next = get_next_point_right (); 2011 break; 2012 default: 2013 break; 2014 } 2015 2016 set_selected_point (next.point, next.path); 2017 GlyphCanvas.redraw (); 2018 } 2019 2020 private static PointSelection get_next_point (double angle) 2021 requires (selected_points.size != 0) { 2022 PointSelection e = selected_points.get (selected_points.size - 1); 2023 double right_angle = e.point.right_handle.angle; 2024 double left_angle = e.point.left_handle.angle; 2025 double min_right, min_left; 2026 double min; 2027 2028 return_val_if_fail (e.point.next != null, new EditPoint ()); 2029 return_val_if_fail (e.point.prev != null, new EditPoint ()); 2030 2031 // angle might be greater than 2 PI or less than 0 2032 min_right = double.MAX; 2033 min_left = double.MAX; 2034 for (double i = -2 * PI; i <= 2 * PI; i += 2 * PI) { 2035 min = fabs (right_angle - (angle + i)); 2036 if (min < min_right) { 2037 min_right = min; 2038 } 2039 2040 min = fabs (left_angle - (angle + i)); 2041 if (min < min_left) { 2042 min_left = min; 2043 } 2044 } 2045 2046 if (min_right < min_left) { 2047 return new PointSelection (e.point.get_next (), e.path); 2048 } 2049 2050 return new PointSelection (e.point.get_prev (), e.path); 2051 } 2052 2053 private static PointSelection get_next_point_up () { 2054 return get_next_point (PI / 2); 2055 } 2056 2057 private static PointSelection get_next_point_down () { 2058 return get_next_point (PI + PI / 2); 2059 } 2060 2061 private static PointSelection get_next_point_left () { 2062 return get_next_point (PI); 2063 } 2064 2065 private static PointSelection get_next_point_right () { 2066 return get_next_point (0); 2067 } 2068 2069 private static void set_selected_point (EditPoint ep, Path p) { 2070 remove_all_selected_points (); 2071 add_selected_point (ep, p); 2072 set_active_edit_point (ep, p); 2073 edit_active_corner = true; 2074 ep.set_selected (true); 2075 set_default_handle_positions (); 2076 } 2077 2078 public static void select_point_up () { 2079 move_select_next_point (Key.UP); 2080 } 2081 2082 public static void select_point_down () { 2083 move_select_next_point (Key.DOWN); 2084 } 2085 2086 public static void select_point_right () { 2087 move_select_next_point (Key.RIGHT); 2088 } 2089 2090 public static void select_point_left () { 2091 move_select_next_point (Key.LEFT); 2092 } 2093 2094 /** 2095 * Move the selected editpoint one pixel with keyboard irrespectivly of 2096 * current zoom. 2097 */ 2098 void move_selected_points (uint keyval) { 2099 Path? last_path = null; 2100 2101 if (!last_selected_is_handle) { 2102 if (keyval == Key.UP) { 2103 foreach (PointSelection e in selected_points) { 2104 e.point.set_position (e.point.x, e.point.y + Glyph.ivz ()); 2105 e.point.recalculate_linear_handles (); 2106 } 2107 } 2108 2109 if (keyval == Key.DOWN) { 2110 foreach (PointSelection e in selected_points) { 2111 e.point.set_position (e.point.x, e.point.y - Glyph.ivz ()); 2112 e.point.recalculate_linear_handles (); 2113 } 2114 } 2115 2116 if (keyval == Key.LEFT) { 2117 foreach (PointSelection e in selected_points) { 2118 e.point.set_position (e.point.x - Glyph.ivz (), e.point.y); 2119 e.point.recalculate_linear_handles (); 2120 } 2121 } 2122 2123 if (keyval == Key.RIGHT) { 2124 foreach (PointSelection e in selected_points) { 2125 e.point.set_position (e.point.x + Glyph.ivz (), e.point.y); 2126 e.point.recalculate_linear_handles (); 2127 } 2128 } 2129 2130 last_path = null; 2131 foreach (PointSelection e in selected_points) { 2132 if (e.path != last_path) { 2133 e.path.update_region_boundaries (); 2134 last_path = e.path; 2135 } 2136 } 2137 2138 } else { 2139 set_type_for_moving_handle (); 2140 active_handle.active = false; 2141 active_handle = new EditPointHandle.empty (); 2142 2143 if (keyval == Key.UP) { 2144 selected_handle.move_delta_coordinate (0, 1 * Glyph.ivz ()); 2145 } 2146 2147 if (keyval == Key.DOWN) { 2148 selected_handle.move_delta_coordinate (0, -1 * Glyph.ivz ()); 2149 } 2150 2151 if (keyval == Key.LEFT) { 2152 selected_handle.move_delta_coordinate (-1 * Glyph.ivz (), 0); 2153 } 2154 2155 if (keyval == Key.RIGHT) { 2156 selected_handle.move_delta_coordinate (1 * Glyph.ivz (), 0); 2157 } 2158 } 2159 2160 reset_stroke (); 2161 2162 // TODO: redraw only the relevant parts 2163 GlyphCanvas.redraw (); 2164 } 2165 2166 public static void convert_point_to_line (EditPoint ep, bool both) { 2167 ep.set_tie_handle (false); 2168 ep.set_reflective_handles (false); 2169 2170 if (ep.next == null) { 2171 // FIXME: write a new function for this case 2172 // warning ("Next is null."); 2173 } 2174 2175 if (ep.prev == null) { 2176 warning ("Prev is null."); 2177 } 2178 2179 if (ep.type == PointType.CUBIC || ep.type == PointType.LINE_CUBIC) { 2180 ep.type = PointType.LINE_CUBIC; 2181 2182 if (both) { 2183 ep.get_left_handle ().type = PointType.LINE_CUBIC; 2184 ep.get_right_handle ().type = PointType.LINE_CUBIC; 2185 } 2186 2187 if (ep.next != null && ep.get_next ().is_selected ()) { 2188 ep.get_right_handle ().type = PointType.LINE_CUBIC; 2189 } 2190 2191 if (ep.prev != null && ep.get_prev ().is_selected ()) { 2192 ep.get_left_handle ().type = PointType.LINE_CUBIC; 2193 } 2194 2195 } 2196 2197 if (ep.type == PointType.DOUBLE_CURVE| ep.type == PointType.LINE_DOUBLE_CURVE) { 2198 ep.type = PointType.LINE_DOUBLE_CURVE; 2199 if (both) { 2200 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE; 2201 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE; 2202 } 2203 2204 if (ep.next != null && ep.get_next ().is_selected ()) { 2205 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE; 2206 } 2207 2208 if (ep.prev != null && ep.get_prev ().is_selected ()) { 2209 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE; 2210 } 2211 } 2212 2213 if (ep.type == PointType.QUADRATIC || ep.type == PointType.LINE_QUADRATIC) { 2214 ep.type = PointType.LINE_QUADRATIC; 2215 2216 if (both) { 2217 ep.get_left_handle ().type = PointType.LINE_QUADRATIC; 2218 ep.get_right_handle ().type = PointType.LINE_QUADRATIC; 2219 2220 if (ep.next != null) { 2221 ep.get_next ().get_left_handle ().type = PointType.LINE_QUADRATIC; 2222 } 2223 2224 if (ep.prev != null) { 2225 ep.get_prev ().get_right_handle ().type = PointType.LINE_QUADRATIC; 2226 } 2227 } 2228 2229 if (ep.next != null && ep.get_next ().is_selected ()) { 2230 ep.get_right_handle ().type = PointType.LINE_QUADRATIC; 2231 ep.get_next ().get_left_handle ().type = PointType.LINE_QUADRATIC; 2232 } 2233 2234 if (ep.prev != null && ep.get_prev ().is_selected ()) { 2235 ep.get_left_handle ().type = PointType.LINE_QUADRATIC; 2236 ep.get_prev ().get_right_handle ().type = PointType.LINE_QUADRATIC; 2237 } 2238 2239 } 2240 2241 ep.recalculate_linear_handles (); 2242 } 2243 2244 public static void convert_segment_to_line () { 2245 if (selected_points.size == 0) { 2246 return; 2247 } 2248 2249 if (selected_points.size == 1) { 2250 convert_point_to_line (selected_points.get (0).point, true); 2251 } else { 2252 foreach (PointSelection p in selected_points) { 2253 convert_point_to_line (p.point, false); 2254 } 2255 } 2256 } 2257 2258 public static bool is_line (PointType t) { 2259 return t == PointType.LINE_QUADRATIC 2260 || t == PointType.LINE_DOUBLE_CURVE 2261 || t == PointType.LINE_CUBIC; 2262 } 2263 2264 public static PointType to_line (PointType t) { 2265 switch (t) { 2266 case PointType.QUADRATIC: 2267 return PointType.LINE_QUADRATIC; 2268 case PointType.DOUBLE_CURVE: 2269 return PointType.LINE_DOUBLE_CURVE; 2270 case PointType.CUBIC: 2271 return PointType.LINE_CUBIC; 2272 default: 2273 break; 2274 } 2275 return t; 2276 } 2277 2278 public static PointType to_curve (PointType t) { 2279 switch (t) { 2280 case PointType.LINE_QUADRATIC: 2281 return PointType.QUADRATIC; 2282 case PointType.LINE_DOUBLE_CURVE: 2283 return PointType.DOUBLE_CURVE; 2284 case PointType.LINE_CUBIC: 2285 return PointType.CUBIC; 2286 default: 2287 break; 2288 } 2289 2290 if (unlikely (t == PointType.NONE)) { 2291 warning ("Type is not set."); 2292 } 2293 2294 return t; 2295 } 2296 2297 public static void set_converted_handle_length (EditPointHandle e, PointType pt) { 2298 2299 if (e.type == PointType.QUADRATIC && pt == PointType.DOUBLE_CURVE) { 2300 e.length *= 2; 2301 e.length /= 4; 2302 } 2303 2304 if (e.type == PointType.QUADRATIC && pt == PointType.CUBIC) { 2305 e.length *= 2; 2306 e.length /= 3; 2307 } 2308 2309 if (e.type == PointType.DOUBLE_CURVE && pt == PointType.QUADRATIC) { 2310 e.length *= 4; 2311 e.length /= 2; 2312 } 2313 2314 if (e.type == PointType.DOUBLE_CURVE && pt == PointType.CUBIC) { 2315 e.length *= 4; 2316 e.length /= 3; 2317 } 2318 2319 if (e.type == PointType.CUBIC && pt == PointType.QUADRATIC) { 2320 e.length *= 3; 2321 e.length /= 2; 2322 } 2323 2324 if (e.type == PointType.CUBIC && pt == PointType.DOUBLE_CURVE) { 2325 e.length *= 3; 2326 e.length /= 4; 2327 } 2328 } 2329 2330 public static void convert_point_segment_type (EditPoint first, EditPoint next, PointType point_type) { 2331 bool line; 2332 2333 set_converted_handle_length (first.get_right_handle (), point_type); 2334 set_converted_handle_length (next.get_left_handle (), point_type); 2335 2336 line = is_line (first.type) 2337 && is_line (first.get_right_handle ().type) 2338 && is_line (next.get_left_handle ().type); 2339 2340 if (!line) { 2341 first.type = point_type; 2342 } else { 2343 first.type = to_line (point_type); 2344 } 2345 2346 if (!line) { 2347 first.get_right_handle ().type = point_type; 2348 } else { 2349 first.get_right_handle ().type = to_line (point_type); 2350 } 2351 2352 if (!line) { 2353 next.get_left_handle ().type = point_type; 2354 } else { 2355 next.get_left_handle ().type = to_line (point_type); 2356 } 2357 2358 // process connected handle 2359 if (point_type == PointType.QUADRATIC) { 2360 first.set_position (first.x, first.y); 2361 first.recalculate_linear_handles (); 2362 } 2363 } 2364 2365 public static void convert_point_type (EditPoint first, PointType point_type) { 2366 convert_point_segment_type (first, first.get_next (), point_type); 2367 } 2368 2369 public static void convert_point_types () { 2370 Glyph glyph = MainWindow.get_current_glyph (); 2371 glyph.store_undo_state (); 2372 PointSelection selected = new PointSelection.empty (); 2373 bool reset_selected = false; 2374 EditPoint e; 2375 2376 if (selected_points.size == 1) { 2377 selected = selected_points.get (0); 2378 if (selected.point.next != null) { 2379 selected_points.add (new PointSelection (selected.point.get_next (), selected.path)); 2380 selected.point.get_next ().set_selected (true); 2381 } 2382 2383 if (selected.point.prev != null) { 2384 selected_points.add (new PointSelection (selected.point.get_prev (), selected.path)); 2385 selected.point.get_next ().set_selected (true); 2386 } 2387 2388 reset_selected = true; 2389 } 2390 2391 foreach (PointSelection ps in selected_points) { 2392 e = ps.point; 2393 2394 // convert segments not control points 2395 if (e.next == null || !e.get_next ().is_selected ()) { 2396 continue; 2397 } 2398 2399 convert_point_type (e, DrawingTools.point_type); 2400 } 2401 2402 if (reset_selected) { 2403 remove_all_selected_points (); 2404 selected_points.add (selected); 2405 selected.point.set_selected (true); 2406 } 2407 2408 foreach (Path p in glyph.path_list) { 2409 p.update_region_boundaries (); 2410 } 2411 } 2412 2413 public static void update_selected_points () { 2414 Glyph g = MainWindow.get_current_glyph (); 2415 selected_points.clear (); 2416 2417 foreach (Path p in g.path_list) { 2418 foreach (EditPoint ep in p.points) { 2419 if (ep.is_selected ()) { 2420 selected_points.add (new PointSelection (ep, p)); 2421 } 2422 } 2423 } 2424 } 2425 2426 public void select_all_points () { 2427 Glyph g = MainWindow.get_current_glyph (); 2428 2429 foreach (Path p in g.path_list) { 2430 foreach (EditPoint ep in p.points) { 2431 ep.set_selected (true); 2432 add_selected_point (ep, p); 2433 } 2434 } 2435 } 2436 2437 public static Path simplify (Path path, bool selected_segments = false, double threshold = 0.3) { 2438 PointSelection ps; 2439 EditPoint ep; 2440 Path p1, new_path; 2441 double d, sumd; 2442 int i; 2443 2444 p1 = path.copy (); 2445 new_path = p1.copy (); 2446 i = 0; 2447 sumd = 0; 2448 while (i < new_path.points.size) { 2449 ep = new_path.points.get (i); 2450 ps = new PointSelection (ep, new_path); 2451 d = PenTool.remove_point_simplify (ps); 2452 sumd += d; 2453 2454 if (sumd < threshold) { 2455 p1 = new_path.copy (); 2456 } else { 2457 new_path = p1.copy (); 2458 sumd = 0; 2459 i++; 2460 } 2461 } 2462 2463 new_path.update_region_boundaries (); 2464 2465 return new_path; 2466 } 2467 2468 public void set_simplification_threshold (double t) { 2469 simplification_threshold = t; 2470 } 2471 } 2472 2473 } 2474