.
1 /*
2 Copyright (C) 2013, 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 using B;
17 using Math;
18 using Gee;
19
20 namespace BirdFont {
21
22 public class KerningClasses : GLib.Object {
23
24 // kerning for classes
25 public Gee.ArrayList<GlyphRange> classes_first;
26 public Gee.ArrayList<GlyphRange> classes_last;
27 public Gee.ArrayList<Kerning> classes_kerning;
28
29 // kerning for single glyphs
30 Gee.HashMap<string, double?> single_kerning;
31 public Gee.ArrayList<string> single_kerning_letters_left;
32 public Gee.ArrayList<string> single_kerning_letters_right;
33
34 public delegate void KerningIterator (KerningPair list);
35 public delegate void KerningClassIterator (string left, string right, double kerning);
36
37 /** Ensure that map iterator is not invalidated because of inserts. */
38 bool protect_map = false;
39
40 public unowned Font font;
41
42 public KerningClasses (Font font) {
43 this.font = font;
44
45 font.font_deleted.connect (() => {
46 this.font = Font.empty;
47 });
48
49 classes_first = new Gee.ArrayList<GlyphRange> ();
50 classes_last = new Gee.ArrayList<GlyphRange> ();
51 classes_kerning = new Gee.ArrayList<Kerning> ();
52
53 single_kerning_letters_left = new Gee.ArrayList<string> ();
54 single_kerning_letters_right = new Gee.ArrayList<string> ();
55
56 single_kerning = new HashMap<string, double?> ();
57 }
58
59 public void update_range (GlyphRange old, GlyphRange new_range) {
60 string o = old.get_all_ranges ();
61
62 foreach (GlyphRange gr in classes_first) {
63 try {
64 if (gr.get_all_ranges () == o) {
65 gr.parse_ranges (new_range.get_all_ranges ());
66 }
67 } catch (GLib.MarkupError e) {
68 warning (e.message);
69 }
70 }
71
72 foreach (GlyphRange gr in classes_last) {
73 try {
74 if (gr.get_all_ranges () == o) {
75 gr.parse_ranges (new_range.get_all_ranges ());
76 }
77 } catch (GLib.MarkupError e) {
78 warning (e.message);
79 }
80 }
81 }
82
83 /** Class based gpos kerning. */
84 public double get_kerning_for_pair (string a, string b, GlyphRange? gr_left, GlyphRange? gr_right) {
85 double k = 0;
86 GlyphRange grl, grr;
87
88 try {
89 if (gr_left == null) {
90 grl = new GlyphRange ();
91 grl.parse_ranges (a);
92 } else {
93 grl = (!) gr_left;
94 }
95
96 if (gr_right == null) {
97 grr = new GlyphRange ();
98 grr.parse_ranges (a);
99 } else {
100 grr = (!) gr_right;
101 }
102
103 if (gr_left == null && gr_right == null) {
104 k = get_kerning (a, b);
105 return k;
106 }
107
108 if (gr_left != null && gr_right != null) {
109 return get_kerning_for_range (grl, grr);
110 }
111
112 if (gr_left != null && gr_right == null) {
113 return get_kern_for_range_to_char (grl, b);
114 }
115
116 if (gr_left == null && gr_right != null) {
117 return get_kern_for_char_to_range (a, grr);
118 }
119 } catch (MarkupError e) {
120 warning (e.message);
121 }
122
123 if (unlikely (k == 0)) {
124 warning ("no kerning found");
125 }
126
127 return 0;
128 }
129
130 public void update_space_class (string c) {
131 double? k;
132
133 foreach (string l in single_kerning_letters_left) {
134 k = get_kerning_for_single_glyphs (l, c);
135
136 if (k != null) {
137 set_kerning_for_single_glyphs (l, c, (!) k);
138 }
139 }
140
141 foreach (string r in single_kerning_letters_right) {
142 k = get_kerning_for_single_glyphs (c, r);
143
144 if (k != null) {
145 set_kerning_for_single_glyphs (c, r, (!) k);
146 }
147 }
148 }
149
150 public double? get_kerning_for_single_glyphs (string first, string next) {
151 double? k = null;
152 string left = GlyphRange.serialize (first);
153 string right = GlyphRange.serialize (next);
154
155 foreach (string l in get_spacing_class (left)) {
156 foreach (string r in get_spacing_class (right)) {
157 k = single_kerning.get (@"$l - $r");
158 }
159 }
160
161 return k;
162 }
163
164 private Gee.ArrayList<string> get_spacing_class (string c) {
165 return font.get_spacing ().get_all_connections (c);
166 }
167
168 public void set_kerning_for_single_glyphs (string le, string ri, double k) {
169 string left = GlyphRange.serialize (le);
170 string right = GlyphRange.serialize (ri);
171 string cleft = (!)GlyphRange.unserialize (left);
172 string cright = (!)GlyphRange.unserialize (right); // FIXME: get_char?
173
174 if (protect_map) {
175 warning ("Map is protected.");
176 return;
177 }
178
179 foreach (string l in get_spacing_class (cleft)) {
180 foreach (string r in get_spacing_class (cright)) {
181 if (!single_kerning_letters_left.contains (cleft)) {
182 single_kerning_letters_left.add (cleft);
183 }
184
185 if (!single_kerning_letters_right.contains (cright)) {
186 single_kerning_letters_right.add (cright);
187 }
188
189 left = GlyphRange.serialize (l);
190 right = GlyphRange.serialize (r);
191 single_kerning.set (@"$left - $right", k);
192 }
193 }
194 }
195
196 public void set_kerning (GlyphRange left_range, GlyphRange right_range, double k, int class_index = -1) {
197 int index;
198
199 if (left_range.get_length () == 0 || right_range.get_length () == 0) {
200 warning ("no glyphs");
201 return;
202 }
203
204 if (protect_map) {
205 warning ("Map is protected.");
206 return;
207 }
208
209 if (!left_range.is_class () && !right_range.is_class ()) {
210 set_kerning_for_single_glyphs (left_range.get_all_ranges (), right_range.get_all_ranges (), k);
211 return;
212 }
213
214 index = get_kerning_item_index (left_range, right_range);
215
216 // keep the list sorted (classes first then single glyphs)
217 if (index == -1) {
218 if (class_index < 0) {
219 classes_first.add (left_range);
220 classes_last.add (right_range);
221 classes_kerning.add (new Kerning (k));
222 } else {
223 classes_first.insert (class_index, left_range);
224 classes_last.insert (class_index, right_range);
225 classes_kerning.insert (class_index, new Kerning (k));
226 }
227 } else {
228 return_if_fail (0 <= index < classes_first.size);
229 classes_kerning.get (index).val = k;
230 }
231 }
232
233 public bool has_kerning (string first, string next) {
234 string f = "";
235 string n = "";
236 GlyphRange gr;
237 GlyphRange gl;
238 int len;
239
240 foreach (string l in get_spacing_class (first)) {
241 foreach (string r in get_spacing_class (next)) {
242 f = GlyphRange.serialize (l);
243 n = GlyphRange.serialize (r);
244 if (single_kerning.has_key (@"$f - $n")) {
245 return true;
246 }
247 }
248 }
249
250 len = (int) classes_first.size;
251
252 return_val_if_fail (len == classes_last.size, false);
253 return_val_if_fail (len == classes_kerning.size, false);
254
255 for (int i = len - 1; i >= 0; i--) {
256 gl = classes_first.get (i);
257 gr = classes_last.get (i);
258
259 if (gl.has_character (first)
260 && gr.has_character (next)) {
261
262 return true;
263 }
264 }
265
266 return false;
267 }
268
269 public double get_kerning_for_range (GlyphRange range_first, GlyphRange range_last) {
270 GlyphRange r;
271 GlyphRange l;
272 int len = (int) classes_first.size;
273
274 len = (int) classes_first.size;
275 return_val_if_fail (len == classes_last.size, 0);
276 return_val_if_fail (len == classes_kerning.size, 0);
277
278 if (!(range_first.is_class () || range_last.is_class ())) {
279 get_kerning_for_single_glyphs (range_first.get_all_ranges (), range_last.get_all_ranges ());
280 return 0;
281 }
282
283 for (int i = len - 1; i >= 0; i--) { // last class is applied first
284 l = classes_first.get (i);
285 r = classes_last.get (i);
286
287 if (l.get_all_ranges () == range_first.get_all_ranges ()
288 && r.get_all_ranges () == range_last.get_all_ranges ()) {
289 return classes_kerning.get (i).val;
290 }
291 }
292
293 return 0;
294 }
295
296 public int get_kerning_item_index (GlyphRange range_first, GlyphRange range_last) {
297 GlyphRange r;
298 GlyphRange l;
299 int len = (int) classes_first.size;
300
301 len = (int) classes_first.size;
302 return_val_if_fail (len == classes_last.size, 0);
303 return_val_if_fail (len == classes_kerning.size, 0);
304
305 if (!(range_first.is_class () || range_last.is_class ())) {
306 warning (@"Expecting a class, $(range_first.get_all_ranges ()) and $(range_last.get_all_ranges ())");
307 return -1;
308 }
309
310 for (int i = len - 1; i >= 0; i--) {
311 l = classes_first.get (i);
312 r = classes_last.get (i);
313
314 if (l.get_all_ranges () == range_first.get_all_ranges ()
315 && r.get_all_ranges () == range_last.get_all_ranges ()) {
316 return i;
317 }
318 }
319
320 return -1;
321 }
322
323 public double get_kerning (string left_glyph, string right_glyph) {
324 GlyphRange r;
325 GlyphRange l;
326 int len = (int) classes_first.size;
327 double? d;
328
329 d = get_kerning_for_single_glyphs (left_glyph, right_glyph);
330 if (d != null) {
331 return (!)d;
332 }
333
334 len = (int)classes_first.size;
335 return_val_if_fail (len == classes_last.size, 0);
336 return_val_if_fail (len == classes_kerning.size, 0);
337
338 for (int i = len - 1; i >= 0; i--) {
339 l = classes_first.get (i);
340 r = classes_last.get (i);
341
342 if (l.has_character (left_glyph)
343 && r.has_character (right_glyph)) {
344
345 return classes_kerning.get (i).val;
346 }
347 }
348
349 return 0;
350 }
351
352 public double get_kern_for_range_to_char (GlyphRange left_range, string right_char) {
353 GlyphRange r;
354 GlyphRange l;
355 int len = (int) classes_first.size;
356
357 len = (int)classes_first.size;
358 return_val_if_fail (len == classes_last.size, 0);
359 return_val_if_fail (len == classes_kerning.size, 0);
360
361 if (unlikely (!left_range.is_class ())) {
362 warning (@"Expecting a class, $(left_range.get_all_ranges ())");
363 return -1;
364 }
365
366 foreach (string right in get_spacing_class (right_char)) {
367 for (int i = len - 1; i >= 0; i--) {
368 l = classes_first.get (i);
369 r = classes_last.get (i);
370
371 if (l.get_all_ranges () == left_range.get_all_ranges ()
372 && r.has_character (right)) {
373 return classes_kerning.get (i).val;
374 }
375 }
376 }
377
378 return 0;
379 }
380
381 public double get_kern_for_char_to_range (string left_char, GlyphRange right_range) {
382 GlyphRange r;
383 GlyphRange l;
384 int len = (int) classes_first.size;
385
386 len = (int)classes_first.size;
387 return_val_if_fail (len == classes_last.size, 0);
388 return_val_if_fail (len == classes_kerning.size, 0);
389
390 if (!right_range.is_class ()) {
391 warning ("Expecting a class");
392 return 0;
393 }
394
395 foreach (string left in get_spacing_class (left_char)) {
396 for (int i = len - 1; i >= 0; i--) {
397 l = classes_first.get (i);
398 r = classes_last.get (i);
399
400 if (l.has_character (left)
401 && r.get_all_ranges () == right_range.get_all_ranges ()) {
402 return classes_kerning.get (i).val;
403 }
404 }
405 }
406
407 return 0;
408 }
409
410 public void print_all () {
411 print ("Kernings classes:\n");
412 for (int i = 0; i < classes_first.size; i++) {
413 print (classes_first.get (i).get_all_ranges ());
414 print ("\t\t");
415 print (classes_last.get (i).get_all_ranges ());
416 print ("\t\t");
417 print (@"$(classes_kerning.get (i).val)");
418 print ("\t\t");
419
420 if (classes_first.get (i).is_class () || classes_last.get (i).is_class ()) {
421 print ("class");
422 }
423
424 print ("\n");
425 }
426
427 print ("\n");
428 print ("Kernings for pairs:\n");
429 if (!set_protect_map (true)) {
430 warning ("Map is protected.");
431 return;
432 }
433
434 foreach (string key in single_kerning.keys) {
435 print (key);
436 print ("\t\t");
437 print (@"$((!) single_kerning.get (key))\n");
438 }
439
440 set_protect_map (false);
441
442 print ("\n");
443 print ("Generated table:\n");
444 all_pairs ((k) => {
445 k.print ();
446 });
447 }
448
449 public void get_classes (KerningClassIterator kerningIterator) {
450 for (int i = 0; i < classes_first.size; i++) {
451 kerningIterator (classes_first.get (i).get_all_ranges (),
452 classes_last.get (i).get_all_ranges (),
453 classes_kerning.get (i).val);
454 }
455 }
456
457 public void get_single_position_pairs (KerningClassIterator kerningIterator) {
458 double k = 0;
459
460 if (!set_protect_map (true)) {
461 warning ("Map is protected.");
462 return;
463 }
464
465 foreach (string key in single_kerning.keys) {
466 var chars = key.split (" - ");
467
468 if (chars.length != 2) {
469 warning (@"Can not parse characters from key: $key");
470 } else {
471 k = (!) single_kerning.get (key);
472 kerningIterator (chars[0], chars[1], k);
473 }
474 }
475
476 set_protect_map (false);
477 }
478
479 public void each_pair (KerningClassIterator iter) {
480 all_pairs ((kl) => {
481 Glyph g2;
482 KerningPair kerning_list = kl;
483 string g1 = kerning_list.character.get_name ();
484 Kerning kerning;
485 int i = 0;
486
487 return_if_fail (kerning_list.kerning.size > 0);
488
489 foreach (Kerning k in kerning_list.kerning) {
490 g2 = k.get_glyph ();
491 kerning = kerning_list.kerning.get (i);
492 iter (g1, g2.get_name (), kerning.val);
493 }
494 });
495 }
496
497 public void all_pairs (KerningIterator kerningIterator) {
498 Gee.ArrayList<Glyph> left_glyphs = new Gee.ArrayList<Glyph> ();
499 Gee.ArrayList<KerningPair> pairs = new Gee.ArrayList<KerningPair> ();
500 double kerning;
501 string right;
502 string name;
503 Glyph? g;
504
505 // Create a list of first glyph in all pairs
506 foreach (GlyphRange r in classes_first) {
507 foreach (UniRange u in r.ranges) {
508 for (unichar c = u.start; c <= u.stop; c++) {
509 name = (!)c.to_string ();
510 g = font.get_glyph (name);
511 if (g != null && !left_glyphs.contains ((!) g)) {
512 left_glyphs.add ((!) g);
513 }
514 }
515 }
516
517 foreach (string n in r.unassigned) {
518 g = font.get_glyph (n);
519 if (g != null && !left_glyphs.contains ((!) g)) {
520 left_glyphs.add ((!) g);
521 }
522 }
523 }
524
525 foreach (string n in single_kerning_letters_left) {
526 g = font.get_glyph (n);
527 if (g != null && !left_glyphs.contains ((!) g)) {
528 left_glyphs.add ((!) g);
529 }
530 }
531
532 // add the right hand glyph and the kerning value
533 foreach (Glyph character in left_glyphs) {
534 KerningPair kl = new KerningPair (character);
535
536 foreach (GlyphRange r in classes_last) {
537 foreach (UniRange u in r.ranges) {
538 for (unichar c = u.start; c <= u.stop; c++) {
539 right = (!)c.to_string ();
540
541 if (font.has_glyph (right) && has_kerning (character.get_name (), right)) {
542 kerning = get_kerning (character.get_name (), right);
543 kl.add_unique ((!) font.get_glyph (right), kerning);
544 }
545 }
546 }
547
548 foreach (string n in r.unassigned) {
549 if (font.has_glyph (n) && has_kerning (character.get_name (), n)) {
550 kerning = get_kerning (character.get_name (), n);
551 kl.add_unique ((!) font.get_glyph (n), kerning);
552 }
553 }
554 }
555
556 // TODO: The get_kerning () function is still slow. Optimize it.
557 foreach (string right_glyph_name in single_kerning_letters_right) {
558 Glyph? gl = font.get_glyph (right_glyph_name);
559 if (gl != null && has_kerning (character.get_name (), right_glyph_name)) {
560 kerning = get_kerning (character.get_name (), right_glyph_name);
561 kl.add_unique ((!) gl , kerning);
562 }
563 }
564
565 if (kl.kerning.size > 0) {
566 pairs.add (kl);
567 }
568
569 if (kl.kerning.size == 0) {
570 warning (@"No kerning pairs for character: $((kl.character.get_name ()))");
571 }
572
573 kl.sort ();
574 }
575
576 // obtain the kerning value
577 foreach (KerningPair p in pairs) {
578 kerningIterator (p);
579 }
580 }
581
582 private bool set_protect_map (bool p) {
583 if (unlikely (p && protect_map)) {
584 warning ("Map is already protected.");
585 return false;
586 }
587
588 protect_map = p;
589 return true;
590 }
591
592 public void delete_kerning_for_class (string left, string right) {
593 int i = 0;
594 int index = -1;
595
596 get_classes ((l, r, kerning) => {
597 if (left == l && r == right) {
598 index = i;
599 }
600 i++;
601 });
602
603 if (unlikely (index < 0)) {
604 warning (@"Kerning class not found for $left to $right");
605 return;
606 }
607
608 classes_first.remove_at (index);
609 classes_last.remove_at (index);
610 classes_kerning.remove_at (index);
611 }
612
613 public void delete_kerning_for_pair (string left, string right) {
614 foreach (string l in get_spacing_class (left)) {
615 foreach (string r in get_spacing_class (right)) {
616 delete_kerning_for_one_pair (l, r);
617 }
618 }
619 }
620
621 private void delete_kerning_for_one_pair (string left, string right) {
622 bool has_left, has_right;
623 string[] p;
624
625 single_kerning.unset (@"$left - $right");
626
627 has_left = false;
628 has_right = false;
629
630 foreach (string k in single_kerning.keys) {
631 p = k.split (" - ");
632 return_if_fail (p.length == 2);
633
634 if (p[0] == left) {
635 has_left = true;
636 }
637
638 if (p[1] == right) {
639 has_right = true;
640 }
641 }
642
643 if (!has_left) {
644 single_kerning_letters_left.remove (left);
645 }
646
647 if (!has_right) {
648 single_kerning_letters_right.remove (left);
649 }
650 }
651
652 public void remove_all_pairs () {
653 if (protect_map) {
654 warning ("Map is protected.");
655 return;
656 }
657
658 classes_first.clear ();
659 classes_last.clear ();
660 classes_kerning.clear ();
661 single_kerning_letters_left.clear ();
662 single_kerning_letters_right.clear ();
663
664 GlyphCanvas.redraw ();
665
666 if (!is_null (MainWindow.get_toolbox ())) { // FIXME: reorganize
667 Toolbox.redraw_tool_box ();
668 }
669
670 single_kerning.clear ();
671 }
672
673 public uint get_number_of_pairs () {
674 return single_kerning.keys.size + classes_first.size;
675 }
676 }
677
678 }
679