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.
Bounding box for SVG rectangles
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 add_single (w.get_char ()); 186 } else if (w == "space") { 187 add_single (' '); 188 } else if (w == "divis") { 189 add_single ('-'); 190 } else if (w == "null") { 191 add_single ('\0'); 192 } else if (w.index_of ("-") > -1) { 193 parse_range (w); 194 } else if (w == "quote") { 195 add_single ('"'); 196 } else if (w == "ampersand") { 197 add_single ('&'); 198 } else { 199 unassigned.add (w); 200 } 201 } 202 } 203 204 /** A readable representation of ranges, see parse_ranges for parsing 205 * this string. This function is used for storing ranges in th .bf format. 206 */ 207 public string get_all_ranges () { 208 bool first = true; 209 StringBuilder s = new StringBuilder (); 210 foreach (UniRange u in ranges) { 211 if (!first) { 212 s.append (" "); 213 } 214 215 if (u.start == u.stop) { 216 s.append (get_serialized_char (u.start)); 217 } else { 218 s.append (get_serialized_char (u.start)); 219 s.append ("-"); 220 s.append (get_serialized_char (u.stop)); 221 } 222 223 first = false; 224 } 225 226 foreach (string ur in unassigned) { 227 if (!first) { 228 s.append (" "); 229 } 230 231 s.append (ur); 232 233 first = false; 234 } 235 236 return s.str; 237 } 238 239 public static string serialize (string s) { 240 if (s == "space") { 241 return s; 242 } 243 244 if (s == "divis") { 245 return s; 246 } 247 248 if (s == "null") { 249 return s; 250 } 251 252 if (s == "quote") { 253 return s; 254 } 255 256 if (s == "ampersand") { 257 return s; 258 } 259 260 if (s == "&quot;") { 261 return s; 262 } 263 264 if (s == "&amp;") { 265 return s; 266 } 267 268 if (s == "&lt;") { 269 return s; 270 } 271 272 if (s == "&gt;") { 273 return s; 274 } 275 276 if (s.char_count () > 1) { 277 return s; // ligature 278 } 279 280 return get_serialized_char (s.get_char (0)); 281 } 282 283 public static string get_serialized_char (unichar c) { 284 StringBuilder s = new StringBuilder (); 285 286 if (c == '&') { 287 return "&amp;"; 288 } 289 290 if (c == '<') { 291 return "&lt;"; 292 } 293 294 if (c == '>') { 295 return "&gt;"; 296 } 297 298 if (c == ' ') { 299 return "space"; 300 } 301 302 if (c == '-') { 303 return "divis"; 304 } 305 306 if (c == '\0') { 307 return "null"; 308 } 309 310 if (c == '"') { 311 return "quote"; 312 } 313 314 if (c == '&') { 315 return "ampersand"; 316 } 317 318 s.append_unichar (c); 319 return s.str; 320 } 321 322 public static string unserialize (string c) { 323 if (c == "&quot;") { 324 return "\""; 325 } 326 327 if (c == "&amp;") { 328 return "&"; 329 } 330 331 if (c == "&lt;") { 332 return "<"; 333 } 334 335 if (c == "&gt;") { 336 return ">"; 337 } 338 339 if (c == "space") { 340 return " "; 341 } 342 343 if (c == "divis") { 344 return "-"; 345 } 346 347 if (c == "null") { 348 return "\0"; 349 } 350 351 if (c == "quote") { 352 return "\""; 353 } 354 355 if (c == "ampersand") { 356 return "&"; 357 } 358 359 return c; 360 } 361 362 private void parse_range (string s) throws MarkupError { 363 string[] r = s.split ("-"); 364 bool null_range = false; 365 366 if (r.length == 2 && r[0] == "null" && r[1] == "null") { 367 null_range = true; 368 } else if (r.length == 2 && r[0] == "null" && unserialize (r[1]).char_count () == 1) { 369 null_range = true; 370 } 371 372 if (!null_range) { 373 if (r.length != 2 374 || unserialize (r[0]).char_count () != 1 375 || unserialize (r[1]).char_count () != 1) { 376 throw new MarkupError.PARSE (@"$s is not a valid range, it should be on the form A-Z."); 377 } 378 } 379 380 append_range (unserialize (r[0]).get_char (), unserialize (r[1]).get_char ()); 381 } 382 383 private void append_range (unichar start, unichar stop) { 384 UniRange r; 385 r = insert_range (start, stop); // insert a unique range 386 merge_range (r); 387 } 388 389 private void merge_range (UniRange r) { 390 Gee.ArrayList<UniRange> deleted = new Gee.ArrayList<UniRange> (); 391 Gee.ArrayList<UniRange> merged = new Gee.ArrayList<UniRange> (); 392 bool updated = false; 393 394 foreach (UniRange u in ranges) { 395 if (u == r) { 396 continue; 397 } 398 399 if (u.start == r.stop + 1) { 400 u.start = r.start; 401 deleted.add (r); 402 merged.add (u); 403 break; 404 } 405 406 if (u.stop == r.start - 1) { 407 u.stop = r.stop; 408 deleted.add (r); 409 merged.add (u); 410 break; 411 } 412 } 413 414 updated = merged.size > 0; 415 416 foreach (UniRange m in deleted) { 417 while (ranges.remove (m)); 418 } 419 420 foreach (UniRange m in merged) { 421 merge_range (m); 422 } 423 424 if (updated) { 425 merge_range (r); 426 } 427 } 428 429 /** Find a range which contains index. */ 430 private void get_unirange_index (uint32 index, out UniRange? range, out uint32 range_start_index) { 431 int lower = 0; 432 int upper = index_size - 1; 433 int i = (lower + upper) / 2; 434 int end = index_size - 1; 435 436 range_start_index = -1; 437 range = null; 438 439 if (unlikely (ranges.size != index_size)) { 440 warning (@"Range size does not match index size: $(ranges.size) != $index_size"); 441 } 442 443 while (true) { 444 if (i == end && range_index[i] <= index) { 445 range_start_index = range_index[i]; 446 range = ranges.get (i); 447 break; 448 } else if (i != end && range_index[i] <= index && range_index[i + 1] > index) { 449 range_start_index = range_index[i]; 450 range = ranges.get (i); 451 break; 452 } 453 454 if (lower >= upper) { 455 break; 456 } 457 458 if (range_index[i] < index) { 459 lower = i + 1; 460 } else { 461 upper = i - 1; 462 } 463 464 i = (lower + upper) / 2; 465 } 466 } 467 468 public string get_char (uint32 index) { 469 StringBuilder sb; 470 471 sb = new StringBuilder (); 472 sb.append_unichar (get_character (index)); 473 474 return sb.str; 475 } 476 477 public unichar get_character (uint32 index) { 478 UniRange r; 479 unichar c; 480 string chr; 481 UniRange? range; 482 uint32 range_start_index; 483 484 if (unlikely (index > len + unassigned.size)) { 485 return '\0'; 486 } 487 488 if (index >= len) { 489 if (unlikely (index - len >= unassigned.size)) { 490 return '\0'; 491 } 492 493 chr = unassigned.get ((int) (index - len)); 494 return chr.get_char (); 495 } 496 497 get_unirange_index (index, out range, out range_start_index); 498 499 if (unlikely (range == null)) { 500 warning (@"No range found for index $index"); 501 return '\0'; 502 } 503 504 if (unlikely (range_start_index > index || range_start_index == -1)) { 505 warning (@"Index out of bounds in glyph range, range_start_index: $range_start_index index: $index"); 506 return '\0'; 507 } 508 509 r = (!) range; 510 c = r.get_char ((unichar) (index - range_start_index)); 511 512 if (unlikely (!c.validate ())) { 513 warning ("Not a valid unicode character."); 514 return '\0'; 515 } 516 517 return c; 518 } 519 520 public uint32 length () { 521 return len; 522 } 523 524 public bool has_character (string c) { 525 unichar s; 526 string uns; 527 528 if (unassigned.index_of (c) != -1) { 529 return true; 530 } 531 532 uns = unserialize (c); 533 534 if (uns.char_count () != 1) { 535 // the glyph was not found by its name because it is not in the list of 536 // unassigned characters 537 return false; 538 } 539 540 s = uns.get_char (); 541 return !unique (s, s); 542 } 543 544 public bool has_unichar (unichar c) { 545 return !unique (c, c); 546 } 547 548 private bool unique (unichar start, unichar stop) { 549 foreach (UniRange u in ranges) { 550 if (inside (start, u.start, u.stop)) { 551 return false; 552 } 553 554 if (inside (stop, u.start, u.stop)) { 555 return false; 556 } 557 558 if (inside (u.start, start, stop)) { 559 return false; 560 } 561 562 if (inside (u.stop, start, stop)) { 563 return false; 564 } 565 } 566 567 return true; 568 } 569 570 private static bool inside (unichar start, unichar u_start, unichar u_stop) { 571 return (u_start <= start <= u_stop); 572 } 573 574 private UniRange insert_range (unichar start, unichar stop) { 575 if (unlikely (start > stop)) { 576 warning ("start > stop"); 577 stop = start; 578 } 579 580 UniRange ur = new UniRange (start, stop); 581 len += ur.length (); 582 ranges.add (ur); 583 584 return ur; 585 } 586 587 public void print_all () { 588 stdout.printf ("Ranges:\n"); 589 stdout.printf (get_all_ranges ()); 590 stdout.printf ("\n"); 591 } 592 593 public string to_string () { 594 return get_all_ranges (); 595 } 596 } 597 598 } 599