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