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