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