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