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 memory leak
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 unowned 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 gr; 233 GlyphRange gl; 234 int len; 235 236 foreach (string l in get_spacing_class (first)) { 237 foreach (string r in get_spacing_class (next)) { 238 f = GlyphRange.serialize (l); 239 n = GlyphRange.serialize (r); 240 if (single_kerning.has_key (@"$f - $n")) { 241 return true; 242 } 243 } 244 } 245 246 len = (int) classes_first.size; 247 248 return_val_if_fail (len == classes_last.size, false); 249 return_val_if_fail (len == classes_kerning.size, false); 250 251 for (int i = len - 1; i >= 0; i--) { 252 gl = classes_first.get (i); 253 gr = classes_last.get (i); 254 255 if (gl.has_character (first) 256 && gr.has_character (next)) { 257 258 return true; 259 } 260 } 261 262 return false; 263 } 264 265 public double get_kerning_for_range (GlyphRange range_first, GlyphRange range_last) { 266 GlyphRange r; 267 GlyphRange l; 268 int len = (int) classes_first.size; 269 270 len = (int) classes_first.size; 271 return_val_if_fail (len == classes_last.size, 0); 272 return_val_if_fail (len == classes_kerning.size, 0); 273 274 if (!(range_first.is_class () || range_last.is_class ())) { 275 get_kerning_for_single_glyphs (range_first.get_all_ranges (), range_last.get_all_ranges ()); 276 return 0; 277 } 278 279 for (int i = len - 1; i >= 0; i--) { // last class is applied first 280 l = classes_first.get (i); 281 r = classes_last.get (i); 282 283 if (l.get_all_ranges () == range_first.get_all_ranges () 284 && r.get_all_ranges () == range_last.get_all_ranges ()) { 285 return classes_kerning.get (i).val; 286 } 287 } 288 289 return 0; 290 } 291 292 public int get_kerning_item_index (GlyphRange range_first, GlyphRange range_last) { 293 GlyphRange r; 294 GlyphRange l; 295 int len = (int) classes_first.size; 296 297 len = (int) classes_first.size; 298 return_val_if_fail (len == classes_last.size, 0); 299 return_val_if_fail (len == classes_kerning.size, 0); 300 301 if (!(range_first.is_class () || range_last.is_class ())) { 302 warning (@"Expecting a class, $(range_first.get_all_ranges ()) and $(range_last.get_all_ranges ())"); 303 return -1; 304 } 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.get_all_ranges () == range_first.get_all_ranges () 311 && r.get_all_ranges () == range_last.get_all_ranges ()) { 312 return i; 313 } 314 } 315 316 return -1; 317 } 318 319 public double get_kerning (string left_glyph, string right_glyph) { 320 GlyphRange r; 321 GlyphRange l; 322 int len = (int) classes_first.size; 323 double? d; 324 325 d = get_kerning_for_single_glyphs (left_glyph, right_glyph); 326 if (d != null) { 327 return (!)d; 328 } 329 330 len = (int)classes_first.size; 331 return_val_if_fail (len == classes_last.size, 0); 332 return_val_if_fail (len == classes_kerning.size, 0); 333 334 for (int i = len - 1; i >= 0; i--) { 335 l = classes_first.get (i); 336 r = classes_last.get (i); 337 338 if (l.has_character (left_glyph) 339 && r.has_character (right_glyph)) { 340 341 return classes_kerning.get (i).val; 342 } 343 } 344 345 return 0; 346 } 347 348 public double get_kern_for_range_to_char (GlyphRange left_range, string right_char) { 349 GlyphRange r; 350 GlyphRange l; 351 int len = (int) classes_first.size; 352 353 len = (int)classes_first.size; 354 return_val_if_fail (len == classes_last.size, 0); 355 return_val_if_fail (len == classes_kerning.size, 0); 356 357 if (unlikely (!left_range.is_class ())) { 358 warning (@"Expecting a class, $(left_range.get_all_ranges ())"); 359 return -1; 360 } 361 362 foreach (string right in get_spacing_class (right_char)) { 363 for (int i = len - 1; i >= 0; i--) { 364 l = classes_first.get (i); 365 r = classes_last.get (i); 366 367 if (l.get_all_ranges () == left_range.get_all_ranges () 368 && r.has_character (right)) { 369 return classes_kerning.get (i).val; 370 } 371 } 372 } 373 374 return 0; 375 } 376 377 public double get_kern_for_char_to_range (string left_char, GlyphRange right_range) { 378 GlyphRange r; 379 GlyphRange l; 380 int len = (int) classes_first.size; 381 382 len = (int)classes_first.size; 383 return_val_if_fail (len == classes_last.size, 0); 384 return_val_if_fail (len == classes_kerning.size, 0); 385 386 if (!right_range.is_class ()) { 387 warning ("Expecting a class"); 388 return 0; 389 } 390 391 foreach (string left in get_spacing_class (left_char)) { 392 for (int i = len - 1; i >= 0; i--) { 393 l = classes_first.get (i); 394 r = classes_last.get (i); 395 396 if (l.has_character (left) 397 && r.get_all_ranges () == right_range.get_all_ranges ()) { 398 return classes_kerning.get (i).val; 399 } 400 } 401 } 402 403 return 0; 404 } 405 406 public void print_all () { 407 print ("Kernings classes:\n"); 408 for (int i = 0; i < classes_first.size; i++) { 409 print (classes_first.get (i).get_all_ranges ()); 410 print ("\t\t"); 411 print (classes_last.get (i).get_all_ranges ()); 412 print ("\t\t"); 413 print (@"$(classes_kerning.get (i).val)"); 414 print ("\t\t"); 415 416 if (classes_first.get (i).is_class () || classes_last.get (i).is_class ()) { 417 print ("class"); 418 } 419 420 print ("\n"); 421 } 422 423 print ("\n"); 424 print ("Kernings for pairs:\n"); 425 if (!set_protect_map (true)) { 426 warning ("Map is protected."); 427 return; 428 } 429 430 foreach (string key in single_kerning.keys) { 431 print (key); 432 print ("\t\t"); 433 print (@"$((!) single_kerning.get (key))\n"); 434 } 435 436 set_protect_map (false); 437 438 print ("\n"); 439 print ("Generated table:\n"); 440 all_pairs ((k) => { 441 k.print (); 442 }); 443 } 444 445 public void get_classes (KerningClassIterator kerningIterator) { 446 for (int i = 0; i < classes_first.size; i++) { 447 kerningIterator (classes_first.get (i).get_all_ranges (), 448 classes_last.get (i).get_all_ranges (), 449 classes_kerning.get (i).val); 450 } 451 } 452 453 public void get_single_position_pairs (KerningClassIterator kerningIterator) { 454 double k = 0; 455 456 if (!set_protect_map (true)) { 457 warning ("Map is protected."); 458 return; 459 } 460 461 foreach (string key in single_kerning.keys) { 462 var chars = key.split (" - "); 463 464 if (chars.length != 2) { 465 warning (@"Can not parse characters from key: $key"); 466 } else { 467 k = (!) single_kerning.get (key); 468 kerningIterator (chars[0], chars[1], k); 469 } 470 } 471 472 set_protect_map (false); 473 } 474 475 public void each_pair (KerningClassIterator iter) { 476 all_pairs ((kl) => { 477 Glyph g2; 478 KerningPair kerning_list = kl; 479 string g1 = kerning_list.character.get_name (); 480 Kerning kerning; 481 int i = 0; 482 483 return_if_fail (kerning_list.kerning.size > 0); 484 485 foreach (Kerning k in kerning_list.kerning) { 486 g2 = k.get_glyph (); 487 kerning = kerning_list.kerning.get (i); 488 iter (g1, g2.get_name (), kerning.val); 489 } 490 }); 491 } 492 493 public void all_pairs (KerningIterator kerningIterator) { 494 Gee.ArrayList<Glyph> left_glyphs = new Gee.ArrayList<Glyph> (); 495 Gee.ArrayList<KerningPair> pairs = new Gee.ArrayList<KerningPair> (); 496 double kerning; 497 string right; 498 string name; 499 Glyph? g; 500 501 // Create a list of first glyph in all pairs 502 foreach (GlyphRange r in classes_first) { 503 foreach (UniRange u in r.ranges) { 504 for (unichar c = u.start; c <= u.stop; c++) { 505 name = (!)c.to_string (); 506 g = font.get_glyph (name); 507 if (g != null && !left_glyphs.contains ((!) g)) { 508 left_glyphs.add ((!) g); 509 } 510 } 511 } 512 513 foreach (string n in r.unassigned) { 514 g = font.get_glyph (n); 515 if (g != null && !left_glyphs.contains ((!) g)) { 516 left_glyphs.add ((!) g); 517 } 518 } 519 } 520 521 foreach (string n in single_kerning_letters_left) { 522 g = font.get_glyph (n); 523 if (g != null && !left_glyphs.contains ((!) g)) { 524 left_glyphs.add ((!) g); 525 } 526 } 527 528 // add the right hand glyph and the kerning value 529 foreach (Glyph character in left_glyphs) { 530 KerningPair kl = new KerningPair (character); 531 532 foreach (GlyphRange r in classes_last) { 533 foreach (UniRange u in r.ranges) { 534 for (unichar c = u.start; c <= u.stop; c++) { 535 right = (!)c.to_string (); 536 537 if (font.has_glyph (right) && has_kerning (character.get_name (), right)) { 538 kerning = get_kerning (character.get_name (), right); 539 kl.add_unique ((!) font.get_glyph (right), kerning); 540 } 541 } 542 } 543 544 foreach (string n in r.unassigned) { 545 if (font.has_glyph (n) && has_kerning (character.get_name (), n)) { 546 kerning = get_kerning (character.get_name (), n); 547 kl.add_unique ((!) font.get_glyph (n), kerning); 548 } 549 } 550 } 551 552 // TODO: The get_kerning () function is still slow. Optimize it. 553 foreach (string right_glyph_name in single_kerning_letters_right) { 554 Glyph? gl = font.get_glyph (right_glyph_name); 555 if (gl != null && has_kerning (character.get_name (), right_glyph_name)) { 556 kerning = get_kerning (character.get_name (), right_glyph_name); 557 kl.add_unique ((!) gl , kerning); 558 } 559 } 560 561 if (kl.kerning.size > 0) { 562 pairs.add (kl); 563 } 564 565 if (kl.kerning.size == 0) { 566 warning (@"No kerning pairs for character: $((kl.character.get_name ()))"); 567 } 568 569 kl.sort (); 570 } 571 572 // obtain the kerning value 573 foreach (KerningPair p in pairs) { 574 kerningIterator (p); 575 } 576 } 577 578 private bool set_protect_map (bool p) { 579 if (unlikely (p && protect_map)) { 580 warning ("Map is already protected."); 581 return false; 582 } 583 584 protect_map = p; 585 return true; 586 } 587 588 public void delete_kerning_for_class (string left, string right) { 589 int i = 0; 590 int index = -1; 591 592 get_classes ((l, r, kerning) => { 593 if (left == l && r == right) { 594 index = i; 595 } 596 i++; 597 }); 598 599 if (unlikely (index < 0)) { 600 warning (@"Kerning class not found for $left to $right"); 601 return; 602 } 603 604 classes_first.remove_at (index); 605 classes_last.remove_at (index); 606 classes_kerning.remove_at (index); 607 } 608 609 public void delete_kerning_for_pair (string left, string right) { 610 foreach (string l in get_spacing_class (left)) { 611 foreach (string r in get_spacing_class (right)) { 612 delete_kerning_for_one_pair (l, r); 613 } 614 } 615 } 616 617 private void delete_kerning_for_one_pair (string left, string right) { 618 bool has_left, has_right; 619 string[] p; 620 621 single_kerning.unset (@"$left - $right"); 622 623 has_left = false; 624 has_right = false; 625 626 foreach (string k in single_kerning.keys) { 627 p = k.split (" - "); 628 return_if_fail (p.length == 2); 629 630 if (p[0] == left) { 631 has_left = true; 632 } 633 634 if (p[1] == right) { 635 has_right = true; 636 } 637 } 638 639 if (!has_left) { 640 single_kerning_letters_left.remove (left); 641 } 642 643 if (!has_right) { 644 single_kerning_letters_right.remove (left); 645 } 646 } 647 648 public void remove_all_pairs () { 649 if (protect_map) { 650 warning ("Map is protected."); 651 return; 652 } 653 654 classes_first.clear (); 655 classes_last.clear (); 656 classes_kerning.clear (); 657 single_kerning_letters_left.clear (); 658 single_kerning_letters_right.clear (); 659 660 GlyphCanvas.redraw (); 661 662 if (!is_null (MainWindow.get_toolbox ())) { // FIXME: reorganize 663 Toolbox.redraw_tool_box (); 664 } 665 666 single_kerning.clear (); 667 } 668 669 public uint get_number_of_pairs () { 670 return single_kerning.keys.size + classes_first.size; 671 } 672 } 673 674 } 675