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