.
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 using BirdFont;
15
16 public const string GETTEXT_PACKAGE = "birdfont";
17
18 namespace BirdFont {
19
20 public static string? settings_directory = null;
21
22 static void print_import_help (string[] arg) {
23 stdout.printf (t_("Usage:"));
24 stdout.printf (arg[0]);
25 stdout.printf (" " + t_("BF-FILE") + " " + t_("SVG-FILES ...") +"\n");
26 stdout.printf ("\n");
27 }
28
29 public static int run_import (string[] arg) {
30 string bf_file = "";
31 Gee.ArrayList<string> svg_files = new Gee.ArrayList<string> ();
32 File bf;
33 File svg;
34 Font font;
35 bool imported;
36
37 Preferences.load ();
38 BirdFont.args = new Argument ("");
39 BirdFont.current_font = new Font ();
40 BirdFont.current_glyph_collection = new GlyphCollection.with_glyph ('\0', "");
41 MainWindow.init ();
42
43 if (arg.length < 3) {
44 print_import_help (arg);
45 return -1;
46 }
47
48 bf_file = build_absoulute_path (arg[1]);
49
50 for (int i = 2; i < arg.length; i++) {
51 svg_files.add (arg[i]);
52 }
53
54 bf = File.new_for_path (bf_file);
55 foreach (string f in svg_files) {
56 svg = File.new_for_path (f);
57
58 if (!svg.query_exists ()) {
59 stdout.printf (@"$f " + t_("does not exist.") + "\n");
60 return -1;
61 }
62 }
63
64 font = BirdFont.get_current_font ();
65
66 if (!bf.query_exists ()) {
67 stdout.printf (@"$bf_file " + t_("does not exist.") + " ");
68 stdout.printf (t_("A new font will be created.") + "\n");
69 font.set_file (bf_file);
70 } else {
71 font.set_file (bf_file);
72 if (!font.load ()) {
73 warning (@"Failed to load font $bf_file.\n");
74
75 if (!bf_file.has_suffix (".bf")) {
76 warning (@"Is it a .bf file?\n");
77 }
78
79 return -1;
80 }
81 }
82
83 font.save_backup ();
84
85 foreach (string f in svg_files) {
86 svg = File.new_for_path (f);
87 imported = import_svg_file (font, svg);
88
89 if (!imported) {
90 stdout.printf (t_("Failed to import") + " " + f + "\n");
91 stdout.printf (t_("Aborting") + "\n");
92 return -1;
93 }
94 }
95
96 font.save_bf ();
97
98 return 0;
99 }
100
101 internal static string build_absoulute_path (string file_name) {
102 File f = File.new_for_path (file_name);
103 return (!) f.get_path ();
104 }
105
106 static bool import_svg_file (Font font, File svg_file) {
107 string file_name = (!) svg_file.get_basename ();
108 string glyph_name;
109 StringBuilder n;
110 Glyph glyph;
111 GlyphCollection? gc = null;
112 GlyphCollection glyph_collection;
113 unichar character;
114 GlyphCanvas canvas;
115
116 glyph_name = file_name.replace (".svg", "");
117 glyph_name = glyph_name.replace (".SVG", "");
118
119 if (glyph_name.char_count () > 1) {
120 if (glyph_name.has_prefix ("U+")) {
121 n = new StringBuilder ();
122 n.append_unichar (Font.to_unichar (glyph_name));
123 glyph_name = n.str;
124 gc = font.get_glyph_collection (glyph_name);
125 } else {
126 gc = font.get_glyph_collection_by_name (glyph_name);
127
128 if (gc == null) {
129 stdout.printf (file_name + " " + t_("is not the name of a glyph or a Unicode value.") + "\n");
130 stdout.printf (t_("Unicode values must start with U+.") + "\n");
131 return false;
132 }
133 }
134 } else {
135 gc = font.get_glyph_collection (glyph_name);
136 }
137
138 if (gc != null) {
139 glyph_collection = (!) gc;
140 character = glyph_collection.get_unicode_character ();
141 glyph = new Glyph (glyph_collection.get_name (), character);
142 glyph.version_id = glyph_collection.get_last_id () + 1;
143 glyph_collection.insert_glyph (glyph, true);
144 } else {
145 return_val_if_fail (glyph_name.char_count () == 1, false);
146 character = glyph_name.get_char (0);
147 glyph_collection = new GlyphCollection (character, glyph_name);
148 glyph = new Glyph (glyph_name, character);
149 glyph_collection.insert_glyph (glyph, true);
150 font.add_glyph_collection (glyph_collection);
151 }
152
153 canvas = MainWindow.get_glyph_canvas ();
154 canvas.set_current_glyph_collection (glyph_collection);
155
156 stdout.printf (t_("Adding"));
157 stdout.printf (" ");
158 stdout.printf ((!) svg_file.get_basename ());
159 stdout.printf (" ");
160 stdout.printf (t_("to"));
161 stdout.printf (" ");
162 stdout.printf (t_("Glyph"));
163 stdout.printf (": ");
164 stdout.printf (glyph.get_name ());
165 stdout.printf (" ");
166 stdout.printf (t_("Version"));
167 stdout.printf (": ");
168 stdout.printf (@"$(glyph.version_id)");
169 stdout.printf ("\n");
170
171 SvgParser.import_svg ((!) svg_file.get_path ());
172
173 return true;
174 }
175
176 static void print_export_help (string[] arg) {
177 stdout.printf (t_("Usage:"));
178 stdout.printf (arg[0]);
179 stdout.printf (" [" + t_("OPTION") + "...] " + t_("FILE") +"\n");
180 stdout.printf ("-h, --help " + t_("print this message\n"));
181 stdout.printf ("-o, --output [DIRECTORY] " + t_("write files to this directory\n"));
182 stdout.printf ("-s, --svg " + t_("write svg file\n"));
183 stdout.printf ("-t, --ttf " + t_("write ttf and eot files\n"));
184 stdout.printf ("\n");
185 }
186
187 public static string get_version () {
188 return VERSION;
189 }
190
191 public static string get_build_stamp () {
192 return BUILD_TIMESTAMP;
193 }
194
195 public static int run_export (string[] arg) {
196 string output_directory = ".";
197 string file_name = "";
198 bool specific_formats = false;
199 bool write_ttf = false;
200 bool write_svg = false;
201 File directory;
202 Font font;
203 MainWindow main_window;
204
205 stdout.printf ("birdfont-export version %s\n", VERSION);
206 stdout.printf ("built on %s\n", BUILD_TIMESTAMP);
207
208 if (arg.length < 2) {
209 print_export_help (arg);
210 return -1;
211 }
212
213 BirdFont.current_font = BirdFont.new_font ();
214 BirdFont.current_glyph_collection = new GlyphCollection.with_glyph ( '\0', "null");
215 main_window = new MainWindow ();
216
217 // FIXME: create a option for this and add structure the log messages
218
219 if (BirdFont.logging) {
220 init_logfile ();
221 }
222
223 for (int i = 1; i < arg.length; i++) {
224
225 if (arg[i] == "-f" || arg[i] == "--fatal-warnings") {
226 BirdFont.fatal_wanings = true;
227 return 0;
228 }
229
230 if (arg[i] == "-h" || arg[i] == "--help") {
231 print_export_help (arg);
232 return 0;
233 }
234
235 if ((arg[i] == "-o" || arg[i] == "--output") && i + 1 < arg.length) {
236 output_directory = arg[i + 1];
237 i++;
238 continue;
239 }
240
241 if (arg[i] == "-s" || arg[i] == "--svg") {
242 write_svg = true;
243 specific_formats = true;
244 continue;
245 }
246
247 if (arg[i] == "-t" || arg[i] == "--ttf") {
248 write_ttf = true;
249 specific_formats = true;
250 continue;
251 }
252
253 if (arg[i].has_prefix ("-")) {
254 print_export_help (arg);
255 return 1;
256 }
257
258 if (!arg[i].has_prefix ("-")) {
259 file_name = arg[i];
260
261 if (i != arg.length - 1) {
262 print_export_help (arg);
263 return 1;
264 }
265
266 break;
267 }
268 }
269
270 if (BirdFont.fatal_wanings) {
271 LogLevelFlags levels = LogLevelFlags.LEVEL_ERROR | LogLevelFlags.LEVEL_CRITICAL | LogLevelFlags.LEVEL_WARNING;
272 Log.set_handler (null, levels, BirdFont.fatal_warning);
273 }
274
275 Preferences.load ();
276
277 BirdFont.args = new Argument ("");
278 BirdFont.current_glyph_collection = new GlyphCollection.with_glyph ('\0', "");
279
280 file_name = build_absoulute_path (file_name);
281
282 font = BirdFont.get_current_font ();
283 font.set_file (file_name);
284 if (!font.load ()) {
285 warning (@"Failed to load font $file_name.\n");
286
287 if (!file_name.has_suffix (".bf")) {
288 warning (@"Is it a .bf file?\n");
289 }
290
291 return 1;
292 }
293
294 directory = File.new_for_path (output_directory);
295
296 if (!directory.query_exists ()) {
297 stderr.printf (t_("Can't find output directory") + @"$((!)directory.get_path ())\n");
298 return 1;
299 }
300
301 if (!specific_formats || write_svg) {
302 print (@"Writing $(ExportSettings.get_file_name (font)).svg to $output_directory\n");
303 ExportTool.export_svg_font_path (File.new_for_path (output_directory));
304 }
305
306 if (!specific_formats || write_ttf) {
307 print (@"Writing $(ExportSettings.get_file_name (font)).ttf to $output_directory\n");
308 ExportTool.export_ttf_font_path (File.new_for_path (output_directory));
309 }
310
311 return 0;
312 }
313
314 public static void set_logging (bool log) {
315 BirdFont.logging = log;
316 }
317
318 public static string wine_to_unix_path (string exec_path) {
319 bool drive_c, drive_z;
320 int i;
321 string p, q;
322
323 p = exec_path;
324 p = p.replace ("\\", "/");
325
326 drive_c = exec_path.index_of ("C:") == 0;
327 drive_z = exec_path.index_of ("Z:") == 0;
328
329 i = p.index_of (":");
330
331 if (i != -1) {
332 p = p.substring (i + 2);
333 }
334
335 if (drive_c) {
336 q = @"/home/$(Environment.get_user_name ())/.wine/drive_c/" + p;
337
338 if (File.new_for_path (q).query_exists ()) {
339 return q;
340 } else {
341 return p;
342 }
343 }
344
345 if (drive_z) {
346 return ("/" + p).dup ();
347 }
348
349 return exec_path.dup ();
350 }
351
352 public bool is_null (void* n) {
353 return n == null;
354 }
355
356 public bool has_flag (uint32 flag, uint32 mask) {
357 return (flag & mask) > 0;
358 }
359
360 public class BirdFont {
361 public static Argument args;
362 public static bool experimental = false;
363 public static bool show_coordinates = false;
364 public static bool fatal_wanings = false;
365 public static bool win32 = false;
366 public static bool mac = false;
367 public static bool android = false;
368 public static string exec_path = "";
369 public static string bundle_path = "";
370
371 public static bool logging = false;
372 public static DataOutputStream? logstream = null;
373
374 public static Font current_font;
375 public static GlyphCollection current_glyph_collection;
376
377 public static Drawing? drawing = null;
378
379 public BirdFont () {
380 set_defaul_drawing_callbacks ();
381 }
382
383 void set_defaul_drawing_callbacks () {
384 if (drawing == null) {
385 drawing = new Drawing ();
386 }
387 }
388
389 /**
390 * @param arg command line arguments
391 * @param program path
392 */
393 public void init (string[] arg, string? program_path) {
394 int err_arg;
395 int i;
396 File font_file;
397 string exec_path;
398 string theme;
399 int default_theme_version;
400 string theme_version;
401
402 args = new Argument.command_line (arg);
403
404 #if ANDROID
405 BirdFont.logging = true;
406
407 __android_log_print (ANDROID_LOG_WARN, "BirdFont", @"libbirdfont version $VERSION");
408 LogLevelFlags log_levels = LogLevelFlags.LEVEL_ERROR | LogLevelFlags.LEVEL_CRITICAL | LogLevelFlags.LEVEL_WARNING;
409 Log.set_handler (null, log_levels, android_warning);
410
411 android = true;
412 #else
413 stdout.printf ("birdfont version %s\n", VERSION);
414 stdout.printf ("built on %s\n", BUILD_TIMESTAMP);
415
416 android = args.has_argument ("--android");
417
418 if (!BirdFont.logging) {
419 BirdFont.logging = args.has_argument ("--log");
420 }
421 #endif
422
423 if (BirdFont.logging) {
424 init_logfile ();
425 }
426
427 if (!args.has_argument ("--no-translation")) {
428 init_gettext ();
429 }
430
431 if (args.has_argument ("--help")) {
432 args.print_help ();
433 Process.exit (0);
434 }
435
436 err_arg = args.validate ();
437 if (err_arg != 0) {
438 stdout.printf (@"Unknown parameter $(arg [err_arg])\n\n");
439 args.print_help ();
440 Process.exit (0);
441 }
442
443 Preferences.load ();
444
445 // always load default theme when names in theme does change
446 default_theme_version = 1;
447 theme = Preferences.get ("theme");
448 theme_version = Preferences.get ("theme_version");
449
450 Theme.set_default_colors ();
451
452 if (theme_version == "" || int.parse (theme_version) < default_theme_version) {
453
454 Theme.load_theme ("dark.theme");
455 Preferences.set ("theme", "dark.theme");
456 } else {
457 if (theme != "") {
458 Theme.load_theme (theme);
459 } else {
460 Theme.load_theme ("dark.theme");
461 }
462 }
463
464 Preferences.set ("theme_version", @"$default_theme_version");
465
466 current_font = new Font ();
467 current_font.set_name ("");
468 current_font.initialised = false;
469 current_glyph_collection = new GlyphCollection.with_glyph ('\0', "");
470
471 experimental = args.has_argument ("--test");
472 show_coordinates = args.has_argument ("--show-coordinates") || experimental;
473 fatal_wanings = args.has_argument ("--fatal-warning");
474 win32 = (arg[0].index_of (".exe") > -1)
475 || arg[0] == "wine"
476 || args.has_argument ("--windows");
477
478 #if MAC
479 mac = true;
480 #else
481 mac = args.has_argument ("--mac");
482 #endif
483
484 if (program_path == null) {
485 exec_path = "";
486
487 if (win32) {
488 // wine hack to get "." folder in win32 environment
489 i = arg[0].last_index_of ("\\");
490
491 if (i != -1) {
492 exec_path = arg[0];
493 exec_path = exec_path.substring (0, i);
494 exec_path = wine_to_unix_path (exec_path);
495 }
496 } else {
497 exec_path = "./";
498 }
499 } else {
500 exec_path = (!) program_path;
501 }
502
503 if (args.get_file () != "") {
504 font_file = File.new_for_path (args.get_file ());
505
506 if (!font_file.query_exists ()) {
507 stderr.printf (@"The file \"$(args.get_file ())\" was not found.\n");
508 Process.exit (-1);
509 }
510 }
511
512 if (fatal_wanings) {
513 LogLevelFlags levels = LogLevelFlags.LEVEL_ERROR | LogLevelFlags.LEVEL_CRITICAL | LogLevelFlags.LEVEL_WARNING;
514 Log.set_handler (null, levels, fatal_warning);
515 }
516
517 Preferences.set_last_file (get_current_font ().get_path ());
518
519 DefaultCharacterSet.create_default_character_sets ();
520 DefaultCharacterSet.get_characters_for_prefered_language ();
521
522 HeadTable.init ();
523
524 if (TestBirdFont.get_singleton ().test_cases_to_run != "All") {
525 TestBirdFont.run_tests ();
526 }
527 }
528
529 public static Argument get_arguments () {
530 return args;
531 }
532
533 public static void set_bundle_path (string path) {
534 bundle_path = path;
535 }
536
537 public static void init_gettext () {
538 // FIXME: android, this should be OK now
539 #if !ANDROID
540 string locale_directory = SearchPaths.get_locale_directory ();
541 Intl.setlocale (LocaleCategory.MESSAGES, "");
542 Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "utf-8");
543 Intl.bindtextdomain (GETTEXT_PACKAGE, locale_directory);
544 #endif
545 }
546
547 public static void load_font_from_command_line () {
548 string file = args.get_file ();
549 if (file != "") {
550 RecentFiles.load_font (file);
551 }
552 }
553
554 public static Font get_current_font () {
555 return current_font;
556 }
557
558 internal static void fatal_warning (string? log_domain, LogLevelFlags log_levels, string message) {
559 bool fatal = true;
560
561 if (log_domain != null) {
562 stderr.printf ("%s: \n", (!) log_domain);
563 }
564
565 stderr.printf ("\n%s\n\n", message);
566 assert (!fatal);
567 }
568
569 #if ANDROID
570 internal static void android_warning (string? log_domain, LogLevelFlags log_levels, string message) {
571 __android_log_print (ANDROID_LOG_WARN, "BirdFont", message);
572 }
573 #endif
574
575 public static Font new_font () {
576 current_font = new Font ();
577
578 if (!is_null (MainWindow.tools)) {
579 MainWindow.get_drawing_tools ().remove_all_grid_buttons ();
580 DrawingTools.add_new_grid (1);
581 DrawingTools.add_new_grid (2);
582 DrawingTools.add_new_grid (4);
583 }
584
585 if (!is_null (Toolbox.background_tools)) {
586 Toolbox.background_tools.remove_images ();
587 }
588
589 KerningTools.update_kerning_classes ();
590
591 return current_font;
592 }
593
594 public static void set_settings_directory (string directory) {
595 settings_directory = directory;
596 }
597
598 public static File get_preview_directory () {
599 File settings = get_settings_directory ();
600 File backup = get_child(settings, "preview");
601
602 if (!backup.query_exists ()) {
603 DirUtils.create ((!) backup.get_path (), 0755);
604 }
605
606 return backup;
607 }
608
609 internal static File get_settings_directory () {
610 string home_path;
611 File home;
612 File settings;
613
614 #if ANDROID
615 home_path = "/data/data/org.birdfont.sefyr/files";
616 home = File.new_for_path (home_path);
617
618 if (!home.query_exists ()) {
619 printd ("Create settings directory.");
620 DirUtils.create ((!) home.get_path (),0755);
621 }
622 #else
623 home_path = (settings_directory != null)
624 ? (!) settings_directory : Environment.get_user_config_dir ();
625
626 if (is_null (home_path)) {
627 warning ("No home directory set.");
628 home_path = ".";
629 }
630
631 home = File.new_for_path (home_path);
632 #endif
633 settings = get_child(home, "birdfont");
634
635 if (!settings.query_exists ()) {
636 DirUtils.create ((!) settings.get_path (), 0755);
637 }
638
639 return settings;
640 }
641
642 internal static File get_backup_directory () {
643 File settings = get_settings_directory ();
644 File backup = get_child (settings, "backup");
645
646 if (!backup.query_exists ()) {
647 DirUtils.create ((!) backup.get_path (), 0755);
648 }
649
650 return backup;
651 }
652
653 public static bool has_argument (string param) {
654 if (is_null (args)) {
655 return false;
656 }
657
658 return args.has_argument (param);
659 }
660
661 internal static string? get_argument (string param) {
662 return args.get_argument (param);
663 }
664 }
665
666 void init_logfile () {
667 DateTime t;
668 File settings;
669 string s;
670 File log;
671
672 try {
673 t = new DateTime.now_local ();
674 settings = BirdFont.get_settings_directory ();
675 s = t.to_string ().replace (":", "_");
676 log = get_child (settings, @"birdfont_$s.log");
677
678 BirdFont.logstream = new DataOutputStream (log.create (FileCreateFlags.REPLACE_DESTINATION));
679 ((!)BirdFont.logstream).put_string ((!) log.get_path ());
680 ((!)BirdFont.logstream).put_string ("\n");
681
682 warning ("Logging to " + (!) log.get_path ());
683 } catch (GLib.Error e) {
684 warning (e.message);
685 warning ((!) log.get_path ());
686 }
687
688 LogLevelFlags levels = LogLevelFlags.LEVEL_ERROR | LogLevelFlags.LEVEL_CRITICAL | LogLevelFlags.LEVEL_WARNING | LogLevelFlags.LEVEL_DEBUG;
689 Log.set_handler (null, levels, log_warning);
690
691 BirdFont.logging = true;
692
693 printd (@"Program version: $(VERSION)\n");
694 printd (@"built on $(BUILD_TIMESTAMP)\n");
695 }
696
697 internal static void log_warning (string? log_domain, LogLevelFlags log_levels, string message) {
698 if (log_domain != null) {
699 printd ((!) log_domain);
700 }
701
702 printd ("\n");
703 printd (message);
704 printd ("\n");
705 printd ("\n");
706 }
707
708 /** Write debug output to logfile. */
709 public static void printd (string s) {
710 #if ANDROID
711 __android_log_print (ANDROID_LOG_WARN, "BirdFont", s);
712 #else
713 if (unlikely (BirdFont.logging)) {
714 try {
715 if (BirdFont.logstream != null) {
716 ((!)BirdFont.logstream).put_string (s);
717 } else {
718 warning ("No logstream.");
719 }
720
721 stderr.printf (s);
722 } catch (GLib.Error e) {
723 warning (e.message);
724 }
725 }
726 #endif
727 }
728
729 /** Translate string */
730 public string t_ (string t) {
731 return _(t);
732 }
733
734 /** Translate mac menu items */
735 public static string translate_mac (string t) {
736 string s = t_(t);
737 return s.replace ("_", "");
738 }
739
740 /** Print a warning if Birdfont was started with the --test argument. */
741 public static void warn_if_test (string message) {
742 if (BirdFont.has_argument ("--test")) {
743 warning (message);
744 }
745 }
746
747 /** Obtain a handle to a file in a folder. */
748 public static File get_child (File folder, string file_name) {
749 string f;
750 string s;
751 string n;
752
753 // avoid drive letter problems on windows
754
755 f = (!) folder.get_path ();
756 s = (BirdFont.win32) ? "\\" : "/";
757
758 n = file_name;
759 if (unlikely (BirdFont.win32 && file_name.index_of ("\\") != -1)) {
760 warning (@"File name contains path separator: $file_name, Directory: $f");
761 n = n.substring (n.last_index_of ("\\")).replace ("\\", "");
762 }
763
764 if (!f.has_suffix (s)) {
765 f += s;
766 }
767
768 printd (@"File: Directory: $f Name: $n\n");
769
770 return File.new_for_path (f + n);
771 }
772
773 public static void set_drawing_callbacks (Drawing callbacks) {
774 BirdFont.drawing = callbacks;
775 }
776
777 }
778