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