Home | History | Annotate | Download | only in ports
      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