1 /* libs/graphics/ports/SkFontHost_fontconfig.cpp 2 ** 3 ** Copyright 2008, Google Inc. 4 ** 5 ** Licensed under the Apache License, Version 2.0 (the "License"); 6 ** you may not use this file except in compliance with the License. 7 ** You may obtain a copy of the License at 8 ** 9 ** http://www.apache.org/licenses/LICENSE-2.0 10 ** 11 ** Unless required by applicable law or agreed to in writing, software 12 ** distributed under the License is distributed on an "AS IS" BASIS, 13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 ** See the License for the specific language governing permissions and 15 ** limitations under the License. 16 */ 17 18 // ----------------------------------------------------------------------------- 19 // This file provides implementations of the font resolution members of 20 // SkFontHost by using the fontconfig[1] library. Fontconfig is usually found 21 // on Linux systems and handles configuration, parsing and caching issues 22 // involved with enumerating and matching fonts. 23 // 24 // [1] http://fontconfig.org 25 // ----------------------------------------------------------------------------- 26 27 #include <map> 28 #include <string> 29 30 #include <fontconfig/fontconfig.h> 31 32 #include "SkFontHost.h" 33 #include "SkStream.h" 34 35 // This is an extern from SkFontHost_FreeType 36 SkTypeface::Style find_name_and_style(SkStream* stream, SkString* name); 37 38 // ----------------------------------------------------------------------------- 39 // The rest of Skia requires that fonts be identified by a unique unsigned id 40 // and that we be able to load them given the id. What we actually get from 41 // fontconfig is the filename of the font so we keep a locked map from 42 // filenames to fileid numbers and back. 43 // 44 // Note that there's also a unique id in the SkTypeface. This is unique over 45 // both filename and style. Thus we encode that id as (fileid << 8) | style. 46 // Although truetype fonts can support multiple faces in a single file, at the 47 // moment Skia doesn't. 48 // ----------------------------------------------------------------------------- 49 static SkMutex global_fc_map_lock; 50 static std::map<std::string, unsigned> global_fc_map; 51 static std::map<unsigned, std::string> global_fc_map_inverted; 52 static std::map<uint32_t, SkTypeface *> global_fc_typefaces; 53 static unsigned global_fc_map_next_id = 0; 54 55 // This is the maximum size of the font cache. 56 static const unsigned kFontCacheMemoryBudget = 2 * 1024 * 1024; // 2MB 57 58 static unsigned UniqueIdToFileId(unsigned uniqueid) 59 { 60 return uniqueid >> 8; 61 } 62 63 static SkTypeface::Style UniqueIdToStyle(unsigned uniqueid) 64 { 65 return static_cast<SkTypeface::Style>(uniqueid & 0xff); 66 } 67 68 static unsigned FileIdAndStyleToUniqueId(unsigned fileid, 69 SkTypeface::Style style) 70 { 71 SkASSERT((style & 0xff) == style); 72 return (fileid << 8) | static_cast<int>(style); 73 } 74 75 // ----------------------------------------------------------------------------- 76 // Normally we only return exactly the font asked for. In last-resort cases, 77 // the request is for one of the basic font names "Sans", "Serif" or 78 // "Monospace". This function tells you whether a given request is for such a 79 // fallback. 80 // ----------------------------------------------------------------------------- 81 static bool IsFallbackFontAllowed(const char* request) 82 { 83 return strcmp(request, "Sans") == 0 || 84 strcmp(request, "Serif") == 0 || 85 strcmp(request, "Monospace") == 0; 86 } 87 88 class FontConfigTypeface : public SkTypeface { 89 public: 90 FontConfigTypeface(Style style, uint32_t id) 91 : SkTypeface(style, id) 92 { } 93 }; 94 95 // ----------------------------------------------------------------------------- 96 // Find a matching font where @type (one of FC_*) is equal to @value. For a 97 // list of types, see http://fontconfig.org/fontconfig-devel/x19.html#AEN27. 98 // The variable arguments are a list of triples, just like the first three 99 // arguments, and must be NULL terminated. 100 // 101 // For example, 102 // FontMatchString(FC_FILE, FcTypeString, "/usr/share/fonts/myfont.ttf", 103 // NULL); 104 // ----------------------------------------------------------------------------- 105 static FcPattern* FontMatch(const char* type, FcType vtype, const void* value, 106 ...) 107 { 108 va_list ap; 109 va_start(ap, value); 110 111 FcPattern* pattern = FcPatternCreate(); 112 const char* family_requested = NULL; 113 114 for (;;) { 115 FcValue fcvalue; 116 fcvalue.type = vtype; 117 switch (vtype) { 118 case FcTypeString: 119 fcvalue.u.s = (FcChar8*) value; 120 break; 121 case FcTypeInteger: 122 fcvalue.u.i = (int)(intptr_t)value; 123 break; 124 default: 125 SkASSERT(!"FontMatch unhandled type"); 126 } 127 FcPatternAdd(pattern, type, fcvalue, 0); 128 129 if (vtype == FcTypeString && strcmp(type, FC_FAMILY) == 0) 130 family_requested = (const char*) value; 131 132 type = va_arg(ap, const char *); 133 if (!type) 134 break; 135 // FcType is promoted to int when passed through ... 136 vtype = static_cast<FcType>(va_arg(ap, int)); 137 value = va_arg(ap, const void *); 138 }; 139 va_end(ap); 140 141 FcConfigSubstitute(0, pattern, FcMatchPattern); 142 FcDefaultSubstitute(pattern); 143 144 // Font matching: 145 // CSS often specifies a fallback list of families: 146 // font-family: a, b, c, serif; 147 // However, fontconfig will always do its best to find *a* font when asked 148 // for something so we need a way to tell if the match which it has found is 149 // "good enough" for us. Otherwise, we can return NULL which gets piped up 150 // and lets WebKit know to try the next CSS family name. However, fontconfig 151 // configs allow substitutions (mapping "Arial -> Helvetica" etc) and we 152 // wish to support that. 153 // 154 // Thus, if a specific family is requested we set @family_requested. Then we 155 // record two strings: the family name after config processing and the 156 // family name after resolving. If the two are equal, it's a good match. 157 // 158 // So consider the case where a user has mapped Arial to Helvetica in their 159 // config. 160 // requested family: "Arial" 161 // post_config_family: "Helvetica" 162 // post_match_family: "Helvetica" 163 // -> good match 164 // 165 // and for a missing font: 166 // requested family: "Monaco" 167 // post_config_family: "Monaco" 168 // post_match_family: "Times New Roman" 169 // -> BAD match 170 // 171 // However, we special-case fallback fonts; see IsFallbackFontAllowed(). 172 FcChar8* post_config_family; 173 FcPatternGetString(pattern, FC_FAMILY, 0, &post_config_family); 174 175 FcResult result; 176 FcPattern* match = FcFontMatch(0, pattern, &result); 177 if (!match) { 178 FcPatternDestroy(pattern); 179 return NULL; 180 } 181 182 FcChar8* post_match_family; 183 FcPatternGetString(match, FC_FAMILY, 0, &post_match_family); 184 const bool family_names_match = 185 !family_requested ? 186 true : 187 strcasecmp((char *)post_config_family, (char *)post_match_family) == 0; 188 189 FcPatternDestroy(pattern); 190 191 if (!family_names_match && !IsFallbackFontAllowed(family_requested)) { 192 FcPatternDestroy(match); 193 return NULL; 194 } 195 196 return match; 197 } 198 199 // ----------------------------------------------------------------------------- 200 // Check to see if the filename has already been assigned a fileid and, if so, 201 // use it. Otherwise, assign one. Return the resulting fileid. 202 // ----------------------------------------------------------------------------- 203 static unsigned FileIdFromFilename(const char* filename) 204 { 205 SkAutoMutexAcquire ac(global_fc_map_lock); 206 207 std::map<std::string, unsigned>::const_iterator i = 208 global_fc_map.find(filename); 209 if (i == global_fc_map.end()) { 210 const unsigned fileid = global_fc_map_next_id++; 211 global_fc_map[filename] = fileid; 212 global_fc_map_inverted[fileid] = filename; 213 return fileid; 214 } else { 215 return i->second; 216 } 217 } 218 219 // static 220 SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace, 221 const char familyName[], 222 const void* data, size_t bytelength, 223 SkTypeface::Style style) 224 { 225 const char* resolved_family_name = NULL; 226 FcPattern* face_match = NULL; 227 228 { 229 SkAutoMutexAcquire ac(global_fc_map_lock); 230 FcInit(); 231 } 232 233 if (familyFace) { 234 // Here we use the inverted global id map to find the filename from the 235 // SkTypeface object. Given the filename we can ask fontconfig for the 236 // familyname of the font. 237 SkAutoMutexAcquire ac(global_fc_map_lock); 238 239 const unsigned fileid = UniqueIdToFileId(familyFace->uniqueID()); 240 std::map<unsigned, std::string>::const_iterator i = 241 global_fc_map_inverted.find(fileid); 242 if (i == global_fc_map_inverted.end()) 243 return NULL; 244 245 FcInit(); 246 face_match = FontMatch(FC_FILE, FcTypeString, i->second.c_str(), 247 NULL); 248 249 if (!face_match) 250 return NULL; 251 FcChar8* family; 252 if (FcPatternGetString(face_match, FC_FAMILY, 0, &family)) { 253 FcPatternDestroy(face_match); 254 return NULL; 255 } 256 // At this point, @family is pointing into the @face_match object so we 257 // cannot release it yet. 258 259 resolved_family_name = reinterpret_cast<char*>(family); 260 } else if (familyName) { 261 resolved_family_name = familyName; 262 } else { 263 return NULL; 264 } 265 266 // At this point, we have a resolved_family_name from somewhere 267 SkASSERT(resolved_family_name); 268 269 const int bold = style & SkTypeface::kBold ? 270 FC_WEIGHT_BOLD : FC_WEIGHT_NORMAL; 271 const int italic = style & SkTypeface::kItalic ? 272 FC_SLANT_ITALIC : FC_SLANT_ROMAN; 273 FcPattern* match = FontMatch(FC_FAMILY, FcTypeString, resolved_family_name, 274 FC_WEIGHT, FcTypeInteger, bold, 275 FC_SLANT, FcTypeInteger, italic, 276 NULL); 277 if (face_match) 278 FcPatternDestroy(face_match); 279 280 if (!match) 281 return NULL; 282 283 FcChar8* filename; 284 if (FcPatternGetString(match, FC_FILE, 0, &filename) != FcResultMatch) { 285 FcPatternDestroy(match); 286 return NULL; 287 } 288 // Now @filename is pointing into @match 289 290 const unsigned fileid = FileIdFromFilename(reinterpret_cast<char*>(filename)); 291 const unsigned id = FileIdAndStyleToUniqueId(fileid, style); 292 SkTypeface* typeface = SkNEW_ARGS(FontConfigTypeface, (style, id)); 293 FcPatternDestroy(match); 294 295 { 296 SkAutoMutexAcquire ac(global_fc_map_lock); 297 global_fc_typefaces[id] = typeface; 298 } 299 300 return typeface; 301 } 302 303 // static 304 SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) 305 { 306 SkASSERT(!"SkFontHost::CreateTypefaceFromStream unimplemented"); 307 return NULL; 308 } 309 310 // static 311 SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) 312 { 313 SkASSERT(!"SkFontHost::CreateTypefaceFromFile unimplemented"); 314 return NULL; 315 } 316 317 // static 318 bool SkFontHost::ValidFontID(SkFontID uniqueID) { 319 SkAutoMutexAcquire ac(global_fc_map_lock); 320 return global_fc_typefaces.find(uniqueID) != global_fc_typefaces.end(); 321 } 322 323 // static 324 SkStream* SkFontHost::OpenStream(uint32_t id) 325 { 326 SkAutoMutexAcquire ac(global_fc_map_lock); 327 const unsigned fileid = UniqueIdToFileId(id); 328 329 std::map<unsigned, std::string>::const_iterator i = 330 global_fc_map_inverted.find(fileid); 331 if (i == global_fc_map_inverted.end()) 332 return NULL; 333 334 return SkNEW_ARGS(SkFILEStream, (i->second.c_str())); 335 } 336 337 size_t SkFontHost::GetFileName(SkFontID fontID, char path[], size_t length, 338 int32_t* index) { 339 SkAutoMutexAcquire ac(global_fc_map_lock); 340 const unsigned fileid = UniqueIdToFileId(fontID); 341 342 std::map<unsigned, std::string>::const_iterator i = 343 global_fc_map_inverted.find(fileid); 344 if (i == global_fc_map_inverted.end()) { 345 return 0; 346 } 347 348 const std::string& str = i->second; 349 if (path) { 350 memcpy(path, str.c_str(), SkMin32(str.size(), length)); 351 } 352 if (index) { // TODO: check if we're in a TTC 353 *index = 0; 354 } 355 return str.size(); 356 } 357 358 void SkFontHost::Serialize(const SkTypeface*, SkWStream*) { 359 SkASSERT(!"SkFontHost::Serialize unimplemented"); 360 } 361 362 SkTypeface* SkFontHost::Deserialize(SkStream* stream) { 363 SkASSERT(!"SkFontHost::Deserialize unimplemented"); 364 return NULL; 365 } 366 367 SkFontID SkFontHost::NextLogicalFont(SkFontID currFontID, SkFontID origFontID) { 368 // We don't handle font fallback, WebKit does. 369 return 0; 370 } 371 372 /////////////////////////////////////////////////////////////////////////////// 373 374 size_t SkFontHost::ShouldPurgeFontCache(size_t sizeAllocatedSoFar) 375 { 376 if (sizeAllocatedSoFar > kFontCacheMemoryBudget) 377 return sizeAllocatedSoFar - kFontCacheMemoryBudget; 378 else 379 return 0; // nothing to do 380 } 381