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