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