The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

GlyphRange.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/GlyphRange.vala.
Merge branch 'master' of github.com:johanmattssonm/birdfont
1 /* 2 Copyright (C) 2012 2014 2015 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 namespace BirdFont { 16 17 public class GlyphRange { 18 19 public string name {get; set;} 20 21 public Gee.ArrayList<UniRange> ranges; 22 23 /** Glyphs without a corresponding unicode value (ligatures). */ 24 public Gee.ArrayList<string> unassigned; 25 26 uint32 len = 0; 27 28 bool range_is_class = false; 29 uint32* range_index = null; 30 int index_size = 0; 31 int index_hint = 0; 32 33 public GlyphRange () { 34 ranges = new Gee.ArrayList<UniRange> (); 35 unassigned = new Gee.ArrayList<string> (); 36 name = "No name"; 37 } 38 39 ~GlyphRange () { 40 if (range_index != null) { 41 delete range_index; 42 } 43 } 44 45 private void generate_unirange_index () { 46 if (range_index != null) { 47 delete range_index; 48 } 49 50 index_size = ranges.size; 51 range_index = new uint32[index_size]; 52 53 int i = 0; 54 uint32 next_index = 0; 55 56 foreach (UniRange range in ranges) { 57 range_index[i] = next_index; 58 next_index += (uint32) range.length (); 59 i++; 60 } 61 } 62 63 public void add_unassigned (string glyph_name) { 64 unassigned.add (glyph_name); 65 } 66 67 public bool is_class () { 68 return range_is_class || len > 1; 69 } 70 71 public void set_class (bool c) { 72 range_is_class = true; 73 } 74 75 public bool is_empty () { 76 return len == 0; 77 } 78 79 public void empty () { 80 unassigned.clear (); 81 ranges.clear (); 82 len = 0; 83 generate_unirange_index (); 84 } 85 86 public unowned Gee.ArrayList<UniRange> get_ranges () { 87 return ranges; 88 } 89 90 // sort by unicode value 91 public void sort () { 92 ranges.sort ((a, b) => { 93 UniRange first, next; 94 bool r; 95 96 first = (UniRange) a; 97 next = (UniRange) b; 98 99 r = first.start > next.start; 100 return_val_if_fail (first.start != next.start, 0); 101 102 return (r) ? 1 : -1; 103 }); 104 105 generate_unirange_index (); 106 } 107 108 public void add_single (unichar c) { 109 add_range (c, c); 110 } 111 112 public uint32 get_length () { 113 uint32 l = len; 114 l += unassigned.size; 115 return l; 116 } 117 118 public void add_range (unichar start, unichar stop) { 119 unichar b, s; 120 if (unique (start, stop)) { 121 append_range (start, stop); 122 } else { 123 124 // make sure this range does not overlap existing ranges 125 b = start; 126 s = b; 127 if (!unique (b, b)) { 128 while (b < stop) { 129 if (!unique (b, b)) { 130 b++; 131 } else { 132 if (s != b) { 133 add_range (b, stop); 134 } 135 136 b++; 137 s = b; 138 } 139 } 140 } else { 141 while (b < stop) { 142 if (unique (b, b)) { 143 b++; 144 } else { 145 if (s != b) { 146 add_range (start, b - 1); 147 } 148 149 b++; 150 s = b; 151 } 152 } 153 } 154 } 155 156 generate_unirange_index (); 157 } 158 159 /** Parse ranges on the form a-z. Single characters can be added as well as 160 * multiple ranges separated by space. The word "space" is used to kern against 161 * the space character and the word "divis" is used to kern against "-". 162 * @param ranges unicode ranges 163 */ 164 public void parse_ranges (string ranges) throws MarkupError { 165 parse_range_string (ranges); 166 generate_unirange_index (); 167 } 168 169 private void parse_range_string (string ranges) throws MarkupError { 170 string[] r; 171 172 if (ranges == " ") { 173 add_single (' '); 174 } 175 176 r = ranges.split (" "); 177 178 foreach (string w in r) { 179 w = w.replace (" ", ""); 180 181 if (w == "") { 182 continue; 183 } 184 185 if (w.char_count () == 1) { 186 add_single (w.get_char ()); 187 } else if (w == "space") { 188 add_single (' '); 189 } else if (w == "divis") { 190 add_single ('-'); 191 } else if (w == "null") { 192 add_single ('\0'); 193 } else if (w.index_of ("-") > -1) { 194 parse_range (w); 195 } else if (w == "quote") { 196 add_single ('"'); 197 } else if (w == "ampersand") { 198 add_single ('&'); 199 } else { 200 unassigned.add (w); 201 } 202 } 203 } 204 205 /** A readable representation of ranges, see parse_ranges for parsing 206 * this string. This function is used for storing ranges in th .bf format. 207 */ 208 public string get_all_ranges () { 209 bool first = true; 210 StringBuilder s = new StringBuilder (); 211 foreach (UniRange u in ranges) { 212 if (!first) { 213 s.append (" "); 214 } 215 216 if (u.start == u.stop) { 217 s.append (get_serialized_char (u.start)); 218 } else { 219 s.append (get_serialized_char (u.start)); 220 s.append ("-"); 221 s.append (get_serialized_char (u.stop)); 222 } 223 224 first = false; 225 } 226 227 foreach (string ur in unassigned) { 228 if (!first) { 229 s.append (" "); 230 } 231 232 s.append (ur); 233 234 first = false; 235 } 236 237 return s.str; 238 } 239 240 public static string serialize (string s) { 241 if (s == "space") { 242 return s; 243 } 244 245 if (s == "divis") { 246 return s; 247 } 248 249 if (s == "null") { 250 return s; 251 } 252 253 if (s == "quote") { 254 return s; 255 } 256 257 if (s == "ampersand") { 258 return s; 259 } 260 261 if (s == "&quot;") { 262 return s; 263 } 264 265 if (s == "&amp;") { 266 return s; 267 } 268 269 if (s == "&lt;") { 270 return s; 271 } 272 273 if (s == "&gt;") { 274 return s; 275 } 276 277 if (s.char_count () > 1) { 278 return s; // ligature 279 } 280 281 return get_serialized_char (s.get_char (0)); 282 } 283 284 public static string get_serialized_char (unichar c) { 285 StringBuilder s = new StringBuilder (); 286 287 if (c == '&') { 288 return "&amp;"; 289 } 290 291 if (c == '<') { 292 return "&lt;"; 293 } 294 295 if (c == '>') { 296 return "&gt;"; 297 } 298 299 if (c == ' ') { 300 return "space"; 301 } 302 303 if (c == '-') { 304 return "divis"; 305 } 306 307 if (c == '\0') { 308 return "null"; 309 } 310 311 if (c == '"') { 312 return "quote"; 313 } 314 315 if (c == '&') { 316 return "ampersand"; 317 } 318 319 s.append_unichar (c); 320 return s.str; 321 } 322 323 public static string unserialize (string c) { 324 if (c == "&quot;") { 325 return "\""; 326 } 327 328 if (c == "&amp;") { 329 return "&"; 330 } 331 332 if (c == "&lt;") { 333 return "<"; 334 } 335 336 if (c == "&gt;") { 337 return ">"; 338 } 339 340 if (c == "space") { 341 return " "; 342 } 343 344 if (c == "divis") { 345 return "-"; 346 } 347 348 if (c == "null") { 349 return "\0"; 350 } 351 352 if (c == "quote") { 353 return "\""; 354 } 355 356 if (c == "ampersand") { 357 return "&"; 358 } 359 360 return c; 361 } 362 363 private void parse_range (string s) throws MarkupError { 364 string[] r = s.split ("-"); 365 bool null_range = false; 366 367 if (r.length == 2 && r[0] == "null" && r[1] == "null") { 368 null_range = true; 369 } else if (r.length == 2 && r[0] == "null" && unserialize (r[1]).char_count () == 1) { 370 null_range = true; 371 } 372 373 if (!null_range) { 374 if (r.length != 2 375 || unserialize (r[0]).char_count () != 1 376 || unserialize (r[1]).char_count () != 1) { 377 throw new MarkupError.PARSE (@"$s is not a valid range, it should be on the form A-Z."); 378 } 379 } 380 381 append_range (unserialize (r[0]).get_char (), unserialize (r[1]).get_char ()); 382 } 383 384 private void append_range (unichar start, unichar stop) { 385 UniRange r; 386 r = insert_range (start, stop); // insert a unique range 387 merge_range (r); 388 } 389 390 private void merge_range (UniRange r) { 391 Gee.ArrayList<UniRange> deleted = new Gee.ArrayList<UniRange> (); 392 Gee.ArrayList<UniRange> merged = new Gee.ArrayList<UniRange> (); 393 bool updated = false; 394 395 foreach (UniRange u in ranges) { 396 if (u == r) { 397 continue; 398 } 399 400 if (u.start == r.stop + 1) { 401 u.start = r.start; 402 deleted.add (r); 403 merged.add (u); 404 break; 405 } 406 407 if (u.stop == r.start - 1) { 408 u.stop = r.stop; 409 deleted.add (r); 410 merged.add (u); 411 break; 412 } 413 } 414 415 updated = merged.size > 0; 416 417 foreach (UniRange m in deleted) { 418 while (ranges.remove (m)); 419 } 420 421 foreach (UniRange m in merged) { 422 merge_range (m); 423 } 424 425 if (updated) { 426 merge_range (r); 427 } 428 } 429 430 /** Find a range which containd index. */ 431 private void get_unirange_index (uint32 index, out UniRange? range, out uint32 range_start_index) { 432 int lower = 0; 433 int upper = index_size - 1; 434 int i; 435 int end = index_size - 1; 436 437 if (index_hint >= 0 && index_hint < index_size) { 438 i = index_hint; 439 } else { 440 i = (lower + upper) / 2; 441 } 442 443 range_start_index = -1; 444 range = null; 445 446 if (unlikely (ranges.size != index_size)) { 447 warning (@"Range size does not match index size: $(ranges.size) != $index_size"); 448 } 449 450 while (true) { 451 if (i == end) { 452 range_start_index = range_index[i]; 453 range = ranges.get (i); 454 index_hint = i; 455 break; 456 } else if (range_index[i] <= index && range_index[i + 1] > index) { 457 range_start_index = range_index[i]; 458 range = ranges.get (i); 459 index_hint = i; 460 break; 461 } 462 463 if (lower >= upper) { 464 break; 465 } 466 467 if (range_index[i] < index) { 468 lower = i + 1; 469 } else { 470 upper = i - 1; 471 } 472 473 i = (lower + upper) / 2; 474 } 475 } 476 477 public string get_char (uint32 index) { 478 StringBuilder sb; 479 480 sb = new StringBuilder (); 481 sb.append_unichar (get_character (index)); 482 483 return sb.str; 484 } 485 486 public unichar get_character (uint32 index) { 487 int64 ti; 488 string chr; 489 UniRange r; 490 unichar c; 491 UniRange? range; 492 uint32 range_start_index; 493 494 if (unlikely (index > len + unassigned.size)) { 495 return '\0'; 496 } 497 498 if (index >= len) { 499 if (unlikely (index - len >= unassigned.size)) { 500 return '\0'; 501 } 502 503 chr = unassigned.get ((int) (index - len)); 504 return chr.get_char (); 505 } 506 507 get_unirange_index (index, out range, out range_start_index); 508 509 if (unlikely (range == null)) { 510 warning (@"No range found for index $index"); 511 return '\0'; 512 } 513 514 if (unlikely (range_start_index > index || range_start_index == -1)) { 515 warning ("Index out of bounds in glyph range."); 516 return '\0'; 517 } 518 519 r = (!) range; 520 c = r.get_char ((unichar) (index - range_start_index)); 521 522 if (unlikely (!c.validate ())) { 523 warning ("Not a valid unicode character."); 524 return '\0'; 525 } 526 527 return c; 528 } 529 530 public uint32 length () { 531 return len; 532 } 533 534 public bool has_character (string c) { 535 unichar s; 536 string uns; 537 538 if (unassigned.index_of (c) != -1) { 539 return true; 540 } 541 542 uns = unserialize (c); 543 544 if (uns.char_count () != 1) { 545 warning (@"Expecting a single character got $c"); 546 } 547 548 s = uns.get_char (); 549 return !unique (s, s); 550 } 551 552 public bool has_unichar (unichar c) { 553 return !unique (c, c); 554 } 555 556 private bool unique (unichar start, unichar stop) { 557 foreach (UniRange u in ranges) { 558 if (inside (start, u.start, u.stop)) { 559 return false; 560 } 561 562 if (inside (stop, u.start, u.stop)) { 563 return false; 564 } 565 566 if (inside (u.start, start, stop)) { 567 return false; 568 } 569 570 if (inside (u.stop, start, stop)) { 571 return false; 572 } 573 } 574 575 return true; 576 } 577 578 private static bool inside (unichar start, unichar u_start, unichar u_stop) { 579 return (u_start <= start <= u_stop); 580 } 581 582 private UniRange insert_range (unichar start, unichar stop) { 583 if (unlikely (start > stop)) { 584 warning ("start > stop"); 585 stop = start; 586 } 587 588 UniRange ur = new UniRange (start, stop); 589 len += ur.length (); 590 ranges.add (ur); 591 592 return ur; 593 } 594 595 public void print_all () { 596 stdout.printf ("Ranges:\n"); 597 stdout.printf (get_all_ranges ()); 598 stdout.printf ("\n"); 599 } 600 601 public string to_string () { 602 return get_all_ranges (); 603 } 604 } 605 606 } 607