The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

KerningClasses.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of libbirdfont/KerningClasses.vala.
Fix zero length handles
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