| /* |
| * Copyright (C) 2008 Alp Toker <alp@atoker.com> |
| * Copyright (C) 2010 Igalia S.L. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "config.h" |
| #include "FontCache.h" |
| |
| #include "CairoUniquePtr.h" |
| #include "CairoUtilities.h" |
| #include "CharacterProperties.h" |
| #include "FcUniquePtr.h" |
| #include "FloatConversion.h" |
| #include "Font.h" |
| #include "FontDescription.h" |
| #include "FontCacheFreeType.h" |
| #include "RefPtrCairo.h" |
| #include "RefPtrFontconfig.h" |
| #include "UTF16UChar32Iterator.h" |
| #include <cairo-ft.h> |
| #include <cairo.h> |
| #include <fontconfig/fcfreetype.h> |
| #include <wtf/Assertions.h> |
| #include <wtf/HashFunctions.h> |
| #include <wtf/HashMap.h> |
| #include <wtf/text/CString.h> |
| |
| #if PLATFORM(GTK) |
| #include "GtkUtilities.h" |
| #endif |
| |
| #if ENABLE(VARIATION_FONTS) |
| #include FT_MULTIPLE_MASTERS_H |
| #endif |
| |
| namespace WebCore { |
| |
| void FontCache::platformInit() |
| { |
| // It's fine to call FcInit multiple times per the documentation. |
| if (!FcInit()) |
| ASSERT_NOT_REACHED(); |
| } |
| |
| static int fontWeightToFontconfigWeight(FontSelectionValue weight) |
| { |
| if (weight < FontSelectionValue(150)) |
| return FC_WEIGHT_THIN; |
| if (weight < FontSelectionValue(250)) |
| return FC_WEIGHT_ULTRALIGHT; |
| if (weight < FontSelectionValue(350)) |
| return FC_WEIGHT_LIGHT; |
| if (weight < FontSelectionValue(450)) |
| return FC_WEIGHT_REGULAR; |
| if (weight < FontSelectionValue(550)) |
| return FC_WEIGHT_MEDIUM; |
| if (weight < FontSelectionValue(650)) |
| return FC_WEIGHT_SEMIBOLD; |
| if (weight < FontSelectionValue(750)) |
| return FC_WEIGHT_BOLD; |
| if (weight < FontSelectionValue(850)) |
| return FC_WEIGHT_EXTRABOLD; |
| return FC_WEIGHT_ULTRABLACK; |
| } |
| |
| static bool configurePatternForFontDescription(FcPattern* pattern, const FontDescription& fontDescription) |
| { |
| if (!FcPatternAddInteger(pattern, FC_SLANT, fontDescription.italic() ? FC_SLANT_ITALIC : FC_SLANT_ROMAN)) |
| return false; |
| if (!FcPatternAddInteger(pattern, FC_WEIGHT, fontWeightToFontconfigWeight(fontDescription.weight()))) |
| return false; |
| if (!FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontDescription.computedPixelSize())) |
| return false; |
| return true; |
| } |
| |
| static void getFontPropertiesFromPattern(FcPattern* pattern, const FontDescription& fontDescription, bool& fixedWidth, bool& syntheticBold, bool& syntheticOblique) |
| { |
| fixedWidth = false; |
| int spacing; |
| if (FcPatternGetInteger(pattern, FC_SPACING, 0, &spacing) == FcResultMatch && spacing == FC_MONO) |
| fixedWidth = true; |
| |
| syntheticBold = false; |
| bool descriptionAllowsSyntheticBold = fontDescription.fontSynthesis() & FontSynthesisWeight; |
| if (descriptionAllowsSyntheticBold && isFontWeightBold(fontDescription.weight())) { |
| // The FC_EMBOLDEN property instructs us to fake the boldness of the font. |
| FcBool fontConfigEmbolden = FcFalse; |
| if (FcPatternGetBool(pattern, FC_EMBOLDEN, 0, &fontConfigEmbolden) == FcResultMatch) |
| syntheticBold = fontConfigEmbolden; |
| |
| // Fallback fonts may not have FC_EMBOLDEN activated even though it's necessary. |
| int weight = 0; |
| if (!syntheticBold && FcPatternGetInteger(pattern, FC_WEIGHT, 0, &weight) == FcResultMatch) |
| syntheticBold = syntheticBold || weight < FC_WEIGHT_DEMIBOLD; |
| } |
| |
| // We requested an italic font, but Fontconfig gave us one that was neither oblique nor italic. |
| syntheticOblique = false; |
| int actualFontSlant; |
| bool descriptionAllowsSyntheticOblique = fontDescription.fontSynthesis() & FontSynthesisStyle; |
| if (descriptionAllowsSyntheticOblique && fontDescription.italic() |
| && FcPatternGetInteger(pattern, FC_SLANT, 0, &actualFontSlant) == FcResultMatch) { |
| syntheticOblique = actualFontSlant == FC_SLANT_ROMAN; |
| } |
| } |
| |
| struct CachedPattern { |
| // The pattern is owned by the CachedFontSet. |
| FcPattern* pattern { nullptr }; |
| FcCharSet* charSet { nullptr }; |
| }; |
| |
| class CachedFontSet { |
| WTF_MAKE_NONCOPYABLE(CachedFontSet); WTF_MAKE_FAST_ALLOCATED; |
| public: |
| explicit CachedFontSet(RefPtr<FcPattern>&& pattern) |
| : m_pattern(WTFMove(pattern)) |
| { |
| FcResult result; |
| m_fontSet.reset(FcFontSort(nullptr, m_pattern.get(), FcTrue, nullptr, &result)); |
| for (int i = 0; i < m_fontSet->nfont; ++i) { |
| FcPattern* pattern = m_fontSet->fonts[i]; |
| FcCharSet* charSet; |
| |
| if (FcPatternGetCharSet(pattern, FC_CHARSET, 0, &charSet) == FcResultMatch) |
| m_patterns.append({ pattern, charSet }); |
| } |
| } |
| |
| RefPtr<FcPattern> bestForCharacters(const UChar* characters, unsigned length) |
| { |
| if (m_patterns.isEmpty()) { |
| FcResult result; |
| return adoptRef(FcFontMatch(nullptr, m_pattern.get(), &result)); |
| } |
| |
| FcUniquePtr<FcCharSet> fontConfigCharSet(FcCharSetCreate()); |
| UTF16UChar32Iterator iterator(characters, length); |
| UChar32 character = iterator.next(); |
| bool hasNonIgnorableCharacters = false; |
| while (character != iterator.end()) { |
| if (!isDefaultIgnorableCodePoint(character)) { |
| FcCharSetAddChar(fontConfigCharSet.get(), character); |
| hasNonIgnorableCharacters = true; |
| } |
| character = iterator.next(); |
| } |
| |
| FcPattern* bestPattern = nullptr; |
| int minScore = std::numeric_limits<int>::max(); |
| if (hasNonIgnorableCharacters) { |
| for (const auto& cachedPattern : m_patterns) { |
| if (!cachedPattern.charSet) |
| continue; |
| |
| int score = FcCharSetSubtractCount(fontConfigCharSet.get(), cachedPattern.charSet); |
| if (!score) |
| return adoptRef(FcFontRenderPrepare(nullptr, m_pattern.get(), cachedPattern.pattern)); |
| |
| if (score < minScore) { |
| bestPattern = cachedPattern.pattern; |
| minScore = score; |
| } |
| } |
| } |
| |
| if (bestPattern) |
| return adoptRef(FcFontRenderPrepare(nullptr, m_pattern.get(), bestPattern)); |
| |
| // If there aren't fonts with the given characters or all characters are ignorable, the first one is the best match. |
| return adoptRef(FcFontRenderPrepare(nullptr, m_pattern.get(), m_patterns[0].pattern)); |
| } |
| |
| private: |
| RefPtr<FcPattern> m_pattern; |
| FcUniquePtr<FcFontSet> m_fontSet; |
| Vector<CachedPattern> m_patterns; |
| }; |
| |
| struct FallbackFontDescriptionKey { |
| FallbackFontDescriptionKey() = default; |
| |
| FallbackFontDescriptionKey(const FontDescription& description, FontCache::PreferColoredFont preferColoredFont) |
| : descriptionKey(description) |
| , coloredFont(preferColoredFont == FontCache::PreferColoredFont::Yes) |
| { |
| } |
| |
| explicit FallbackFontDescriptionKey(WTF::HashTableDeletedValueType deletedValue) |
| : descriptionKey(deletedValue) |
| { |
| } |
| |
| bool operator==(const FallbackFontDescriptionKey& other) const |
| { |
| return descriptionKey == other.descriptionKey && coloredFont == other.coloredFont; |
| } |
| |
| bool operator!=(const FallbackFontDescriptionKey& other) const |
| { |
| return !(*this == other); |
| } |
| |
| bool isHashTableDeletedValue() const { return descriptionKey.isHashTableDeletedValue(); } |
| |
| unsigned computeHash() const |
| { |
| return WTF::pairIntHash(descriptionKey.computeHash(), WTF::DefaultHash<bool>::Hash::hash(coloredFont)); |
| } |
| |
| FontDescriptionKey descriptionKey; |
| bool coloredFont { false }; |
| }; |
| |
| struct FallbackFontDescriptionKeyHash { |
| static unsigned hash(const FallbackFontDescriptionKey& key) { return key.computeHash(); } |
| static bool equal(const FallbackFontDescriptionKey& a, const FallbackFontDescriptionKey& b) { return a == b; } |
| static const bool safeToCompareToEmptyOrDeleted = true; |
| }; |
| |
| using SystemFallbackCache = HashMap<FallbackFontDescriptionKey, std::unique_ptr<CachedFontSet>, FallbackFontDescriptionKeyHash, SimpleClassHashTraits<FallbackFontDescriptionKey>>; |
| static SystemFallbackCache& systemFallbackCache() |
| { |
| static NeverDestroyed<SystemFallbackCache> cache; |
| return cache.get(); |
| } |
| |
| RefPtr<Font> FontCache::systemFallbackForCharacters(const FontDescription& description, const Font*, IsForPlatformFont, PreferColoredFont preferColoredFont, const UChar* characters, unsigned length) |
| { |
| auto addResult = systemFallbackCache().ensure(FallbackFontDescriptionKey(description, preferColoredFont), [&description, preferColoredFont]() -> std::unique_ptr<CachedFontSet> { |
| RefPtr<FcPattern> pattern = adoptRef(FcPatternCreate()); |
| FcPatternAddBool(pattern.get(), FC_SCALABLE, FcTrue); |
| #ifdef FC_COLOR |
| if (preferColoredFont == PreferColoredFont::Yes) |
| FcPatternAddBool(pattern.get(), FC_COLOR, FcTrue); |
| #endif |
| if (!configurePatternForFontDescription(pattern.get(), description)) |
| return nullptr; |
| |
| FcConfigSubstitute(nullptr, pattern.get(), FcMatchPattern); |
| cairo_ft_font_options_substitute(getDefaultCairoFontOptions(), pattern.get()); |
| FcDefaultSubstitute(pattern.get()); |
| |
| return makeUnique<CachedFontSet>(WTFMove(pattern)); |
| }); |
| |
| if (!addResult.iterator->value) |
| return nullptr; |
| |
| RefPtr<FcPattern> resultPattern = addResult.iterator->value->bestForCharacters(characters, length); |
| if (!resultPattern) |
| return nullptr; |
| |
| bool fixedWidth, syntheticBold, syntheticOblique; |
| getFontPropertiesFromPattern(resultPattern.get(), description, fixedWidth, syntheticBold, syntheticOblique); |
| |
| RefPtr<cairo_font_face_t> fontFace = adoptRef(cairo_ft_font_face_create_for_pattern(resultPattern.get())); |
| FontPlatformData alternateFontData(fontFace.get(), WTFMove(resultPattern), description.computedPixelSize(), fixedWidth, syntheticBold, syntheticOblique, description.orientation()); |
| return fontForPlatformData(alternateFontData); |
| } |
| |
| void FontCache::platformPurgeInactiveFontData() |
| { |
| systemFallbackCache().clear(); |
| } |
| |
| static Vector<String> patternToFamilies(FcPattern& pattern) |
| { |
| char* patternChars = reinterpret_cast<char*>(FcPatternFormat(&pattern, reinterpret_cast<const FcChar8*>("%{family}"))); |
| String patternString = String::fromUTF8(patternChars); |
| free(patternChars); |
| |
| return patternString.split(','); |
| } |
| |
| Vector<String> FontCache::systemFontFamilies() |
| { |
| RefPtr<FcPattern> scalablesOnlyPattern = adoptRef(FcPatternCreate()); |
| FcPatternAddBool(scalablesOnlyPattern.get(), FC_SCALABLE, FcTrue); |
| |
| FcUniquePtr<FcObjectSet> familiesOnly(FcObjectSetBuild(FC_FAMILY, nullptr)); |
| FcUniquePtr<FcFontSet> fontSet(FcFontList(nullptr, scalablesOnlyPattern.get(), familiesOnly.get())); |
| |
| Vector<String> fontFamilies; |
| for (int i = 0; i < fontSet->nfont; i++) { |
| FcPattern* pattern = fontSet->fonts[i]; |
| FcChar8* family = nullptr; |
| FcPatternGetString(pattern, FC_FAMILY, 0, &family); |
| if (family) |
| fontFamilies.appendVector(patternToFamilies(*pattern)); |
| } |
| |
| return fontFamilies; |
| } |
| |
| bool FontCache::isSystemFontForbiddenForEditing(const String&) |
| { |
| return false; |
| } |
| |
| Ref<Font> FontCache::lastResortFallbackFont(const FontDescription& fontDescription) |
| { |
| // We want to return a fallback font here, otherwise the logic preventing FontConfig |
| // matches for non-fallback fonts might return 0. See isFallbackFontAllowed. |
| static AtomString timesStr("serif"); |
| if (RefPtr<Font> font = fontForFamily(fontDescription, timesStr)) |
| return *font; |
| |
| // This could be reached due to improperly-installed or misconfigured fontconfig. |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| Vector<FontSelectionCapabilities> FontCache::getFontSelectionCapabilitiesInFamily(const AtomString&, AllowUserInstalledFonts) |
| { |
| return { }; |
| } |
| |
| static String getFamilyNameStringFromFamily(const AtomString& family) |
| { |
| // If we're creating a fallback font (e.g. "-webkit-monospace"), convert the name into |
| // the fallback name (like "monospace") that fontconfig understands. |
| if (family.length() && !family.startsWith("-webkit-")) |
| return family.string(); |
| |
| if (family == standardFamily || family == serifFamily) |
| return "serif"; |
| if (family == sansSerifFamily) |
| return "sans-serif"; |
| if (family == monospaceFamily) |
| return "monospace"; |
| if (family == cursiveFamily) |
| return "cursive"; |
| if (family == fantasyFamily) |
| return "fantasy"; |
| |
| #if PLATFORM(GTK) |
| if (family == systemUiFamily || family == "-webkit-system-font") |
| return defaultGtkSystemFont(); |
| #endif |
| |
| return ""; |
| } |
| |
| // This is based on Chromium BSD code from Skia (src/ports/SkFontMgr_fontconfig.cpp). It is a |
| // hack for lack of API in Fontconfig: https://bugs.freedesktop.org/show_bug.cgi?id=19375 |
| // FIXME: This is horrible. It should be deleted once Fontconfig can do this itself. |
| enum class AliasStrength { |
| Weak, |
| Strong, |
| Done |
| }; |
| |
| static AliasStrength strengthOfFirstAlias(const FcPattern& original) |
| { |
| // Ideally there would exist a call like |
| // FcResult FcPatternIsWeak(pattern, object, id, FcBool* isWeak); |
| // |
| // However, there is no such call and as of Fc 2.11.0 even FcPatternEquals ignores the weak bit. |
| // Currently, the only reliable way of finding the weak bit is by its effect on matching. |
| // The weak bit only affects the matching of FC_FAMILY and FC_POSTSCRIPT_NAME object values. |
| // A element with the weak bit is scored after FC_LANG, without the weak bit is scored before. |
| // Note that the weak bit is stored on the element, not on the value it holds. |
| FcValue value; |
| FcResult result = FcPatternGet(&original, FC_FAMILY, 0, &value); |
| if (result != FcResultMatch) |
| return AliasStrength::Done; |
| |
| RefPtr<FcPattern> pattern = adoptRef(FcPatternDuplicate(&original)); |
| FcBool hasMultipleFamilies = true; |
| while (hasMultipleFamilies) |
| hasMultipleFamilies = FcPatternRemove(pattern.get(), FC_FAMILY, 1); |
| |
| // Create a font set with two patterns. |
| // 1. the same FC_FAMILY as pattern and a lang object with only 'nomatchlang'. |
| // 2. a different FC_FAMILY from pattern and a lang object with only 'matchlang'. |
| FcUniquePtr<FcFontSet> fontSet(FcFontSetCreate()); |
| |
| FcUniquePtr<FcLangSet> strongLangSet(FcLangSetCreate()); |
| FcLangSetAdd(strongLangSet.get(), reinterpret_cast<const FcChar8*>("nomatchlang")); |
| // Ownership of this FcPattern will be transferred with FcFontSetAdd. |
| FcPattern* strong = FcPatternDuplicate(pattern.get()); |
| FcPatternAddLangSet(strong, FC_LANG, strongLangSet.get()); |
| |
| FcUniquePtr<FcLangSet> weakLangSet(FcLangSetCreate()); |
| FcLangSetAdd(weakLangSet.get(), reinterpret_cast<const FcChar8*>("matchlang")); |
| // Ownership of this FcPattern will be transferred via FcFontSetAdd. |
| FcPattern* weak = FcPatternCreate(); |
| FcPatternAddString(weak, FC_FAMILY, reinterpret_cast<const FcChar8*>("nomatchstring")); |
| FcPatternAddLangSet(weak, FC_LANG, weakLangSet.get()); |
| |
| FcFontSetAdd(fontSet.get(), strong); |
| FcFontSetAdd(fontSet.get(), weak); |
| |
| // Add 'matchlang' to the copy of the pattern. |
| FcPatternAddLangSet(pattern.get(), FC_LANG, weakLangSet.get()); |
| |
| // Run a match against the copy of the pattern. |
| // If the first element was weak, then we should match the pattern with 'matchlang'. |
| // If the first element was strong, then we should match the pattern with 'nomatchlang'. |
| |
| // Note that this config is only used for FcFontRenderPrepare, which we don't even want. |
| // However, there appears to be no way to match/sort without it. |
| RefPtr<FcConfig> config = adoptRef(FcConfigCreate()); |
| FcFontSet* fontSets[1] = { fontSet.get() }; |
| RefPtr<FcPattern> match = adoptRef(FcFontSetMatch(config.get(), fontSets, 1, pattern.get(), &result)); |
| |
| FcLangSet* matchLangSet; |
| FcPatternGetLangSet(match.get(), FC_LANG, 0, &matchLangSet); |
| return FcLangEqual == FcLangSetHasLang(matchLangSet, reinterpret_cast<const FcChar8*>("matchlang")) |
| ? AliasStrength::Weak : AliasStrength::Strong; |
| } |
| |
| static Vector<String> strongAliasesForFamily(const String& family) |
| { |
| RefPtr<FcPattern> pattern = adoptRef(FcPatternCreate()); |
| if (!FcPatternAddString(pattern.get(), FC_FAMILY, reinterpret_cast<const FcChar8*>(family.utf8().data()))) |
| return Vector<String>(); |
| |
| FcConfigSubstitute(nullptr, pattern.get(), FcMatchPattern); |
| cairo_ft_font_options_substitute(getDefaultCairoFontOptions(), pattern.get()); |
| FcDefaultSubstitute(pattern.get()); |
| |
| FcUniquePtr<FcObjectSet> familiesOnly(FcObjectSetBuild(FC_FAMILY, nullptr)); |
| RefPtr<FcPattern> minimal = adoptRef(FcPatternFilter(pattern.get(), familiesOnly.get())); |
| |
| // We really want to match strong (preferred) and same (acceptable) only here. |
| // If a family name was specified, assume that any weak matches after the last strong match |
| // are weak (default) and ignore them. |
| // The reason for is that after substitution the pattern for 'sans-serif' looks like |
| // "wwwwwwwwwwwwwwswww" where there are many weak but preferred names, followed by defaults. |
| // So it is possible to have weakly matching but preferred names. |
| // In aliases, bindings are weak by default, so this is easy and common. |
| // If no family name was specified, we'll probably only get weak matches, but that's ok. |
| int lastStrongId = -1; |
| int numIds = 0; |
| for (int id = 0; ; ++id) { |
| AliasStrength result = strengthOfFirstAlias(*minimal); |
| if (result == AliasStrength::Done) { |
| numIds = id; |
| break; |
| } |
| if (result == AliasStrength::Strong) |
| lastStrongId = id; |
| if (!FcPatternRemove(minimal.get(), FC_FAMILY, 0)) |
| return Vector<String>(); |
| } |
| |
| // If they were all weak, then leave the pattern alone. |
| if (lastStrongId < 0) |
| return Vector<String>(); |
| |
| // Remove everything after the last strong. |
| for (int id = lastStrongId + 1; id < numIds; ++id) { |
| if (!FcPatternRemove(pattern.get(), FC_FAMILY, lastStrongId + 1)) { |
| ASSERT_NOT_REACHED(); |
| return Vector<String>(); |
| } |
| } |
| |
| return patternToFamilies(*pattern); |
| } |
| |
| static bool areStronglyAliased(const String& familyA, const String& familyB) |
| { |
| for (auto& family : strongAliasesForFamily(familyA)) { |
| if (family == familyB) |
| return true; |
| } |
| return false; |
| } |
| |
| static inline bool isCommonlyUsedGenericFamily(const String& familyNameString) |
| { |
| return equalLettersIgnoringASCIICase(familyNameString, "sans") |
| || equalLettersIgnoringASCIICase(familyNameString, "sans-serif") |
| || equalLettersIgnoringASCIICase(familyNameString, "serif") |
| || equalLettersIgnoringASCIICase(familyNameString, "monospace") |
| || equalLettersIgnoringASCIICase(familyNameString, "fantasy") |
| #if PLATFORM(GTK) |
| || equalLettersIgnoringASCIICase(familyNameString, "-webkit-system-font") |
| || equalLettersIgnoringASCIICase(familyNameString, "-webkit-system-ui") |
| #endif |
| || equalLettersIgnoringASCIICase(familyNameString, "cursive"); |
| } |
| |
| std::unique_ptr<FontPlatformData> FontCache::createFontPlatformData(const FontDescription& fontDescription, const AtomString& family, const FontFeatureSettings* fontFaceFeatures, FontSelectionSpecifiedCapabilities) |
| { |
| // The CSS font matching algorithm (http://www.w3.org/TR/css3-fonts/#font-matching-algorithm) |
| // says that we must find an exact match for font family, slant (italic or oblique can be used) |
| // and font weight (we only match bold/non-bold here). |
| RefPtr<FcPattern> pattern = adoptRef(FcPatternCreate()); |
| // Never choose unscalable fonts, as they pixelate when displayed at different sizes. |
| FcPatternAddBool(pattern.get(), FC_SCALABLE, FcTrue); |
| #if ENABLE(VARIATION_FONTS) |
| FcPatternAddBool(pattern.get(), FC_VARIABLE, FcDontCare); |
| #endif |
| String familyNameString(getFamilyNameStringFromFamily(family)); |
| if (!FcPatternAddString(pattern.get(), FC_FAMILY, reinterpret_cast<const FcChar8*>(familyNameString.utf8().data()))) |
| return nullptr; |
| |
| if (!configurePatternForFontDescription(pattern.get(), fontDescription)) |
| return nullptr; |
| |
| // The strategy is originally from Skia (src/ports/SkFontHost_fontconfig.cpp): |
| // |
| // We do not normally allow fontconfig to substitute one font family for another, since this |
| // would break CSS font family fallback: the website should be in control of fallback. During |
| // normal font matching, the only font family substitution permitted is for generic families |
| // (sans, serif, monospace) or for strongly-aliased fonts (which are to be treated as |
| // effectively identical). This is because the font matching step is designed to always find a |
| // match for the font, which we don't want. |
| // |
| // Fontconfig is used in two stages: (1) configuration and (2) matching. During the |
| // configuration step, before any matching occurs, we allow arbitrary family substitutions, |
| // since this is an exact matter of respecting the user's font configuration. |
| FcConfigSubstitute(nullptr, pattern.get(), FcMatchPattern); |
| cairo_ft_font_options_substitute(getDefaultCairoFontOptions(), pattern.get()); |
| FcDefaultSubstitute(pattern.get()); |
| |
| FcChar8* fontConfigFamilyNameAfterConfiguration; |
| FcPatternGetString(pattern.get(), FC_FAMILY, 0, &fontConfigFamilyNameAfterConfiguration); |
| String familyNameAfterConfiguration = String::fromUTF8(reinterpret_cast<char*>(fontConfigFamilyNameAfterConfiguration)); |
| |
| FcResult fontConfigResult; |
| RefPtr<FcPattern> resultPattern = adoptRef(FcFontMatch(nullptr, pattern.get(), &fontConfigResult)); |
| if (!resultPattern) // No match. |
| return nullptr; |
| |
| // Loop through each font family of the result to see if it fits the one we requested. |
| bool matchedFontFamily = false; |
| FcChar8* fontConfigFamilyNameAfterMatching; |
| for (int i = 0; FcPatternGetString(resultPattern.get(), FC_FAMILY, i, &fontConfigFamilyNameAfterMatching) == FcResultMatch; ++i) { |
| // If Fontconfig gave us a different font family than the one we requested, we should ignore it |
| // and allow WebCore to give us the next font on the CSS fallback list. The exceptions are if |
| // this family name is a commonly-used generic family, or if the families are strongly-aliased. |
| // Checking for a strong alias comes last, since it is slow. |
| String familyNameAfterMatching = String::fromUTF8(reinterpret_cast<char*>(fontConfigFamilyNameAfterMatching)); |
| if (equalIgnoringASCIICase(familyNameAfterConfiguration, familyNameAfterMatching) || isCommonlyUsedGenericFamily(familyNameString) || areStronglyAliased(familyNameAfterConfiguration, familyNameAfterMatching)) { |
| matchedFontFamily = true; |
| break; |
| } |
| } |
| |
| if (!matchedFontFamily) |
| return nullptr; |
| |
| bool fixedWidth, syntheticBold, syntheticOblique; |
| getFontPropertiesFromPattern(resultPattern.get(), fontDescription, fixedWidth, syntheticBold, syntheticOblique); |
| |
| if (fontFaceFeatures) { |
| for (auto& fontFaceFeature : *fontFaceFeatures) { |
| if (fontFaceFeature.enabled()) { |
| const auto& tag = fontFaceFeature.tag(); |
| const char buffer[] = { tag[0], tag[1], tag[2], tag[3], '\0' }; |
| FcPatternAddString(resultPattern.get(), FC_FONT_FEATURES, reinterpret_cast<const FcChar8*>(buffer)); |
| } |
| } |
| } |
| |
| RefPtr<cairo_font_face_t> fontFace = adoptRef(cairo_ft_font_face_create_for_pattern(resultPattern.get())); |
| #if ENABLE(VARIATION_FONTS) |
| // Cairo doesn't have API to get the FT_Face of an unscaled font, so we need to |
| // create a temporary scaled font to get the FT_Face. |
| CairoUniquePtr<cairo_font_options_t> options(cairo_font_options_copy(getDefaultCairoFontOptions())); |
| cairo_matrix_t matrix; |
| cairo_matrix_init_identity(&matrix); |
| RefPtr<cairo_scaled_font_t> scaledFont = adoptRef(cairo_scaled_font_create(fontFace.get(), &matrix, &matrix, options.get())); |
| CairoFtFaceLocker cairoFtFaceLocker(scaledFont.get()); |
| if (FT_Face freeTypeFace = cairoFtFaceLocker.ftFace()) { |
| auto variants = buildVariationSettings(freeTypeFace, fontDescription); |
| if (!variants.isEmpty()) |
| FcPatternAddString(resultPattern.get(), FC_FONT_VARIATIONS, reinterpret_cast<const FcChar8*>(variants.utf8().data())); |
| } |
| #endif |
| auto platformData = makeUnique<FontPlatformData>(fontFace.get(), WTFMove(resultPattern), fontDescription.computedPixelSize(), fixedWidth, syntheticBold, syntheticOblique, fontDescription.orientation()); |
| // Verify that this font has an encoding compatible with Fontconfig. Fontconfig currently |
| // supports three encodings in FcFreeTypeCharIndex: Unicode, Symbol and AppleRoman. |
| // If this font doesn't have one of these three encodings, don't select it. |
| if (!platformData->hasCompatibleCharmap()) |
| return nullptr; |
| |
| return platformData; |
| } |
| |
| const AtomString& FontCache::platformAlternateFamilyName(const AtomString&) |
| { |
| return nullAtom(); |
| } |
| |
| #if ENABLE(VARIATION_FONTS) |
| struct VariationDefaults { |
| float defaultValue; |
| float minimumValue; |
| float maximumValue; |
| }; |
| |
| typedef HashMap<FontTag, VariationDefaults, FourCharacterTagHash, FourCharacterTagHashTraits> VariationDefaultsMap; |
| typedef HashMap<FontTag, float, FourCharacterTagHash, FourCharacterTagHashTraits> VariationsMap; |
| |
| static VariationDefaultsMap defaultVariationValues(FT_Face face) |
| { |
| VariationDefaultsMap result; |
| FT_MM_Var* ftMMVar; |
| if (FT_Get_MM_Var(face, &ftMMVar)) |
| return result; |
| |
| for (unsigned i = 0; i < ftMMVar->num_axis; ++i) { |
| auto tag = ftMMVar->axis[i].tag; |
| auto b1 = 0xFF & (tag >> 24); |
| auto b2 = 0xFF & (tag >> 16); |
| auto b3 = 0xFF & (tag >> 8); |
| auto b4 = 0xFF & (tag >> 0); |
| FontTag resultKey = {{ static_cast<char>(b1), static_cast<char>(b2), static_cast<char>(b3), static_cast<char>(b4) }}; |
| VariationDefaults resultValues = { narrowPrecisionToFloat(ftMMVar->axis[i].def / 65536.), narrowPrecisionToFloat(ftMMVar->axis[i].minimum / 65536.), narrowPrecisionToFloat(ftMMVar->axis[i].maximum / 65536.) }; |
| result.set(resultKey, resultValues); |
| } |
| FT_Done_MM_Var(face->glyph->library, ftMMVar); |
| return result; |
| } |
| |
| String buildVariationSettings(FT_Face face, const FontDescription& fontDescription) |
| { |
| auto defaultValues = defaultVariationValues(face); |
| const auto& variations = fontDescription.variationSettings(); |
| |
| VariationsMap variationsToBeApplied; |
| auto applyVariation = [&](const FontTag& tag, float value) { |
| auto iterator = defaultValues.find(tag); |
| if (iterator == defaultValues.end()) |
| return; |
| float valueToApply = clampTo(value, iterator->value.minimumValue, iterator->value.maximumValue); |
| variationsToBeApplied.set(tag, valueToApply); |
| }; |
| |
| for (auto& variation : variations) |
| applyVariation(variation.tag(), variation.value()); |
| |
| StringBuilder builder; |
| for (auto& variation : variationsToBeApplied) { |
| if (!builder.isEmpty()) |
| builder.append(','); |
| builder.append(variation.key[0]); |
| builder.append(variation.key[1]); |
| builder.append(variation.key[2]); |
| builder.append(variation.key[3]); |
| builder.append('='); |
| builder.append(FormattedNumber::fixedPrecision(variation.value)); |
| } |
| return builder.toString(); |
| } |
| #endif // ENABLE(VARIATION_FONTS) |
| |
| } |