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