• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
UniqueIdToFileId(unsigned uniqueid)58 static unsigned UniqueIdToFileId(unsigned uniqueid)
59 {
60     return uniqueid >> 8;
61 }
62 
UniqueIdToStyle(unsigned uniqueid)63 static SkTypeface::Style UniqueIdToStyle(unsigned uniqueid)
64 {
65     return static_cast<SkTypeface::Style>(uniqueid & 0xff);
66 }
67 
FileIdAndStyleToUniqueId(unsigned fileid,SkTypeface::Style style)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 // -----------------------------------------------------------------------------
IsFallbackFontAllowed(const char * request)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:
FontConfigTypeface(Style style,uint32_t id)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 // -----------------------------------------------------------------------------
FontMatch(const char * type,FcType vtype,const void * value,...)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 // -----------------------------------------------------------------------------
FileIdFromFilename(const char * filename)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
CreateTypeface(const SkTypeface * familyFace,const char familyName[],const void * data,size_t bytelength,SkTypeface::Style style)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
CreateTypefaceFromStream(SkStream * stream)304 SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream)
305 {
306     SkASSERT(!"SkFontHost::CreateTypefaceFromStream unimplemented");
307     return NULL;
308 }
309 
310 // static
CreateTypefaceFromFile(const char path[])311 SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[])
312 {
313     SkASSERT(!"SkFontHost::CreateTypefaceFromFile unimplemented");
314     return NULL;
315 }
316 
317 // static
ValidFontID(SkFontID uniqueID)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
OpenStream(uint32_t id)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 
GetFileName(SkFontID fontID,char path[],size_t length,int32_t * index)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 
Serialize(const SkTypeface *,SkWStream *)358 void SkFontHost::Serialize(const SkTypeface*, SkWStream*) {
359     SkASSERT(!"SkFontHost::Serialize unimplemented");
360 }
361 
Deserialize(SkStream * stream)362 SkTypeface* SkFontHost::Deserialize(SkStream* stream) {
363     SkASSERT(!"SkFontHost::Deserialize unimplemented");
364     return NULL;
365 }
366 
NextLogicalFont(SkFontID currFontID,SkFontID origFontID)367 SkFontID SkFontHost::NextLogicalFont(SkFontID currFontID, SkFontID origFontID) {
368     // We don't handle font fallback, WebKit does.
369     return 0;
370 }
371 
372 ///////////////////////////////////////////////////////////////////////////////
373 
ShouldPurgeFontCache(size_t sizeAllocatedSoFar)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