.
1 /*
2 Copyright (C) 2012, 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
17 namespace BirdFont {
18
19 public enum FontFormat {
20 BIRDFONT,
21 BIRDFONT_PART,
22 FFI,
23 SVG,
24 FREETYPE
25 }
26
27 public class Font : GLib.Object {
28
29 /** Table with glyphs sorted by their unicode value. */
30 public GlyphTable glyph_cache;
31
32 /** Table with glyphs sorted by their name. */
33 public GlyphTable glyph_name;
34
35 /** Table with ligatures. */
36 public GlyphTable ligature;
37
38 public Gee.ArrayList<BackgroundImage> background_images;
39 public string background_scale = "1";
40
41 /** Top margin */
42 public double top_limit;
43
44 /** Height of upper case letters. */
45 public double top_position;
46
47 /** x-height upper bearing from origo. */
48 public double xheight_position;
49
50 /** Base line coordinate from origo. */
51 public double base_line;
52
53 /** Descender position */
54 public double bottom_position;
55
56 /** Bottom margin */
57 public double bottom_limit;
58
59 public string? font_file = null;
60
61 bool modified = false;
62
63 // name table descriptions
64 public string postscript_name;
65 public string name;
66 public string subfamily;
67 public string full_name;
68 public string unique_identifier;
69 public string version;
70 public string description;
71 public string copyright;
72
73 public bool bold = false;
74 public bool italic = false;
75 public int weight = 400;
76
77 public bool initialised = true;
78
79 OpenFontFormatReader otf;
80 bool otf_font = false;
81
82 public Gee.ArrayList<string> grid_width;
83
84 /** File format. */
85 public FontFormat format = FontFormat.BIRDFONT;
86
87 SpacingData spacing;
88
89 bool read_only = false;
90
91 /** Save font as many .bfp files instead of one big .bf */
92 bool bfp = false;
93 BirdFontPart bfp_file;
94
95 public Gee.ArrayList<Glyph> deleted_glyphs;
96
97 Ligatures ligatures_substitution;
98
99 public static string? default_license = null;
100
101 public Font () {
102 KerningClasses kerning_classes;
103
104 postscript_name = "Typeface";
105 name = "Typeface";
106 subfamily = "Regular";
107 full_name = "Typeface";
108 unique_identifier = "Typeface";
109 version = "Version 1.0";
110 description = "";
111 copyright = default_license != null ? ((!) default_license).dup () : "";
112
113 glyph_cache = new GlyphTable ();
114 glyph_name = new GlyphTable ();
115 ligature = new GlyphTable ();
116
117 grid_width = new Gee.ArrayList<string> ();
118
119 kerning_classes = new KerningClasses (this);
120 spacing = new SpacingData (kerning_classes);
121
122 top_limit = 84 ;
123 top_position = 72;
124 xheight_position = 56;
125 base_line = 0;
126 bottom_position = -20;
127 bottom_limit = -27;
128
129 bfp_file = new BirdFontPart (this);
130
131 deleted_glyphs = new Gee.ArrayList<Glyph> ();
132 ligatures_substitution = new Ligatures ();
133
134 background_images = new Gee.ArrayList<BackgroundImage> ();
135 }
136
137 public static void set_default_license (string license) {
138 default_license = license;
139 }
140
141 public Ligatures get_ligatures () {
142 return ligatures_substitution;
143 }
144
145 public void set_weight (string w) {
146 int wi = int.parse (w);
147
148 if (wi > 0) {
149 weight = wi;
150 }
151 }
152
153 public string get_weight () {
154 return @"$weight";
155 }
156
157 public void touch () {
158 modified = true;
159 }
160
161 public KerningClasses get_kerning_classes () {
162 return spacing.get_kerning_classes ();
163 }
164
165 public SpacingData get_spacing () {
166 return spacing;
167 }
168
169 public File get_backgrounds_folder () {
170 string fn = @"$(get_name ()) backgrounds";
171 File f = get_child (BirdFont.get_settings_directory (), fn);
172 return f;
173 }
174
175 /** Retuns true if the current font has be modified */
176 public bool is_modified () {
177 return modified;
178 }
179
180 /** Full path to this font file. */
181 public string get_path () {
182 int i = 0;
183 string fn;
184 File f;
185 File file;
186
187 if (font_file != null) {
188 fn = (!) font_file;
189
190 // assume only absolute paths are used on windows
191 if (BirdFont.win32) {
192 return fn;
193 } else {
194 file = File.new_for_path (fn);
195 return (!) file.resolve_relative_path ("").get_path ();
196 }
197 }
198
199 StringBuilder sb = new StringBuilder ();
200 sb.append (Environment.get_home_dir ());
201 sb.append (@"/$(get_name ()).bf");
202
203 f = File.new_for_path (sb.str);
204
205 while (f.query_exists ()) {
206 sb.erase ();
207 sb.append (Environment.get_home_dir ());
208 sb.append (@"/$(get_name ())$(++i).bf");
209 f = File.new_for_path (sb.str);
210 }
211
212 return sb.str;
213 }
214
215 public string get_file_name () {
216 string p = get_path ();
217 int i = p.last_index_of ("/");
218
219 if (i == -1) {
220 i = p.last_index_of ("\\");
221 }
222
223 p = p.substring (i + 1);
224
225 return p;
226 }
227
228 /** @return an absolute path to the font folder. */
229 public File get_folder () {
230 string p = get_folder_path ();
231 File fp = File.new_for_path (p);
232
233 if (BirdFont.win32) {
234 if (p.index_of (":\\") == -1) {
235 p = (!) fp.resolve_relative_path ("").get_path ();
236 }
237 } else {
238 if (!p.has_prefix ("/")) {
239 p = (!) fp.resolve_relative_path ("").get_path ();
240 }
241 }
242
243 return File.new_for_path (p);
244 }
245
246 /** @return a path to the font folder, it can be relative. */
247 public string get_folder_path () {
248 string p = get_path ();
249 int i = p.last_index_of ("/");
250
251 if (i == -1) {
252 i = p.last_index_of ("\\");
253 }
254
255 if (i == -1) {
256 warning (@"Can not find folder in $p.");
257 p = ".";
258 } else {
259 p = p.substring (0, i);
260 }
261
262 if (p.index_of (":") != -1 && p.char_count () == 2) {
263 p += "\\";
264 }
265
266 return p;
267 }
268
269 public double get_height () {
270 double r = top_position - base_line;
271 return (r > 0) ? r : -r;
272 }
273
274 public void set_name (string name) {
275 string n = name;
276 this.name = n;
277 }
278
279 public string get_full_name () {
280 return full_name;
281 }
282
283 public string get_name () {
284 return name;
285 }
286
287 public void print_all () {
288 stdout.printf ("Unicode:\n");
289 glyph_cache.for_each((g) => {
290 stdout.printf (@"$(g.get_unicode ())\n");
291 });
292
293 stdout.printf ("Names:\n");
294 glyph_name.for_each((g) => {
295 stdout.printf (@"$(g.get_name ())\n");
296 });
297 }
298
299 public bool has_glyph (string n) {
300 return get_glyph (n) != null;
301 }
302
303 public GlyphCollection get_nonmarking_return () {
304 Glyph g;
305 GlyphCollection gc;
306
307 if (has_glyph ("nonmarkingreturn")) {
308 return (!) get_glyph_collection ("nonmarkingreturn");
309 }
310
311 gc = new GlyphCollection ('\r', "nonmarkingreturn");
312
313 g = new Glyph ("nonmarkingreturn", '\r');
314 g.left_limit = 0;
315 g.right_limit = 0;
316 g.remove_empty_paths ();
317
318 gc.set_unassigned (false);
319 gc.add_glyph (g);
320
321 return gc;
322 }
323
324 public GlyphCollection get_null_character () {
325 Glyph n;
326 GlyphCollection gc;
327
328 if (has_glyph ("null")) {
329 return (!) get_glyph_collection ("null");
330 }
331
332 gc = new GlyphCollection ('\0', "null");
333 n = new Glyph ("null", '\0');
334
335 gc.add_glyph (n);
336 gc.set_unassigned (false);
337
338 n.left_limit = 0;
339 n.right_limit = 0;
340 n.remove_empty_paths ();
341
342 assert (n.path_list.size == 0);
343
344 return gc;
345 }
346
347 public GlyphCollection get_space () {
348 Glyph n;
349 GlyphCollection gc;
350
351 if (has_glyph (" ")) {
352 return (!) get_glyph_collection (" ");
353 }
354
355 if (has_glyph ("space")) {
356 return (!) get_glyph_collection ("space");
357 }
358
359 gc = new GlyphCollection (' ', "space");
360
361 n = new Glyph ("space", ' ');
362 n.left_limit = 0;
363 n.right_limit = 27;
364 n.remove_empty_paths ();
365
366 gc.add_glyph (n);
367 gc.set_unassigned (false);
368
369 return gc;
370 }
371
372 public GlyphCollection get_not_def_character () {
373 Glyph g;
374 GlyphCollection gc;
375
376 Path p;
377 Path i;
378
379 if (has_glyph (".notdef")) {
380 return (!) get_glyph_collection (".notdef");
381 }
382
383 gc = new GlyphCollection ('\0', ".notdef");
384 g = new Glyph (".notdef", 0);
385 p = new Path ();
386 i = new Path ();
387
388 gc.set_unassigned (true);
389 gc.add_glyph (g);
390
391 g.left_limit = -20;
392 g.right_limit = 33;
393
394 g.add_help_lines ();
395
396 p.add (-20, top_position - 5);
397 p.add (20, top_position - 5);
398 p.add (20, base_line + 5);
399 p.add (-20, base_line + 5);
400 p.close ();
401
402 i.add (-15, top_position - 10);
403 i.add (15, top_position - 10);
404 i.add (15, base_line + 10);
405 i.add (-15, base_line + 10);
406 i.reverse ();
407 i.close ();
408
409 g.add_path (i);
410 g.add_path (p);
411
412 i.recalculate_linear_handles ();
413 p.recalculate_linear_handles ();
414
415 return gc;
416 }
417
418 public void add_glyph_collection (GlyphCollection glyph_collection) {
419 GlyphCollection? gc;
420
421 if (unlikely (glyph_collection.get_name () == "")) {
422 warning ("Refusing to add glyph with name \"\", null character should be named null.");
423 return;
424 }
425
426 gc = glyph_name.get (glyph_collection.get_name ());
427 if (unlikely (gc != null)) {
428 warning ("glyph has already been added");
429 return;
430 }
431
432 glyph_name.insert (glyph_collection.get_name (), glyph_collection);
433
434 if (glyph_collection.get_unicode () != "") {
435 glyph_cache.insert ((!) glyph_collection.get_unicode (), glyph_collection);
436 } else {
437 glyph_cache.insert ((!) glyph_collection.get_name (), glyph_collection);
438 }
439
440 if (glyph_collection.is_unassigned ()) {
441 ligature.insert (glyph_collection.get_name (), glyph_collection);
442 }
443 }
444
445 public string get_name_for_character (unichar c) {
446 StringBuilder sb;
447
448 if (c == 0) {
449 return ".null".dup ();
450 }
451
452 sb = new StringBuilder ();
453 sb.append_unichar (c);
454 return sb.str;
455 }
456
457 public bool has_name (string name) {
458 return glyph_name.has_key (name);
459 }
460
461 public void delete_glyph (GlyphCollection glyph) {
462 glyph_cache.remove (glyph.get_unicode ());
463 glyph_cache.remove (glyph.get_name ());
464 glyph_name.remove (glyph.get_name ());
465 ligature.remove (glyph.get_current ().get_name ());
466
467 foreach (Glyph g in glyph.get_version_list ().glyphs) {
468 deleted_glyphs.add (g);
469 }
470 }
471
472 // FIXME: the order of ligature substitutions
473 public GlyphCollection? get_ligature (uint indice) {
474 return ligature.nth (indice);
475 }
476
477 /** Obtain all versions and alterntes for this glyph. */
478 public GlyphCollection? get_glyph_collection (string glyph) {
479 GlyphCollection? gc = get_cached_glyph_collection (glyph);
480 Glyph? g;
481
482 if (gc == null && otf_font) {
483 // load it from otf file if we need to
484 g = otf.read_glyph (glyph);
485
486 if (g != null) {
487 return get_cached_glyph_collection (glyph);
488 }
489 }
490
491 return gc;
492 }
493
494 /** Get glyph collection by unichar code. */
495 public GlyphCollection? get_cached_glyph_collection (string unichar_code) {
496 GlyphCollection? gc = null;
497 gc = glyph_cache.get (unichar_code);
498 return gc;
499 }
500
501 /** Get glyph collection by name. */
502 public GlyphCollection? get_glyph_collection_by_name (string? glyph) {
503 // TODO: load from disk here if needed.
504 GlyphCollection? gc = null;
505
506 if (glyph != null) {
507 gc = glyph_name.get ((!) glyph);
508 }
509
510 return gc;
511 }
512
513 /** Get glyph by name. */
514 public Glyph? get_glyph_by_name (string glyph) {
515 GlyphCollection? gc = get_glyph_collection_by_name (glyph);
516
517 if (gc == null) {
518 return null;
519 }
520
521 return ((!)gc).get_current ();
522 }
523
524 public Glyph? get_glyph (string name) {
525 GlyphCollection? gc = null;
526 gc = glyph_name.get (name);
527
528 if (gc == null || ((!)gc).length () == 0) {
529 return null;
530 }
531
532 return ((!)gc).get_current ();
533 }
534
535 public GlyphCollection? get_glyph_collection_indice (unichar glyph_indice) {
536 if (!(0 <= glyph_indice < glyph_name.length ())) {
537 return null;
538 }
539
540 return glyph_name.nth (glyph_indice);
541 }
542
543 public Glyph? get_glyph_indice (unichar glyph_indice) {
544 GlyphCollection? gc;
545
546 gc = get_glyph_collection_indice (glyph_indice);
547
548 if (gc != null) {
549 return ((!) gc).get_current ();
550 }
551
552 return null;
553 }
554
555 public void add_background_image (BackgroundImage image) {
556 background_images.add (image);
557 }
558
559 /** Delete temporary rescue files. */
560 public void delete_backup () {
561 File dir = BirdFont.get_backup_directory ();
562 File? new_file = null;
563 File file;
564 string backup_file;
565
566 new_file = get_child (dir, @"$(name).bf");
567 backup_file = (!) ((!) new_file).get_path ();
568
569 try {
570 file = File.new_for_path (backup_file);
571 if (file.query_exists ()) {
572 file.delete ();
573 }
574 } catch (GLib.Error e) {
575 stderr.printf (@"Failed to delete backup\n");
576 warning (@"$(e.message) \n");
577 }
578 }
579
580 /** Returns path to backup file. */
581 public string save_backup () {
582 File dir = BirdFont.get_backup_directory ();
583 File? temp_file = null;
584 string backup_file;
585 BirdFontFile birdfont_file = new BirdFontFile (this);
586
587 temp_file = get_child (dir, @"$(name).bf");
588 backup_file = (!) ((!) temp_file).get_path ();
589 backup_file = backup_file.replace (" ", "_");
590
591 if (get_path () == backup_file) {
592 warning ("Refusing to write backup of a backup.");
593 return backup_file;
594 }
595
596 birdfont_file.write_font_file (backup_file, true);
597 return backup_file;
598 }
599
600 public void init_bfp (string directory) {
601 try {
602 bfp_file = new BirdFontPart (this);
603 bfp_file.create_directory (directory);
604 bfp_file.save ();
605 this.bfp = true;
606 } catch (GLib.Error e) {
607 warning (e.message);
608 // FIXME: notify user
609 }
610 }
611
612 public void set_bfp (bool bfp) {
613 this.bfp = bfp;
614 }
615
616 public bool is_bfp () {
617 return bfp;
618 }
619
620 public void save () {
621 if (is_bfp ()) {
622 save_bfp ();
623 } else {
624 save_bf ();
625 }
626 }
627
628 public bool save_bfp () {
629 return bfp_file.save ();
630 }
631
632 public void save_bf () {
633 Font font;
634 BirdFontFile birdfont_file = new BirdFontFile (this);
635 string path;
636 bool file_written;
637
638 if (font_file == null) {
639 warning ("File name not set.");
640 return;
641 }
642
643 path = (!) font_file;
644 file_written = birdfont_file.write_font_file (path);
645
646 if (read_only) {
647 warning (@"$path is write protected.");
648 TooltipArea.show_text (t_("The file is write protected."));
649 return;
650 }
651
652 if (!path.has_suffix (".bf")) {
653 warning ("Expecting .bf format.");
654 return;
655 }
656
657 if (file_written) {
658 // delete the backup when the font is saved
659 font = BirdFont.get_current_font ();
660 font.delete_backup ();
661 }
662
663 modified = false;
664 }
665
666 public void set_font_file (string path) {
667 font_file = path;
668 modified = false;
669 }
670
671 public uint length () {
672 return glyph_name.length ();
673 }
674
675 public bool is_empty () {
676 return (glyph_name.length () == 0);
677 }
678
679 public void set_file (string path) {
680 font_file = path;
681 }
682
683 public bool load () {
684 string path;
685 bool loaded = false;
686
687 initialised = true;
688 otf_font = false;
689
690 if (font_file == null) {
691 warning ("No file name.");
692 return false;
693 }
694
695 path = (!) font_file;
696
697 grid_width.clear ();
698
699 // empty cache and fill it with new glyphs from disk
700 glyph_cache.remove_all ();
701 glyph_name.remove_all ();
702 ligature.remove_all ();
703
704 if (path.has_suffix (".svg")) {
705 Toolbox.select_tool_by_name ("cubic_points");
706 loaded = parse_svg_file (path);
707
708 if (!loaded) {
709 warning ("Failed to load SVG font.");
710 }
711
712 format = FontFormat.SVG;
713 }
714
715 if (path.has_suffix (".ffi")) {
716 loaded = parse_bf_file (path);
717 format = FontFormat.FFI;
718 }
719
720 if (path.has_suffix (".bf")) {
721 loaded = parse_bf_file (path);
722 format = FontFormat.BIRDFONT;
723 }
724
725 if (path.has_suffix (".bfp")) {
726 loaded = parse_bfp_file (path);
727 format = FontFormat.BIRDFONT_PART;
728 }
729
730 if (path.has_suffix (".ttf")) {
731 loaded = parse_freetype_file (path);
732
733 if (!loaded) {
734 warning ("Failed to load TTF font.");
735 }
736
737 format = FontFormat.FREETYPE;
738
739 // run the old parser for debugging puposes
740 if (BirdFont.has_argument ("--test")) {
741 try {
742 OpenFontFormatReader or = new OpenFontFormatReader ();
743 or.parse_index (path);
744 } catch (GLib.Error e) {
745 warning (e.message);
746 }
747 }
748
749 font_file = null; // make sure BirdFont asks where to save the file
750 }
751
752 if (path.has_suffix (".otf")) {
753 loaded = parse_freetype_file (path);
754
755 if (!loaded) {
756 warning ("Failed to load OTF font.");
757 }
758
759 format = FontFormat.FREETYPE;
760
761 font_file = null;
762 }
763
764 return loaded;
765 }
766
767 private bool parse_bfp_file (string path) {
768 return bfp_file.load (path);
769 }
770
771 private bool parse_bf_file (string path) {
772 BirdFontFile font = new BirdFontFile (this);
773 return font.load (path);
774 }
775
776 private bool parse_freetype_file (string path) {
777 string font_data;
778 StringBuilder? data;
779 int error;
780 bool parsed;
781 BirdFontFile bf_font = new BirdFontFile (this);
782
783 data = load_freetype_font (path, out error);
784
785 if (error != 0) {
786 warning ("Failed to load freetype font.");
787 return false;
788 }
789
790 if (data == null) {
791 warning ("No svg data.");
792 return false;
793 }
794
795 font_data = ((!) data).str;
796 parsed = bf_font.load_data (font_data);
797
798 if (!parsed) {
799 warning ("Failed to parse loaded freetype font.");
800 }
801
802 return parsed;
803 }
804
805 private bool parse_svg_file (string path) {
806 SvgFont svg_font = new SvgFont (this);
807 svg_font.load (path);
808 return true;
809 }
810
811 public bool parse_otf_file (string path) throws GLib.Error {
812 otf = new OpenFontFormatReader ();
813 otf_font = true;
814 otf.parse_index (path);
815 return true;
816 }
817
818 public void set_read_only (bool r) {
819 read_only = r;
820 }
821
822 public static unichar to_unichar (string unicode) {
823 int index = 2;
824 int i = 0;
825 unichar c;
826 unichar rc = 0;
827 bool r;
828
829 if (unlikely (!unicode.has_prefix ("U+") && !unicode.has_prefix ("u+"))) {
830 warning (@"All unicode values must begin with U+ ($unicode)");
831 return '\0';
832 }
833
834 try {
835 while (r = unicode.get_next_char (ref index, out c)) {
836 rc <<= 4;
837 rc += hex_to_oct (c);
838
839 if (++i > 6) {
840 throw new ConvertError.FAILED ("i > 6");
841 }
842 }
843 } catch (ConvertError e) {
844 warning (@"unicode: $unicode\n");
845 warning (e.message);
846 rc = '\0';
847 }
848
849 return rc;
850 }
851
852 private static string oct_to_hex (uint8 o) {
853 switch (o) {
854 case 10: return "a";
855 case 11: return "b";
856 case 12: return "c";
857 case 13: return "d";
858 case 14: return "e";
859 case 15: return "f";
860 }
861
862 return_val_if_fail (0 <= o <= 9, "-".dup ());
863
864 return o.to_string ();
865 }
866
867 private static uint8 hex_to_oct (unichar o)
868 throws ConvertError {
869 StringBuilder s = new StringBuilder ();
870 s.append_unichar (o);
871
872 switch (o) {
873 case 'a': return 10;
874 case 'b': return 11;
875 case 'c': return 12;
876 case 'd': return 13;
877 case 'e': return 14;
878 case 'f': return 15;
879 case 'A': return 10;
880 case 'B': return 11;
881 case 'C': return 12;
882 case 'D': return 13;
883 case 'E': return 14;
884 case 'F': return 15;
885 }
886
887 if (!('0' <= o <= '9')) {
888 throw new ConvertError.FAILED (@"Expecting a number ($(s.str)).");
889 }
890
891 return (uint8) (o - '0');
892 }
893
894 public static string to_hex (unichar ch) {
895 StringBuilder s = new StringBuilder ();
896 s.append ("U+");
897 s.append (to_hex_code (ch));
898 return s.str;
899 }
900
901 public static string to_hex_code (unichar ch) {
902 StringBuilder s = new StringBuilder ();
903
904 uint8 a = (uint8)(ch & 0x00000F);
905 uint8 b = (uint8)((ch & 0x0000F0) >> 4 * 1);
906 uint8 c = (uint8)((ch & 0x000F00) >> 4 * 2);
907 uint8 d = (uint8)((ch & 0x00F000) >> 4 * 3);
908 uint8 e = (uint8)((ch & 0x0F0000) >> 4 * 4);
909 uint8 f = (uint8)((ch & 0xF00000) >> 4 * 5);
910
911 if (e != 0 || f != 0) {
912 s.append (oct_to_hex (f));
913 s.append (oct_to_hex (e));
914 }
915
916 if (c != 0 || d != 0) {
917 s.append (oct_to_hex (d));
918 s.append (oct_to_hex (c));
919 }
920
921 s.append (oct_to_hex (b));
922 s.append (oct_to_hex (a));
923
924 return s.str;
925 }
926 }
927
928 }
929