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