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