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