| /* libs/graphics/ports/SkFontHost_fontconfig_direct.cpp |
| ** |
| ** Copyright 2009, Google Inc. |
| ** |
| ** Licensed under the Apache License, Version 2.0 (the "License"); |
| ** you may not use this file except in compliance with the License. |
| ** You may obtain a copy of the License at |
| ** |
| ** http://www.apache.org/licenses/LICENSE-2.0 |
| ** |
| ** Unless required by applicable law or agreed to in writing, software |
| ** distributed under the License is distributed on an "AS IS" BASIS, |
| ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| ** See the License for the specific language governing permissions and |
| ** limitations under the License. |
| */ |
| |
| #include "SkFontHost_fontconfig_direct.h" |
| |
| #include <unistd.h> |
| #include <fcntl.h> |
| |
| #include <fontconfig/fontconfig.h> |
| |
| FontConfigDirect::FontConfigDirect() |
| : next_file_id_(0) { |
| FcInit(); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Normally we only return exactly the font asked for. In last-resort cases, |
| // the request is for one of the basic font names "Sans", "Serif" or |
| // "Monospace". This function tells you whether a given request is for such a |
| // fallback. |
| // ----------------------------------------------------------------------------- |
| static bool IsFallbackFontAllowed(const std::string& family) |
| { |
| const char* family_cstr = family.c_str(); |
| return strcasecmp(family_cstr, "sans") == 0 || |
| strcasecmp(family_cstr, "serif") == 0 || |
| strcasecmp(family_cstr, "monospace") == 0; |
| } |
| |
| bool FontConfigDirect::Match(std::string* result_family, |
| unsigned* result_fileid, |
| bool fileid_valid, unsigned fileid, |
| const std::string& family, bool* is_bold, |
| bool* is_italic) { |
| SkAutoMutexAcquire ac(mutex_); |
| FcPattern* pattern = FcPatternCreate(); |
| |
| FcValue fcvalue; |
| if (fileid_valid) { |
| const std::map<unsigned, std::string>::const_iterator |
| i = fileid_to_filename_.find(fileid); |
| if (i == fileid_to_filename_.end()) { |
| FcPatternDestroy(pattern); |
| return false; |
| } |
| |
| fcvalue.type = FcTypeString; |
| fcvalue.u.s = (FcChar8*) i->second.c_str(); |
| FcPatternAdd(pattern, FC_FILE, fcvalue, 0); |
| } |
| if (!family.empty()) { |
| fcvalue.type = FcTypeString; |
| fcvalue.u.s = (FcChar8*) family.c_str(); |
| FcPatternAdd(pattern, FC_FAMILY, fcvalue, 0); |
| } |
| |
| fcvalue.type = FcTypeInteger; |
| fcvalue.u.i = is_bold && *is_bold ? FC_WEIGHT_BOLD : FC_WEIGHT_NORMAL; |
| FcPatternAdd(pattern, FC_WEIGHT, fcvalue, 0); |
| |
| fcvalue.type = FcTypeInteger; |
| fcvalue.u.i = is_italic && *is_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN; |
| FcPatternAdd(pattern, FC_SLANT, fcvalue, 0); |
| |
| fcvalue.type = FcTypeBool; |
| fcvalue.u.b = FcTrue; |
| FcPatternAdd(pattern, FC_SCALABLE, fcvalue, 0); |
| |
| FcConfigSubstitute(0, pattern, FcMatchPattern); |
| FcDefaultSubstitute(pattern); |
| |
| // Font matching: |
| // CSS often specifies a fallback list of families: |
| // font-family: a, b, c, serif; |
| // However, fontconfig will always do its best to find *a* font when asked |
| // for something so we need a way to tell if the match which it has found is |
| // "good enough" for us. Otherwise, we can return NULL which gets piped up |
| // and lets WebKit know to try the next CSS family name. However, fontconfig |
| // configs allow substitutions (mapping "Arial -> Helvetica" etc) and we |
| // wish to support that. |
| // |
| // Thus, if a specific family is requested we set @family_requested. Then we |
| // record two strings: the family name after config processing and the |
| // family name after resolving. If the two are equal, it's a good match. |
| // |
| // So consider the case where a user has mapped Arial to Helvetica in their |
| // config. |
| // requested family: "Arial" |
| // post_config_family: "Helvetica" |
| // post_match_family: "Helvetica" |
| // -> good match |
| // |
| // and for a missing font: |
| // requested family: "Monaco" |
| // post_config_family: "Monaco" |
| // post_match_family: "Times New Roman" |
| // -> BAD match |
| // |
| // However, we special-case fallback fonts; see IsFallbackFontAllowed(). |
| FcChar8* post_config_family; |
| FcPatternGetString(pattern, FC_FAMILY, 0, &post_config_family); |
| |
| FcResult result; |
| FcFontSet* font_set = FcFontSort(0, pattern, 0, 0, &result); |
| if (!font_set) { |
| FcPatternDestroy(pattern); |
| return false; |
| } |
| |
| // Older versions of fontconfig have a bug where they cannot select |
| // only scalable fonts so we have to manually filter the results. |
| FcPattern* match = NULL; |
| for (int i = 0; i < font_set->nfont; ++i) { |
| FcPattern* current = font_set->fonts[i]; |
| FcBool is_scalable; |
| |
| if (FcPatternGetBool(current, FC_SCALABLE, 0, |
| &is_scalable) != FcResultMatch || |
| !is_scalable) { |
| continue; |
| } |
| |
| // fontconfig can also return fonts which are unreadable |
| FcChar8* c_filename; |
| if (FcPatternGetString(current, FC_FILE, 0, &c_filename) != FcResultMatch) |
| continue; |
| |
| if (access(reinterpret_cast<char*>(c_filename), R_OK) != 0) |
| continue; |
| |
| match = current; |
| break; |
| } |
| |
| if (!match) { |
| FcPatternDestroy(pattern); |
| FcFontSetDestroy(font_set); |
| return false; |
| } |
| |
| if (!IsFallbackFontAllowed(family)) { |
| bool family_names_match = false; |
| for (int id = 0; id < 255; ++id) { |
| FcChar8* post_match_family; |
| if (FcPatternGetString(match, FC_FAMILY, id, &post_match_family) != |
| FcResultMatch) |
| break; |
| family_names_match = |
| family.empty() ? |
| true : |
| strcasecmp((char *)post_config_family, |
| (char *)post_match_family) == 0; |
| if (family_names_match) |
| break; |
| } |
| if (!family.empty() && !family_names_match) { |
| FcPatternDestroy(pattern); |
| FcFontSetDestroy(font_set); |
| return false; |
| } |
| } |
| |
| FcPatternDestroy(pattern); |
| |
| FcChar8* c_filename; |
| if (FcPatternGetString(match, FC_FILE, 0, &c_filename) != FcResultMatch) { |
| FcFontSetDestroy(font_set); |
| return false; |
| } |
| const std::string filename((char *) c_filename); |
| |
| unsigned out_fileid; |
| if (fileid_valid) { |
| out_fileid = fileid; |
| } else { |
| const std::map<std::string, unsigned>::const_iterator |
| i = filename_to_fileid_.find(filename); |
| if (i == filename_to_fileid_.end()) { |
| out_fileid = next_file_id_++; |
| filename_to_fileid_[filename] = out_fileid; |
| fileid_to_filename_[out_fileid] = filename; |
| } else { |
| out_fileid = i->second; |
| } |
| } |
| |
| if (result_fileid) |
| *result_fileid = out_fileid; |
| |
| FcChar8* c_family; |
| if (FcPatternGetString(match, FC_FAMILY, 0, &c_family)) { |
| FcFontSetDestroy(font_set); |
| return false; |
| } |
| |
| int resulting_bold; |
| if (FcPatternGetInteger(match, FC_WEIGHT, 0, &resulting_bold)) |
| resulting_bold = FC_WEIGHT_NORMAL; |
| |
| int resulting_italic; |
| if (FcPatternGetInteger(match, FC_SLANT, 0, &resulting_italic)) |
| resulting_italic = FC_SLANT_ROMAN; |
| |
| // If we ask for an italic font, fontconfig might take a roman font and set |
| // the undocumented property FC_MATRIX to a skew matrix. It'll then say |
| // that the font is italic or oblique. So, if we see a matrix, we don't |
| // believe that it's italic. |
| FcValue matrix; |
| const bool have_matrix = FcPatternGet(match, FC_MATRIX, 0, &matrix) == 0; |
| |
| // If we ask for an italic font, fontconfig might take a roman font and set |
| // FC_EMBOLDEN. |
| FcValue embolden; |
| const bool have_embolden = |
| FcPatternGet(match, FC_EMBOLDEN, 0, &embolden) == 0; |
| |
| if (is_bold) |
| *is_bold = resulting_bold >= FC_WEIGHT_BOLD && !have_embolden; |
| if (is_italic) |
| *is_italic = resulting_italic > FC_SLANT_ROMAN && !have_matrix; |
| |
| if (result_family) |
| *result_family = (char *) c_family; |
| |
| FcFontSetDestroy(font_set); |
| |
| return true; |
| } |
| |
| int FontConfigDirect::Open(unsigned fileid) { |
| SkAutoMutexAcquire ac(mutex_); |
| const std::map<unsigned, std::string>::const_iterator |
| i = fileid_to_filename_.find(fileid); |
| if (i == fileid_to_filename_.end()) |
| return -1; |
| |
| return open(i->second.c_str(), O_RDONLY); |
| } |