PassRefPtr<FontData> CSSSegmentedFontFace::getFontData(const FontDescription& fontDescription) { if (!isValid()) return nullptr; FontTraits desiredTraits = fontDescription.traits(); FontCacheKey key = fontDescription.cacheKey(FontFaceCreationParams(), desiredTraits); RefPtr<SegmentedFontData>& fontData = m_fontDataTable.add(key.hash(), nullptr).storedValue->value; if (fontData && fontData->numFaces()) return fontData; // No release, we have a reference to an object in the cache which should retain the ref count it has. if (!fontData) fontData = SegmentedFontData::create(); FontDescription requestedFontDescription(fontDescription); requestedFontDescription.setTraits(m_traits); requestedFontDescription.setSyntheticBold(m_traits.weight() < FontWeight600 && desiredTraits.weight() >= FontWeight600); requestedFontDescription.setSyntheticItalic(m_traits.style() == FontStyleNormal && desiredTraits.style() == FontStyleItalic); for (FontFaceList::reverse_iterator it = m_fontFaces.rbegin(); it != m_fontFaces.rend(); ++it) { if (!(*it)->cssFontFace()->isValid()) continue; if (RefPtr<SimpleFontData> faceFontData = (*it)->cssFontFace()->getFontData(requestedFontDescription)) { ASSERT(!faceFontData->isSegmented()); fontData->appendFace(FontDataForRangeSet(faceFontData.release(), (*it)->cssFontFace()->ranges())); } } if (fontData->numFaces()) return fontData; // No release, we have a reference to an object in the cache which should retain the ref count it has. return nullptr; }
void FontDescription::setTraits(FontTraits traits) { setStyle(traits.style()); setVariant(traits.variant()); setWeight(traits.weight()); setStretch(traits.stretch()); }
FontCacheKey FontDescription::cacheKey(const FontFaceCreationParams& creationParams, FontTraits desiredTraits) const { FontTraits fontTraits = desiredTraits.bitfield() ? desiredTraits : traits(); unsigned options = static_cast<unsigned>(m_syntheticItalic) << 5 | // bit 6 static_cast<unsigned>(m_syntheticBold) << 4 | // bit 5 static_cast<unsigned>(m_textRendering) << 2 | // bits 3-4 static_cast<unsigned>(m_orientation) << 1 | // bit 2 static_cast<unsigned>(m_subpixelTextPosition); // bit 1 return FontCacheKey(creationParams, effectiveFontSize(), options | fontTraits.bitfield() << 8); }
FontCacheKey FontDescription::cacheKey(const AtomicString& familyName, FontTraits desiredTraits) const { FontTraits fontTraits = desiredTraits.mask() ? desiredTraits : traits(); unsigned options = static_cast<unsigned>(m_syntheticItalic) << 7 | // bit 8 static_cast<unsigned>(m_syntheticBold) << 6 | // bit 7 static_cast<unsigned>(m_fontSmoothing) << 4 | // bits 5-6 static_cast<unsigned>(m_textRendering) << 2 | // bits 3-4 static_cast<unsigned>(m_orientation) << 1 | // bit 2 static_cast<unsigned>(m_subpixelTextPosition); // bit 1 return FontCacheKey(familyName, effectiveFontSize(), options | fontTraits.mask() << 8); }
CSSSegmentedFontFace* FontFaceCache::get(const FontDescription& fontDescription, const AtomicString& family) { TraitsMap* familyFontFaces = m_fontFaces.get(family); if (!familyFontFaces || familyFontFaces->isEmpty()) return 0; FamilyToTraitsMap::AddResult traitsResult = m_fonts.add(family, nullptr); if (!traitsResult.storedValue->value) traitsResult.storedValue->value = adoptPtr(new TraitsMap); FontTraits traits = fontDescription.traits(); TraitsMap::AddResult faceResult = traitsResult.storedValue->value->add(traits.bitfield(), nullptr); if (!faceResult.storedValue->value) { for (TraitsMap::const_iterator i = familyFontFaces->begin(); i != familyFontFaces->end(); ++i) { CSSSegmentedFontFace* candidate = i->value.get(); FontTraits candidateTraits = candidate->traits(); if (traits.style() == FontStyleNormal && candidateTraits.style() != FontStyleNormal) continue; if (traits.variant() == FontVariantNormal && candidateTraits.variant() != FontVariantNormal) continue; if (!faceResult.storedValue->value || compareFontFaces(candidate, faceResult.storedValue->value.get(), traits)) faceResult.storedValue->value = candidate; } } return faceResult.storedValue->value.get(); }
static inline unsigned styleScore(FontTraits desired, FontTraits candidate) { static_assert(FontStyleNormal == 0 && FontStyleItalic == 2, "Enumeration values need to match lookup table."); unsigned styleScoreLookupTable[][FontStyleItalic + 1] = { // "If the value is normal, normal faces are checked first, then oblique faces, then italic faces." // i.e. normal has the highest score, then oblique, then italic. { 2, 1, 0 }, // "If the value is oblique, oblique faces are checked first, then italic faces and then normal faces." // i.e. normal gets the lowest score, oblique gets the highest, italic second best. { 0, 2, 1 }, // "If the value of font-style is italic, italic faces are checked first, then oblique, then normal faces" // i.e. normal gets the lowest score, oblique is second best, italic highest. { 0, 1, 2 } }; ASSERT_WITH_SECURITY_IMPLICATION(desired.style() < FontStyleItalic + 1); ASSERT_WITH_SECURITY_IMPLICATION(candidate.style() < FontStyleItalic + 1); return styleScoreLookupTable[desired.style()][candidate.style()]; }
PassRefPtr<FontData> CSSSegmentedFontFace::getFontData(const FontDescription& fontDescription) { if (!isValid()) return nullptr; FontTraits desiredTraits = fontDescription.traits(); AtomicString emptyFontFamily = ""; FontCacheKey key = fontDescription.cacheKey(emptyFontFamily, desiredTraits); RefPtr<SegmentedFontData>& fontData = m_fontDataTable.add(key.hash(), nullptr).storedValue->value; if (fontData && fontData->numRanges()) return fontData; // No release, we have a reference to an object in the cache which should retain the ref count it has. if (!fontData) fontData = SegmentedFontData::create(); FontDescription requestedFontDescription(fontDescription); requestedFontDescription.setTraits(m_traits); requestedFontDescription.setSyntheticBold(m_traits.weight() < FontWeight600 && desiredTraits.weight() >= FontWeight600); requestedFontDescription.setSyntheticItalic(m_traits.style() == FontStyleNormal && desiredTraits.style() == FontStyleItalic); for (FontFaceList::reverse_iterator it = m_fontFaces.rbegin(); it != m_fontFaces.rend(); ++it) { if (!(*it)->cssFontFace()->isValid()) continue; if (RefPtr<SimpleFontData> faceFontData = (*it)->cssFontFace()->getFontData(requestedFontDescription)) { ASSERT(!faceFontData->isSegmented()); #if ENABLE(SVG_FONTS) // For SVG Fonts that specify that they only support the "normal" variant, we will assume they are incapable // of small-caps synthesis and just ignore the font face. if (faceFontData->isSVGFont() && desiredTraits.variant() == FontVariantSmallCaps && m_traits.variant() == FontVariantNormal) continue; #endif appendFontData(fontData.get(), faceFontData.release(), (*it)->cssFontFace()->ranges()); } } if (fontData->numRanges()) return fontData; // No release, we have a reference to an object in the cache which should retain the ref count it has. return nullptr; }
CSSSegmentedFontFace* FontFaceCache::get(const FontDescription& fontDescription, const AtomicString& family) { TraitsMap* familyFontFaces = m_fontFaces.get(family); if (!familyFontFaces || familyFontFaces->isEmpty()) return nullptr; FamilyToTraitsMap::AddResult traitsResult = m_fonts.add(family, nullptr); if (!traitsResult.storedValue->value) traitsResult.storedValue->value = new TraitsMap; FontTraits traits = fontDescription.traits(); TraitsMap::AddResult faceResult = traitsResult.storedValue->value->add(traits.bitfield(), nullptr); if (!faceResult.storedValue->value) { for (const auto& item : *familyFontFaces) { CSSSegmentedFontFace* candidate = item.value.get(); FontStyleMatcher styleMatcher(traits); if (!faceResult.storedValue->value || styleMatcher.isCandidateBetter(candidate, faceResult.storedValue->value.get())) faceResult.storedValue->value = candidate; } } return faceResult.storedValue->value.get(); }
static inline unsigned weightScore(FontTraits desired, FontTraits candidate) { static_assert(FontWeight100 == 0 && FontWeight900 - FontWeight100 == 8, "Enumeration values need to match lookup table."); static const unsigned scoreLookupSize = FontWeight900 + 1; // https://drafts.csswg.org/css-fonts/#font-style-matching // "..if the desired weight is available that face matches. " static const unsigned weightScoreLookup[scoreLookupSize][scoreLookupSize] = { // "If the desired weight is less than 400, weights below the desired // weight are checked in descending order followed by weights above the // desired weight in ascending order until a match is found." { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, // FontWeight100 desired { 8, 9, 7, 6, 5, 4, 3, 2, 1 }, // FontWeight200 desired { 7, 8, 9, 6, 5, 4, 3, 2, 1 }, // FontWeight300 desired // "If the desired weight is 400, 500 is checked first and then the rule // for desired weights less than 400 is used." { 5, 6, 7, 9, 8, 4, 3, 2, 1 }, // FontWeight400 desired // "If the desired weight is 500, 400 is checked first and then the rule // for desired weights less than 400 is used." { 5, 6, 7, 8, 9, 4, 3, 2, 1 }, // FontWeight500 desired // "If the desired weight is greater than 500, weights above the desired // weight are checked in ascending order followed by weights below the // desired weight in descending order until a match is found." { 1, 2, 3, 4, 5, 9, 8, 7, 6 }, // FontWeight600 desired { 1, 2, 3, 4, 5, 6, 9, 8, 7 }, // FontWeight700 desired { 1, 2, 3, 4, 5, 6, 7, 9, 8 }, // FontWeight800 desired { 1, 2, 3, 4, 5, 6, 7, 8, 9 } // FontWeight900 desired }; unsigned desiredScoresLookup = static_cast<unsigned>(desired.weight()); unsigned candidateScoreLookup = static_cast<unsigned>(candidate.weight()); ASSERT_WITH_SECURITY_IMPLICATION(desiredScoresLookup < scoreLookupSize); ASSERT_WITH_SECURITY_IMPLICATION(candidateScoreLookup < scoreLookupSize); return weightScoreLookup[desiredScoresLookup][candidateScoreLookup]; }
static inline unsigned stretchDistanceToDesired(FontTraits desired, FontTraits candidate) { return abs(static_cast<int>(desired.stretch() - candidate.stretch())); }
static inline bool compareFontFaces(CSSSegmentedFontFace* first, CSSSegmentedFontFace* second, FontTraits desiredTraits) { const FontTraits& firstTraits = first->traits(); const FontTraits& secondTraits = second->traits(); bool firstHasDesiredVariant = firstTraits.variant() == desiredTraits.variant(); bool secondHasDesiredVariant = secondTraits.variant() == desiredTraits.variant(); if (firstHasDesiredVariant != secondHasDesiredVariant) return firstHasDesiredVariant; // We need to check font-variant css property for CSS2.1 compatibility. if (desiredTraits.variant() == FontVariantSmallCaps) { // Prefer a font that has indicated that it can only support small-caps to a font that claims to support // all variants. The specialized font is more likely to be true small-caps and not require synthesis. bool firstRequiresSmallCaps = firstTraits.variant() == FontVariantSmallCaps; bool secondRequiresSmallCaps = secondTraits.variant() == FontVariantSmallCaps; if (firstRequiresSmallCaps != secondRequiresSmallCaps) return firstRequiresSmallCaps; } bool firstHasDesiredStyle = firstTraits.style() == desiredTraits.style(); bool secondHasDesiredStyle = secondTraits.style() == desiredTraits.style(); if (firstHasDesiredStyle != secondHasDesiredStyle) return firstHasDesiredStyle; if (desiredTraits.style() == FontStyleItalic) { // Prefer a font that has indicated that it can only support italics to a font that claims to support // all styles. The specialized font is more likely to be the one the author wants used. bool firstRequiresItalics = firstTraits.style() == FontStyleItalic; bool secondRequiresItalics = secondTraits.style() == FontStyleItalic; if (firstRequiresItalics != secondRequiresItalics) return firstRequiresItalics; } if (secondTraits.weight() == desiredTraits.weight()) return false; if (firstTraits.weight() == desiredTraits.weight()) return true; // http://www.w3.org/TR/2011/WD-css3-fonts-20111004/#font-matching-algorithm says : // - If the desired weight is less than 400, weights below the desired weight are checked in descending order followed by weights above the desired weight in ascending order until a match is found. // - If the desired weight is greater than 500, weights above the desired weight are checked in ascending order followed by weights below the desired weight in descending order until a match is found. // - If the desired weight is 400, 500 is checked first and then the rule for desired weights less than 400 is used. // - If the desired weight is 500, 400 is checked first and then the rule for desired weights less than 400 is used. static const unsigned fallbackRuleSets = 9; static const unsigned rulesPerSet = 8; static const FontWeight weightFallbackRuleSets[fallbackRuleSets][rulesPerSet] = { { FontWeight200, FontWeight300, FontWeight400, FontWeight500, FontWeight600, FontWeight700, FontWeight800, FontWeight900 }, { FontWeight100, FontWeight300, FontWeight400, FontWeight500, FontWeight600, FontWeight700, FontWeight800, FontWeight900 }, { FontWeight200, FontWeight100, FontWeight400, FontWeight500, FontWeight600, FontWeight700, FontWeight800, FontWeight900 }, { FontWeight500, FontWeight300, FontWeight200, FontWeight100, FontWeight600, FontWeight700, FontWeight800, FontWeight900 }, { FontWeight400, FontWeight300, FontWeight200, FontWeight100, FontWeight600, FontWeight700, FontWeight800, FontWeight900 }, { FontWeight700, FontWeight800, FontWeight900, FontWeight500, FontWeight400, FontWeight300, FontWeight200, FontWeight100 }, { FontWeight800, FontWeight900, FontWeight600, FontWeight500, FontWeight400, FontWeight300, FontWeight200, FontWeight100 }, { FontWeight900, FontWeight700, FontWeight600, FontWeight500, FontWeight400, FontWeight300, FontWeight200, FontWeight100 }, { FontWeight800, FontWeight700, FontWeight600, FontWeight500, FontWeight400, FontWeight300, FontWeight200, FontWeight100 } }; unsigned ruleSetIndex = static_cast<unsigned>(desiredTraits.weight()); ASSERT(ruleSetIndex < fallbackRuleSets); const FontWeight* weightFallbackRule = weightFallbackRuleSets[ruleSetIndex]; for (unsigned i = 0; i < rulesPerSet; ++i) { if (secondTraits.weight() == weightFallbackRule[i]) return false; if (firstTraits.weight() == weightFallbackRule[i]) return true; } return false; }