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