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 36SkTypeface::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// ----------------------------------------------------------------------------- 49static SkMutex global_fc_map_lock; 50static std::map<std::string, unsigned> global_fc_map; 51static std::map<unsigned, std::string> global_fc_map_inverted; 52static std::map<uint32_t, SkTypeface *> global_fc_typefaces; 53static unsigned global_fc_map_next_id = 0; 54 55// This is the maximum size of the font cache. 56static const unsigned kFontCacheMemoryBudget = 2 * 1024 * 1024; // 2MB 57 58static unsigned UniqueIdToFileId(unsigned uniqueid) 59{ 60 return uniqueid >> 8; 61} 62 63static SkTypeface::Style UniqueIdToStyle(unsigned uniqueid) 64{ 65 return static_cast<SkTypeface::Style>(uniqueid & 0xff); 66} 67 68static 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// ----------------------------------------------------------------------------- 81static bool IsFallbackFontAllowed(const char* request) 82{ 83 return strcmp(request, "Sans") == 0 || 84 strcmp(request, "Serif") == 0 || 85 strcmp(request, "Monospace") == 0; 86} 87 88class FontConfigTypeface : public SkTypeface { 89public: 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// ----------------------------------------------------------------------------- 105static 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// ----------------------------------------------------------------------------- 203static 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 220SkTypeface* 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 304SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) 305{ 306 SkASSERT(!"SkFontHost::CreateTypefaceFromStream unimplemented"); 307 return NULL; 308} 309 310// static 311SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) 312{ 313 SkASSERT(!"SkFontHost::CreateTypefaceFromFile unimplemented"); 314 return NULL; 315} 316 317// static 318bool 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 324SkStream* 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 337size_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 358void SkFontHost::Serialize(const SkTypeface*, SkWStream*) { 359 SkASSERT(!"SkFontHost::Serialize unimplemented"); 360} 361 362SkTypeface* SkFontHost::Deserialize(SkStream* stream) { 363 SkASSERT(!"SkFontHost::Deserialize unimplemented"); 364 return NULL; 365} 366 367SkFontID SkFontHost::NextLogicalFont(SkFontID currFontID, SkFontID origFontID) { 368 // We don't handle font fallback, WebKit does. 369 return 0; 370} 371 372/////////////////////////////////////////////////////////////////////////////// 373 374size_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