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