.
1 /*
2 Copyright (C) 2012, 2014 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
17 namespace BirdFont {
18
19 /** Kerning context. */
20 public class KerningDisplay : FontDisplay {
21
22 public bool suppress_input = false;
23
24 Gee.ArrayList <GlyphSequence> row;
25 int active_handle = -1;
26 int selected_handle = -1;
27 bool moving = false;
28 Glyph left_active_glyph = new Glyph ("null", '\0');
29 Glyph right_active_glyph = new Glyph ("null", '\0');
30
31 double begin_handle_x = 0;
32 double begin_handle_y = 0;
33
34 double last_handle_x = 0;
35
36 public bool text_input = false;
37
38 Gee.ArrayList<UndoItem> undo_items;
39 Gee.ArrayList<UndoItem> redo_items;
40 bool first_update = true;
41
42 Font current_font = new Font ();
43 Text kerning_label = new Text ();
44
45 public bool adjust_side_bearings = false;
46 public bool right_side_bearing = true;
47
48 public KerningDisplay () {
49 GlyphSequence w = new GlyphSequence ();
50 row = new Gee.ArrayList <GlyphSequence> ();
51 undo_items = new Gee.ArrayList <UndoItem> ();
52 redo_items = new Gee.ArrayList <UndoItem> ();
53 row.add (w);
54 }
55
56 public GlyphSequence get_first_row () {
57 return row.size > 0 ? row.get (0) : new GlyphSequence ();
58 }
59
60 public override string get_label () {
61 return t_("Kerning");
62 }
63
64 public override string get_name () {
65 return "Kerning";
66 }
67
68 public void show_parse_error () {
69 string line1 = t_("The current kerning class is malformed.");
70 string line2 = t_("Add single characters separated by space and ranges on the form A-Z.");
71 string line3 = t_("Type “space” to kern the space character and “divis” to kern -.");
72
73 MainWindow.show_dialog (new MessageDialog (line1 + " " + line2 + " " + line3));
74 }
75
76 public override void draw (WidgetAllocation allocation, Context cr) {
77 draw_kerning_pairs (allocation, cr);
78 }
79
80 public double get_row_height () {
81 return current_font.top_limit - current_font.bottom_limit;
82 }
83
84 public void draw_kerning_pairs (WidgetAllocation allocation, Context cr) {
85 Glyph glyph;
86 double x, y, w, kern, alpha;
87 double x2;
88 double caret_y;
89 int i, wi;
90 Glyph? prev;
91 GlyphSequence word_with_ligatures;
92 GlyphRange? gr_left, gr_right;
93 bool first_row = true;
94 double row_height;
95 Font font;
96 double item_size = 1.0 / KerningTools.font_size;
97 double item_size2 = 2.0 / KerningTools.font_size;
98
99 font = current_font;
100 i = 0;
101
102 // bg color
103 cr.save ();
104 Theme.color (cr, "Background 1");
105 cr.rectangle (0, 0, allocation.width, allocation.height);
106 cr.fill ();
107 cr.restore ();
108
109 cr.save ();
110 cr.scale (KerningTools.font_size, KerningTools.font_size);
111
112 glyph = MainWindow.get_current_glyph ();
113
114 row_height = get_row_height ();
115
116 alpha = 1;
117 y = get_row_height () + font.base_line + 20;
118 x = 20;
119 w = 0;
120 prev = null;
121 kern = 0;
122
123 foreach (GlyphSequence word in row) {
124 wi = 0;
125 word_with_ligatures = word.process_ligatures (font);
126 gr_left = null;
127 gr_right = null;
128 foreach (Glyph? g in word_with_ligatures.glyph) {
129 if (g == null) {
130 continue;
131 }
132
133 if (prev == null || wi == 0) {
134 kern = 0;
135 } else {
136 return_if_fail (wi < word_with_ligatures.ranges.size);
137 return_if_fail (wi - 1 >= 0);
138
139 gr_left = word_with_ligatures.ranges.get (wi - 1);
140 gr_right = word_with_ligatures.ranges.get (wi);
141
142 kern = get_kerning_for_pair (((!)prev).get_name (), ((!)g).get_name (), gr_left, gr_right);
143 }
144
145 // draw glyph
146 if (g == null) {
147 w = 50;
148 alpha = 1;
149 } else {
150 alpha = 0;
151 glyph = (!) g;
152
153 cr.save ();
154 glyph.add_help_lines ();
155 cr.translate (kern + x - glyph.get_lsb () - Glyph.xc (), glyph.get_baseline () + y - Glyph.yc ());
156 glyph.draw_paths (cr);
157 cr.restore ();
158
159 w = glyph.get_width ();
160 }
161
162 // handle
163 if (first_row && (active_handle == i || selected_handle == i)) {
164 x2 = x + kern / 2.0;
165
166 cr.save ();
167
168 if (selected_handle == i) {
169 Theme.color (cr, "Foreground 1");
170 } else {
171 cr.set_source_rgba (123/255.0, 123/255.0, 123/255.0, 1);
172 }
173
174 if (!adjust_side_bearings) {
175 cr.move_to (x2 - 5 * item_size, y + 20 * item_size);
176 cr.line_to (x2, y + 20 * item_size - 5 * item_size);
177 cr.line_to (x2 + 5 * item_size, y + 20* item_size);
178 cr.fill ();
179
180 if (gr_left != null || gr_right != null) {
181 cr.move_to (x2 - 5 * item_size, y + 20 * item_size);
182 cr.line_to (x2 + 5 * item_size, y + 20 * item_size);
183 cr.line_to (x2 + 5 * item_size, y + 24 * item_size);
184 cr.line_to (x2 - 5 * item_size, y + 24 * item_size);
185 cr.fill ();
186 }
187 } else {
188 if (right_side_bearing) {
189 cr.move_to (x2 - 5 * item_size2, y + 20 * item_size2);
190 cr.line_to (x2, y + 20 * item_size2 - 5 * item_size2);
191 cr.line_to (x2, y + 20* item_size2);
192 cr.fill ();
193 } else {
194 cr.move_to (x2, y + 20 * item_size2);
195 cr.line_to (x2, y + 20 * item_size2 - 5 * item_size2);
196 cr.line_to (x2 + 5 * item_size2, y + 20* item_size2);
197 cr.fill ();
198 }
199 }
200
201 if (active_handle == i && !adjust_side_bearings) {
202 cr.save ();
203 cr.scale (1 / KerningTools.font_size, 1 / KerningTools.font_size);
204 kerning_label.widget_x = x2 * KerningTools.font_size;
205 kerning_label.widget_y = y * KerningTools.font_size + 40;
206 kerning_label.draw (cr);
207 cr.fill ();
208 cr.restore ();
209 }
210
211 cr.restore ();
212 }
213
214 x += w + kern;
215
216 // caption
217 if (g == null || ((!)g).is_empty ()) {
218 cr.save ();
219 cr.set_source_rgba (153/255.0, 153/255.0, 153/255.0, alpha);
220 cr.move_to (x - w / 2.0 - 5, y + 20);
221 cr.set_font_size (10 * item_size);
222 cr.show_text ("?");
223 cr.restore ();
224 }
225
226 prev = g;
227
228 wi++;
229 i++;
230 }
231
232 // draw caret
233 if (first_row) {
234 x2 = x;
235 caret_y = get_row_height () + font.base_line + 20;
236 cr.save ();
237 cr.set_line_width (1.0 / KerningTools.font_size);
238 Theme.color_opacity (cr, "Foreground 1", 0.5);
239 cr.move_to (x2, caret_y + 20);
240 cr.line_to (x2, 20);
241 cr.stroke ();
242 cr.restore ();
243
244 y += 30 * MainWindow.units;
245 }
246
247 y += row_height + 20;
248 x = 20;
249 first_row = false;
250
251 if (y > allocation.height) {
252 break;
253 }
254 }
255
256 for (int j = row.size - 1; j > 30; j--) {
257 row.remove_at (j);
258 }
259
260 cr.fill ();
261 cr.restore ();
262 }
263
264 private void display_kerning_value (double k) {
265 string kerning = round (k);
266 kerning_label = new Text (@"$(kerning)", 17 * MainWindow.units);
267 }
268
269 private void set_active_handle_index (int h) {
270 double kern = get_kerning_for_handle (h);
271 active_handle = h;
272
273 if (1 <= active_handle < row.get (0).glyph.size) {
274 display_kerning_value (kern);
275 }
276 }
277
278 private double get_kerning_for_handle (int handle) {
279 string a, b;
280 GlyphRange? gr_left, gr_right;
281 bool got_pair;
282
283 got_pair = get_kerning_pair (handle, out a, out b, out gr_left, out gr_right);
284
285 if (got_pair) {
286 return get_kerning_for_pair (a, b, gr_left, gr_right);
287 }
288
289 return 0;
290 }
291
292 private bool get_kerning_pair (int handle, out string left, out string right,
293 out GlyphRange? range_left, out GlyphRange? range_right) {
294 string a, b;
295 Font font;
296 int wi = 0;
297 GlyphSequence word_with_ligatures;
298 int ranges_index = 0;
299 GlyphRange? gr_left, gr_right;
300 int row_index = 0;
301
302 font = current_font;
303
304 font.touch ();
305
306 a = "";
307 b = "";
308
309 left = "";
310 right = "";
311 range_left = null;
312 range_right = null;
313
314 if (handle <= 0) {
315 return false;
316 }
317
318 foreach (GlyphSequence word in row) {
319 word_with_ligatures = word.process_ligatures (font);
320 ranges_index = 0;
321 foreach (Glyph? g in word_with_ligatures.glyph) {
322
323 if (g == null) {
324 continue;
325 }
326
327 b = ((!) g).get_name ();
328
329 if (handle == wi && row_index == 0) {
330 if (wi >= word_with_ligatures.ranges.size) {
331 return false;
332 }
333 return_val_if_fail (wi - 1 >= 0, false);
334
335 if (word_with_ligatures.ranges.size != word_with_ligatures.glyph.size) {
336 return false;
337 }
338
339 gr_left = word_with_ligatures.ranges.get (wi - 1);
340 gr_right = word_with_ligatures.ranges.get (wi);
341
342 left = a;
343 right = b;
344 range_left = gr_left;
345 range_right = gr_right;
346
347 return true;
348 }
349
350 wi++;
351
352 a = b;
353 }
354
355 row_index++;
356 }
357
358 return false;
359 }
360
361 public void set_absolute_kerning (int handle, double val) {
362 double kern;
363
364 if (MenuTab.suppress_event) {
365 return;
366 }
367
368 if (!adjust_side_bearings) {
369 kern = get_kerning_for_handle (handle);
370 set_space (handle, val - kern);
371 }
372 }
373
374
375 /** Adjust kerning or right side bearing. */
376 private void set_space (int handle, double val) {
377 string a, b;
378 Font font;
379 GlyphRange? gr_left, gr_right;
380
381 font = current_font;
382 font.touch ();
383
384 if (!adjust_side_bearings) {
385 get_kerning_pair (handle, out a, out b, out gr_left, out gr_right);
386 set_kerning_pair (a, b, ref gr_left, ref gr_right, val);
387 } else {
388 if (right_side_bearing) {
389 left_active_glyph.right_limit += val;
390 left_active_glyph.remove_lines ();
391 left_active_glyph.add_help_lines ();
392 left_active_glyph.update_other_spacing_classes ();
393 } else {
394 right_active_glyph.left_limit -= val;
395 right_active_glyph.remove_lines ();
396 right_active_glyph.add_help_lines ();
397 right_active_glyph.update_other_spacing_classes ();
398 }
399 }
400 }
401
402 /** Class based gpos kerning. */
403 public void set_kerning_pair (string a, string b,
404 ref GlyphRange? gr_left, ref GlyphRange? gr_right,
405 double val) {
406 double kern;
407 GlyphRange grl, grr;
408 KerningClasses classes;
409 string n, f;
410 bool has_kerning;
411 Font font;
412
413 font = current_font;
414 font.touch ();
415 classes = font.get_kerning_classes ();
416
417 kern = get_kerning_for_pair (a, b, gr_left, gr_right);
418
419 try {
420 if (gr_left == null) {
421 grl = new GlyphRange ();
422 grl.parse_ranges (a);
423 gr_left = grl; // update the range list
424 } else {
425 grl = (!) gr_left;
426 }
427
428 if (gr_right == null) {
429 grr = new GlyphRange ();
430 grr.parse_ranges (b);
431 gr_right = grr;
432 } else {
433 grr = (!) gr_right;
434 }
435
436 if (first_update) {
437 f = grl.get_all_ranges ();
438 n = grr.get_all_ranges ();
439 has_kerning = classes.has_kerning (f, n);
440 undo_items.add (new UndoItem (f, n, kern, has_kerning));
441 redo_items.clear ();
442 first_update = false;
443 }
444
445 classes.set_kerning (grl, grr, kern + val);
446 display_kerning_value (kern + val);
447 } catch (MarkupError e) {
448 // FIXME: unassigned glyphs and ligatures
449 warning (e.message);
450 }
451 }
452
453 public static double get_kerning_for_pair (string a, string b, GlyphRange? gr_left, GlyphRange? gr_right) {
454 KerningClasses k = BirdFont.get_current_font ().get_kerning_classes ();
455 return k.get_kerning_for_pair (a, b, gr_left, gr_right);
456 }
457
458 public void set_current_font (Font f) {
459 current_font = f;
460 }
461
462 public override void selected_canvas () {
463 Glyph g;
464 GlyphSequence w;
465 StringBuilder s = new StringBuilder ();
466 bool append_char = false;
467
468 current_font = BirdFont.get_current_font ();
469
470 KeyBindings.set_require_modifier (true);
471
472 g = MainWindow.get_current_glyph ();
473 s.append_unichar (g.get_unichar ());
474
475 if (row.size == 0) {
476 append_char = true;
477 }
478
479 if (append_char) {
480 w = new GlyphSequence ();
481 row.add (w);
482 w.glyph.insert (0, current_font.get_glyph (s.str));
483 }
484 }
485
486 public void add_kerning_class (int index) {
487 add_range (KerningTools.get_kerning_class (index));
488 }
489
490 public void add_range (GlyphRange range) {
491 Font font = current_font;
492 Glyph? glyph;
493
494 glyph = font.get_glyph_by_name (range.get_char (0));
495
496 if (glyph == null) {
497 warning ("Kerning range is not represented by a valid glyph.");
498 return;
499 }
500
501 row.get (0).glyph.add ((!) glyph);
502 row.get (0).ranges.add (range);
503
504 GlyphCanvas.redraw ();
505 }
506
507 void set_selected_handle (int handle) {
508 Glyph? g;
509 selected_handle = handle;
510 GlyphSequence sequence_with_ligatures;
511 Font font = BirdFont.get_current_font ();
512
513 sequence_with_ligatures = row.get (0).process_ligatures (font);
514
515 if (selected_handle <= 0) {
516 selected_handle = 1;
517 }
518
519 if (selected_handle >= sequence_with_ligatures.glyph.size) {
520 selected_handle = (int) sequence_with_ligatures.glyph.size - 1;
521 }
522
523 set_active_handle_index (handle);
524
525 if (0 <= selected_handle - 1 < sequence_with_ligatures.glyph.size) {
526 g = sequence_with_ligatures.glyph.get (selected_handle - 1);
527 if (g != null) {
528 left_active_glyph = (!) g;
529 }
530 }
531
532 if (0 <= selected_handle < sequence_with_ligatures.glyph.size) {
533 g = sequence_with_ligatures.glyph.get (selected_handle);
534 if (g != null) {
535 right_active_glyph = (!) g;
536 }
537 }
538
539 GlyphCanvas.redraw ();
540 }
541
542 public static void previous_pair () {
543 KerningDisplay kd;
544 FontDisplay fd;
545 SpacingTab st;
546
547 fd = MainWindow.get_current_display ();
548
549 if (fd is SpacingTab) {
550 st = (SpacingTab) fd;
551 if (!st.right_side_bearing) {
552 st.right_side_bearing = true;
553 } else {
554 st.right_side_bearing = false;
555 st.set_selected_handle (st.selected_handle - 1);
556 }
557 } else if (fd is KerningDisplay) {
558 kd = (KerningDisplay) fd;
559 kd.set_selected_handle (kd.selected_handle - 1);
560 }
561 }
562
563 public static void next_pair () {
564 KerningDisplay kd;
565 FontDisplay fd;
566 SpacingTab st;
567
568 fd = MainWindow.get_current_display ();
569
570 if (fd is SpacingTab) {
571 st = (SpacingTab) fd;
572 if (st.right_side_bearing) {
573 st.right_side_bearing = false;
574 } else {
575 st.right_side_bearing = true;
576 st.set_selected_handle (st.selected_handle + 1);
577 }
578 } else if (fd is KerningDisplay) {
579 kd = (KerningDisplay) fd;
580 kd.set_selected_handle (kd.selected_handle + 1);
581 }
582 }
583
584 private static string round (double d) {
585 char[] b = new char [22];
586 unowned string s = d.format (b, "%.2f");
587 string n = s.dup ();
588
589 n = n.replace (",", ".");
590
591 if (n == "-0.00") {
592 n = "0.00";
593 }
594
595 return n;
596 }
597
598 public override void key_press (uint keyval) {
599 unichar c;
600
601 if (MenuTab.suppress_event) { // don't update kerning while saving font
602 return;
603 }
604
605 c = (unichar) keyval;
606
607 if (suppress_input) {
608 return;
609 }
610
611 if ((keyval == 'u' || keyval == 'U') && KeyBindings.has_ctrl ()) {
612 insert_unichar ();
613 } else {
614 if (keyval == Key.LEFT && KeyBindings.modifier == NONE) {
615 first_update = true;
616 set_space (selected_handle, -1 / KerningTools.font_size);
617 }
618
619 if (keyval == Key.RIGHT && KeyBindings.modifier == NONE) {
620 first_update = true;
621 set_space (selected_handle, 1 / KerningTools.font_size);
622 }
623
624 if (KeyBindings.modifier == NONE
625 || KeyBindings.modifier == SHIFT
626 || KeyBindings.modifier == ALT) {
627
628 if (keyval == Key.BACK_SPACE && row.size > 0 && row.get (0).glyph.size > 0) {
629 row.get (0).glyph.remove_at (row.get (0).glyph.size - 1);
630 row.get (0).ranges.remove_at (row.get (0).ranges.size - 1);
631 }
632
633 if (row.size == 0 || c == Key.ENTER) {
634 new_line ();
635 }
636
637 add_character (c);
638 }
639 }
640
641 GlyphCanvas.redraw ();
642 }
643
644 public void insert_unichar () {
645 TextListener listener;
646 string submitted_value = "";
647 string unicodestart;
648
649 unicodestart = (KeyBindings.has_shift ()) ? "" : "U+";
650
651 listener = new TextListener (t_("Unicode"), unicodestart, t_("Insert"));
652
653 listener.signal_text_input.connect ((text) => {
654 submitted_value = text;
655
656 if (MenuTab.suppress_event) {
657 return;
658 }
659
660 GlyphCanvas.redraw ();
661 });
662
663 listener.signal_submit.connect (() => {
664 unichar c;
665 TabContent.hide_text_input ();
666
667 text_input = false;
668 suppress_input = false;
669
670 if (submitted_value.has_prefix ("u+") || submitted_value.has_prefix ("U+")) {
671 c = Font.to_unichar (submitted_value);
672 add_character (c);
673 } else {
674 add_text (submitted_value);
675 }
676 });
677
678 suppress_input = true;
679 text_input = true;
680 TabContent.show_text_input (listener);
681 }
682
683 public void new_line () {
684 row.insert (0, new GlyphSequence ());
685 }
686
687 void add_character (unichar c) {
688 Glyph? g;
689 string name;
690 Font f;
691
692 if (MenuTab.suppress_event) {
693 return;
694 }
695
696 f = current_font;
697
698 if (!is_modifier_key (c) && c.validate ()) {
699 name = f.get_name_for_character (c);
700 g = f.get_glyph_by_name (name);
701 inser_glyph (g);
702 }
703 }
704
705 public void inser_glyph (Glyph? g) {
706 if (g != null) {
707 row.get (0).glyph.add (g);
708 row.get (0).ranges.add (null);
709
710 set_selected_handle ((int) row.get (0).glyph.size - 1);
711 set_active_handle_index (selected_handle);
712 }
713 }
714
715 public override void motion_notify (double ex, double ey) {
716 double k, y;
717
718 if (MenuTab.suppress_event) {
719 return;
720 }
721
722 if (!moving) {
723 set_active_handle (ex, ey);
724 } else {
725 y = 1;
726
727 if (Math.fabs (ey - begin_handle_y) > 20) {
728 y = ((Math.fabs (ey - begin_handle_y) / 100) + 1);
729 }
730
731 k = (ex - last_handle_x) / y; // y-axis is for variable precision
732 k /= KerningTools.font_size;
733 set_space (selected_handle, k);
734 GlyphCanvas.redraw ();
735 }
736
737 last_handle_x = ex;
738 }
739
740 public void set_active_handle (double ex, double ey) {
741 double item_size = 1.0 / KerningTools.font_size;
742 double y = 100 * item_size;
743 double x = 20;
744 double w = 0;
745 double d, kern;
746 double min = double.MAX;
747 int i = 0;
748 int row_index = 0;
749 int col_index = 0;
750 double fs = KerningTools.font_size;
751 Glyph glyph = new Glyph.no_lines ("");
752 Font font = BirdFont.get_current_font ();
753
754 GlyphRange? gr_left, gr_right;
755
756 Glyph? prev = null;
757 string gl_name = "";
758 GlyphSequence word_with_ligatures;
759
760 foreach (GlyphSequence word in row) {
761 col_index = 0;
762
763 word_with_ligatures = word.process_ligatures (font);
764 foreach (Glyph? g in word_with_ligatures.glyph) {
765 if (g == null) {
766 w = 50;
767 warning ("glyph does not exist");
768 } else {
769 glyph = (!) g;
770 w = glyph.get_width ();
771 }
772
773 gl_name = glyph.get_name ();
774
775 if (prev == null && col_index != 0) {
776 warning (@"previous glyph does not exist row: $row_index column: $col_index");
777 }
778
779 if (prev == null || col_index == 0) {
780 kern = 0;
781 } else {
782 return_if_fail (col_index < word_with_ligatures.ranges.size);
783 return_if_fail (col_index - 1 >= 0);
784
785 gr_left = word_with_ligatures.ranges.get (col_index - 1);
786 gr_right = word_with_ligatures.ranges.get (col_index);
787
788 kern = get_kerning_for_pair (((!)prev).get_name (), ((!)g).get_name (), gr_left, gr_right);
789 }
790
791 d = Math.pow (fs * (x + kern) - ex, 2) + Math.pow (fs * (y - ey), 2);
792
793 if (d < min) {
794 min = d;
795
796 if (ex != fs * (x + kern)) { // don't swap direction after button release
797 right_side_bearing = ex < fs * (x + kern); // right or left side bearing handle
798 }
799
800 if (active_handle != i - row_index) {
801 set_active_handle_index (i - row_index);
802 GlyphCanvas.redraw ();
803 }
804
805 if (col_index == word.glyph.size || col_index == 0) {
806 set_active_handle_index (-1);
807 } else {
808 set_active_handle_index (active_handle + row_index);
809 }
810 }
811
812 prev = g;
813 x += w + kern;
814 i++;
815 col_index++;
816 }
817
818 row_index++;
819 y += MainWindow.get_current_glyph ().get_height () + 20;
820 x = 20;
821 }
822 }
823
824 public override void button_release (int button, double ex, double ey) {
825 set_active_handle (ex, ey);
826 moving = false;
827 first_update = true;
828
829 if (button == 3 || text_input) {
830 set_kerning_by_text ();
831 }
832 }
833
834 public void set_kerning_by_text () {
835 TextListener listener;
836 string kerning = @"$(get_kerning_for_handle (selected_handle))";
837
838 if (MenuTab.suppress_event) {
839 return;
840 }
841
842 if (selected_handle == -1) {
843 set_selected_handle (0);
844 }
845
846 listener = new TextListener (t_("Kerning"), kerning, t_("Close"));
847
848 listener.signal_text_input.connect ((text) => {
849 string submitted_value;
850 double parsed_value;
851
852 if (MenuTab.suppress_event) {
853 return;
854 }
855
856 submitted_value = text.replace (",", ".");
857 parsed_value = double.parse (submitted_value);
858 set_absolute_kerning (selected_handle, parsed_value);
859 GlyphCanvas.redraw ();
860 });
861
862 listener.signal_submit.connect (() => {
863 TabContent.hide_text_input ();
864 text_input = false;
865 suppress_input = false;
866 });
867
868 suppress_input = true;
869 text_input = true;
870 TabContent.show_text_input (listener);
871
872 GlyphCanvas.redraw ();
873 }
874
875 public override void button_press (uint button, double ex, double ey) {
876 set_active_handle (ex, ey);
877 set_selected_handle (active_handle);
878 begin_handle_x = ex;
879 begin_handle_y = ey;
880 last_handle_x = ex;
881 moving = true;
882 }
883
884 /** Insert text form clipboard. */
885 public void add_text (string t) {
886 int c;
887
888 if (MenuTab.suppress_event) {
889 return;
890 }
891
892 c = t.char_count ();
893 for (int i = 0; i <= c; i++) {
894 add_character (t.get_char (t.index_of_nth_char (i)));
895 }
896
897 GlyphCanvas.redraw ();
898 }
899
900 public override void undo () {
901 UndoItem ui;
902 UndoItem redo_state;
903
904 if (MenuTab.suppress_event) {
905 return;
906 }
907
908 if (undo_items.size == 0) {
909 return;
910 }
911
912 ui = undo_items.get (undo_items.size - 1);
913
914 redo_state = apply_undo (ui);
915 redo_items.add (redo_state);
916
917 undo_items.remove_at (undo_items.size - 1);
918 }
919
920 public override void redo () {
921 UndoItem ui;
922
923 if (MenuTab.suppress_event) {
924 return;
925 }
926
927 if (redo_items.size == 0) {
928 return;
929 }
930
931 ui = redo_items.get (redo_items.size - 1);
932 apply_undo (ui);
933 redo_items.remove_at (redo_items.size - 1);
934 }
935
936 /** @return redo state. */
937 public UndoItem apply_undo (UndoItem ui) {
938 KerningClasses classes = BirdFont.get_current_font ().get_kerning_classes ();
939 GlyphRange glyph_range_first, glyph_range_next;
940 Font font = current_font;
941 string l, r;
942 UndoItem redo_state = new UndoItem ("", "", 0, false);
943 double? k;
944
945 l = GlyphRange.unserialize (ui.first);
946 r = GlyphRange.unserialize (ui.next);
947
948 try {
949 glyph_range_first = new GlyphRange ();
950 glyph_range_next = new GlyphRange ();
951
952 glyph_range_first.parse_ranges (ui.first);
953 glyph_range_next.parse_ranges (ui.next);
954
955 if (!ui.has_kerning) {
956 if (glyph_range_first.is_class () || glyph_range_next.is_class ()) {
957 redo_state.first = glyph_range_first.get_all_ranges ();
958 redo_state.next = glyph_range_next.get_all_ranges ();
959 redo_state.has_kerning = true;
960 redo_state.kerning = classes.get_kerning_for_range (glyph_range_first, glyph_range_next);
961
962 classes.delete_kerning_for_class (ui.first, ui.next);
963 } else {
964
965 redo_state.first = ui.first;
966 redo_state.next = ui.next;
967 redo_state.has_kerning = true;
968 k = classes.get_kerning_for_single_glyphs (ui.first, ui.next);
969
970 if (k != null) {
971 redo_state.kerning = (!) k;
972 } else {
973 warning ("No kerning");
974 }
975
976 classes.delete_kerning_for_pair (ui.first, ui.next);
977 }
978 } else if (glyph_range_first.is_class () || glyph_range_next.is_class ()) {
979 glyph_range_first = new GlyphRange ();
980 glyph_range_next = new GlyphRange ();
981
982 glyph_range_first.parse_ranges (ui.first);
983 glyph_range_next.parse_ranges (ui.next);
984
985 redo_state.first = glyph_range_first.get_all_ranges ();
986 redo_state.next = glyph_range_next.get_all_ranges ();
987 k = classes.get_kerning_for_range (glyph_range_first, glyph_range_next);
988
989 if (k != null) {
990 redo_state.kerning = (!) k;
991 redo_state.has_kerning = true;
992 } else {
993 redo_state.has_kerning = false;
994 }
995
996 classes.set_kerning (glyph_range_first, glyph_range_next, ui.kerning);
997 } else {
998 redo_state.first = ui.first;
999 redo_state.next = ui.next;
1000 redo_state.has_kerning = true;
1001 k = classes.get_kerning_for_single_glyphs (ui.first, ui.next);
1002
1003 if (k != null) {
1004 redo_state.kerning = (!) k;
1005 redo_state.has_kerning = true;
1006 } else {
1007 redo_state.has_kerning = false;
1008 }
1009
1010 classes.set_kerning_for_single_glyphs (ui.first, ui.next, ui.kerning);
1011 }
1012 } catch (MarkupError e) {
1013 warning (e.message);
1014 }
1015
1016 font.touch ();
1017 GlyphCanvas.redraw ();
1018
1019 return redo_state;
1020 }
1021
1022 public override void zoom_in () {
1023 KerningTools.font_size += 0.1;
1024
1025 if (KerningTools.font_size > 3) {
1026 KerningTools.font_size = 3;
1027 }
1028
1029 KerningTools.zoom_bar.set_zoom (KerningTools.font_size / 3);
1030 GlyphCanvas.redraw ();
1031 }
1032
1033 public override void zoom_out () {
1034 KerningTools.font_size -= 0.1;
1035
1036 if (KerningTools.font_size < 0.3) {
1037 KerningTools.font_size = 0.3;
1038 }
1039
1040 KerningTools.zoom_bar.set_zoom (KerningTools.font_size / 3);
1041 GlyphCanvas.redraw ();
1042 }
1043
1044 public override bool needs_modifier () {
1045 return true;
1046 }
1047
1048 public class UndoItem : GLib.Object {
1049 public string first;
1050 public string next;
1051 public double kerning;
1052 public bool has_kerning;
1053
1054 public UndoItem (string first, string next, double kerning, bool has_kerning) {
1055 this.first = first;
1056 this.next = next;
1057 this.kerning = kerning;
1058 this.has_kerning = has_kerning;
1059 }
1060 }
1061 }
1062
1063 }
1064