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