The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

PenTool.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

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