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