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