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.
Split path before creating stroke
1 /* 2 Copyright (C) 2012, 2013, 2014, 2015 Johan Mattsson 3 4 This library is free software; you can redistribute it and/or modify 5 it under the terms of the GNU Lesser General Public License as 6 published by the Free Software Foundation; either version 3 of the 7 License, or (at your option) any later version. 8 9 This library is distributed in the hope that it will be useful, but 10 WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 Lesser General Public License for more details. 13 */ 14 15 using Math; 16 using Cairo; 17 18 namespace BirdFont { 19 20 /** Create new points. */ 21 public class PenTool : Tool { 22 23 private static double contact_surface { 24 get { 25 return MainWindow.units * 20; 26 } 27 } 28 29 public static bool move_selected = false; 30 public static bool move_selected_handle = false; 31 32 public static bool move_point_on_path = false; 33 34 public static bool edit_active_corner = false; 35 36 public static bool move_point_independent_of_handle = false; 37 38 public static Gee.ArrayList<PointSelection> selected_points; 39 40 public static EditPointHandle active_handle; 41 public static EditPointHandle selected_handle; 42 public static PointSelection handle_selection; 43 44 public static EditPoint? active_edit_point; 45 public static Path active_path; 46 47 public static EditPoint selected_point; 48 49 public static double last_point_x = 0; 50 public static double last_point_y = 0; 51 52 public static bool show_selection_box = false; 53 private static double selection_box_x = 0; 54 private static double selection_box_y = 0; 55 private static double selection_box_last_x = 0; 56 private static double selection_box_last_y = 0; 57 58 private static bool point_selection_image = false; 59 60 public static double precision = 1; 61 62 // The pixel where the user pressed the mouse button 63 public static int begin_action_x = 0; 64 public static int begin_action_y = 0; 65 66 /* First move action must move the current point in to the grid. */ 67 bool first_move_action = false; 68 69 /** Move curve handle instead of control point. */ 70 private bool last_selected_is_handle = false; 71 72 static Gee.ArrayList<Path> clockwise; 73 static Gee.ArrayList<Path> counter_clockwise; 74 75 public static double path_stroke_width = 0; 76 public static double simplification_threshold = 0.5; 77 78 public static bool retain_angle = false; 79 80 public PenTool (string name) { 81 base (name, t_("Add new points")); 82 83 selected_points = new Gee.ArrayList<PointSelection> (); 84 85 active_handle = new EditPointHandle.empty (); 86 selected_handle = new EditPointHandle.empty (); 87 handle_selection = new PointSelection.empty (); 88 89 active_edit_point = new EditPoint (); 90 active_path = new Path (); 91 92 selected_point = new EditPoint (); 93 clockwise = new Gee.ArrayList<Path> (); 94 counter_clockwise = new Gee.ArrayList<Path> (); 95 96 select_action.connect ((self) => { 97 }); 98 99 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 // don't retain direction 853 clockwise.clear (); 854 counter_clockwise.clear (); 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 bool is_counter_path (Path path) { 870 Glyph g = MainWindow.get_current_glyph (); 871 PathList pl = new PathList (); 872 873 foreach (Path p in g.path_list) { 874 pl.add (p); 875 } 876 877 return Path.is_counter (pl, path); 878 } 879 880 public void remove_from_selected (EditPoint ep) 881 requires (selected_points.size > 0) { 882 883 Gee.ArrayList<PointSelection> remove = new Gee.ArrayList<PointSelection> (); 884 885 foreach (PointSelection e in selected_points) { 886 if (e.point.equals (e.point)) { 887 remove.add (e); 888 } 889 } 890 891 foreach (PointSelection e in remove) { 892 selected_points.remove (e); 893 } 894 } 895 896 public void select_active_point (double x, double y) { 897 Glyph? g = MainWindow.get_current_glyph (); 898 Glyph glyph = (!) g; 899 bool reverse; 900 901 control_point_event (x, y); 902 903 // continue adding points from the other end of the selected path 904 reverse = false; 905 906 foreach (Path p in glyph.path_list) { 907 908 if (p.is_open () && p.points.size >= 1 909 && (active_edit_point == p.points.get (0) 910 || active_edit_point == p.points.get (p.points.size - 1))) { 911 active_path = p; 912 glyph.set_active_path (p); 913 914 update_selection (); 915 reverse = true; 916 control_point_event (x, y); 917 break; 918 } 919 } 920 921 foreach (Path p in glyph.path_list) { 922 if (p.is_open () && p.points.size > 1 && active_edit_point == p.points.get (0)) { 923 p.reverse (); 924 update_selection (); 925 reverse = true; 926 control_point_event (x, y); 927 break; 928 } 929 } 930 931 if (active_edit_point == null) { 932 if (KeyBindings.modifier != SHIFT) { 933 remove_all_selected_points (); 934 return; 935 } 936 } 937 938 move_selected = true; 939 move_point_on_path = true; 940 941 if (active_edit_point != null) { 942 if (KeyBindings.modifier == SHIFT) { 943 if (((!)active_edit_point).is_selected () && selected_points.size > 1) { 944 ((!)active_edit_point).set_selected (false); 945 remove_from_selected ((!)active_edit_point); 946 selected_point = new EditPoint (); 947 last_selected_is_handle = false; 948 } else { 949 ((!)active_edit_point).set_selected (true); 950 selected_point = (!)active_edit_point; 951 add_selected_point (selected_point, active_path); 952 last_selected_is_handle = false; 953 } 954 } else { 955 selected_point = (!)active_edit_point; 956 957 if (!((!)active_edit_point).is_selected ()) { 958 remove_all_selected_points (); 959 ((!)active_edit_point).set_selected (true); 960 selected_point = (!)active_edit_point; 961 add_selected_point (selected_point, active_path); // FIXME: double check active path 962 last_selected_is_handle = false; 963 } 964 965 // alt+click creates a point with symmetrical handles 966 if (KeyBindings.has_alt () || KeyBindings.has_ctrl ()) { 967 selected_point.set_reflective_handles (true); 968 selected_point.process_symmetrical_handles (); 969 GlyphCanvas.redraw (); 970 } 971 } 972 } 973 974 if (reverse) { 975 clockwise.clear (); 976 counter_clockwise.clear (); 977 } 978 } 979 980 private static Path? find_path_to_join () { 981 Path? m = null; 982 Glyph glyph = MainWindow.get_current_glyph (); 983 EditPoint ep_last, ep_first; 984 985 foreach (Path path in glyph.path_list) { 986 if (path.points.size == 0) { 987 continue; 988 } 989 990 ep_last = path.points.get (path.points.size - 1); 991 ep_first = path.points.get (0); 992 993 if (active_edit_point == ep_last) { 994 m = path; 995 break; 996 } 997 998 if (active_edit_point == ep_first) { 999 m = path; 1000 break; 1001 } 1002 } 1003 1004 return m; 1005 } 1006 1007 private static void join_paths (double x, double y) { 1008 Glyph glyph = MainWindow.get_current_glyph (); 1009 Path? p; 1010 Path path; 1011 bool direction_changed = false; 1012 Path union, second_path; 1013 EditPoint first_point; 1014 int px, py; 1015 bool last_segment_is_line; 1016 bool first_segment_is_line; 1017 1018 if (glyph.path_list.size == 0) { 1019 return; 1020 } 1021 1022 p = find_path_to_join (); 1023 if (p == null) { 1024 warning ("No path to join."); 1025 return; 1026 } 1027 path = (!) p; 1028 1029 if (!path.is_open ()) { 1030 warning ("Path is closed."); 1031 return; 1032 } 1033 1034 return_if_fail (path.points.size > 1); 1035 1036 if (active_edit_point == path.points.get (0)) { 1037 path.reverse (); 1038 update_selection (); 1039 path.recalculate_linear_handles (); 1040 direction_changed = true; 1041 active_edit_point = path.points.get (path.points.size - 1); 1042 active_path = path; 1043 } 1044 1045 if (path.points.get (0) == active_edit_point) { 1046 warning ("Wrong direction."); 1047 return; 1048 } 1049 1050 // join path with it self 1051 px = Glyph.reverse_path_coordinate_x (((!) active_edit_point).x); 1052 py = Glyph.reverse_path_coordinate_y (((!) active_edit_point).y); 1053 if (is_endpoint ((!) active_edit_point) 1054 && is_close_to_point (path.points.get (0), px, py)) { 1055 1056 last_segment_is_line = path.get_last_point ().get_left_handle ().is_line (); 1057 first_segment_is_line = path.get_first_point ().get_right_handle ().is_line (); 1058 1059 // TODO: set point type 1060 path.points.get (0).left_handle.move_to_coordinate ( 1061 path.points.get (path.points.size - 1).left_handle.x, 1062 path.points.get (path.points.size - 1).left_handle.y); 1063 1064 path.points.get (0).left_handle.type = 1065 path.points.get (path.points.size - 1).left_handle.type; 1066 1067 path.points.get (0).recalculate_linear_handles (); 1068 path.points.get (path.points.size - 1).recalculate_linear_handles (); 1069 1070 // force the connected handle to move 1071 path.points.get (0).set_position ( 1072 path.points.get (0).x, path.points.get (0).y); 1073 1074 path.points.remove_at (path.points.size - 1); 1075 1076 path.close (); 1077 glyph.close_path (); 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 force_direction (); 1090 1091 if (direction_changed) { 1092 path.reverse (); 1093 update_selection (); 1094 } 1095 1096 remove_all_selected_points (); 1097 1098 return; 1099 } 1100 1101 union = new Path (); 1102 foreach (EditPoint ep in path.points) { 1103 union.add_point (ep.copy ()); 1104 } 1105 1106 foreach (Path merge in glyph.path_list) { 1107 // don't join path with itself here 1108 if (path == merge) { 1109 continue; 1110 } 1111 1112 // we need both start and end points 1113 if (merge.points.size < 1 || path.points.size < 1) { 1114 continue; 1115 } 1116 1117 if (is_close_to_point (merge.points.get (merge.points.size - 1), px, py)) { 1118 merge.reverse (); 1119 update_selection (); 1120 direction_changed = !direction_changed; 1121 } 1122 1123 return_if_fail (merge.points.size > 0); 1124 1125 if (is_close_to_point (merge.points.get (0), px, py)) { 1126 merge.points.get (0).set_tie_handle (false); 1127 merge.points.get (0).set_reflective_handles (false); 1128 1129 merge.points.get (merge.points.size - 1).set_tie_handle (false); 1130 merge.points.get (merge.points.size - 1).set_reflective_handles (false); 1131 1132 path.points.get (path.points.size - 1).set_tie_handle (false); 1133 path.points.get (path.points.size - 1).set_reflective_handles (false); 1134 1135 path.points.get (0).set_tie_handle (false); 1136 path.points.get (0).set_reflective_handles (false); 1137 1138 second_path = merge.copy (); 1139 1140 first_point = second_path.get_first_point (); 1141 1142 if (union.get_last_point ().get_left_handle ().is_curve ()) { 1143 first_point.get_left_handle ().convert_to_curve (); 1144 } else { 1145 first_point.get_left_handle ().convert_to_line (); 1146 } 1147 1148 first_point.get_left_handle ().move_to_coordinate_internal (union.get_last_point ().get_left_handle ().x, union.get_last_point ().get_left_handle ().y); 1149 1150 union.delete_last_point (); 1151 1152 union.append_path (second_path); 1153 glyph.add_path (union); 1154 1155 glyph.delete_path (path); 1156 glyph.delete_path (merge); 1157 glyph.clear_active_paths (); 1158 1159 union.reopen (); 1160 union.create_list (); 1161 1162 force_direction (); 1163 1164 if (direction_changed) { 1165 path.reverse (); 1166 update_selection (); 1167 } 1168 1169 union.update_region_boundaries (); 1170 1171 return; 1172 } 1173 } 1174 1175 if (direction_changed) { 1176 path.reverse (); 1177 update_selection (); 1178 } 1179 } 1180 1181 /** Merge paths if ends are close. */ 1182 public static bool is_close_to_point (EditPoint ep, double x, double y) { 1183 double px, py, distance; 1184 1185 px = Glyph.reverse_path_coordinate_x (ep.x); 1186 py = Glyph.reverse_path_coordinate_y (ep.y); 1187 1188 distance = sqrt (fabs (pow (px - x, 2)) + fabs (pow (py - y, 2))); 1189 1190 return (distance < 8 * MainWindow.units); 1191 } 1192 1193 /** Show the user that curves will be merged on release. */ 1194 public void draw_on_canvas (Context cr, Glyph glyph) { 1195 if (show_selection_box) { 1196 draw_selection_box (cr); 1197 } 1198 1199 if (point_selection_image) { 1200 draw_point_selection_circle (cr); 1201 } 1202 1203 draw_merge_icon (cr); 1204 } 1205 1206 /** Higlight the selected point on Android. */ 1207 void draw_point_selection_circle (Context cr) { 1208 PointSelection ps; 1209 1210 if (active_handle.active) { 1211 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x), 1212 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Control Point Handle")); 1213 } else if (selected_points.size > 0) { 1214 ps = selected_points.get (selected_points.size - 1); 1215 1216 if (ps.point.type == PointType.CUBIC) { 1217 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x), 1218 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Selected Cubic Control Point")); 1219 } else { 1220 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x), 1221 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Selected Quadratic Control Point")); 1222 } 1223 } 1224 } 1225 1226 void draw_selection_box (Context cr) { 1227 double x, y, w, h; 1228 1229 x = fmin (selection_box_x, selection_box_last_x); 1230 y = fmin (selection_box_y, selection_box_last_y); 1231 w = fmax (selection_box_x, selection_box_last_x) - x; 1232 h = fmax (selection_box_y, selection_box_last_y) - y; 1233 1234 cr.save (); 1235 Theme.color (cr, "Foreground 1"); 1236 cr.set_line_width (2); 1237 cr.rectangle (x, y, w, h); 1238 cr.stroke (); 1239 cr.restore (); 1240 } 1241 1242 public static void draw_join_icon (Context cr, double x, double y) { 1243 cr.save (); 1244 Theme.color (cr, "Merge"); 1245 cr.move_to (x, y); 1246 cr.arc (x, y, 15, 0, 2 * Math.PI); 1247 cr.close_path (); 1248 cr.fill (); 1249 cr.restore (); 1250 } 1251 1252 void draw_merge_icon (Context cr) { 1253 double x, y; 1254 get_tie_position (out x, out y); 1255 draw_join_icon (cr, x, y); 1256 } 1257 1258 /** Obtain the position where to ends meet. */ 1259 void get_tie_position (out double x, out double y) { 1260 Glyph glyph; 1261 EditPoint active; 1262 double px, py; 1263 1264 x = -100; 1265 y = -100; 1266 1267 if (active_edit_point == null) { 1268 return; 1269 } 1270 1271 if (!is_endpoint ((!) active_edit_point)) { 1272 return; 1273 } 1274 1275 glyph = MainWindow.get_current_glyph (); 1276 active = (!) active_edit_point; 1277 1278 return_if_fail (!is_null (glyph)); 1279 1280 px = Glyph.reverse_path_coordinate_x (active.x); 1281 py = Glyph.reverse_path_coordinate_y (active.y); 1282 1283 foreach (Path path in glyph.path_list) { 1284 1285 if (!path.is_open ()) { 1286 continue; 1287 } 1288 1289 if (path.points.size == 0) { 1290 continue; 1291 } 1292 1293 foreach (EditPoint ep in path.points) { 1294 if (ep == active || !is_endpoint (ep)) { 1295 continue; 1296 } 1297 1298 if (is_close_to_point (ep, px, py)) { 1299 x = Glyph.reverse_path_coordinate_x (ep.x); 1300 y = Glyph.reverse_path_coordinate_y (ep.y); 1301 return; 1302 } 1303 } 1304 } 1305 } 1306 1307 public static bool is_endpoint (EditPoint ep) { 1308 EditPoint start; 1309 EditPoint end; 1310 Glyph glyph = MainWindow.get_current_glyph (); 1311 1312 foreach (Path path in glyph.path_list) { 1313 if (path.points.size < 1) { 1314 continue; 1315 } 1316 1317 start = path.points.get (0); 1318 end = path.points.get (path.points.size - 1); 1319 1320 if (ep == start || ep == end) { 1321 return true; 1322 } 1323 } 1324 1325 return false; 1326 } 1327 1328 public static void set_active_edit_point (EditPoint? e, Path path) { 1329 bool redraw; 1330 Glyph g = MainWindow.get_current_glyph (); 1331 1332 foreach (var p in g.path_list) { 1333 foreach (var ep in p.points) { 1334 ep.set_active (false); 1335 } 1336 } 1337 1338 redraw = active_edit_point != e; 1339 active_edit_point = e; 1340 active_path = path; 1341 1342 if (e != null) { 1343 ((!)e).set_active (true); 1344 } 1345 1346 if (redraw) { 1347 g.redraw_area (0, 0, g.allocation.width, g.allocation.height); 1348 } 1349 } 1350 1351 PointSelection? get_closest_point (double ex, double ey, out Path? path) { 1352 double x = Glyph.path_coordinate_x (ex); 1353 double y = Glyph.path_coordinate_y (ey); 1354 double d = double.MAX; 1355 double nd; 1356 PointSelection? ep = null; 1357 Glyph g = MainWindow.get_current_glyph (); 1358 1359 path = null; 1360 1361 foreach (Path current_path in g.path_list) { 1362 if (is_close_to_path (current_path, ex, ey)) { 1363 foreach (EditPoint e in current_path.points) { 1364 nd = e.get_distance (x, y); 1365 1366 if (nd < d) { 1367 d = nd; 1368 ep = new PointSelection (e, current_path); 1369 path = current_path; 1370 } 1371 } 1372 } 1373 } 1374 1375 return ep; 1376 } 1377 1378 public double get_distance_to_closest_edit_point (double event_x, double event_y) { 1379 Path? p; 1380 PointSelection e; 1381 PointSelection? ep = get_closest_point (event_x, event_y, out p); 1382 1383 double x = Glyph.path_coordinate_x (event_x); 1384 double y = Glyph.path_coordinate_y (event_y); 1385 1386 if (ep == null) { 1387 return double.MAX; 1388 } 1389 1390 e = (!) ep; 1391 1392 return e.point.get_distance (x, y); 1393 } 1394 1395 public void control_point_event (double event_x, double event_y) { 1396 Path? p; 1397 PointSelection? ep = get_closest_point (event_x, event_y, out p); 1398 Glyph g = MainWindow.get_current_glyph (); 1399 double x = Glyph.path_coordinate_x (event_x); 1400 double y = Glyph.path_coordinate_y (event_y); 1401 double distance; 1402 PointSelection e; 1403 set_active_edit_point (null, new Path ()); 1404 1405 if (ep == null) { 1406 return; 1407 } 1408 1409 e = (!) ep; 1410 distance = e.point.get_distance (x, y) * g.view_zoom; 1411 1412 if (distance < contact_surface) { 1413 set_active_edit_point (e.point, e.path); 1414 } 1415 } 1416 1417 public PointSelection new_point_action (int x, int y) { 1418 Glyph glyph; 1419 PointSelection new_point; 1420 glyph = MainWindow.get_current_glyph (); 1421 glyph.open_path (); 1422 1423 remove_all_selected_points (); 1424 1425 new_point = add_new_edit_point (x, y); 1426 new_point.point.set_selected (true); 1427 1428 selected_point = new_point.point; 1429 active_edit_point = new_point.point; 1430 1431 return_if_fail (glyph.active_paths.size > 0); 1432 add_selected_point (selected_point, glyph.active_paths.get (glyph.active_paths.size - 1)); 1433 1434 move_selected = true; 1435 1436 return new_point; 1437 } 1438 1439 public static PointSelection add_new_edit_point (int x, int y) { 1440 Glyph glyph; 1441 PointSelection new_point; 1442 1443 glyph = MainWindow.get_current_glyph (); 1444 1445 new_point = glyph.add_new_edit_point (x, y); 1446 new_point.path.update_region_boundaries (); 1447 1448 selected_point = new_point.point; 1449 active_edit_point = new_point.point; 1450 1451 set_point_type (selected_point); 1452 set_default_handle_positions (); 1453 1454 selected_points.clear (); 1455 add_selected_point (new_point.point, new_point.path); 1456 1457 return new_point; 1458 } 1459 1460 static void set_point_type (EditPoint p) { 1461 if (p.prev != null && p.get_prev ().right_handle.type == PointType.QUADRATIC) { 1462 p.left_handle.type = PointType.QUADRATIC; 1463 p.right_handle.type = PointType.LINE_QUADRATIC; 1464 p.type = PointType.QUADRATIC; 1465 } else if (DrawingTools.get_selected_point_type () == PointType.QUADRATIC) { 1466 p.left_handle.type = PointType.LINE_QUADRATIC; 1467 p.right_handle.type = PointType.LINE_QUADRATIC; 1468 p.type = PointType.LINE_QUADRATIC; 1469 } else if (DrawingTools.get_selected_point_type () == PointType.DOUBLE_CURVE) { 1470 p.left_handle.type = PointType.LINE_DOUBLE_CURVE; 1471 p.right_handle.type = PointType.LINE_DOUBLE_CURVE; 1472 p.type = PointType.DOUBLE_CURVE; 1473 } else { 1474 p.left_handle.type = PointType.LINE_CUBIC; 1475 p.right_handle.type = PointType.LINE_CUBIC; 1476 p.type = PointType.CUBIC; 1477 } 1478 } 1479 1480 public static void set_default_handle_positions () { 1481 Glyph g = MainWindow.get_current_glyph (); 1482 foreach (var p in g.path_list) { 1483 if (p.is_editable ()) { 1484 p.create_list (); 1485 set_default_handle_positions_on_path (p); 1486 } 1487 } 1488 } 1489 1490 static void set_default_handle_positions_on_path (Path path) { 1491 foreach (EditPoint e in path.points) { 1492 if (!e.tie_handles && !e.reflective_point) { 1493 e.recalculate_linear_handles (); 1494 } 1495 } 1496 } 1497 1498 private bool is_over_handle (double event_x, double event_y) { 1499 Glyph g = MainWindow.get_current_glyph (); 1500 double distance_to_edit_point = g.view_zoom * get_distance_to_closest_edit_point (event_x, event_y); 1501 1502 if (!Path.show_all_line_handles) { 1503 foreach (PointSelection selected_corner in selected_points) { 1504 if (is_close_to_handle (selected_corner.point, event_x, event_y, distance_to_edit_point)) { 1505 return true; 1506 } 1507 } 1508 } else { 1509 foreach (Path p in g.path_list) { 1510 if (is_close_to_path (p, event_x, event_y)) { 1511 foreach (EditPoint ep in p.points) { 1512 if (is_close_to_handle (ep, event_x, event_y, distance_to_edit_point)) { 1513 return true; 1514 } 1515 } 1516 } 1517 } 1518 } 1519 1520 return false; 1521 } 1522 1523 bool is_close_to_path (Path p, double event_x, double event_y) { 1524 double c = contact_surface * Glyph.ivz (); 1525 double x = Glyph.path_coordinate_x (event_x); 1526 double y = Glyph.path_coordinate_y (event_y); 1527 1528 if (unlikely (!p.has_region_boundaries ())) { 1529 if (p.points.size > 0) { 1530 warning (@"No bounding box. $(p.points.size)"); 1531 p.update_region_boundaries (); 1532 } 1533 } 1534 1535 return p.xmin - c <= x <= p.xmax + c && p.ymin - c <= y <= p.ymax + c; 1536 } 1537 1538 private bool is_close_to_handle (EditPoint selected_corner, double event_x, double event_y, double distance_to_edit_point) { 1539 double x = Glyph.path_coordinate_x (event_x); 1540 double y = Glyph.path_coordinate_y (event_y); 1541 Glyph g = MainWindow.get_current_glyph (); 1542 double d_point = distance_to_edit_point; 1543 double dl, dr; 1544 1545 dl = g.view_zoom * selected_corner.get_left_handle ().get_point ().get_distance (x, y); 1546 dr = g.view_zoom * selected_corner.get_right_handle ().get_point ().get_distance (x, y); 1547 1548 if (dl < contact_surface && dl < d_point) { 1549 return true; 1550 } 1551 1552 if (dr < contact_surface && dr < d_point) { 1553 return true; 1554 } 1555 1556 return false; 1557 } 1558 1559 PointSelection get_closest_handle (double event_x, double event_y) { 1560 EditPointHandle left, right; 1561 double x = Glyph.path_coordinate_x (event_x); 1562 double y = Glyph.path_coordinate_y (event_y); 1563 EditPointHandle eh = new EditPointHandle.empty(); 1564 Glyph g = MainWindow.get_current_glyph (); 1565 double d = double.MAX; 1566 double dn; 1567 Path path = new Path (); 1568 bool left_handle = false; 1569 EditPoint parent_point; 1570 EditPoint tied_point; 1571 1572 foreach (Path p in g.path_list) { 1573 if (is_close_to_path (p, event_x, event_y) || p == active_path) { 1574 foreach (EditPoint ep in p.points) { 1575 if (ep.is_selected () || Path.show_all_line_handles) { 1576 left = ep.get_left_handle (); 1577 right = ep.get_right_handle (); 1578 1579 dn = left.get_point ().get_distance (x, y); 1580 1581 if (dn < d) { 1582 eh = left; 1583 d = dn; 1584 path = p; 1585 left_handle = true; 1586 } 1587 1588 dn = right.get_point ().get_distance (x, y); 1589 1590 if (dn < d) { 1591 eh = right; 1592 d = dn; 1593 path = p; 1594 left_handle = false; 1595 } 1596 } 1597 } 1598 } 1599 } 1600 1601 // Make sure the selected handle belongs to the selected point if 1602 // the current segment is quadratic. 1603 if (eh.type == PointType.QUADRATIC) { 1604 parent_point = eh.get_parent (); 1605 1606 if (left_handle) { 1607 if (parent_point.prev != null) { 1608 tied_point = parent_point.get_prev (); 1609 if (tied_point.selected_point) { 1610 eh = tied_point.get_right_handle (); 1611 } 1612 } 1613 } else { 1614 if (parent_point.next != null) { 1615 tied_point = parent_point.get_next (); 1616 if (tied_point.selected_point) { 1617 eh = tied_point.get_left_handle (); 1618 } 1619 } 1620 } 1621 } 1622 1623 return new PointSelection.handle_selection (eh, path); 1624 } 1625 1626 private void curve_active_corner_event (double event_x, double event_y) { 1627 PointSelection eh; 1628 1629 active_handle.active = false; 1630 1631 if (!is_over_handle (event_x, event_y)) { 1632 return; 1633 } 1634 1635 eh = get_closest_handle (event_x, event_y); 1636 eh.handle.active = true; 1637 active_handle = eh.handle; 1638 active_path = eh.path; 1639 } 1640 1641 private void curve_corner_event (double event_x, double event_y) { 1642 MainWindow.get_current_glyph ().open_path (); 1643 PointSelection p; 1644 1645 if (!is_over_handle (event_x, event_y)) { 1646 return; 1647 } 1648 1649 move_selected_handle = true; 1650 last_selected_is_handle = true; 1651 selected_handle.selected = false; 1652 p = get_closest_handle (event_x, event_y); 1653 selected_handle = p.handle; 1654 handle_selection = p; 1655 active_path = p.path; 1656 selected_handle.selected = true; 1657 } 1658 1659 public static void add_selected_point (EditPoint p, Path path) { 1660 foreach (PointSelection ep in selected_points) { 1661 if (p == ep.point) { 1662 return; 1663 } 1664 } 1665 1666 selected_points.add (new PointSelection (p, path)); 1667 } 1668 1669 public static void remove_all_selected_points () { 1670 Glyph g = MainWindow.get_current_glyph (); 1671 1672 foreach (PointSelection ep in selected_points) { 1673 ep.point.set_active (false); 1674 ep.point.set_selected (false); 1675 } 1676 1677 selected_points.clear (); 1678 1679 foreach (Path p in g.path_list) { 1680 foreach (EditPoint e in p.points) { 1681 e.set_active (false); 1682 e.set_selected (false); 1683 } 1684 } 1685 } 1686 1687 static void move_select_next_point (uint keyval) { 1688 PointSelection next = new PointSelection.empty (); 1689 Glyph g = MainWindow.get_current_glyph(); 1690 1691 if (selected_points.size == 0) { 1692 return; 1693 } 1694 1695 switch (keyval) { 1696 case Key.UP: 1697 next = get_next_point_up (); 1698 break; 1699 case Key.DOWN: 1700 next = get_next_point_down (); 1701 break; 1702 case Key.LEFT: 1703 next = get_next_point_left (); 1704 break; 1705 case Key.RIGHT: 1706 next = get_next_point_right (); 1707 break; 1708 default: 1709 break; 1710 } 1711 1712 set_selected_point (next.point, next.path); 1713 g.redraw_area (0, 0, g.allocation.width, g.allocation.height); 1714 } 1715 1716 private static PointSelection get_next_point (double angle) 1717 requires (selected_points.size != 0) { 1718 PointSelection e = selected_points.get (selected_points.size - 1); 1719 double right_angle = e.point.right_handle.angle; 1720 double left_angle = e.point.left_handle.angle; 1721 double min_right, min_left; 1722 double min; 1723 1724 return_val_if_fail (e.point.next != null, new EditPoint ()); 1725 return_val_if_fail (e.point.prev != null, new EditPoint ()); 1726 1727 // angle might be greater than 2 PI or less than 0 1728 min_right = double.MAX; 1729 min_left = double.MAX; 1730 for (double i = -2 * PI; i <= 2 * PI; i += 2 * PI) { 1731 min = fabs (right_angle - (angle + i)); 1732 if (min < min_right) { 1733 min_right = min; 1734 } 1735 1736 min = fabs (left_angle - (angle + i)); 1737 if (min < min_left) { 1738 min_left = min; 1739 } 1740 } 1741 1742 if (min_right < min_left) { 1743 return new PointSelection (e.point.get_next (), e.path); 1744 } 1745 1746 return new PointSelection (e.point.get_prev (), e.path); 1747 } 1748 1749 private static PointSelection get_next_point_up () { 1750 return get_next_point (PI / 2); 1751 } 1752 1753 private static PointSelection get_next_point_down () { 1754 return get_next_point (PI + PI / 2); 1755 } 1756 1757 private static PointSelection get_next_point_left () { 1758 return get_next_point (PI); 1759 } 1760 1761 private static PointSelection get_next_point_right () { 1762 return get_next_point (0); 1763 } 1764 1765 private static void set_selected_point (EditPoint ep, Path p) { 1766 remove_all_selected_points (); 1767 add_selected_point (ep, p); 1768 set_active_edit_point (ep, p); 1769 edit_active_corner = true; 1770 ep.set_selected (true); 1771 set_default_handle_positions (); 1772 } 1773 1774 public static void select_point_up () { 1775 move_select_next_point (Key.UP); 1776 } 1777 1778 public static void select_point_down () { 1779 move_select_next_point (Key.DOWN); 1780 } 1781 1782 public static void select_point_right () { 1783 move_select_next_point (Key.RIGHT); 1784 } 1785 1786 public static void select_point_left () { 1787 move_select_next_point (Key.LEFT); 1788 } 1789 1790 /** 1791 * Move the selected editpoint one pixel with keyboard irrespectivly of 1792 * current zoom. 1793 */ 1794 void move_selected_points (uint keyval) { 1795 Glyph g = MainWindow.get_current_glyph(); 1796 Path? last_path = null; 1797 1798 if (!last_selected_is_handle) { 1799 if (keyval == Key.UP) { 1800 foreach (PointSelection e in selected_points) { 1801 e.point.set_position (e.point.x, e.point.y + Glyph.ivz ()); 1802 e.point.recalculate_linear_handles (); 1803 } 1804 } 1805 1806 if (keyval == Key.DOWN) { 1807 foreach (PointSelection e in selected_points) { 1808 e.point.set_position (e.point.x, e.point.y - Glyph.ivz ()); 1809 e.point.recalculate_linear_handles (); 1810 } 1811 } 1812 1813 if (keyval == Key.LEFT) { 1814 foreach (PointSelection e in selected_points) { 1815 e.point.set_position (e.point.x - Glyph.ivz (), e.point.y); 1816 e.point.recalculate_linear_handles (); 1817 } 1818 } 1819 1820 if (keyval == Key.RIGHT) { 1821 foreach (PointSelection e in selected_points) { 1822 e.point.set_position (e.point.x + Glyph.ivz (), e.point.y); 1823 e.point.recalculate_linear_handles (); 1824 } 1825 } 1826 1827 last_path = null; 1828 foreach (PointSelection e in selected_points) { 1829 if (e.path != last_path) { 1830 e.path.update_region_boundaries (); 1831 last_path = e.path; 1832 } 1833 } 1834 1835 } else { 1836 set_type_for_moving_handle (); 1837 active_handle.active = false; 1838 active_handle = new EditPointHandle.empty (); 1839 1840 if (keyval == Key.UP) { 1841 selected_handle.move_delta_coordinate (0, 1 * Glyph.ivz ()); 1842 } 1843 1844 if (keyval == Key.DOWN) { 1845 selected_handle.move_delta_coordinate (0, -1 * Glyph.ivz ()); 1846 } 1847 1848 if (keyval == Key.LEFT) { 1849 selected_handle.move_delta_coordinate (-1 * Glyph.ivz (), 0); 1850 } 1851 1852 if (keyval == Key.RIGHT) { 1853 selected_handle.move_delta_coordinate (1 * Glyph.ivz (), 0); 1854 } 1855 } 1856 1857 // TODO: redraw only the relevant parts 1858 g.redraw_area (0, 0, g.allocation.width, g.allocation.height); 1859 } 1860 1861 public static void convert_point_to_line (EditPoint ep, bool both) { 1862 ep.set_tie_handle (false); 1863 ep.set_reflective_handles (false); 1864 1865 if (ep.next == null) { 1866 // FIXME: write a new function for this case 1867 // warning ("Next is null."); 1868 } 1869 1870 if (ep.prev == null) { 1871 warning ("Prev is null."); 1872 } 1873 1874 if (ep.type == PointType.CUBIC || ep.type == PointType.LINE_CUBIC) { 1875 ep.type = PointType.LINE_CUBIC; 1876 1877 if (both) { 1878 ep.get_left_handle ().type = PointType.LINE_CUBIC; 1879 ep.get_right_handle ().type = PointType.LINE_CUBIC; 1880 } 1881 1882 if (ep.next != null && ep.get_next ().is_selected ()) { 1883 ep.get_right_handle ().type = PointType.LINE_CUBIC; 1884 } 1885 1886 if (ep.prev != null && ep.get_prev ().is_selected ()) { 1887 ep.get_left_handle ().type = PointType.LINE_CUBIC; 1888 } 1889 1890 } 1891 1892 if (ep.type == PointType.DOUBLE_CURVE| ep.type == PointType.LINE_DOUBLE_CURVE) { 1893 ep.type = PointType.LINE_DOUBLE_CURVE; 1894 if (both) { 1895 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE; 1896 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE; 1897 } 1898 1899 if (ep.next != null && ep.get_next ().is_selected ()) { 1900 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE; 1901 } 1902 1903 if (ep.prev != null && ep.get_prev ().is_selected ()) { 1904 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE; 1905 } 1906 } 1907 1908 if (ep.type == PointType.QUADRATIC || ep.type == PointType.LINE_QUADRATIC) { 1909 ep.type = PointType.LINE_QUADRATIC; 1910 1911 if (both) { 1912 ep.get_left_handle ().type = PointType.LINE_QUADRATIC; 1913 ep.get_right_handle ().type = PointType.LINE_QUADRATIC; 1914 1915 if (ep.next != null) { 1916 ep.get_next ().get_left_handle ().type = PointType.LINE_QUADRATIC; 1917 } 1918 1919 if (ep.prev != null) { 1920 ep.get_prev ().get_right_handle ().type = PointType.LINE_QUADRATIC; 1921 } 1922 } 1923 1924 if (ep.next != null && ep.get_next ().is_selected ()) { 1925 ep.get_right_handle ().type = PointType.LINE_QUADRATIC; 1926 ep.get_next ().get_left_handle ().type = PointType.LINE_QUADRATIC; 1927 } 1928 1929 if (ep.prev != null && ep.get_prev ().is_selected ()) { 1930 ep.get_left_handle ().type = PointType.LINE_QUADRATIC; 1931 ep.get_prev ().get_right_handle ().type = PointType.LINE_QUADRATIC; 1932 } 1933 1934 } 1935 1936 ep.recalculate_linear_handles (); 1937 } 1938 1939 public static void convert_segment_to_line () { 1940 if (selected_points.size == 0) { 1941 return; 1942 } 1943 1944 if (selected_points.size == 1) { 1945 convert_point_to_line (selected_points.get (0).point, true); 1946 } else { 1947 foreach (PointSelection p in selected_points) { 1948 convert_point_to_line (p.point, false); 1949 } 1950 } 1951 } 1952 1953 public static bool is_line (PointType t) { 1954 return t == PointType.LINE_QUADRATIC 1955 || t == PointType.LINE_DOUBLE_CURVE 1956 || t == PointType.LINE_CUBIC; 1957 } 1958 1959 public static PointType to_line (PointType t) { 1960 switch (t) { 1961 case PointType.QUADRATIC: 1962 return PointType.LINE_QUADRATIC; 1963 case PointType.DOUBLE_CURVE: 1964 return PointType.LINE_DOUBLE_CURVE; 1965 case PointType.CUBIC: 1966 return PointType.LINE_CUBIC; 1967 default: 1968 break; 1969 } 1970 return t; 1971 } 1972 1973 public static PointType to_curve (PointType t) { 1974 switch (t) { 1975 case PointType.LINE_QUADRATIC: 1976 return PointType.QUADRATIC; 1977 case PointType.LINE_DOUBLE_CURVE: 1978 return PointType.DOUBLE_CURVE; 1979 case PointType.LINE_CUBIC: 1980 return PointType.CUBIC; 1981 default: 1982 break; 1983 } 1984 1985 if (unlikely (t == PointType.NONE)) { 1986 warning ("Type is not set."); 1987 } 1988 1989 return t; 1990 } 1991 1992 public static void set_converted_handle_length (EditPointHandle e, PointType pt) { 1993 1994 if (e.type == PointType.QUADRATIC && pt == PointType.DOUBLE_CURVE) { 1995 e.length *= 2; 1996 e.length /= 4; 1997 } 1998 1999 if (e.type == PointType.QUADRATIC && pt == PointType.CUBIC) { 2000 e.length *= 2; 2001 e.length /= 3; 2002 } 2003 2004 if (e.type == PointType.DOUBLE_CURVE && pt == PointType.QUADRATIC) { 2005 e.length *= 4; 2006 e.length /= 2; 2007 } 2008 2009 if (e.type == PointType.DOUBLE_CURVE && pt == PointType.CUBIC) { 2010 e.length *= 4; 2011 e.length /= 3; 2012 } 2013 2014 if (e.type == PointType.CUBIC && pt == PointType.QUADRATIC) { 2015 e.length *= 3; 2016 e.length /= 2; 2017 } 2018 2019 if (e.type == PointType.CUBIC && pt == PointType.DOUBLE_CURVE) { 2020 e.length *= 3; 2021 e.length /= 4; 2022 } 2023 } 2024 2025 public static void convert_point_segment_type (EditPoint first, EditPoint next, PointType point_type) { 2026 bool line; 2027 2028 set_converted_handle_length (first.get_right_handle (), point_type); 2029 set_converted_handle_length (next.get_left_handle (), point_type); 2030 2031 line = is_line (first.type) 2032 && is_line (first.get_right_handle ().type) 2033 && is_line (next.get_left_handle ().type); 2034 2035 if (!line) { 2036 first.type = point_type; 2037 } else { 2038 first.type = to_line (point_type); 2039 } 2040 2041 if (!line) { 2042 first.get_right_handle ().type = point_type; 2043 } else { 2044 first.get_right_handle ().type = to_line (point_type); 2045 } 2046 2047 if (!line) { 2048 next.get_left_handle ().type = point_type; 2049 } else { 2050 next.get_left_handle ().type = to_line (point_type); 2051 } 2052 2053 // process connected handle 2054 if (point_type == PointType.QUADRATIC) { 2055 first.set_position (first.x, first.y); 2056 first.recalculate_linear_handles (); 2057 } 2058 } 2059 2060 public static void convert_point_type (EditPoint first, PointType point_type) { 2061 convert_point_segment_type (first, first.get_next (), point_type); 2062 } 2063 2064 public static void convert_point_types () { 2065 Glyph glyph = MainWindow.get_current_glyph (); 2066 glyph.store_undo_state (); 2067 PointSelection selected = new PointSelection.empty (); 2068 bool reset_selected = false; 2069 EditPoint e; 2070 2071 if (selected_points.size == 1) { 2072 selected = selected_points.get (0); 2073 if (selected.point.next != null) { 2074 selected_points.add (new PointSelection (selected.point.get_next (), selected.path)); 2075 selected.point.get_next ().set_selected (true); 2076 } 2077 2078 if (selected.point.prev != null) { 2079 selected_points.add (new PointSelection (selected.point.get_prev (), selected.path)); 2080 selected.point.get_next ().set_selected (true); 2081 } 2082 2083 reset_selected = true; 2084 } 2085 2086 foreach (PointSelection ps in selected_points) { 2087 e = ps.point; 2088 // convert segments not control points 2089 if (e.next == null || !e.get_next ().is_selected ()) { 2090 continue; 2091 } 2092 2093 convert_point_type (e, DrawingTools.point_type); 2094 } 2095 2096 if (reset_selected) { 2097 remove_all_selected_points (); 2098 selected_points.add (selected); 2099 selected.point.set_selected (true); 2100 } 2101 } 2102 2103 public static void update_selected_points () { 2104 Glyph g = MainWindow.get_current_glyph (); 2105 selected_points.clear (); 2106 2107 foreach (Path p in g.path_list) { 2108 foreach (EditPoint ep in p.points) { 2109 if (ep.is_selected ()) { 2110 selected_points.add (new PointSelection (ep, p)); 2111 } 2112 } 2113 } 2114 } 2115 2116 public void select_all_points () { 2117 Glyph g = MainWindow.get_current_glyph (); 2118 2119 foreach (Path p in g.path_list) { 2120 foreach (EditPoint ep in p.points) { 2121 ep.set_selected (true); 2122 add_selected_point (ep, p); 2123 } 2124 } 2125 } 2126 2127 public static Path simplify (Path path, bool selected_segments = false, double threshold = 0.3) { 2128 PointSelection ps; 2129 EditPoint ep; 2130 Path p1, new_path; 2131 double d, sumd; 2132 int i; 2133 2134 p1 = path.copy (); 2135 new_path = p1.copy (); 2136 i = 0; 2137 sumd = 0; 2138 while (i < new_path.points.size) { 2139 ep = new_path.points.get (i); 2140 ps = new PointSelection (ep, new_path); 2141 d = PenTool.remove_point_simplify (ps); 2142 sumd += d; 2143 2144 if (sumd < threshold) { 2145 p1 = new_path.copy (); 2146 } else { 2147 new_path = p1.copy (); 2148 sumd = 0; 2149 i++; 2150 } 2151 } 2152 2153 new_path.update_region_boundaries (); 2154 2155 return new_path; 2156 } 2157 2158 public void set_simplification_threshold (double t) { 2159 simplification_threshold = t; 2160 } 2161 } 2162 2163 } 2164