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