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