1/* WebFont.vala
2 *
3 * Copyright (C) 2020 - 2021 Jerry Casiano
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.
17 *
18 * If not, see <http://www.gnu.org/licenses/gpl-3.0.txt>.
19*/
20
21#if HAVE_WEBKIT
22
23namespace FontManager.GoogleFonts {
24
25    const string FONT_FACE = "@font-face { font-family: '%s'; font-style: %s; font-weight: %i; src: local('%s'), local('%s'), url(%s) format('truetype'); }";
26
27    public enum FileStatus {
28        NOT_INSTALLED,
29        INSTALLED,
30        REQUIRES_UPDATE;
31    }
32
33    public async bool download_font_files (Font [] fonts) {
34        var session = new Soup.Session();
35        bool retval = true;
36        foreach (var font in fonts) {
37            string font_dir = Path.build_filename(get_font_directory(), font.family);
38            if (DirUtils.create_with_parents(font_dir, 0755) != 0) {
39                warning("Failed to create directory : %s", font_dir);
40                return false;
41            }
42            string filename = font.get_filename();
43            string filepath = Path.build_filename(font_dir, filename);
44            var message = new Soup.Message(GET, font.url);
45            session.queue_message(message, (s, m) => {
46                if (message.status_code != Soup.Status.OK) {
47                    warning("Failed to download data for : %s :: %i", filename, (int) message.status_code);
48                    retval = false;
49                    return;
50                }
51                try {
52                    Bytes bytes = message.response_body.flatten().get_as_bytes();
53                    File font_file = File.new_for_path(filepath);
54                    if (font_file.query_exists())
55                        font_file.delete();
56                    FileOutputStream stream = font_file.create(FileCreateFlags.PRIVATE);
57                    stream.write_bytes_async.begin(bytes, Priority.DEFAULT, null, (obj, res) => {
58                        try {
59                            stream.write_bytes_async.end(res);
60                            stream.close();
61                        } catch (Error e) {
62                            warning("Failed to write data for : %s :: %i : %s", filename, e.code, e.message);
63                            retval = false;
64                            return;
65                        }
66                    });
67                } catch (Error e) {
68                    warning("Failed to write data for : %s :: %i : %s", filename, e.code, e.message);
69                    retval = false;
70                    return;
71                }
72
73            });
74            Idle.add(download_font_files.callback);
75            yield;
76        }
77        return retval;
78    }
79
80    public class Family : Object {
81
82        public string family { get; set; }
83        public string category { get; set; }
84        public GenericArray <Font> variants { get; set; }
85        public StringSet subsets { get; set; }
86        public int count { get; private set; default = 0; }
87        public int version { get; private set; default = 1; }
88
89        public Family (Json.Object source) {
90            family = source.get_string_member("family");
91            category = source.get_string_member("category");
92            variants = new GenericArray <Font> ();
93            subsets = new StringSet();
94            var filelist = source.get_object_member("files");
95            source.get_array_member("variants").foreach_element((array, index, node) => {
96                var entry = node.get_string();
97                var variant = new Font(family, entry, filelist.get_string_member(entry), subsets);
98                variants.insert((int) index, variant);
99                count++;
100            });
101            source.get_array_member("subsets").foreach_element((array, index, node) => {
102                subsets.add(node.get_string());
103            });
104            string [] _version = variants[0].url.split("/");
105            version = int.parse(_version[_version.length - 2].replace("v", ""));
106        }
107
108        public Font get_default_variant () {
109            Font result = variants[0];
110            variants.foreach((variant) => {
111                if (variant.italic)
112                    return;
113                if ((Weight) variant.weight == Weight.REGULAR)
114                    result = variant;
115            });
116            return result;
117        }
118
119        public string to_stylesheet () {
120            var builder = new StringBuilder();
121            variants.foreach((variant) => { builder.append(variant.to_font_face_rule()); });
122            return builder.str;
123        }
124
125        public FileStatus get_installation_status () {
126            for (int i = 0; i < variants.length; i++) {
127                var status = variants[i].get_installation_status();
128                if (status != FileStatus.NOT_INSTALLED)
129                    return status;
130            }
131            return FileStatus.NOT_INSTALLED;
132        }
133
134    }
135
136    public class Font : Object {
137
138        public string family { get; set; }
139        public string url { get; set; }
140        public int weight { get; set; }
141        public bool italic { get; set; }
142        public string style { get { return italic ? "italic" : "normal"; } }
143        public int version { get; private set; default = 1; }
144        public StringSet subsets { get; set; }
145
146        public Font (string family, string variant, string url, StringSet subsets) {
147            Object(family: family, url: url, subsets: subsets);
148            italic = variant.contains("italic");
149            if (variant == "regular" || variant == "italic")
150                weight = (int) Weight.REGULAR;
151            else
152                weight = int.parse(italic ? variant.replace("italic", "") : variant);
153            string [] _version = url.split("/");
154            version = int.parse(_version[_version.length - 2].replace("v", ""));
155        }
156
157        public string to_display_name () {
158            string ital = italic ? _("Italic") : "";
159            if (italic && weight == Weight.REGULAR)
160                return ital;
161            return "%s %s".printf(((Weight) weight).to_translatable_string(), ital).strip();
162        }
163
164        public string to_description () {
165            string ital = italic ? "Italic" : "";
166            if (italic && (Weight) weight == Weight.REGULAR)
167                return ital;
168            return "%s %s".printf(((Weight) weight).to_string(), ital).strip();
169        }
170
171        public string to_font_face_rule () {
172            string description = to_description();
173            string local = "%s %s".printf(family, description);
174            string _local = "%s-%s".printf(family.replace(" ", ""), description.replace(" ", ""));
175            string style = italic ? "italic" : "normal";
176            return FONT_FACE.printf(family, style, weight, local, _local, url);
177        }
178
179        public string get_filename () {
180            string ext = get_file_extension(url);
181            string style = to_description().replace(" ", "_");
182            string family = family.replace(" ", "_");
183            return "%s_%s.%i.%s".printf(family, style, version, ext);
184        }
185
186        public FileStatus get_installation_status () {
187            string dir = get_font_directory();
188            string filepath = Path.build_filename(dir, family, get_filename());
189            File font = File.new_for_path(filepath);
190            if (font.query_exists())
191                return FileStatus.INSTALLED;
192            File font_dir = File.new_for_path(Path.build_filename(dir, family));
193            if (font_dir.query_exists()) {
194                string ext = get_file_extension(url);
195                string style = to_description().replace(" ", "_");
196                string family = family.replace(" ", "_");
197                string filename =  "%s_%s.%i.%s".printf(family, style, version - 1, ext);
198                File outdated = font_dir.get_child(filename);
199                if (outdated.query_exists())
200                    return FileStatus.REQUIRES_UPDATE;
201            }
202            return FileStatus.NOT_INSTALLED;
203        }
204
205    }
206
207}
208
209#endif /* HAVE_WEBKIT */
210