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