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