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