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