The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

Glyph.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/Glyph.vala.
Redraw moving canvas and keep OTF feature tab open
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 Cairo; 16 using Math; 17 using Gee; 18 19 namespace BirdFont { 20 21 public class Glyph : FontDisplay { 22 23 // Background image 24 BackgroundImage? background_image = null; 25 bool background_image_visible = true; 26 27 // Glyph zoom level 28 public double view_zoom = 0.1; 29 30 public double view_offset_x = 0; 31 public double view_offset_y = 0; 32 Gee.ArrayList<ZoomView> zoom_list = new Gee.ArrayList<ZoomView> (); 33 int zoom_list_index = 0; 34 35 // The point where edit event begun 36 double pointer_begin_x = 0; 37 double pointer_begin_y = 0; 38 39 // Tap last tap event position in pixels 40 int last_tap0_y = 0; 41 int last_tap0_x = 0; 42 int last_tap1_y = 0; 43 int last_tap1_x = 0; 44 double zoom_distance = 0; 45 bool change_view; 46 47 bool ignore_input = false; 48 49 // Current pointer position 50 public double motion_x = 0; 51 public double motion_y = 0; 52 53 // Zoom area 54 public double zoom_x1 = 0; 55 public double zoom_y1 = 0; 56 public double zoom_x2 = 0; 57 public double zoom_y2 = 0; 58 public bool zoom_area_is_visible = false; 59 60 bool view_is_moving = false; 61 public double move_offset_x = 0; 62 public double move_offset_y = 0; 63 bool move_canvas = false; 64 65 public WidgetAllocation allocation = new WidgetAllocation (); 66 67 // FIXME: name and unichar should be moved to to glyph collection 68 public unichar unichar_code = 0; 69 public string name; 70 71 public double left_limit { 72 get { 73 return _left_limit; 74 } 75 76 set { 77 ttf_data = null; 78 _left_limit = value; 79 } 80 } 81 82 public double right_limit { 83 get { 84 return _right_limit; 85 } 86 87 set { 88 ttf_data = null; 89 _right_limit = value; 90 } 91 } 92 93 private double _right_limit = 0; 94 private double _left_limit = 0; 95 96 // x-height, lsb, etc. 97 public Gee.ArrayList<Line> vertical_help_lines = new Gee.ArrayList<Line> (); 98 public Gee.ArrayList<Line> horizontal_help_lines = new Gee.ArrayList<Line> (); 99 public bool show_help_lines = true; 100 bool xheight_lines_visible = false; 101 bool margin_boundaries_visible = false; 102 string new_guide_name = ""; 103 104 Gee.ArrayList<Glyph> undo_list = new Gee.ArrayList<Glyph> (); 105 Gee.ArrayList<Glyph> redo_list = new Gee.ArrayList<Glyph> (); 106 107 string glyph_sequence = ""; 108 bool open = true; 109 110 public static Glyph? background_glyph = null; 111 112 bool empty = false; 113 114 /** Id in the version list. */ 115 public int version_id = 0; 116 117 /** Cache quadratic form on export. */ 118 GlyfData? ttf_data = null; 119 120 Line left_line; 121 Line right_line; 122 123 /** Cache for Cairo surface rendering */ 124 HashMap<string, Surface> glyph_cache = new HashMap<string, Surface> (); 125 126 public const double CANVAS_MIN = -10000; 127 public const double CANVAS_MAX = 10000; 128 129 public static bool show_orientation_arrow = false; 130 public static double orientation_arrow_opacity = 1; 131 132 public Layer layers = new Layer (); 133 public int current_layer = 0; 134 public Gee.ArrayList<Path> active_paths = new Gee.ArrayList<Path> (); 135 public Gee.ArrayList<Layer> selected_groups = new Gee.ArrayList<Layer> (); 136 137 // used if this glyph is fetched from a fallback font 138 public double top_limit = 0; 139 public double baseline = 0; 140 public double bottom_limit = 0; 141 142 public Surface? overview_thumbnail = null; 143 144 public Glyph (string name, unichar unichar_code = 0) { 145 this.name = name; 146 this.unichar_code = unichar_code; 147 148 add_help_lines (); 149 150 left_limit = -28; 151 right_limit = 28; 152 } 153 154 public Glyph.no_lines (string name, unichar unichar_code = 0) { 155 this.name = name; 156 this.unichar_code = unichar_code; 157 } 158 159 public Gee.ArrayList<Path> get_active_paths () { 160 return active_paths; 161 } 162 163 public Layer get_current_layer () { 164 return_val_if_fail (0 <= current_layer < layers.subgroups.size, new Layer ()); 165 return layers.subgroups.get (current_layer); 166 } 167 168 public void set_current_layer (Layer layer) { 169 int i = 0; 170 foreach (Layer l in layers.subgroups) { 171 if (likely (l == layer)) { 172 current_layer = i; 173 return; 174 } 175 i++; 176 } 177 178 warning ("Layer is not added to glyph."); 179 } 180 181 public Gee.ArrayList<Path> get_visible_paths () { 182 return layers.get_visible_paths ().paths; 183 } 184 185 public PathList get_visible_path_list () { 186 return layers.get_visible_paths (); 187 } 188 189 public Gee.ArrayList<Path> get_paths_in_current_layer () { 190 return get_current_layer ().get_all_paths ().paths; 191 } 192 193 public Gee.ArrayList<Path> get_all_paths () { 194 return layers.get_all_paths ().paths; 195 } 196 197 public void add_new_layer () { 198 layers.add_layer (new Layer ()); 199 current_layer = layers.subgroups.size - 1; 200 } 201 202 public int get_layer_index (Layer layer) { 203 return layers.index_of (layer); 204 } 205 206 public GlyfData get_ttf_data () { 207 if (ttf_data == null) { 208 209 ttf_data = new GlyfData (this); 210 } 211 212 return (!) ttf_data; 213 } 214 215 public PathList get_quadratic_paths () { 216 PointConverter pc; 217 PathList pl; 218 PathList stroke; 219 220 pl = new PathList (); 221 222 foreach (Path p in get_visible_paths ()) { 223 if (p.stroke > 0) { 224 stroke = p.get_completed_stroke (); 225 foreach (Path stroke_part in stroke.paths) { 226 pc = new PointConverter (stroke_part); 227 pl.add (pc.get_quadratic_path ()); 228 } 229 } else { 230 pc = new PointConverter (p); 231 pl.add (pc.get_quadratic_path ()); 232 } 233 } 234 235 return pl; 236 } 237 238 public override void close () { 239 undo_list.clear (); 240 redo_list.clear (); 241 } 242 243 public void set_empty_ttf (bool e) { 244 empty = e; 245 } 246 247 public bool is_empty_ttf () { 248 return empty; 249 } 250 251 public void clear_active_paths () { 252 selected_groups.clear (); 253 active_paths.clear (); 254 } 255 256 public void add_active_path (Layer? group, Path? p) { 257 Path path; 258 Layer g; 259 260 if (p != null) { 261 path = (!) p; 262 263 if (Toolbox.get_move_tool ().is_selected ()) { 264 if (path.stroke > 0) { 265 Toolbox.set_object_stroke (path.stroke); 266 } 267 } 268 269 if (!active_paths.contains (path)) { 270 active_paths.add (path); 271 } 272 PenTool.active_path = path; 273 } 274 275 if (group != null) { 276 g = (!) group; 277 if (!selected_groups.contains (g)) { 278 selected_groups.add (g); 279 } 280 } 281 } 282 283 public void delete_background () { 284 store_undo_state (); 285 background_image = null; 286 GlyphCanvas.redraw (); 287 } 288 289 public Path? get_active_path () { 290 return_val_if_fail (active_paths.size > 0, null); 291 return active_paths.get (active_paths.size - 1); 292 } 293 294 public bool boundaries (out double x1, out double y1, out double x2, out double y2) { 295 var paths = get_all_paths (); 296 297 if (paths.size == 0) { 298 x1 = 0; 299 y1 = 0; 300 x2 = 0; 301 y2 = 0; 302 return false; 303 } 304 305 x1 = CANVAS_MAX; 306 x2 = CANVAS_MIN; 307 y1 = CANVAS_MAX; 308 y2 = CANVAS_MIN; 309 310 foreach (Path p in paths) { 311 p.update_region_boundaries (); 312 313 if (p.points.size > 1) { 314 if (p.xmin < x1) { 315 x1 = p.xmin; 316 } 317 318 if (p.xmax > x2) { 319 x2 = p.xmax; 320 } 321 322 if (p.ymin < y1) { 323 y1 = p.ymin; 324 } 325 326 if (p.ymax > y2) { 327 y2 = p.ymax; 328 } 329 } 330 } 331 332 return x1 != double.MAX; 333 } 334 335 public void selection_boundaries (out double x, out double y, out double w, out double h) { 336 double px, py, px2, py2; 337 338 px = 10000; 339 py = 10000; 340 px2 = -10000; 341 py2 = -10000; 342 343 foreach (Path p in active_paths) { 344 if (p.xmin < px) { 345 px = p.xmin; 346 } 347 348 if (p.ymin < py) { 349 py = p.ymin; 350 } 351 352 if (p.xmax > px2) { 353 px2 = p.xmax; 354 } 355 356 if (p.ymax > py2) { 357 py2 = p.ymax; 358 } 359 } 360 361 if (px2 == -10000 || px == 10000) { 362 warning (@"No box for selected paths. ($(active_paths.size))"); 363 px = 0; 364 py = 0; 365 px2 = 0; 366 py2 = 0; 367 } 368 369 x = px; 370 y = py2; 371 w = px2 - px; 372 h = py2 - py; 373 } 374 375 /** @return centrum pixel for x coordinates. */ 376 public static double xc () { 377 double c = MainWindow.get_current_glyph ().allocation.width / 2.0; 378 return c; 379 } 380 381 /** @return centrum pixel for y coordinates. */ 382 public static double yc () { 383 double c = MainWindow.get_current_glyph ().allocation.height / 2.0; 384 return c; 385 } 386 387 /** @return 1/view_zoom */ 388 public static double ivz () { 389 return 1 / MainWindow.get_current_glyph ().view_zoom; 390 } 391 392 public void resized (WidgetAllocation alloc) { 393 double a, b, c, d; 394 395 a = Glyph.path_coordinate_x (0); 396 b = Glyph.path_coordinate_y (0); 397 398 this.allocation = alloc; 399 400 c = Glyph.path_coordinate_x (0); 401 d = Glyph.path_coordinate_y (0); 402 403 view_offset_x -= c - a; 404 view_offset_y -= b - d; 405 } 406 407 public void set_background_image (BackgroundImage? b) { 408 BackgroundImage bg; 409 410 if (b == null) { 411 background_image = null; 412 } else { 413 bg = (!) b; 414 background_image = bg; 415 } 416 417 BirdFont.get_current_font ().touch (); 418 } 419 420 public BackgroundImage? get_background_image () { 421 return background_image; 422 } 423 424 public override void scroll_wheel (double x, double y, 425 double pixeldelta_x, double pixeldelta_y) { 426 427 if (KeyBindings.has_alt () || KeyBindings.has_ctrl ()) { 428 if (pixeldelta_y > 0) { 429 zoom_in_at_point (x, y, pixeldelta_y); 430 } else { 431 zoom_out_at_point (x, y, pixeldelta_y); 432 } 433 } else { 434 if (!KeyBindings.has_shift ()) { 435 view_offset_x -= pixeldelta_x / view_zoom; 436 view_offset_y -= pixeldelta_y / view_zoom; 437 } else { 438 // move canvas a long x axis instead of y 439 view_offset_x -= pixeldelta_y / view_zoom; 440 view_offset_y -= pixeldelta_x / view_zoom; 441 } 442 } 443 444 redraw_area (0, 0, allocation.width, allocation.height); 445 } 446 447 public virtual void add_path (Path p) { 448 if (layers.subgroups.size == 0) { 449 layers.add_layer (new Layer ()); 450 } 451 452 get_current_layer ().add_path (p); 453 } 454 455 public override void selected_canvas () { 456 TimeoutSource input_delay; 457 458 ttf_data = null; // recreate quatradic path on export 459 overview_thumbnail = null; 460 461 ignore_input = true; // make sure that tripple clicks in overview are ignored 462 463 input_delay = new TimeoutSource (250); 464 input_delay.set_callback(() => { 465 ignore_input = false; 466 return false; 467 }); 468 input_delay.attach (null); 469 470 add_help_lines (); 471 KeyBindings.set_require_modifier (false); 472 glyph_sequence = Preferences.get ("glyph_sequence"); 473 474 GridTool.update_lines (); 475 476 if (!is_null (MainWindow.native_window)) { 477 MainWindow.native_window.set_scrollbar_size (0); 478 } 479 480 update_zoom_bar (); 481 482 Font font = BirdFont.get_current_font (); 483 string index = font.settings.get_setting (@"Active Layer $(get_name ())"); 484 485 if (index != "") { 486 int i = int.parse (index); 487 488 if (0 <= i < layers.subgroups.size) { 489 current_layer = i; 490 } 491 } 492 493 DrawingTools.update_layers (); 494 MainWindow.get_toolbox ().update_expanders (); 495 } 496 497 void update_zoom_bar () { 498 if (!is_null (Toolbox.drawing_tools) 499 && !is_null (Toolbox.drawing_tools.zoom_bar)) { 500 Toolbox.drawing_tools.zoom_bar.set_zoom ((view_zoom - 1) / 20); 501 } 502 } 503 504 public void remove_lines () { 505 vertical_help_lines.clear (); 506 horizontal_help_lines.clear (); 507 } 508 509 public void add_help_lines () { 510 remove_lines (); 511 512 return_if_fail (!is_null (BirdFont.get_current_font ())); 513 514 double bgt = BirdFont.get_current_font ().top_limit; 515 Line top_margin_line = new Line ("top margin", t_("top margin"), bgt, false); 516 top_margin_line.set_color_theme ("Guide 2"); 517 top_margin_line.position_updated.connect ((pos) => { 518 BirdFont.get_current_font ().top_limit = pos; 519 }); 520 521 double thp = BirdFont.get_current_font ().top_position; 522 Line top_line = new Line ("top", t_("top"), thp, false); 523 top_line.position_updated.connect ((pos) => { 524 Font f = BirdFont.get_current_font (); 525 f.top_position = pos; 526 }); 527 528 double xhp = BirdFont.get_current_font ().xheight_position; 529 Line xheight_line = new Line ("x-height", t_("x-height"), xhp, false); 530 xheight_line.set_color_theme ("Guide 3"); 531 xheight_line.dashed = true; 532 xheight_line.position_updated.connect ((pos) => { 533 Font f = BirdFont.get_current_font (); 534 f.xheight_position = pos; 535 }); 536 537 double xbl = BirdFont.get_current_font ().base_line; 538 Line base_line = new Line ("baseline", t_("baseline"), xbl, false); 539 base_line.position_updated.connect ((pos) => { 540 Font f = BirdFont.get_current_font (); 541 f.base_line = pos; 542 }); 543 544 double bp = BirdFont.get_current_font ().bottom_position; 545 Line bottom_line = new Line ("bottom", t_("bottom"), bp, false); 546 bottom_line.position_updated.connect ((pos) => { 547 BirdFont.get_current_font ().bottom_position = pos; 548 }); 549 550 double bgb = BirdFont.get_current_font ().bottom_limit; 551 Line bottom_margin_line = new Line ("bottom margin", t_("bottom margin"), bgb, false); 552 bottom_margin_line.set_color_theme ("Guide 2"); 553 bottom_margin_line.position_updated.connect ((pos) => { 554 BirdFont.get_current_font ().bottom_limit = pos; 555 }); 556 557 left_line = new Line ("left", t_("left"), left_limit, true); 558 left_line.lsb = true; 559 left_line.position_updated.connect ((pos) => { 560 left_limit = pos; 561 update_other_spacing_classes (); 562 left_line.set_metrics (get_left_side_bearing ()); 563 }); 564 left_line.set_metrics (get_left_side_bearing ()); 565 566 right_line = new Line ("right", t_("right"), right_limit, true); 567 right_line.rsb = true; 568 right_line.position_updated.connect ((pos) => { 569 right_limit = pos; 570 update_other_spacing_classes (); 571 right_line.set_metrics (get_right_side_bearing ()); 572 }); 573 right_line.set_metrics (get_right_side_bearing ()); 574 575 // lists of lines are sorted and lines are added only if 576 // they are relevant for a particular glyph. 577 578 // left to right 579 add_line (left_line); 580 add_line (right_line); 581 582 bool glyph_has_top = has_top_line (); 583 584 // top to bottom 585 add_line (top_margin_line); 586 top_margin_line.set_visible (margin_boundaries_visible); 587 588 add_line (top_line); 589 top_line.set_visible (glyph_has_top || xheight_lines_visible); 590 591 add_line (xheight_line); 592 xheight_line.set_visible (!glyph_has_top || xheight_lines_visible); 593 594 add_line (base_line); 595 596 add_line (bottom_line); 597 bottom_line.set_visible (CharDatabase.has_descender (unichar_code) || xheight_lines_visible); 598 599 add_line (bottom_margin_line); 600 bottom_margin_line.set_visible (margin_boundaries_visible); 601 602 foreach (Line guide in BirdFont.get_current_font ().custom_guides) { 603 add_line (guide); 604 } 605 } 606 607 public double get_left_side_bearing () { 608 double x1, y1, x2, y2; 609 610 if (get_boundaries (out x1, out y1, out x2, out y2)) { 611 return x1 - left_limit; 612 } else { 613 return right_limit - left_limit; 614 } 615 } 616 617 public double get_right_side_bearing () { 618 double x1, y1, x2, y2; 619 620 if (get_boundaries (out x1, out y1, out x2, out y2)) { 621 return right_limit - x2; 622 } else { 623 return right_limit - left_limit; 624 } 625 } 626 627 public bool get_boundaries (out double x1, out double y1, 628 out double x2, out double y2) { 629 630 double max_x, min_x, max_y, min_y; 631 PathList pl; 632 var paths = get_all_paths (); 633 634 if (paths.size == 0) { 635 x1 = 0; 636 y1 = 0; 637 x2 = 0; 638 y2 = 0; 639 return false; 640 } 641 642 max_x = CANVAS_MIN; 643 min_x = CANVAS_MAX; 644 max_y = CANVAS_MIN; 645 min_y = CANVAS_MAX; 646 647 // FIXME: optimize 648 foreach (Path p in paths) { 649 650 if (p.stroke > 0) { 651 pl = p.get_stroke_fast (); 652 653 foreach (Path part in pl.paths) { 654 boundaries_for_path (part, ref min_x, ref max_x, ref min_y, ref max_y); 655 } 656 } else { 657 boundaries_for_path (p, ref min_x, ref max_x, ref min_y, ref max_y); 658 } 659 } 660 661 x1 = min_x; 662 y1 = max_y; 663 x2 = max_x; 664 y2 = min_y; 665 666 return max_x != CANVAS_MIN; 667 } 668 669 void boundaries_for_path (Path p, ref double min_x, ref double max_x, 670 ref double min_y, ref double max_y) { 671 672 double max_x2, max_y2, min_x2, min_y2; 673 674 max_x2 = max_x; 675 min_x2 = min_x; 676 max_y2 = max_y; 677 min_y2 = min_y; 678 679 p.all_of_path ((x, y, t) => { 680 if (x > max_x2) { 681 max_x2 = x; 682 } 683 684 if (y > max_y2) { 685 max_y2 = y; 686 } 687 688 if (x < min_x2) { 689 min_x2 = x; 690 } 691 692 if (y < min_y2) { 693 min_y2 = y; 694 } 695 696 return true; 697 }); 698 699 max_x = max_x2; 700 min_x = min_x2; 701 max_y = max_y2; 702 min_y = min_y2; 703 } 704 705 bool has_top_line () { 706 return !unichar_code.islower () || CharDatabase.has_ascender (unichar_code); 707 } 708 709 public bool get_show_help_lines () { 710 return show_help_lines; 711 } 712 713 /** Show both x-height and top lines. */ 714 public bool get_xheight_lines_visible () { 715 return xheight_lines_visible; 716 } 717 718 /** Show both x-height and top lines. */ 719 public void set_xheight_lines_visible (bool x) { 720 xheight_lines_visible = x; 721 add_help_lines (); 722 } 723 724 public void set_margin_lines_visible (bool m) { 725 margin_boundaries_visible = m; 726 add_help_lines (); 727 } 728 729 public bool get_margin_lines_visible () { 730 return margin_boundaries_visible; 731 } 732 733 public void remove_empty_paths () { 734 foreach (Path p in get_all_paths ()) { 735 if (p.points.size < 2) { 736 delete_path (p); 737 remove_empty_paths (); 738 return; 739 } 740 } 741 } 742 743 public void delete_path (Path p) { 744 layers.remove_path(p); 745 } 746 747 public string get_svg_data () { 748 return Svg.to_svg_glyph (this); 749 } 750 751 public int get_height () { 752 Font f = BirdFont.get_current_font (); 753 return (int) Math.fabs (f.top_limit - f.bottom_limit); 754 } 755 756 public double get_width () { 757 return Math.fabs (right_limit - left_limit); 758 } 759 760 public unichar get_unichar () { 761 return unichar_code; 762 } 763 764 public string get_unichar_string () { 765 string? s = (!)get_unichar ().to_string (); 766 767 if (unlikely (s == null)) { 768 warning ("Invalid character."); 769 return "".dup (); 770 } 771 772 return (!) s; 773 } 774 775 public void redraw_help_lines () { 776 redraw_area (0, 0, allocation.width, allocation.height); 777 } 778 779 public void set_show_help_lines (bool hl) { 780 show_help_lines = hl; 781 } 782 783 private void add_line (Line line) { 784 if (line.is_vertical ()) { 785 vertical_help_lines.add (line); 786 } else { 787 horizontal_help_lines.add (line); 788 } 789 790 sort_help_lines (); 791 792 line.queue_draw_area.connect ((x, y, w, h) => { 793 this.redraw_area (x, y, w, h); 794 }); 795 } 796 797 public void sort_help_lines () { 798 vertical_help_lines.sort ((a, b) => { 799 Line first, next; 800 first = (Line) a; 801 next = (Line) b; 802 return (int) (first.get_pos () - next.get_pos ()); 803 }); 804 805 horizontal_help_lines.sort ((a, b) => { 806 Line first, next; 807 first = (Line) a; 808 next = (Line) b; 809 return (int) (first.get_pos () - next.get_pos ()); 810 }); 811 } 812 813 public override string get_name () { 814 return name; 815 } 816 817 public override string get_label () { 818 return name; 819 } 820 821 private void help_line_event (int x, int y) { 822 bool m = false; 823 824 foreach (Line line in vertical_help_lines) { 825 if (!m && line.event_move_to (x, y, allocation)) { 826 m = true; 827 } 828 } 829 830 foreach (Line line in horizontal_help_lines) { 831 if (!m && line.event_move_to (x, y, allocation)) { 832 m = true; 833 } 834 } 835 } 836 837 public override void key_release (uint keyval) { 838 Tool t; 839 t = MainWindow.get_toolbox ().get_current_tool (); 840 t.key_release_action (t, keyval); 841 842 if (keyval == (uint)' ') { 843 move_canvas = false; 844 } 845 } 846 847 public override void key_press (uint keyval) { 848 Tool t = MainWindow.get_toolbox ().get_current_tool (); 849 t.key_press_action (t, keyval); 850 851 if (keyval == (uint)' ') { 852 move_canvas = true; 853 } 854 855 switch (keyval) { 856 case Key.NUM_PLUS: 857 zoom_in (); 858 break; 859 case Key.NUM_MINUS: 860 zoom_out (); 861 break; 862 } 863 } 864 865 /** Delete edit point from path. 866 * @return false if no points was deleted 867 */ 868 public bool process_deleted () { 869 Gee.ArrayList<Path> deleted_paths = new Gee.ArrayList<Path> (); 870 foreach (Path p in get_all_paths ()) { 871 if (p.points.size > 0) { 872 if (process_deleted_points_in_path (p)) { 873 return true; 874 } 875 } else { 876 deleted_paths.add (p); 877 } 878 } 879 880 foreach (Path p in deleted_paths) { 881 delete_path (p); 882 } 883 884 return false; 885 } 886 887 private bool process_deleted_points_in_path (Path p) { 888 PathList remaining_points; 889 remaining_points = p.process_deleted_points (); 890 foreach (Path path in remaining_points.paths) { 891 add_path (path); 892 path.reopen (); 893 path.create_list (); 894 895 add_active_path (null, path); 896 } 897 898 if (remaining_points.paths.size > 0) { 899 delete_path (p); 900 return true; 901 } 902 903 return false; 904 } 905 906 public override void motion_notify (double x, double y) { 907 Tool t = MainWindow.get_toolbox ().get_current_tool (); 908 909 if (view_is_moving) { 910 move_view_offset (x, y); 911 return; 912 } 913 914 help_line_event ((int) x, (int) y); 915 t.move_action (t, (int) x, (int) y); 916 917 motion_x = x * ivz () - xc () + view_offset_x; 918 motion_y = yc () - y * ivz () - view_offset_y; 919 } 920 921 public override void button_release (int button, double ex, double ey) { 922 bool line_moving = false; 923 view_is_moving = false; 924 925 foreach (Line line in get_all_help_lines ()) { 926 if (!line.set_move (false)) { 927 line_moving = true; 928 } 929 } 930 931 if (!line_moving) { 932 Tool t = MainWindow.get_toolbox ().get_current_tool (); 933 t.release_action (t, (int) button, (int) ex, (int) ey); 934 } 935 936 update_view (); 937 } 938 939 private Gee.ArrayList<Line> get_all_help_lines () { 940 Gee.ArrayList<Line> all_lines = new Gee.ArrayList<Line> (); 941 942 foreach (Line l in vertical_help_lines) { 943 all_lines.add (l); 944 } 945 946 foreach (Line l in horizontal_help_lines) { 947 all_lines.add (l); 948 } 949 950 if (GridTool.is_visible ()) { 951 foreach (Line l in GridTool.get_vertical_lines ()) { 952 all_lines.add (l); 953 } 954 955 foreach (Line l in GridTool.get_horizontal_lines ()) { 956 all_lines.add (l); 957 } 958 } 959 960 return all_lines; 961 } 962 963 public void update_view () { 964 GlyphCanvas.redraw (); 965 } 966 967 public override void double_click (uint button, double ex, double ey) { 968 Tool t = MainWindow.get_toolbox ().get_current_tool (); 969 t.double_click_action (t, (int) button, (int) ex, (int) ey); 970 } 971 972 public override void button_press (uint button, double ex, double ey) { 973 bool moving_lines = false; 974 975 pointer_begin_x = ex; 976 pointer_begin_y = ey; 977 978 foreach (Line line in horizontal_help_lines) { 979 if (!moving_lines && line.is_visible () && line.button_press (button)) { 980 moving_lines = true; 981 } 982 } 983 984 foreach (Line line in vertical_help_lines) { 985 if (!moving_lines && line.is_visible () && line.button_press (button)) { 986 moving_lines = true; 987 } 988 } 989 990 if (moving_lines) { 991 return; 992 } 993 994 if (move_canvas || DrawingTools.move_canvas.is_selected ()) { 995 view_is_moving = true; 996 move_offset_x = view_offset_x; 997 move_offset_y = view_offset_y; 998 } else { 999 Tool t = MainWindow.get_toolbox ().get_current_tool (); 1000 t.press_action (t, (int) button, (int) ex, (int) ey); 1001 } 1002 } 1003 1004 /** Add new points to this path. */ 1005 public void set_active_path (Path p) { 1006 p.reopen (); 1007 clear_active_paths (); 1008 add_active_path (null, p); 1009 } 1010 1011 /** Move view port centrum to this coordinate. */ 1012 public void set_center (double x, double y) { 1013 x -= allocation.width / 2.0; 1014 y -= allocation.height / 2.0; 1015 1016 view_offset_x += x / view_zoom; 1017 view_offset_y += y / view_zoom; 1018 } 1019 1020 public void set_zoom_from_area () { 1021 double x = Math.fmin (zoom_x1, zoom_x2); 1022 double y = Math.fmin (zoom_y1, zoom_y2); 1023 1024 double w = Math.fabs (zoom_x1 - zoom_x2); 1025 double h = Math.fabs (zoom_y1 - zoom_y2); 1026 1027 double view_zoom_x, view_zoom_y; 1028 double ivz, off; 1029 1030 if (move_canvas) { 1031 return; 1032 } 1033 1034 if (Path.distance (x, x + w, y, y + h) < 7) { 1035 zoom_in (); 1036 } else { 1037 view_offset_x += x / view_zoom; 1038 view_offset_y += y / view_zoom; 1039 1040 if (unlikely (allocation.width == 0 || allocation.height == 0)) { 1041 return; 1042 } 1043 1044 view_zoom_x = allocation.width * view_zoom / w; 1045 view_zoom_y = allocation.height * view_zoom / h; 1046 1047 // TODO: there is a max zoom level 1048 1049 if (view_zoom_x * allocation.width < view_zoom_y * allocation.height) { 1050 view_zoom = view_zoom_x; 1051 ivz = 1 / view_zoom; 1052 1053 off = (view_zoom / view_zoom_y) * allocation.height / view_zoom; 1054 off = allocation.height/view_zoom - off; 1055 off /= 2; 1056 1057 view_offset_y -= off; 1058 1059 } else { 1060 view_zoom = view_zoom_y; 1061 ivz = 1 / view_zoom; 1062 1063 off = (view_zoom / view_zoom_x) * allocation.width / view_zoom; 1064 off = allocation.width / view_zoom - off; 1065 off /= 2; 1066 1067 view_offset_x -= off; 1068 } 1069 1070 zoom_area_is_visible = false; 1071 store_current_view (); 1072 } 1073 1074 update_zoom_bar (); 1075 } 1076 1077 public void show_zoom_area (int sx, int sy, int nx, int ny) { 1078 double x, y, w, h; 1079 1080 set_zoom_area (sx, sy, nx, ny); 1081 1082 zoom_area_is_visible = true; 1083 1084 x = Math.fmin (zoom_x1, zoom_x2) - 50; 1085 y = Math.fmin (zoom_y1, zoom_y2) - 50; 1086 1087 w = Math.fabs (zoom_x1 - zoom_x2) + 100; 1088 h = Math.fabs (zoom_y1 - zoom_y2) + 100; 1089 1090 redraw_area ((int)x, (int)y, (int)w, (int)h); 1091 } 1092 1093 public void set_zoom_area (int sx, int sy, int nx, int ny) { 1094 zoom_x1 = sx; 1095 zoom_y1 = sy; 1096 zoom_x2 = nx; 1097 zoom_y2 = ny; 1098 } 1099 1100 public static void validate_zoom () { 1101 Glyph g = MainWindow.get_current_glyph (); 1102 if (unlikely (g.view_zoom == 0)) { 1103 warning ("Zoom is zero."); 1104 g.reset_zoom (); 1105 1106 if (g.view_zoom == 0) { 1107 g.view_zoom = 1; 1108 g.view_offset_x = 0; 1109 g.view_offset_y = 0; 1110 } 1111 } 1112 } 1113 1114 public static double path_coordinate_x (double x) { 1115 Glyph g = MainWindow.get_current_glyph (); 1116 validate_zoom (); 1117 return x * ivz () - xc () + g.view_offset_x; 1118 } 1119 1120 public static int reverse_path_coordinate_x (double x) { 1121 Glyph g = MainWindow.get_current_glyph (); 1122 validate_zoom (); 1123 return (int) Math.rint ((x - g.view_offset_x + xc ()) * g.view_zoom); 1124 } 1125 1126 public static double precise_reverse_path_coordinate_x (double x) { 1127 Glyph g = MainWindow.get_current_glyph (); 1128 validate_zoom (); 1129 return (x - g.view_offset_x + xc ()) * g.view_zoom; 1130 } 1131 1132 public static double path_coordinate_y (double y) { 1133 Glyph g = MainWindow.get_current_glyph (); 1134 validate_zoom (); 1135 return yc () - y * ivz () - g.view_offset_y; 1136 } 1137 1138 public static int reverse_path_coordinate_y (double y) { 1139 Glyph g = MainWindow.get_current_glyph (); 1140 validate_zoom (); 1141 y = Math.rint ((y + g.view_offset_y - yc ()) * g.view_zoom); 1142 return (int) (-y); 1143 } 1144 1145 public static double precise_reverse_path_coordinate_y (double y) { 1146 Glyph g = MainWindow.get_current_glyph (); 1147 validate_zoom (); 1148 y = (y + g.view_offset_y - yc ()) * g.view_zoom; 1149 return -y; 1150 } 1151 1152 public Layer? get_path_at (double x, double y) { 1153 Layer? group = null; 1154 bool found = false; 1155 1156 foreach (Layer layer in get_current_layer ().subgroups) { 1157 foreach (Path pt in layer.paths.paths) { 1158 if (pt.is_over (x, y)) { 1159 found = true; 1160 group = layer; 1161 } 1162 } 1163 } 1164 1165 if (!found) { 1166 foreach (Path pt in get_paths_in_current_layer ()) { 1167 if (pt.is_over (x, y)) { 1168 Layer layer = new Layer (); 1169 layer.is_counter = true; 1170 layer.single_path = true; 1171 layer.add_path (pt); 1172 group = layer; 1173 } 1174 } 1175 } 1176 1177 return group; 1178 } 1179 1180 public bool select_path (double x, double y) { 1181 Path? p = null; 1182 bool found = false; 1183 1184 foreach (Path pt in get_paths_in_current_layer ()) { 1185 if (pt.is_over (x, y)) { 1186 p = pt; 1187 found = true; 1188 } 1189 } 1190 1191 if (!KeyBindings.has_shift ()) { 1192 clear_active_paths (); 1193 } 1194 1195 add_active_path (null, p); 1196 1197 return found; 1198 } 1199 1200 public bool is_over_selected_path (double x, double y) { 1201 foreach (Path pt in active_paths) { 1202 if (pt.is_over (x, y)) { 1203 return true; 1204 } 1205 } 1206 return false; 1207 } 1208 1209 public void queue_redraw_path (Path path) { 1210 redraw_path (path.xmin, path.ymin, path.xmax, path.ymax); 1211 } 1212 1213 private void redraw_path (double xmin, double ymin, double xmax, double ymax) { 1214 int yc = (int)(allocation.height / 2.0); 1215 1216 double yta = yc - ymin - view_offset_y; 1217 double ytb = yc - ymax - view_offset_y; 1218 1219 double xta = -view_offset_x - xmin; 1220 double xtb = -view_offset_x - xmax; 1221 1222 redraw_area ((int)xtb - 10, (int)yta - 10, (int)(xtb - xta) + 10, (int) (yta - ytb) + 10); 1223 } 1224 1225 public Path get_closeset_path (double x, double y) { 1226 double d; 1227 EditPoint ep = new EditPoint (); 1228 1229 Path min_point = new Path (); 1230 double min_distance = double.MAX; 1231 1232 double xt = path_coordinate_x (x); 1233 double yt = path_coordinate_y (y); 1234 var paths = get_visible_paths (); 1235 1236 foreach (Path p in paths) { 1237 if (p.is_over (xt, yt)) { 1238 return p; 1239 } 1240 } 1241 1242 foreach (Path p in paths) { 1243 if (p.points.size == 0) continue; 1244 1245 p.get_closest_point_on_path (ep, xt, yt); 1246 d = Math.pow (ep.x - xt, 2) + Math.pow (ep.y - yt, 2); 1247 1248 if (d < min_distance) { 1249 min_distance = d; 1250 min_point = p; 1251 } 1252 1253 } 1254 1255 // a path without any editpoints 1256 if (paths.size > 0) { 1257 return paths.get (0); 1258 } 1259 1260 if (unlikely (min_distance == double.MAX)) { 1261 warning (@"No path found in path_list."); 1262 } 1263 1264 return min_point; 1265 } 1266 1267 public void move_selected_edit_point_coordinates (EditPoint selected_point, double xt, double yt) { 1268 double x, y; 1269 1270 BirdFont.get_current_font ().touch (); 1271 1272 x = reverse_path_coordinate_x (xt); 1273 y = reverse_path_coordinate_y (yt); 1274 1275 // redraw control point 1276 redraw_area ((int)(x - 4*view_zoom), (int)(y - 4*view_zoom), (int)(x + 3*view_zoom), (int)(y + 3*view_zoom)); 1277 1278 // update position of selected point 1279 selected_point.set_position (xt, yt); 1280 1281 if (view_zoom >= 2) { 1282 redraw_area (0, 0, allocation.width, allocation.height); 1283 } else { 1284 redraw_last_stroke (x, y); 1285 } 1286 } 1287 1288 public void move_selected_edit_point (EditPoint selected_point, double x, double y) { 1289 double xt = path_coordinate_x (x); 1290 double yt = path_coordinate_y (y); 1291 move_selected_edit_point_coordinates (selected_point, xt, yt); 1292 } 1293 1294 public void redraw_segment (EditPoint a, EditPoint b) { 1295 double margin = 10; 1296 double x = Math.fmin (reverse_path_coordinate_x (a.x), reverse_path_coordinate_x (b.x)) - margin; 1297 double y = Math.fmin (reverse_path_coordinate_y (a.y), reverse_path_coordinate_y(b.y)) - margin; 1298 double w = Math.fabs (reverse_path_coordinate_x (a.x) - reverse_path_coordinate_x(b.x)) + 2 * margin; 1299 double h = Math.fabs (reverse_path_coordinate_y (a.y) - reverse_path_coordinate_y (b.y)) + 2 * margin; 1300 1301 redraw_area ((int)x, (int)y, (int)w, (int)h); 1302 } 1303 1304 private void redraw_last_stroke (double x, double y) { 1305 // redraw line, if we have more than one new point on path 1306 double px = 0; 1307 double py = 0; 1308 int tw = 0; 1309 int th = 0; 1310 1311 double xc = (allocation.width / 2.0); 1312 1313 if (active_paths.size == 0) { 1314 return; 1315 } 1316 1317 foreach (Path path in active_paths) { 1318 EditPoint p; 1319 EditPoint pl = path.get_last_point (); 1320 1321 if (pl.prev != null) { 1322 p = pl.get_prev (); 1323 1324 px = p.x + xc; 1325 py = p.y - xc; 1326 1327 tw = (px > x) ? (int) (px - x) : (int) (x - px); 1328 th = (py > y) ? (int) (py - y) : (int) (y - py); 1329 1330 if (px > x) px -= tw + 60; 1331 if (py > y) py -= th + 60; 1332 1333 } else { 1334 px = x - 60; 1335 py = y - 60; 1336 tw = 0; 1337 th = 0; 1338 } 1339 } 1340 1341 redraw_area ((int)px - 20, (int)py - 20, tw + 120, th + 120); 1342 } 1343 1344 public bool has_active_path () { 1345 return active_paths.size > 0; 1346 } 1347 1348 public bool is_open () { 1349 return open; 1350 } 1351 1352 /** Close all editable paths and return false if no path have been closed. */ 1353 public bool close_path () { 1354 bool r = false; 1355 1356 foreach (Path p in get_all_paths ()) { 1357 if (p.is_editable ()) { 1358 r = true; 1359 p.set_editable (false); 1360 } 1361 } 1362 1363 open = false; 1364 clear_active_paths (); 1365 GlyphCanvas.redraw (); 1366 1367 MainWindow.set_cursor (NativeWindow.VISIBLE); 1368 1369 return r; 1370 } 1371 1372 public void open_path () { 1373 foreach (Path p in get_visible_paths ()) { 1374 p.set_editable (true); 1375 p.recalculate_linear_handles (); 1376 1377 if (p.is_open () && p.points.size > 0) { 1378 p.get_first_point ().set_reflective_handles (false); 1379 p.get_first_point ().set_tie_handle (false); 1380 p.get_last_point ().set_reflective_handles (false); 1381 p.get_last_point ().set_tie_handle (false); 1382 } 1383 } 1384 1385 open = true; 1386 redraw_area (0, 0, allocation.width, allocation.height); 1387 } 1388 1389 public void redraw_path_region (Path p) { 1390 int x, y, w, h; 1391 1392 p.update_region_boundaries (); 1393 1394 x = reverse_path_coordinate_x (p.xmin); 1395 y = reverse_path_coordinate_x (p.xmin); 1396 w = reverse_path_coordinate_x (p.xmax) - x; 1397 h = reverse_path_coordinate_x (p.ymax) - y; // FIXME: redraw path 1398 1399 redraw_area (x, y, w, h); 1400 } 1401 1402 public Line get_line (string name) { 1403 foreach (Line line in vertical_help_lines) { 1404 if (likely (line.get_label () == name)) { 1405 return line; 1406 } 1407 } 1408 1409 foreach (Line line in horizontal_help_lines) { 1410 if (likely (line.get_label () == name)) { 1411 return line; 1412 } 1413 } 1414 1415 warning (@"No line with label $name found"); 1416 return new Line ("Err"); 1417 } 1418 1419 public override void zoom_in () { 1420 if (move_canvas) { 1421 return; 1422 } 1423 1424 set_zoom_area (10, 10, allocation.width - 10, allocation.height - 10); 1425 set_zoom_from_area (); 1426 update_view (); 1427 update_zoom_bar (); 1428 } 1429 1430 public override void zoom_out () { 1431 double w = allocation.width; 1432 int n = (int) (10 * ((w - 10) / allocation.width)); 1433 set_zoom_area (-n, -n, allocation.width + n, allocation.height + n); 1434 set_zoom_from_area (); 1435 update_view (); 1436 update_zoom_bar (); 1437 } 1438 1439 public override void zoom_max () { 1440 default_zoom (); 1441 update_zoom_bar (); 1442 } 1443 1444 public override void zoom_min () { 1445 double ax = 1000; 1446 double ay = 1000; 1447 double bx = -1000; 1448 double by = -1000; 1449 1450 int iax, iay, ibx, iby; 1451 1452 reset_zoom (); 1453 1454 foreach (var p in get_visible_paths ()) { 1455 p.update_region_boundaries (); 1456 1457 if (p.points.size > 2) { 1458 if (p.xmin < ax) ax = p.xmin; 1459 if (p.ymin < ay) ay = p.ymin; 1460 if (p.xmax > bx) bx = p.xmax; 1461 if (p.ymax > by) by = p.ymax; 1462 } 1463 } 1464 1465 if (ax == 1000) return; // empty page 1466 1467 iax = (int) ((ax + view_offset_x + allocation.width / 2.0) * view_zoom); 1468 iay = (int) ((-ay + view_offset_y + allocation.height / 2.0) * view_zoom); 1469 ibx = (int) ((bx + view_offset_x + allocation.width / 2.0) * view_zoom); 1470 iby = (int) ((-by + view_offset_y + allocation.height / 2.0) * view_zoom); 1471 1472 show_zoom_area (iax, iay, ibx, iby); // set this later on button release 1473 set_zoom_from_area (); 1474 zoom_out (); // add some margin 1475 1476 redraw_area (0, 0, allocation.width, allocation.height); 1477 update_zoom_bar (); 1478 } 1479 1480 public override void store_current_view () { 1481 ZoomView n; 1482 1483 if (zoom_list_index + 1 < zoom_list.size) { 1484 n = zoom_list.get (zoom_list_index); 1485 while (n != zoom_list.get (zoom_list.size - 1)) { 1486 zoom_list.remove_at (zoom_list.size - 1); 1487 } 1488 } 1489 1490 zoom_list.add (new ZoomView (view_offset_x, view_offset_y, view_zoom, allocation)); 1491 zoom_list_index = (int) zoom_list.size - 1; 1492 1493 if (zoom_list.size > 50) { 1494 zoom_list.remove_at (0); 1495 } 1496 } 1497 1498 public override void restore_last_view () { 1499 if (zoom_list.size == 0 || zoom_list_index - 1 < 0) { 1500 return; 1501 } 1502 1503 zoom_list_index--; 1504 1505 ZoomView z = zoom_list.get (zoom_list_index); 1506 1507 view_offset_x = z.x; 1508 view_offset_y = z.y; 1509 view_zoom = z.zoom; 1510 allocation = z.allocation; 1511 1512 update_zoom_bar (); 1513 } 1514 1515 public override void next_view () { 1516 ZoomView z; 1517 1518 if (zoom_list.size == 0 || zoom_list_index + 1 >= zoom_list.size) { 1519 return; 1520 } 1521 1522 zoom_list_index++; 1523 1524 z = zoom_list.get (zoom_list_index); 1525 1526 view_offset_x = z.x; 1527 view_offset_y = z.y; 1528 view_zoom = z.zoom; 1529 allocation = z.allocation; 1530 1531 update_zoom_bar (); 1532 } 1533 1534 public override void reset_zoom () { 1535 view_offset_x = 0; 1536 view_offset_y = 0; 1537 1538 set_zoom (1); 1539 1540 store_current_view (); 1541 update_zoom_bar (); 1542 } 1543 1544 /** Get x-height or top line. */ 1545 public Line get_upper_line () { 1546 if (has_top_line () || xheight_lines_visible) { 1547 return get_line ("top"); 1548 } 1549 1550 return get_line ("x-height"); 1551 } 1552 1553 /** Get base line. */ 1554 public Line get_lower_line () { 1555 return get_line ("baseline"); 1556 } 1557 1558 /** Set default zoom. See default_zoom. */ 1559 public void set_default_zoom () { 1560 int bottom = 0; 1561 int top = 0; 1562 int left = 0; 1563 int right = 0; 1564 1565 return_if_fail (vertical_help_lines.size != 0); 1566 return_if_fail (horizontal_help_lines.size != 0); 1567 1568 reset_zoom (); 1569 1570 bottom = get_lower_line ().get_position_pixel (); 1571 top = get_upper_line ().get_position_pixel (); 1572 1573 left = vertical_help_lines.get (vertical_help_lines.size - 1).get_position_pixel (); 1574 right = vertical_help_lines.get (0).get_position_pixel (); 1575 1576 set_zoom_area (left + 10, top - 10, right - 10, bottom + 10); 1577 set_zoom_from_area (); 1578 } 1579 1580 /** Set default zoom and redraw canvas. */ 1581 public void default_zoom () { 1582 set_default_zoom (); 1583 update_view (); 1584 } 1585 1586 public bool is_empty () { 1587 foreach (Path p in get_visible_paths ()) { 1588 if (p.points.size > 0) { 1589 return false; 1590 } 1591 } 1592 1593 return true; 1594 } 1595 1596 public void set_zoom (double z) 1597 requires (z > 0) 1598 { 1599 view_zoom = z; 1600 } 1601 1602 public void set_background_visible (bool visibility) { 1603 background_image_visible = visibility; 1604 } 1605 1606 public bool get_background_visible () { 1607 return background_image_visible; 1608 } 1609 1610 public void draw_coordinate (Context cr) { 1611 Theme.color (cr, "Table Border"); 1612 cr.set_font_size (12); 1613 cr.move_to (0, 10); 1614 cr.show_text (@"($motion_x, $motion_y)"); 1615 cr.stroke (); 1616 } 1617 1618 /** Draw filled paths. */ 1619 public void draw_paths (Context cr, Color? c = null) { 1620 PathList stroke; 1621 Color color; 1622 bool open; 1623 1624 cr.save (); 1625 cr.new_path (); 1626 foreach (Path p in get_visible_paths ()) { 1627 if (c != null) { 1628 color = (!) c; 1629 } else if (p.color != null) { 1630 color = (!) p.color; 1631 } else { 1632 color = Color.black (); 1633 } 1634 1635 if (p.stroke > 0) { 1636 stroke = p.get_stroke_fast (); 1637 draw_path_list (stroke, cr, color); 1638 } else { 1639 open = p.is_open (); 1640 1641 if (open) { 1642 p.close (); 1643 p.recalculate_linear_handles (); 1644 } 1645 1646 p.draw_path (cr, this, color); 1647 1648 if (open) { 1649 p.reopen (); 1650 } 1651 } 1652 } 1653 cr.fill (); 1654 cr.restore (); 1655 } 1656 1657 public void draw_path (Context cr) { 1658 PathList stroke; 1659 Color color; 1660 1661 cr.save (); 1662 cr.new_path (); 1663 foreach (Path p in get_visible_paths ()) { 1664 if (p.stroke > 0) { 1665 stroke = p.get_stroke_fast (); 1666 1667 if (p.is_editable ()) { 1668 color = Theme.get_color ("Filled Stroke"); 1669 color.a = 0.8; 1670 } else { 1671 color = Color.black (); 1672 } 1673 1674 draw_path_list (stroke, cr, color); 1675 } 1676 } 1677 cr.fill (); 1678 cr.restore (); 1679 1680 if (!(MainWindow.get_toolbox ().get_current_tool () is PenTool) 1681 && !(MainWindow.get_toolbox ().get_current_tool () is PointTool) 1682 && !(MainWindow.get_toolbox ().get_current_tool () is TrackTool) 1683 && !(MainWindow.get_toolbox ().get_current_tool () is BezierTool)) { 1684 cr.save (); 1685 cr.new_path (); 1686 foreach (Path p in active_paths) { 1687 if (p.stroke > 0) { 1688 stroke = p.get_stroke_fast (); 1689 color = Theme.get_color ("Selected Objects"); 1690 draw_path_list (stroke, cr, color); 1691 } 1692 } 1693 cr.fill (); 1694 cr.restore (); 1695 } 1696 1697 if (is_open () && Path.fill_open_path) { 1698 cr.save (); 1699 cr.new_path (); 1700 foreach (Path p in get_visible_paths ()) { 1701 if (p.stroke == 0) { 1702 color = p.color == null ? get_path_fill_color () : (!) p.color; 1703 p.draw_path (cr, this, color); 1704 } 1705 } 1706 cr.fill (); 1707 cr.restore (); 1708 } 1709 1710 if (is_open ()) { 1711 cr.save (); 1712 cr.new_path (); 1713 foreach (Path p in get_visible_paths ()) { 1714 p.draw_outline (cr); 1715 p.draw_edit_points (cr); 1716 } 1717 cr.restore (); 1718 } 1719 1720 if (!is_open ()) { 1721 // This was good for testing but it is way too slow: 1722 // Svg.draw_svg_path (cr, get_svg_data (), Glyph.xc () + left, Glyph.yc () - baseline); 1723 1724 cr.save (); 1725 cr.new_path (); 1726 foreach (Path p in get_visible_paths ()) { 1727 if (p.stroke == 0) { 1728 color = p.color == null ? Color.black () : (!) p.color; 1729 p.draw_path (cr, this, color); 1730 } 1731 } 1732 cr.close_path (); 1733 cr.fill (); 1734 cr.restore (); 1735 1736 foreach (Path p in active_paths) { 1737 cr.save (); 1738 cr.new_path (); 1739 if (p.stroke == 0) { 1740 p.draw_path (cr, this); 1741 } 1742 cr.close_path (); 1743 cr.fill (); 1744 cr.restore (); 1745 } 1746 } 1747 1748 if (show_orientation_arrow) { 1749 foreach (Path p in get_visible_paths ()) { 1750 if (p.stroke > 0) { 1751 stroke = p.get_stroke_fast (); 1752 foreach (Path ps in stroke.paths) { 1753 ps.draw_orientation_arrow (cr, orientation_arrow_opacity); 1754 } 1755 } else { 1756 p.draw_orientation_arrow (cr, orientation_arrow_opacity); 1757 } 1758 } 1759 } 1760 } 1761 1762 private Color get_path_fill_color () { 1763 return Theme.get_color ("Fill Color"); 1764 } 1765 1766 public void draw_path_list (PathList pl, Context cr, Color? c = null) { 1767 foreach (Path p in pl.paths) { 1768 p.draw_path (cr, this, c); 1769 } 1770 } 1771 1772 public void draw_background_color (Context cr, double opacity) { 1773 if (opacity > 0) { 1774 cr.save (); 1775 cr.rectangle (0, 0, allocation.width, allocation.height); 1776 Theme.color (cr, "Canvas Background"); 1777 cr.fill (); 1778 cr.restore (); 1779 } 1780 } 1781 1782 public void draw_help_lines (Context cr) { 1783 foreach (Line line in get_all_help_lines ()) { 1784 cr.save (); 1785 line.draw (cr, allocation); 1786 cr.restore (); 1787 } 1788 } 1789 1790 public void set_allocation (WidgetAllocation a) { 1791 allocation = a; 1792 } 1793 1794 public override void draw (WidgetAllocation allocation, Context cmp) { 1795 Tool tool; 1796 1797 this.allocation = allocation; 1798 1799 cmp.save (); 1800 draw_background_color (cmp, 1); 1801 cmp.restore (); 1802 1803 if (background_image != null && background_image_visible) { 1804 ((!)background_image).draw (cmp, allocation, view_offset_x, view_offset_y, view_zoom); 1805 } 1806 1807 if (unlikely (Preferences.draw_boundaries)) { 1808 foreach (Path p in get_visible_paths ()) { 1809 p.draw_boundaries (cmp); 1810 } 1811 } 1812 1813 draw_background_glyph (allocation, cmp); 1814 juxtapose (allocation, cmp); 1815 1816 if (BirdFont.show_coordinates) { 1817 draw_coordinate (cmp); 1818 } 1819 1820 if (show_help_lines) { 1821 cmp.save (); 1822 draw_help_lines (cmp); 1823 cmp.restore (); 1824 } 1825 1826 if (!is_empty ()) { 1827 cmp.save (); 1828 cmp.scale (view_zoom, view_zoom); 1829 cmp.translate (-view_offset_x, -view_offset_y); 1830 draw_path (cmp); 1831 cmp.restore (); 1832 } 1833 1834 cmp.save (); 1835 tool = MainWindow.get_toolbox ().get_current_tool (); 1836 tool.draw_action (tool, cmp, this); 1837 cmp.restore (); 1838 } 1839 1840 private void zoom_in_at_point (double x, double y, double amount = 15) { 1841 int n = (int) (-amount); 1842 zoom_at_point (x, y, n); 1843 } 1844 1845 private void zoom_out_at_point (double x, double y, double amount = 15) { 1846 double a = -amount; 1847 int n = (int) (a * ((allocation.width - a) / allocation.width)); 1848 zoom_at_point (x, y, n); 1849 } 1850 1851 public void zoom_tap (double distance) { 1852 int w = (int) (distance); 1853 if (distance != 0) { 1854 show_zoom_area (-w , -w, allocation.width + w, allocation.height + w); 1855 set_zoom_from_area (); 1856 } 1857 } 1858 1859 /** Zoom in @param n pixels. */ 1860 private void zoom_at_point (double x, double y, int n) { 1861 double w = allocation.width; 1862 double h = allocation.height; 1863 1864 double rx = Math.fabs (w / 2 - x) / (w / 2); 1865 double ry = Math.fabs (h / 2 - y) / (h / 2); 1866 1867 int xd = (x < w / 2) ? (int) (n * rx) : (int) (-n * rx); 1868 int yd = (y < h / 2) ? (int) (n * ry) : (int) (-n * ry); 1869 1870 show_zoom_area (-n + xd, -n + yd, allocation.width + n + xd, allocation.height + n + yd); 1871 set_zoom_from_area (); 1872 } 1873 1874 private void move_view_offset (double x, double y) { 1875 view_offset_x = move_offset_x + (pointer_begin_x - x) * (1/view_zoom); 1876 view_offset_y = move_offset_y + (pointer_begin_y - y) * (1/view_zoom); 1877 GlyphCanvas.redraw (); 1878 } 1879 1880 public void store_undo_state (bool clear_redo = false) { 1881 Glyph g = copy (); 1882 undo_list.add (g); 1883 1884 if (clear_redo) { 1885 redo_list.clear (); 1886 } 1887 } 1888 1889 public void store_redo_state () { 1890 Glyph g = copy (); 1891 redo_list.add (g); 1892 } 1893 1894 public Glyph copy () { 1895 Glyph g = new Glyph.no_lines (name, unichar_code); 1896 1897 g.current_layer = current_layer; 1898 1899 g.left_limit = left_limit; 1900 g.right_limit = right_limit; 1901 1902 g.remove_lines (); 1903 1904 foreach (Line line in get_all_help_lines ()) { 1905 g.add_line (line.copy ()); 1906 } 1907 1908 g.layers = layers.copy (); 1909 1910 foreach (Path p in active_paths) { 1911 g.active_paths.add (p); 1912 } 1913 1914 if (background_image != null) { 1915 g.background_image = ((!) background_image).copy (); 1916 } 1917 1918 g.empty = empty; 1919 g.open = open; 1920 g.version_id = version_id; 1921 1922 return g; 1923 } 1924 1925 public void reload () { 1926 Font f = BirdFont.get_current_font (); 1927 1928 if (f.has_glyph (name)) { 1929 set_glyph_data ((!) f.get_glyph (name)); 1930 } 1931 } 1932 1933 public override void undo () { 1934 Glyph g; 1935 Tool tool; 1936 1937 if (undo_list.size == 0) { 1938 return; 1939 } 1940 1941 tool = MainWindow.get_toolbox ().get_current_tool (); 1942 tool.before_undo (); 1943 1944 g = undo_list.get (undo_list.size - 1); 1945 1946 store_redo_state (); 1947 set_glyph_data (g); 1948 1949 undo_list.remove_at (undo_list.size - 1); 1950 1951 DrawingTools.update_layers (); 1952 PenTool.update_selected_points (); 1953 1954 clear_active_paths (); 1955 1956 tool.after_undo (); 1957 } 1958 1959 public override void redo () { 1960 Glyph g; 1961 1962 if (redo_list.size == 0) { 1963 return; 1964 } 1965 1966 g = redo_list.get (redo_list.size - 1); 1967 1968 store_undo_state (false); 1969 set_glyph_data (g); 1970 1971 redo_list.remove_at (redo_list.size - 1); 1972 1973 DrawingTools.update_layers (); 1974 PenTool.update_selected_points (); 1975 1976 clear_active_paths (); 1977 } 1978 1979 void set_glyph_data (Glyph g) { 1980 current_layer = g.current_layer; 1981 layers = g.layers.copy (); 1982 1983 left_limit = g.left_limit; 1984 right_limit = g.right_limit; 1985 1986 remove_lines (); 1987 foreach (Line line in g.get_all_help_lines ()) { 1988 add_line (line.copy ()); 1989 } 1990 1991 add_help_lines (); 1992 1993 if (g.background_image != null) { 1994 background_image = ((!) g.background_image).copy (); 1995 } 1996 1997 clear_active_paths (); 1998 foreach (Path p in g.active_paths) { 1999 add_active_path (null, p); 2000 } 2001 2002 redraw_area (0, 0, allocation.width, allocation.height); 2003 } 2004 2005 /** Split curve in two parts and add a new point in between. 2006 * @return the new point 2007 */ 2008 public void insert_new_point_on_path (double x, double y) { 2009 double min, distance; 2010 Path? p = null; 2011 Path path; 2012 EditPoint? np = null; 2013 EditPoint lep; 2014 2015 double xt; 2016 double yt; 2017 2018 xt = x * ivz () - xc () + view_offset_x; 2019 yt = yc () - y * ivz () - view_offset_y; 2020 2021 min = double.MAX; 2022 2023 foreach (Path pp in get_visible_paths ()) { 2024 lep = new EditPoint (); 2025 pp.get_closest_point_on_path (lep, xt, yt); 2026 distance = Math.sqrt (Math.pow (Math.fabs (xt - lep.x), 2) + Math.pow (Math.fabs (yt - lep.y), 2)); 2027 2028 if (distance < min) { 2029 min = distance; 2030 p = pp; 2031 np = lep; 2032 } 2033 } 2034 2035 if (p == null) { 2036 return; 2037 } 2038 2039 path = (!) p; 2040 2041 lep = new EditPoint (); 2042 path.get_closest_point_on_path (lep, xt, yt); 2043 path.insert_new_point_on_path (lep); 2044 } 2045 2046 static bool in_range (double offset_x, double coordinate_x1, double coordinate_x2) { 2047 return coordinate_x1 <= offset_x <= coordinate_x2; 2048 } 2049 2050 public void juxtapose (WidgetAllocation allocation, Context cr) { 2051 string glyph_sequence = Preferences.get ("glyph_sequence"); 2052 unichar c; 2053 Font font = BirdFont.get_current_font (); 2054 Glyph glyph = MainWindow.get_current_glyph (); 2055 Glyph juxtaposed; 2056 StringBuilder current = new StringBuilder (); 2057 int pos; 2058 string name; 2059 double x, kern; 2060 double left, baseline; 2061 string last_name; 2062 2063 double box_x1, box_x2, box_y1, box_y2; 2064 double marker_x, marker_y; 2065 2066 KerningClasses classes = font.get_kerning_classes (); 2067 2068 x = 0; 2069 2070 box_x1 = path_coordinate_x (0); 2071 box_y1 = path_coordinate_y (0); 2072 box_x2 = path_coordinate_x (allocation.width); 2073 box_y2 = path_coordinate_y (allocation.height); 2074 2075 current.append_unichar (glyph.unichar_code); 2076 pos = glyph_sequence.index_of (current.str); 2077 2078 baseline = font.base_line;; 2079 left = glyph.get_line ("left").pos; 2080 2081 x = glyph.get_width (); 2082 last_name = glyph.name; 2083 for (int i = pos + 1; i < glyph_sequence.char_count (); i++) { 2084 c = glyph_sequence.get_char (i); 2085 name = (!) c.to_string (); 2086 juxtaposed = (font.has_glyph (name)) ? (!) font.get_glyph (name) : font.get_space ().get_current (); 2087 2088 if (font.has_glyph (last_name) && font.has_glyph (name)) { 2089 kern = classes.get_kerning (last_name, name); 2090 } else { 2091 kern = 0; 2092 } 2093 2094 if (!juxtaposed.is_empty () 2095 && (in_range (left + x + kern, box_x1, box_x2) // the letter is visible 2096 || in_range (left + x + kern + juxtaposed.get_width (), box_x1, box_x2))) { 2097 2098 marker_x = Glyph.xc () + left + x + kern - glyph.view_offset_x; 2099 marker_y = Glyph.yc () - baseline - glyph.view_offset_y; 2100 2101 cr.save (); 2102 cr.scale (glyph.view_zoom, glyph.view_zoom); 2103 Theme.color (cr, "Foreground 1"); 2104 2105 Svg.draw_svg_path (cr, juxtaposed.get_svg_data (), marker_x, marker_y); 2106 cr.restore (); 2107 } 2108 2109 x += juxtaposed.get_width () + kern; 2110 2111 last_name = name; 2112 } 2113 2114 x = 0; 2115 last_name = glyph.name; 2116 for (int i = pos - 1; i >= 0; i--) { 2117 c = glyph_sequence.get_char (i); 2118 name = (!) c.to_string (); 2119 juxtaposed = (font.has_glyph (name)) ? (!) font.get_glyph (name) : font.get_space ().get_current (); 2120 2121 if (font.has_glyph (last_name) && font.has_glyph (name)) { 2122 kern = classes.get_kerning (name, last_name); 2123 } else { 2124 kern = 0; 2125 } 2126 2127 x -= juxtaposed.get_width (); 2128 x -= kern; 2129 2130 marker_x = Glyph.xc () + left + x; 2131 marker_y = Glyph.yc () - baseline; 2132 if (!juxtaposed.is_empty () 2133 &&(in_range (left + x, box_x1, box_x2) 2134 || in_range (left + x + juxtaposed.get_width (), box_x1, box_x2))) { 2135 cr.save (); 2136 cr.scale (glyph.view_zoom, glyph.view_zoom); 2137 cr.translate (-glyph.view_offset_x, -glyph.view_offset_y); 2138 Theme.color (cr, "Foreground 1"); 2139 Svg.draw_svg_path (cr, juxtaposed.get_svg_data (), marker_x, marker_y); 2140 cr.restore (); 2141 } 2142 2143 last_name = name; 2144 } 2145 } 2146 2147 /** @return left side bearing */ 2148 public double get_lsb () { 2149 return get_line ("left").pos; 2150 } 2151 2152 /** @return bottom line */ 2153 public double get_baseline () { 2154 Font font = BirdFont.get_current_font (); 2155 return font.base_line; 2156 } 2157 2158 void draw_background_glyph (WidgetAllocation allocation, Context cr) { 2159 double left, baseline, current_left; 2160 Glyph g; 2161 Font font = BirdFont.get_current_font (); 2162 2163 current_left = get_line ("left").pos; 2164 2165 if (background_glyph != null) { 2166 g = (!) background_glyph; 2167 baseline = font.base_line; 2168 left = g.get_line ("left").pos; 2169 cr.save (); 2170 cr.scale (view_zoom, view_zoom); 2171 cr.translate (-view_offset_x, -view_offset_y); 2172 Theme.color (cr, "Background Glyph"); 2173 2174 Svg.draw_svg_path (cr, g.get_svg_data (), 2175 Glyph.xc () + left - (left - current_left) , 2176 Glyph.yc () - baseline); 2177 cr.restore (); 2178 } 2179 2180 } 2181 2182 public string get_hex () { 2183 return Font.to_hex_code (unichar_code); 2184 } 2185 2186 public override void move_view (double x, double y) { 2187 view_offset_x += x / view_zoom; 2188 view_offset_y += y / view_zoom; 2189 GlyphCanvas.redraw (); 2190 } 2191 2192 /** Scroll or zoom from tap events. */ 2193 public void change_view_event (int finger, int x, int y) { 2194 double dx, dy; 2195 double last_distance, new_distance; 2196 2197 dx = 0; 2198 dy = 0; 2199 2200 new_distance = 0; 2201 2202 if (last_tap0_y == -1 || last_tap0_x == -1 || last_tap1_y == -1 || last_tap1_x == -1) { 2203 return; 2204 } 2205 2206 if (finger == 0) { 2207 dx = last_tap0_x - x; 2208 dy = last_tap0_y - y; 2209 new_distance = Path.distance (last_tap1_x, x, last_tap1_y, y); 2210 } 2211 2212 if (finger == 1) { 2213 dx = last_tap1_x - x; 2214 dy = last_tap1_y - y; 2215 new_distance = Path.distance (last_tap0_x, x, last_tap0_y, y); 2216 } 2217 2218 last_distance = Path.distance (last_tap0_x, last_tap1_x, last_tap0_y, last_tap1_y); 2219 2220 if (zoom_distance != 0) { 2221 zoom_tap (zoom_distance - new_distance); 2222 } 2223 2224 if (finger == 1) { 2225 warning (@"dx $dx dy $dy last_tap1_x $last_tap1_x last_tap1_y $last_tap1_y x $x y $y"); 2226 move_view (dx, dy); 2227 } 2228 2229 zoom_distance = new_distance; 2230 } 2231 2232 public override void tap_down (int finger, int x, int y) { 2233 TimeoutSource delay; 2234 2235 if (finger == 0) { 2236 delay = new TimeoutSource (400); // wait for second finger 2237 delay.set_callback(() => { 2238 if (!change_view && !ignore_input) { 2239 button_press (1, x, y); 2240 } 2241 return false; 2242 }); 2243 delay.attach (null); 2244 2245 last_tap0_x = x; 2246 last_tap0_y = y; 2247 } 2248 2249 if (finger == 1) { 2250 change_view = true; 2251 last_tap1_x = x; 2252 last_tap1_y = y; 2253 } 2254 } 2255 2256 public override void tap_up (int finger, int x, int y) { 2257 if (finger == 0) { 2258 button_release (1, x, y); 2259 2260 last_tap0_x = -1; 2261 last_tap0_y = -1; 2262 } 2263 2264 if (finger == 1) { 2265 last_tap1_x = -1; 2266 last_tap1_y = -1; 2267 2268 change_view = false; 2269 zoom_distance = 0; 2270 } 2271 } 2272 2273 public override void tap_move (int finger, int x, int y) { 2274 if (!change_view) { 2275 motion_notify (x, y); 2276 } else { 2277 change_view_event (finger, x, y); 2278 } 2279 2280 if (finger == 0) { 2281 last_tap0_x = x; 2282 last_tap0_y = y; 2283 } 2284 2285 if (finger == 1) { 2286 last_tap1_x = x; 2287 last_tap1_y = y; 2288 } 2289 } 2290 2291 public void update_spacing_class () { 2292 Font font = BirdFont.get_current_font (); 2293 GlyphCollection? g; 2294 GlyphCollection gc; 2295 Glyph glyph; 2296 Gee.ArrayList<string> s; 2297 SpacingData sd; 2298 2299 sd = font.get_spacing (); 2300 s = sd.get_all_connections ((!) unichar_code.to_string ()); 2301 foreach (string l in s) { 2302 if (l != (!) unichar_code.to_string ()) { 2303 g = font.get_glyph_collection (l); 2304 if (g != null) { 2305 gc = (!) g; 2306 glyph = gc.get_current (); 2307 2308 if (glyph.left_limit == glyph.right_limit) { 2309 warning ("Zero width glyph in kerning class."); 2310 } 2311 2312 left_limit = glyph.left_limit; 2313 right_limit = glyph.right_limit; 2314 2315 break; 2316 } 2317 } 2318 } 2319 2320 add_help_lines (); 2321 } 2322 2323 public void update_other_spacing_classes () { 2324 Font font = BirdFont.get_current_font (); 2325 GlyphCollection? g; 2326 GlyphCollection gc; 2327 Glyph glyph; 2328 Gee.ArrayList<string> s; 2329 SpacingData sd; 2330 2331 sd = font.get_spacing (); 2332 s = sd.get_all_connections ((!) unichar_code.to_string ()); 2333 2334 foreach (string l in s) { 2335 if (l != (!) unichar_code.to_string ()) { 2336 g = font.get_glyph_collection (l); 2337 if (g != null) { 2338 gc = (!) g; 2339 glyph = gc.get_current (); 2340 glyph.left_limit = left_limit; 2341 glyph.right_limit = right_limit; 2342 } 2343 } 2344 } 2345 } 2346 2347 public void set_cache (string key, Surface cache) { 2348 glyph_cache.set (key, cache); 2349 } 2350 2351 public bool has_cache (string key) { 2352 return glyph_cache.has_key (key); 2353 } 2354 2355 public Surface get_cache (string key) { 2356 if (unlikely (!has_cache (key))) { 2357 warning ("No cache for glyph."); 2358 return new ImageSurface (Cairo.Format.ARGB32, 1, 1); 2359 } 2360 2361 return glyph_cache.get (key); 2362 } 2363 2364 public void add_custom_guide () { 2365 TextListener listener; 2366 2367 listener = new TextListener (t_("Guide"), "", t_("Add")); 2368 2369 listener.signal_text_input.connect ((text) => { 2370 new_guide_name = text; 2371 }); 2372 2373 listener.signal_submit.connect (() => { 2374 Line guide; 2375 double position; 2376 2377 position = path_coordinate_y (allocation.height / 2.0); 2378 guide = new Line (new_guide_name, new_guide_name, position); 2379 horizontal_help_lines.add (guide); 2380 2381 BirdFont.get_current_font ().custom_guides.add (guide); 2382 2383 TabContent.hide_text_input (); 2384 GlyphCanvas.redraw (); 2385 }); 2386 2387 TabContent.show_text_input (listener); 2388 } 2389 2390 public Path? get_last_path () { 2391 var paths = get_all_paths (); 2392 return_val_if_fail (paths.size > 0, null); 2393 return paths.get (paths.size - 1); 2394 } 2395 2396 public void move_layer_up () { 2397 Layer layer = get_current_layer (); 2398 2399 if (current_layer + 2 <= layers.subgroups.size) { 2400 return_if_fail (0 <= current_layer + 2 <= layers.subgroups.size); 2401 layers.subgroups.insert (current_layer + 2, layer); 2402 2403 return_if_fail (0 <= current_layer + 1 < layers.subgroups.size); 2404 layers.subgroups.remove_at (current_layer); 2405 2406 set_current_layer (layer); 2407 } 2408 } 2409 2410 public void move_layer_down () { 2411 Layer layer = get_current_layer (); 2412 2413 if (current_layer >= 1) { 2414 return_if_fail (0 <= current_layer - 1 < layers.subgroups.size); 2415 layers.subgroups.insert (current_layer - 1, layer); 2416 2417 return_if_fail (0 <= current_layer + 1 < layers.subgroups.size); 2418 layers.subgroups.remove_at (current_layer + 1); 2419 2420 set_current_layer (layer); 2421 } 2422 } 2423 2424 public void fix_curve_orientation () { 2425 int inside_count; 2426 bool inside; 2427 Path outline; 2428 2429 foreach (Path p1 in get_visible_paths ()) { 2430 inside_count = 0; 2431 2432 foreach (Path p2 in get_visible_paths ()) { 2433 if (p1 != p2) { 2434 inside = true; 2435 outline = p2.flatten (); 2436 2437 foreach (EditPoint ep in p1.points) { 2438 if (!SvgParser.is_inside (ep, outline)) { 2439 inside = false; 2440 } 2441 } 2442 2443 if (inside) { 2444 inside_count++; 2445 } 2446 } 2447 } 2448 2449 if (inside_count % 2 == 0) { 2450 p1.force_direction (Direction.CLOCKWISE); 2451 } else { 2452 p1.force_direction (Direction.COUNTER_CLOCKWISE); 2453 } 2454 } 2455 } 2456 2457 public override void magnify (double magnification) { 2458 double x_center = path_coordinate_x (xc ()); 2459 double y_center = path_coordinate_y (yc ()); 2460 2461 view_zoom *= 1 + magnification; 2462 2463 view_offset_x -= path_coordinate_x (xc ()) - x_center; 2464 view_offset_y += path_coordinate_y (yc ()) - y_center; 2465 2466 GlyphCanvas.redraw (); 2467 } 2468 2469 public Glyph self_interpolate (double weight) { 2470 Glyph g1 = copy (); 2471 Glyph g2 = copy (); 2472 2473 g1.fix_curve_orientation (); 2474 g2.layers = new Layer (); // remove all paths 2475 2476 foreach (Path p in g1.get_visible_paths ()) { 2477 bool counter = !p.is_clockwise (); 2478 2479 g2.add_path (p.copy ()); 2480 2481 p.remove_points_on_points (); 2482 Path master = p.get_self_interpolated_master (counter, weight); 2483 p = p.interpolate_estimated_path (master, weight); 2484 p.recalculate_linear_handles (); 2485 2486 g2.add_path (p); 2487 g2.add_path (master); 2488 } 2489 2490 return g2; 2491 } 2492 } 2493 2494 } 2495