.
1 /*
2 Copyright (C) 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 /** BirdFontPart is a class for parsing .bfp files. The file format is
18 * identical to .bf but the font is split in many parts. Each part
19 * contains a few elements and all parent nodes from the root node and
20 * downwards. The .bfp files can be parsed in any order. The root directory
21 * of a .bfp tree must have a file with the name "description.bfp", this file
22 * tells the parser that bfp files in parent directories should be excluded.
23 */
24 public class BirdFontPart : GLib.Object{
25 Font font;
26 Gee.ArrayList<string> parts;
27 string root_directory;
28
29 static string FILE_ATTRIBUTES = "standard::*";
30
31 public BirdFontPart (Font font) {
32 this.font = font;
33 parts = new Gee.ArrayList<string> ();
34 root_directory = "";
35 }
36
37 public bool load (string bfp_file) {
38 BirdFontFile bf = new BirdFontFile (font);
39 File bfp_dir;
40 File image_dir;
41
42 try {
43 find_all_parts (bfp_file);
44 font.set_bfp (true);
45
46 font.background_images.clear ();
47
48 bfp_dir = File.new_for_path (root_directory);
49 image_dir = get_child (bfp_dir, "images");
50 copy_backgrounds ((!) image_dir.get_path ());
51
52 foreach (string fn in parts) {
53 bf.load_part (fn);
54 }
55 } catch (GLib.Error e) {
56 warning (e.message);
57 return false;
58 }
59
60 return true;
61 }
62
63 public string get_path () {
64 string path = "";
65
66 try {
67 path = (!) get_destination_file (@"$(font.full_name).bfp").get_path ();
68 } catch (GLib.Error e) {
69 warning (e.message);
70 }
71
72 return path;
73 }
74
75 public bool save () {
76 DataOutputStream os;
77 BirdFontFile bf = new BirdFontFile (font);
78 bool error = false;
79 string file_name;
80 string glyph_dir_name;
81 File glyph_file;
82
83 if (root_directory == "") {
84 warning ("No directory is created for this birdfont part.");
85 return false;
86 }
87
88 try {
89 // remove deleted glyphs
90 foreach (Glyph g in font.deleted_glyphs) {
91 file_name = get_glyph_base_file_name (g) + ".bfp";
92 glyph_dir_name = get_subdir_name (file_name);
93 glyph_file = get_destination_file (file_name, "glyphs", glyph_dir_name);
94
95 if (glyph_file.query_exists ()) {
96 glyph_file.delete ();
97 }
98
99 print (@"$((!)glyph_file.get_path ())\n");
100 }
101
102 os = create_file (@"$(font.full_name).bfp");
103 bf.write_root_tag (os);
104 bf.write_closing_root_tag (os);
105 os.close ();
106
107 os = create_file ("description.bfp");
108 bf.write_root_tag (os);
109 bf.write_description (os);
110 bf.write_closing_root_tag (os);
111 os.close ();
112
113 os = create_file ("lines.bfp");
114 bf.write_root_tag (os);
115 bf.write_lines (os);
116 bf.write_closing_root_tag (os);
117 os.close ();
118
119 os = create_file ("settings.bfp");
120 bf.write_root_tag (os);
121 bf.write_settings (os);
122 bf.write_closing_root_tag (os);
123 os.close ();
124
125 os = create_file ("spacing.bfp");
126 bf.write_root_tag (os);
127 bf.write_spacing_classes (os);
128 bf.write_closing_root_tag (os);
129 os.close ();
130
131 os = create_file ("ligatures.bfp");
132 bf.write_root_tag (os);
133 bf.write_ligatures (os);
134 bf.write_closing_root_tag (os);
135 os.close ();
136
137 font.glyph_cache.for_each ((gc) => {
138 try {
139 string selected_file_name;
140 string dir_name;
141
142 if (is_null (gc)) {
143 warning ("No glyph collection");
144 }
145
146 selected_file_name = get_first_number_in_unicode (((!)gc).get_current ());
147 dir_name = get_subdir_name (selected_file_name);
148
149 os = create_file (@"selected_$(selected_file_name).bfp", "glyphs", dir_name);
150 bf.write_root_tag (os);
151 bf.write_glyph_collection_start (gc, os);
152 bf.write_selected ((!) gc, os);
153 bf.write_glyph_collection_end (os);
154 bf.write_closing_root_tag (os);
155 os.close ();
156
157 foreach (Glyph g in gc.get_version_list ().glyphs) {
158 try {
159 write_glyph (bf, gc, g);
160 write_glyph_background_image (bf, gc, g);
161 } catch (GLib.Error e) {
162 warning (e.message);
163 }
164 }
165 } catch (GLib.Error e) {
166 warning (@"Can not save bfp files to $root_directory\n");
167 warning (@"$(e.message) \n");
168 error = true;
169 }
170 });
171
172 os = create_file ("kerning.bfp");
173 bf.write_root_tag (os);
174 bf.write_kerning (os);
175 bf.write_closing_root_tag (os);
176 os.close ();
177
178 os = create_file ("images.bfp");
179 bf.write_root_tag (os);
180 bf.write_images (os);
181 bf.write_closing_root_tag (os);
182 os.close ();
183
184 } catch (GLib.Error e) {
185 warning (@"Failed to save bfp files to $root_directory\n");
186 warning (@"$(e.message) \n");
187 error = true;
188 }
189
190 return !error;
191 }
192
193 void copy_backgrounds (string folder) throws GLib.Error {
194 FileInfo info;
195 FileInfo? fi;
196 FileEnumerator e;
197 string name;
198 File image_dir;
199 BackgroundImage bg;
200 File found;
201 File parts;
202 File dest;
203
204 image_dir = File.new_for_path (folder);
205
206 if (image_dir.query_exists ()) {
207 info = image_dir.query_info (FILE_ATTRIBUTES, FileQueryInfoFlags.NONE);
208 if (info.get_file_type () != FileType.DIRECTORY) {
209 warning (@"$((!) image_dir.get_path ()) is not a directory.");
210 throw new FileError.NOTDIR ("Not a directory.");
211 }
212
213 e = image_dir.enumerate_children (FILE_ATTRIBUTES, 0);
214 while ((fi = e.next_file ()) != null) {
215 info = (!) fi;
216 name = info.get_name ();
217
218 if (info.get_file_type () == FileType.DIRECTORY) {
219 found = get_child (image_dir, name);
220 copy_backgrounds ((!) found.get_path ());
221 }
222
223 if (name.has_suffix (".png")) {
224 found = get_child (image_dir, name);
225 parts = get_child (font.get_backgrounds_folder (), "parts");
226 dest = get_child (parts, name);
227 bg = new BackgroundImage ((!) found.get_path ());
228 bg.create_background_folders (font);
229 bg.copy_if_new (dest);
230 }
231 }
232 }
233 }
234
235 string get_first_number_in_unicode (Glyph g) throws GLib.Error {
236 string s = Font.to_hex (g.unichar_code);
237 s = s.replace ("U+", "");
238 return s;
239 }
240
241 string get_glyph_base_file_name (Glyph g) throws GLib.Error {
242 string s = get_first_number_in_unicode (g);
243 s = @"U+$(s)_$(g.version_id)";
244 return s;
245 }
246
247 public string get_subdir_name (string file_name) {
248 string d = file_name;
249
250 if (file_name.has_prefix ("U+")) {
251 d = file_name.replace ("U+", "");
252 }
253
254 return (!) d.get_char ().to_string ();
255 }
256
257 void write_glyph (BirdFontFile bf, GlyphCollection gc, Glyph g) throws GLib.Error {
258 string file_name;
259 string dir_name;
260 DataOutputStream os;
261
262 file_name = get_glyph_base_file_name (g);
263 dir_name = get_subdir_name (file_name);
264
265 os = create_file (@"$(file_name).bfp", "glyphs", dir_name);
266 bf.write_root_tag (os);
267 bf.write_glyph_collection_start (gc, os);
268 bf.write_glyph (g, gc, os);
269 bf.write_glyph_collection_end (os);
270 bf.write_closing_root_tag (os);
271 os.close ();
272 }
273
274 void write_glyph_background_image (BirdFontFile bf, GlyphCollection gc, Glyph g) throws GLib.Error {
275 string file_name;
276 string dir_name;
277 BackgroundImage bg;
278 File file;
279
280 if (g.get_background_image () != null) {
281 bg = (!) g.get_background_image ();
282
283 if (bg.is_valid ()) {
284 file_name = @"$(bg.get_sha1 ()).png";
285 dir_name = get_subdir_name (file_name);
286 file = get_destination_file (file_name, "images", dir_name);
287 bg.copy_if_new (file);
288
289 // FIXME: GIT ADD
290 }
291 }
292 }
293
294 public void create_directory (string directory) throws GLib.Error {
295 File dir = File.new_for_path (directory);
296 File bfp_dir;
297 int i = 2;
298
299 if (directory.has_suffix (font.get_full_name ())) {
300 bfp_dir = dir;
301 } else {
302 bfp_dir = get_child (dir, font.get_full_name ());
303 }
304
305 while (bfp_dir.query_exists ()) {
306 bfp_dir = get_child (dir, @"$(font.get_full_name ())_$(i)");
307 i++;
308 }
309
310 if (!dir.query_exists ()) {
311 DirUtils.create ((!) dir.get_path (), 0755);
312 }
313
314 root_directory = (!) bfp_dir.get_path ();
315 DirUtils.create (root_directory, 0755);
316 }
317
318 private void find_all_parts (string bfp_file) throws GLib.Error {
319 File start = File.new_for_path (bfp_file);
320 FileInfo info;
321 File root;
322
323 info = start.query_info (FILE_ATTRIBUTES, FileQueryInfoFlags.NONE);
324 if (info.get_file_type () != FileType.DIRECTORY) {
325 start = (!) start.get_parent ();
326 }
327
328 root = find_root ((!)start.get_path ());
329 root_directory = (!)root.get_path ();
330
331 find_parts (root_directory);
332 }
333
334 private void find_parts (string directory) throws GLib.Error {
335 File start = File.new_for_path (directory);
336 File found;
337 FileInfo info;
338 FileInfo? fi;
339 FileEnumerator e;
340 string name;
341
342
343 info = start.query_info (FILE_ATTRIBUTES, FileQueryInfoFlags.NONE);
344 if (info.get_file_type () != FileType.DIRECTORY) {
345 warning (@"$directory is not a directory.");
346 throw new FileError.NOTDIR ("Not a directory.");
347 }
348
349 e = start.enumerate_children (FILE_ATTRIBUTES, 0);
350 while ((fi = e.next_file ()) != null) {
351 info = (!) fi;
352 name = info.get_name ();
353 if (info.get_file_type () == FileType.DIRECTORY) {
354 find_parts ((!) ((!) get_child (start, name)).get_path ());
355 } else if (name.has_suffix (".bfp")) {
356 found = get_child (start, name);
357 parts.add ((!) found.get_path ());
358 }
359 }
360 }
361
362 private File find_root (string directory) throws GLib.Error {
363 File start = File.new_for_path (directory);
364 FileInfo info;
365 FileInfo? fi;
366 FileEnumerator e;
367
368 info = start.query_info (FILE_ATTRIBUTES, FileQueryInfoFlags.NONE);
369 if (info.get_file_type () != FileType.DIRECTORY) {
370 warning ("Not a directory.");
371 throw new FileError.NOTDIR ("Not a directory.");
372 }
373
374 e = start.enumerate_children (FILE_ATTRIBUTES, 0);
375 while ((fi = e.next_file ()) != null) {
376 info = (!) fi;
377 if (info.get_name () == "description.bfp") {
378 return start;
379 }
380 }
381
382 if (start.get_parent () == null) {
383 warning ("description.bfp not found");
384 throw new FileError.FAILED ("description.bfp not found");
385 }
386
387 return find_root ((!)((!) start.get_parent ()).get_path ());
388 }
389
390 private File new_subdirectory (File d, string subdir) throws GLib.Error {
391 FileInfo info;
392 File dir;
393
394 dir = d;
395 dir = get_child (dir, subdir);
396
397 if (!dir.query_exists ()) {
398 DirUtils.create ((!) dir.get_path (), 0755);
399 } else {
400 info = dir.query_info (FILE_ATTRIBUTES, FileQueryInfoFlags.NONE);
401 if (info.get_file_type () != FileType.DIRECTORY) {
402 throw new FileError.FAILED (@"Can't save font, $subdir is not a directory.");
403 }
404 }
405 return dir;
406 }
407
408 private File get_destination_file (string name, string subdir1 = "", string subdir2 = "") throws GLib.Error {
409 File file;
410 File dir;
411
412 dir = File.new_for_path (root_directory);
413
414 if (subdir1 != "") {
415 dir = new_subdirectory (dir, subdir1);
416 }
417
418 if (subdir2 != "") {
419 dir = new_subdirectory (dir, subdir2);
420 }
421
422 file = get_child (dir, name);
423
424 if (file.query_file_type (0) == FileType.DIRECTORY) {
425 throw new FileError.FAILED (@"Can't save font, $name is a directory.");
426 }
427
428 return file;
429 }
430
431 private DataOutputStream create_file (string name, string subdir1 = "", string subdir2 = "") throws GLib.Error {
432 DataOutputStream os;
433 File file;
434 string git_path;
435
436 file = get_destination_file (name, subdir1, subdir2);
437
438 if (file.query_exists ()) {
439 file.delete ();
440 }
441
442 os = new DataOutputStream (file.create (FileCreateFlags.REPLACE_DESTINATION));
443
444 if (subdir2 != "") {
445 git_path = subdir1 + "/" + subdir2 + "/" + name;
446 } else if (subdir1 != "") {
447 git_path = subdir1 + "/" + name;
448 } else {
449 git_path = name;
450 }
451
452 // FIXME: git_index.add_path (git_path);
453
454 return os;
455 }
456 }
457
458 }
459