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