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