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