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