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