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