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