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