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.
Suppress zoom when canvas is moving
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