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