.
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 += (50 / KerningTools.font_size) * 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 w = 0;
742 double d, kern;
743 double min = double.MAX;
744 int i = 0;
745 int row_index = 0;
746 int col_index = 0;
747 Glyph glyph = new Glyph.no_lines ("");
748 Font font = BirdFont.get_current_font ();
749 double fs = KerningTools.font_size;
750 double x = 20;
751
752 GlyphRange? gr_left, gr_right;
753
754 Glyph? prev = null;
755 string gl_name = "";
756 GlyphSequence word_with_ligatures;
757
758 foreach (GlyphSequence word in row) {
759 col_index = 0;
760
761 word_with_ligatures = word.process_ligatures (font);
762 foreach (Glyph? g in word_with_ligatures.glyph) {
763 if (g == null) {
764 w = 50;
765 warning ("glyph does not exist");
766 } else {
767 glyph = (!) g;
768 w = glyph.get_width ();
769 }
770
771 gl_name = glyph.get_name ();
772
773 if (prev == null && col_index != 0) {
774 warning (@"previous glyph does not exist row: $row_index column: $col_index");
775 }
776
777 if (prev == null || col_index == 0) {
778 kern = 0;
779 } else {
780 return_if_fail (col_index < word_with_ligatures.ranges.size);
781 return_if_fail (col_index - 1 >= 0);
782
783 gr_left = word_with_ligatures.ranges.get (col_index - 1);
784 gr_right = word_with_ligatures.ranges.get (col_index);
785
786 kern = get_kerning_for_pair (((!)prev).get_name (), ((!)g).get_name (), gr_left, gr_right);
787 }
788
789 d = Math.pow (fs * (x + kern) - ex, 2);
790
791 if (d < min) {
792 min = d;
793
794 if (ex != fs * (x + kern)) { // don't swap direction after button release
795 right_side_bearing = ex < fs * (x + kern); // right or left side bearing handle
796 }
797
798 if (active_handle != i - row_index) {
799 set_active_handle_index (i - row_index);
800 GlyphCanvas.redraw ();
801 }
802
803 if (col_index == word.glyph.size || col_index == 0) {
804 set_active_handle_index (-1);
805 } else {
806 set_active_handle_index (active_handle + row_index);
807 }
808 }
809
810 prev = g;
811 x += w + kern;
812 i++;
813 col_index++;
814 }
815
816 row_index++;
817 x = 20;
818 return;
819 }
820 }
821
822 public override void button_release (int button, double ex, double ey) {
823 set_active_handle (ex, ey);
824 moving = false;
825 first_update = true;
826
827 if (button == 3 || text_input) {
828 set_kerning_by_text ();
829 }
830 }
831
832 public void set_kerning_by_text () {
833 TextListener listener;
834 string kerning = @"$(get_kerning_for_handle (selected_handle))";
835
836 if (MenuTab.suppress_event) {
837 return;
838 }
839
840 if (selected_handle == -1) {
841 set_selected_handle (0);
842 }
843
844 listener = new TextListener (t_("Kerning"), kerning, t_("Close"));
845
846 listener.signal_text_input.connect ((text) => {
847 string submitted_value;
848 double parsed_value;
849
850 if (MenuTab.suppress_event) {
851 return;
852 }
853
854 submitted_value = text.replace (",", ".");
855 parsed_value = double.parse (submitted_value);
856 set_absolute_kerning (selected_handle, parsed_value);
857 GlyphCanvas.redraw ();
858 });
859
860 listener.signal_submit.connect (() => {
861 TabContent.hide_text_input ();
862 text_input = false;
863 suppress_input = false;
864 });
865
866 suppress_input = true;
867 text_input = true;
868 TabContent.show_text_input (listener);
869
870 GlyphCanvas.redraw ();
871 }
872
873 public override void button_press (uint button, double ex, double ey) {
874 set_active_handle (ex, ey);
875 set_selected_handle (active_handle);
876 begin_handle_x = ex;
877 begin_handle_y = ey;
878 last_handle_x = ex;
879 moving = true;
880 }
881
882 /** Insert text form clipboard. */
883 public void add_text (string t) {
884 int c;
885
886 if (MenuTab.suppress_event) {
887 return;
888 }
889
890 c = t.char_count ();
891 for (int i = 0; i <= c; i++) {
892 add_character (t.get_char (t.index_of_nth_char (i)));
893 }
894
895 GlyphCanvas.redraw ();
896 }
897
898 public override void undo () {
899 UndoItem ui;
900 UndoItem redo_state;
901
902 if (MenuTab.suppress_event) {
903 return;
904 }
905
906 if (undo_items.size == 0) {
907 return;
908 }
909
910 ui = undo_items.get (undo_items.size - 1);
911
912 redo_state = apply_undo (ui);
913 redo_items.add (redo_state);
914
915 undo_items.remove_at (undo_items.size - 1);
916 }
917
918 public override void redo () {
919 UndoItem ui;
920
921 if (MenuTab.suppress_event) {
922 return;
923 }
924
925 if (redo_items.size == 0) {
926 return;
927 }
928
929 ui = redo_items.get (redo_items.size - 1);
930 apply_undo (ui);
931 redo_items.remove_at (redo_items.size - 1);
932 }
933
934 /** @return redo state. */
935 public UndoItem apply_undo (UndoItem ui) {
936 KerningClasses classes = BirdFont.get_current_font ().get_kerning_classes ();
937 GlyphRange glyph_range_first, glyph_range_next;
938 Font font = current_font;
939 string l, r;
940 UndoItem redo_state = new UndoItem ("", "", 0, false);
941 double? k;
942
943 l = GlyphRange.unserialize (ui.first);
944 r = GlyphRange.unserialize (ui.next);
945
946 try {
947 glyph_range_first = new GlyphRange ();
948 glyph_range_next = new GlyphRange ();
949
950 glyph_range_first.parse_ranges (ui.first);
951 glyph_range_next.parse_ranges (ui.next);
952
953 if (!ui.has_kerning) {
954 if (glyph_range_first.is_class () || glyph_range_next.is_class ()) {
955 redo_state.first = glyph_range_first.get_all_ranges ();
956 redo_state.next = glyph_range_next.get_all_ranges ();
957 redo_state.has_kerning = true;
958 redo_state.kerning = classes.get_kerning_for_range (glyph_range_first, glyph_range_next);
959
960 classes.delete_kerning_for_class (ui.first, ui.next);
961 } else {
962
963 redo_state.first = ui.first;
964 redo_state.next = ui.next;
965 redo_state.has_kerning = true;
966 k = classes.get_kerning_for_single_glyphs (ui.first, ui.next);
967
968 if (k != null) {
969 redo_state.kerning = (!) k;
970 } else {
971 warning ("No kerning");
972 }
973
974 classes.delete_kerning_for_pair (ui.first, ui.next);
975 }
976 } else if (glyph_range_first.is_class () || glyph_range_next.is_class ()) {
977 glyph_range_first = new GlyphRange ();
978 glyph_range_next = new GlyphRange ();
979
980 glyph_range_first.parse_ranges (ui.first);
981 glyph_range_next.parse_ranges (ui.next);
982
983 redo_state.first = glyph_range_first.get_all_ranges ();
984 redo_state.next = glyph_range_next.get_all_ranges ();
985 k = classes.get_kerning_for_range (glyph_range_first, glyph_range_next);
986
987 if (k != null) {
988 redo_state.kerning = (!) k;
989 redo_state.has_kerning = true;
990 } else {
991 redo_state.has_kerning = false;
992 }
993
994 classes.set_kerning (glyph_range_first, glyph_range_next, ui.kerning);
995 } else {
996 redo_state.first = ui.first;
997 redo_state.next = ui.next;
998 redo_state.has_kerning = true;
999 k = classes.get_kerning_for_single_glyphs (ui.first, ui.next);
1000
1001 if (k != null) {
1002 redo_state.kerning = (!) k;
1003 redo_state.has_kerning = true;
1004 } else {
1005 redo_state.has_kerning = false;
1006 }
1007
1008 classes.set_kerning_for_single_glyphs (ui.first, ui.next, ui.kerning);
1009 }
1010 } catch (MarkupError e) {
1011 warning (e.message);
1012 }
1013
1014 font.touch ();
1015 GlyphCanvas.redraw ();
1016
1017 return redo_state;
1018 }
1019
1020 public override void zoom_in () {
1021 KerningTools.font_size += 0.1;
1022
1023 if (KerningTools.font_size > 3) {
1024 KerningTools.font_size = 3;
1025 }
1026
1027 KerningTools.zoom_bar.set_zoom (KerningTools.font_size / 3);
1028 GlyphCanvas.redraw ();
1029 }
1030
1031 public override void zoom_out () {
1032 KerningTools.font_size -= 0.1;
1033
1034 if (KerningTools.font_size < 0.3) {
1035 KerningTools.font_size = 0.3;
1036 }
1037
1038 KerningTools.zoom_bar.set_zoom (KerningTools.font_size / 3);
1039 GlyphCanvas.redraw ();
1040 }
1041
1042 public override bool needs_modifier () {
1043 return true;
1044 }
1045
1046 public class UndoItem : GLib.Object {
1047 public string first;
1048 public string next;
1049 public double kerning;
1050 public bool has_kerning;
1051
1052 public UndoItem (string first, string next, double kerning, bool has_kerning) {
1053 this.first = first;
1054 this.next = next;
1055 this.kerning = kerning;
1056 this.has_kerning = has_kerning;
1057 }
1058 }
1059 }
1060
1061 }
1062