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