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.
Thread safety in text rendering
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