The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

PenTool.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

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