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