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