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