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