.
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 os = create_file ("alternates.bfp");
143 bf.write_root_tag (os);
144 bf.write_alternates (os);
145 bf.write_closing_root_tag (os);
146 os.close ();
147
148 font.glyph_cache.for_each ((gc) => {
149 try {
150 string selected_file_name;
151 string dir_name;
152
153 if (is_null (gc)) {
154 warning ("No glyph collection");
155 }
156
157 selected_file_name = get_first_number_in_unicode (((!)gc).get_current ());
158 dir_name = get_subdir_name (selected_file_name);
159
160 os = create_file (@"selected_$(selected_file_name).bfp", "glyphs", dir_name);
161 bf.write_root_tag (os);
162 bf.write_glyph_collection_start (gc, os);
163 bf.write_selected ((!) gc, os);
164 bf.write_glyph_collection_end (os);
165 bf.write_closing_root_tag (os);
166 os.close ();
167
168 foreach (Glyph g in gc.glyphs) {
169 try {
170 write_glyph (bf, gc, g);
171 write_glyph_background_image (bf, gc, g);
172 } catch (GLib.Error e) {
173 warning (e.message);
174 }
175 }
176 } catch (GLib.Error e) {
177 warning (@"Can not save bfp files to $root_directory\n");
178 warning (@"$(e.message) \n");
179 error = true;
180 }
181 });
182
183 os = create_file ("kerning.bfp");
184 bf.write_root_tag (os);
185 bf.write_kerning (os);
186 bf.write_closing_root_tag (os);
187 os.close ();
188
189 os = create_file ("images.bfp");
190 bf.write_root_tag (os);
191 bf.write_images (os);
192 bf.write_closing_root_tag (os);
193 os.close ();
194
195 } catch (GLib.Error e) {
196 warning (@"Failed to save bfp files to $root_directory\n");
197 warning (@"$(e.message) \n");
198 error = true;
199 }
200
201 return !error;
202 }
203
204 void copy_backgrounds (string folder) throws GLib.Error {
205 FileInfo info;
206 FileInfo? fi;
207 FileEnumerator e;
208 string name;
209 File image_dir;
210 BackgroundImage bg;
211 File found;
212 File parts;
213 File dest;
214
215 image_dir = File.new_for_path (folder);
216
217 if (image_dir.query_exists ()) {
218 info = image_dir.query_info (FILE_ATTRIBUTES, FileQueryInfoFlags.NONE);
219 if (info.get_file_type () != FileType.DIRECTORY) {
220 warning (@"$((!) image_dir.get_path ()) is not a directory.");
221 throw new FileError.NOTDIR ("Not a directory.");
222 }
223
224 e = image_dir.enumerate_children (FILE_ATTRIBUTES, 0);
225 while ((fi = e.next_file ()) != null) {
226 info = (!) fi;
227 name = info.get_name ();
228
229 if (info.get_file_type () == FileType.DIRECTORY) {
230 found = get_child (image_dir, name);
231 copy_backgrounds ((!) found.get_path ());
232 }
233
234 if (name.has_suffix (".png")) {
235 found = get_child (image_dir, name);
236 parts = get_child (font.get_backgrounds_folder (), "parts");
237 dest = get_child (parts, name);
238 bg = new BackgroundImage ((!) found.get_path ());
239 bg.create_background_folders (font);
240 bg.copy_if_new (dest);
241 }
242 }
243 }
244 }
245
246 string get_first_number_in_unicode (Glyph g) throws GLib.Error {
247 string s = Font.to_hex (g.unichar_code);
248 s = s.replace ("U+", "");
249 return s;
250 }
251
252 string get_glyph_base_file_name (Glyph g) throws GLib.Error {
253 string s = get_first_number_in_unicode (g);
254 s = @"U+$(s)_$(g.version_id)";
255 return s;
256 }
257
258 public string get_subdir_name (string file_name) {
259 string d = file_name;
260
261 if (file_name.has_prefix ("U+")) {
262 d = file_name.replace ("U+", "");
263 }
264
265 return (!) d.get_char ().to_string ();
266 }
267
268 void write_glyph (BirdFontFile bf, GlyphCollection gc, Glyph g) throws GLib.Error {
269 string file_name;
270 string dir_name;
271 DataOutputStream os;
272
273 file_name = get_glyph_base_file_name (g);
274 dir_name = get_subdir_name (file_name);
275
276 os = create_file (@"$(file_name).bfp", "glyphs", dir_name);
277 bf.write_root_tag (os);
278 bf.write_glyph_collection_start (gc, os);
279 bf.write_glyph (g, os);
280 bf.write_glyph_collection_end (os);
281 bf.write_closing_root_tag (os);
282 os.close ();
283 }
284
285 void write_glyph_background_image (BirdFontFile bf, GlyphCollection gc, Glyph g) throws GLib.Error {
286 string file_name;
287 string dir_name;
288 BackgroundImage bg;
289 File file;
290
291 if (g.get_background_image () != null) {
292 bg = (!) g.get_background_image ();
293
294 if (bg.is_valid ()) {
295 file_name = @"$(bg.get_sha1 ()).png";
296 dir_name = get_subdir_name (file_name);
297 file = get_destination_file (file_name, "images", dir_name);
298 bg.copy_if_new (file);
299
300 // FIXME: GIT ADD
301 }
302 }
303 }
304
305 public void create_directory (string directory) throws GLib.Error {
306 File dir = File.new_for_path (directory);
307 File bfp_dir;
308 int i = 2;
309
310 if (directory.has_suffix (font.get_full_name ())) {
311 bfp_dir = dir;
312 } else {
313 bfp_dir = get_child (dir, font.get_full_name ());
314 }
315
316 while (bfp_dir.query_exists ()) {
317 bfp_dir = get_child (dir, @"$(font.get_full_name ())_$(i)");
318 i++;
319 }
320
321 if (!dir.query_exists ()) {
322 DirUtils.create ((!) dir.get_path (), 0755);
323 }
324
325 root_directory = (!) bfp_dir.get_path ();
326 DirUtils.create (root_directory, 0755);
327 }
328
329 private void find_all_parts (string bfp_file) throws GLib.Error {
330 File start = File.new_for_path (bfp_file);
331 FileInfo info;
332 File root;
333
334 info = start.query_info (FILE_ATTRIBUTES, FileQueryInfoFlags.NONE);
335 if (info.get_file_type () != FileType.DIRECTORY) {
336 start = (!) start.get_parent ();
337 }
338
339 root = find_root ((!)start.get_path ());
340 root_directory = (!)root.get_path ();
341
342 find_parts (root_directory);
343 }
344
345 private void find_parts (string directory) throws GLib.Error {
346 File start = File.new_for_path (directory);
347 File found;
348 FileInfo info;
349 FileInfo? fi;
350 FileEnumerator e;
351 string name;
352
353
354 info = start.query_info (FILE_ATTRIBUTES, FileQueryInfoFlags.NONE);
355 if (info.get_file_type () != FileType.DIRECTORY) {
356 warning (@"$directory is not a directory.");
357 throw new FileError.NOTDIR ("Not a directory.");
358 }
359
360 e = start.enumerate_children (FILE_ATTRIBUTES, 0);
361 while ((fi = e.next_file ()) != null) {
362 info = (!) fi;
363 name = info.get_name ();
364 if (info.get_file_type () == FileType.DIRECTORY) {
365 find_parts ((!) ((!) get_child (start, name)).get_path ());
366 } else if (name.has_suffix (".bfp")) {
367 found = get_child (start, name);
368 parts.add ((!) found.get_path ());
369 }
370 }
371 }
372
373 private File find_root (string directory) throws GLib.Error {
374 File start = File.new_for_path (directory);
375 FileInfo info;
376 FileInfo? fi;
377 FileEnumerator e;
378
379 info = start.query_info (FILE_ATTRIBUTES, FileQueryInfoFlags.NONE);
380 if (info.get_file_type () != FileType.DIRECTORY) {
381 warning ("Not a directory.");
382 throw new FileError.NOTDIR ("Not a directory.");
383 }
384
385 e = start.enumerate_children (FILE_ATTRIBUTES, 0);
386 while ((fi = e.next_file ()) != null) {
387 info = (!) fi;
388 if (info.get_name () == "description.bfp") {
389 return start;
390 }
391 }
392
393 if (start.get_parent () == null) {
394 warning ("description.bfp not found");
395 throw new FileError.FAILED ("description.bfp not found");
396 }
397
398 return find_root ((!)((!) start.get_parent ()).get_path ());
399 }
400
401 private File new_subdirectory (File d, string subdir) throws GLib.Error {
402 FileInfo info;
403 File dir;
404
405 dir = d;
406 dir = get_child (dir, subdir);
407
408 if (!dir.query_exists ()) {
409 DirUtils.create ((!) dir.get_path (), 0755);
410 } else {
411 info = dir.query_info (FILE_ATTRIBUTES, FileQueryInfoFlags.NONE);
412 if (info.get_file_type () != FileType.DIRECTORY) {
413 throw new FileError.FAILED (@"Can't save font, $subdir is not a directory.");
414 }
415 }
416 return dir;
417 }
418
419 private File get_destination_file (string name, string subdir1 = "", string subdir2 = "") throws GLib.Error {
420 File file;
421 File dir;
422
423 dir = File.new_for_path (root_directory);
424
425 if (subdir1 != "") {
426 dir = new_subdirectory (dir, subdir1);
427 }
428
429 if (subdir2 != "") {
430 dir = new_subdirectory (dir, subdir2);
431 }
432
433 file = get_child (dir, name);
434
435 if (file.query_file_type (0) == FileType.DIRECTORY) {
436 throw new FileError.FAILED (@"Can't save font, $name is a directory.");
437 }
438
439 return file;
440 }
441
442 private DataOutputStream create_file (string name, string subdir1 = "", string subdir2 = "") throws GLib.Error {
443 DataOutputStream os;
444 File file;
445 string git_path;
446
447 file = get_destination_file (name, subdir1, subdir2);
448
449 if (file.query_exists ()) {
450 file.delete ();
451 }
452
453 os = new DataOutputStream (file.create (FileCreateFlags.REPLACE_DESTINATION));
454
455 if (subdir2 != "") {
456 git_path = subdir1 + "/" + subdir2 + "/" + name;
457 } else if (subdir1 != "") {
458 git_path = subdir1 + "/" + name;
459 } else {
460 git_path = name;
461 }
462
463 // FIXME: git_index.add_path (git_path);
464
465 return os;
466 }
467 }
468
469 }
470