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