/* * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. * (C) 2007, 2008 Nikolas Zimmermann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "CSSFontSelector.h" #include "CachedFont.h" #include "CSSFontFace.h" #include "CSSFontFaceRule.h" #include "CSSFontFaceSource.h" #include "CSSFontFaceSrcValue.h" #include "CSSMutableStyleDeclaration.h" #include "CSSPrimitiveValue.h" #include "CSSPropertyNames.h" #include "CSSSegmentedFontFace.h" #include "CSSUnicodeRangeValue.h" #include "CSSValueKeywords.h" #include "CSSValueList.h" #include "CachedResourceLoader.h" #include "Document.h" #include "FontCache.h" #include "FontFamilyValue.h" #include "Frame.h" #include "RenderObject.h" #include "Settings.h" #include "SimpleFontData.h" #include #if ENABLE(SVG) #include "SVGFontFaceElement.h" #include "SVGNames.h" #endif namespace WebCore { CSSFontSelector::CSSFontSelector(Document* document) : m_document(document) { // FIXME: An old comment used to say there was no need to hold a reference to m_document // because "we are guaranteed to be destroyed before the document". But there does not // seem to be any such guarantee. ASSERT(m_document); fontCache()->addClient(this); } CSSFontSelector::~CSSFontSelector() { fontCache()->removeClient(this); deleteAllValues(m_fontFaces); deleteAllValues(m_locallyInstalledFontFaces); deleteAllValues(m_fonts); } bool CSSFontSelector::isEmpty() const { return m_fonts.isEmpty(); } CachedResourceLoader* CSSFontSelector::cachedResourceLoader() const { return m_document ? m_document->cachedResourceLoader() : 0; } void CSSFontSelector::addFontFaceRule(const CSSFontFaceRule* fontFaceRule) { // Obtain the font-family property and the src property. Both must be defined. const CSSMutableStyleDeclaration* style = fontFaceRule->style(); RefPtr fontFamily = style->getPropertyCSSValue(CSSPropertyFontFamily); RefPtr src = style->getPropertyCSSValue(CSSPropertySrc); RefPtr unicodeRange = style->getPropertyCSSValue(CSSPropertyUnicodeRange); if (!fontFamily || !src || !fontFamily->isValueList() || !src->isValueList() || (unicodeRange && !unicodeRange->isValueList())) return; CSSValueList* familyList = static_cast(fontFamily.get()); if (!familyList->length()) return; CSSValueList* srcList = static_cast(src.get()); if (!srcList->length()) return; CSSValueList* rangeList = static_cast(unicodeRange.get()); unsigned traitsMask = 0; if (RefPtr fontStyle = style->getPropertyCSSValue(CSSPropertyFontStyle)) { if (fontStyle->isPrimitiveValue()) { RefPtr list = CSSValueList::createCommaSeparated(); list->append(fontStyle); fontStyle = list; } else if (!fontStyle->isValueList()) return; CSSValueList* styleList = static_cast(fontStyle.get()); unsigned numStyles = styleList->length(); if (!numStyles) return; for (unsigned i = 0; i < numStyles; ++i) { switch (static_cast(styleList->itemWithoutBoundsCheck(i))->getIdent()) { case CSSValueAll: traitsMask |= FontStyleMask; break; case CSSValueNormal: traitsMask |= FontStyleNormalMask; break; case CSSValueItalic: case CSSValueOblique: traitsMask |= FontStyleItalicMask; break; default: break; } } } else traitsMask |= FontStyleMask; if (RefPtr fontWeight = style->getPropertyCSSValue(CSSPropertyFontWeight)) { if (fontWeight->isPrimitiveValue()) { RefPtr list = CSSValueList::createCommaSeparated(); list->append(fontWeight); fontWeight = list; } else if (!fontWeight->isValueList()) return; CSSValueList* weightList = static_cast(fontWeight.get()); unsigned numWeights = weightList->length(); if (!numWeights) return; for (unsigned i = 0; i < numWeights; ++i) { switch (static_cast(weightList->itemWithoutBoundsCheck(i))->getIdent()) { case CSSValueAll: traitsMask |= FontWeightMask; break; case CSSValueBolder: case CSSValueBold: case CSSValue700: traitsMask |= FontWeight700Mask; break; case CSSValueNormal: case CSSValue400: traitsMask |= FontWeight400Mask; break; case CSSValue900: traitsMask |= FontWeight900Mask; break; case CSSValue800: traitsMask |= FontWeight800Mask; break; case CSSValue600: traitsMask |= FontWeight600Mask; break; case CSSValue500: traitsMask |= FontWeight500Mask; break; case CSSValue300: traitsMask |= FontWeight300Mask; break; case CSSValueLighter: case CSSValue200: traitsMask |= FontWeight200Mask; break; case CSSValue100: traitsMask |= FontWeight100Mask; break; default: break; } } } else traitsMask |= FontWeightMask; if (RefPtr fontVariant = style->getPropertyCSSValue(CSSPropertyFontVariant)) { if (fontVariant->isPrimitiveValue()) { RefPtr list = CSSValueList::createCommaSeparated(); list->append(fontVariant); fontVariant = list; } else if (!fontVariant->isValueList()) return; CSSValueList* variantList = static_cast(fontVariant.get()); unsigned numVariants = variantList->length(); if (!numVariants) return; for (unsigned i = 0; i < numVariants; ++i) { switch (static_cast(variantList->itemWithoutBoundsCheck(i))->getIdent()) { case CSSValueAll: traitsMask |= FontVariantMask; break; case CSSValueNormal: traitsMask |= FontVariantNormalMask; break; case CSSValueSmallCaps: traitsMask |= FontVariantSmallCapsMask; break; default: break; } } } else traitsMask |= FontVariantMask; // Each item in the src property's list is a single CSSFontFaceSource. Put them all into a CSSFontFace. RefPtr fontFace; int srcLength = srcList->length(); bool foundSVGFont = false; for (int i = 0; i < srcLength; i++) { // An item in the list either specifies a string (local font name) or a URL (remote font to download). CSSFontFaceSrcValue* item = static_cast(srcList->itemWithoutBoundsCheck(i)); CSSFontFaceSource* source = 0; #if ENABLE(SVG_FONTS) foundSVGFont = item->isSVGFontFaceSrc() || item->svgFontFaceElement(); #endif if (!item->isLocal()) { Settings* settings = m_document ? m_document->frame() ? m_document->frame()->settings() : 0 : 0; bool allowDownloading = foundSVGFont || (settings && settings->downloadableBinaryFontsEnabled()); if (allowDownloading && item->isSupportedFormat() && m_document) { ResourceRequest request(m_document->completeURL(item->resource())); CachedFont* cachedFont = m_document->cachedResourceLoader()->requestFont(request); if (cachedFont) { source = new CSSFontFaceSource(item->resource(), cachedFont); #if ENABLE(SVG_FONTS) if (foundSVGFont) source->setHasExternalSVGFont(true); #endif } } } else { source = new CSSFontFaceSource(item->resource()); } if (!fontFace) fontFace = CSSFontFace::create(static_cast(traitsMask)); if (source) { #if ENABLE(SVG_FONTS) source->setSVGFontFaceElement(item->svgFontFaceElement()); #endif fontFace->addSource(source); } } ASSERT(fontFace); if (fontFace && !fontFace->isValid()) return; if (rangeList) { unsigned numRanges = rangeList->length(); for (unsigned i = 0; i < numRanges; i++) { CSSUnicodeRangeValue* range = static_cast(rangeList->itemWithoutBoundsCheck(i)); fontFace->addRange(range->from(), range->to()); } } // Hash under every single family name. int familyLength = familyList->length(); for (int i = 0; i < familyLength; i++) { CSSPrimitiveValue* item = static_cast(familyList->itemWithoutBoundsCheck(i)); String familyName; if (item->primitiveType() == CSSPrimitiveValue::CSS_STRING) familyName = static_cast(item)->familyName(); else if (item->primitiveType() == CSSPrimitiveValue::CSS_IDENT) { // We need to use the raw text for all the generic family types, since @font-face is a way of actually // defining what font to use for those types. String familyName; switch (item->getIdent()) { case CSSValueSerif: familyName = "-webkit-serif"; break; case CSSValueSansSerif: familyName = "-webkit-sans-serif"; break; case CSSValueCursive: familyName = "-webkit-cursive"; break; case CSSValueFantasy: familyName = "-webkit-fantasy"; break; case CSSValueMonospace: familyName = "-webkit-monospace"; break; default: break; } } if (familyName.isEmpty()) continue; Vector >* familyFontFaces = m_fontFaces.get(familyName); if (!familyFontFaces) { familyFontFaces = new Vector >; m_fontFaces.set(familyName, familyFontFaces); ASSERT(!m_locallyInstalledFontFaces.contains(familyName)); Vector >* familyLocallyInstalledFaces; Vector locallyInstalledFontsTraitsMasks; fontCache()->getTraitsInFamily(familyName, locallyInstalledFontsTraitsMasks); unsigned numLocallyInstalledFaces = locallyInstalledFontsTraitsMasks.size(); if (numLocallyInstalledFaces) { familyLocallyInstalledFaces = new Vector >; m_locallyInstalledFontFaces.set(familyName, familyLocallyInstalledFaces); for (unsigned i = 0; i < numLocallyInstalledFaces; ++i) { RefPtr locallyInstalledFontFace = CSSFontFace::create(static_cast(locallyInstalledFontsTraitsMasks[i]), true); locallyInstalledFontFace->addSource(new CSSFontFaceSource(familyName)); ASSERT(locallyInstalledFontFace->isValid()); familyLocallyInstalledFaces->append(locallyInstalledFontFace); } } } familyFontFaces->append(fontFace); } } void CSSFontSelector::registerForInvalidationCallbacks(FontSelectorClient* client) { m_clients.add(client); } void CSSFontSelector::unregisterForInvalidationCallbacks(FontSelectorClient* client) { m_clients.remove(client); } void CSSFontSelector::dispatchInvalidationCallbacks() { Vector clients; copyToVector(m_clients, clients); for (size_t i = 0; i < clients.size(); ++i) clients[i]->fontsNeedUpdate(this); // FIXME: Make Document a FontSelectorClient so that it can simply register for invalidation callbacks. if (!m_document || m_document->inPageCache() || !m_document->renderer()) return; m_document->scheduleForcedStyleRecalc(); } void CSSFontSelector::fontLoaded() { dispatchInvalidationCallbacks(); } void CSSFontSelector::fontCacheInvalidated() { dispatchInvalidationCallbacks(); } void CSSFontSelector::retireCustomFont(FontData* fontData) { if (m_document) m_document->retireCustomFont(fontData); else { GlyphPageTreeNode::pruneTreeCustomFontData(fontData); delete fontData; } } static FontData* fontDataForGenericFamily(Document* document, const FontDescription& fontDescription, const AtomicString& familyName) { if (!document || !document->frame()) return 0; const Settings* settings = document->frame()->settings(); if (!settings) return 0; AtomicString genericFamily; if (familyName == "-webkit-serif") genericFamily = settings->serifFontFamily(); else if (familyName == "-webkit-sans-serif") genericFamily = settings->sansSerifFontFamily(); else if (familyName == "-webkit-cursive") genericFamily = settings->cursiveFontFamily(); else if (familyName == "-webkit-fantasy") genericFamily = settings->fantasyFontFamily(); else if (familyName == "-webkit-monospace") genericFamily = settings->fixedFontFamily(); else if (familyName == "-webkit-standard") genericFamily = settings->standardFontFamily(); if (!genericFamily.isEmpty()) return fontCache()->getCachedFontData(fontDescription, genericFamily); return 0; } static FontTraitsMask desiredTraitsMaskForComparison; static inline bool compareFontFaces(CSSFontFace* first, CSSFontFace* second) { FontTraitsMask firstTraitsMask = first->traitsMask(); FontTraitsMask secondTraitsMask = second->traitsMask(); bool firstHasDesiredVariant = firstTraitsMask & desiredTraitsMaskForComparison & FontVariantMask; bool secondHasDesiredVariant = secondTraitsMask & desiredTraitsMaskForComparison & FontVariantMask; if (firstHasDesiredVariant != secondHasDesiredVariant) return firstHasDesiredVariant; if ((desiredTraitsMaskForComparison & FontVariantSmallCapsMask) && !first->isLocalFallback() && !second->isLocalFallback()) { // 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 = (firstTraitsMask & FontVariantSmallCapsMask) && !(firstTraitsMask & FontVariantNormalMask); bool secondRequiresSmallCaps = (secondTraitsMask & FontVariantSmallCapsMask) && !(secondTraitsMask & FontVariantNormalMask); if (firstRequiresSmallCaps != secondRequiresSmallCaps) return firstRequiresSmallCaps; } bool firstHasDesiredStyle = firstTraitsMask & desiredTraitsMaskForComparison & FontStyleMask; bool secondHasDesiredStyle = secondTraitsMask & desiredTraitsMaskForComparison & FontStyleMask; if (firstHasDesiredStyle != secondHasDesiredStyle) return firstHasDesiredStyle; if ((desiredTraitsMaskForComparison & FontStyleItalicMask) && !first->isLocalFallback() && !second->isLocalFallback()) { // 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 = (firstTraitsMask & FontStyleItalicMask) && !(firstTraitsMask & FontStyleNormalMask); bool secondRequiresItalics = (secondTraitsMask & FontStyleItalicMask) && !(secondTraitsMask & FontStyleNormalMask); if (firstRequiresItalics != secondRequiresItalics) return firstRequiresItalics; } if (secondTraitsMask & desiredTraitsMaskForComparison & FontWeightMask) return false; if (firstTraitsMask & desiredTraitsMaskForComparison & FontWeightMask) return true; // http://www.w3.org/TR/2002/WD-css3-webfonts-20020802/#q46 says: "If there are fewer then 9 weights in the family, the default algorithm // for filling the "holes" is as follows. If '500' is unassigned, it will be assigned the same font as '400'. If any of the values '600', // '700', '800', or '900' remains unassigned, they are assigned to the same face as the next darker assigned keyword, if any, or the next // lighter one otherwise. If any of '300', '200', or '100' remains unassigned, it is assigned to the next lighter assigned keyword, if any, // or the next darker otherwise." // For '400', we made up our own rule (which then '500' follows). static const unsigned fallbackRuleSets = 9; static const unsigned rulesPerSet = 8; static const FontTraitsMask weightFallbackRuleSets[fallbackRuleSets][rulesPerSet] = { { FontWeight200Mask, FontWeight300Mask, FontWeight400Mask, FontWeight500Mask, FontWeight600Mask, FontWeight700Mask, FontWeight800Mask, FontWeight900Mask }, { FontWeight100Mask, FontWeight300Mask, FontWeight400Mask, FontWeight500Mask, FontWeight600Mask, FontWeight700Mask, FontWeight800Mask, FontWeight900Mask }, { FontWeight200Mask, FontWeight100Mask, FontWeight400Mask, FontWeight500Mask, FontWeight600Mask, FontWeight700Mask, FontWeight800Mask, FontWeight900Mask }, { FontWeight500Mask, FontWeight300Mask, FontWeight600Mask, FontWeight200Mask, FontWeight700Mask, FontWeight100Mask, FontWeight800Mask, FontWeight900Mask }, { FontWeight400Mask, FontWeight300Mask, FontWeight600Mask, FontWeight200Mask, FontWeight700Mask, FontWeight100Mask, FontWeight800Mask, FontWeight900Mask }, { FontWeight700Mask, FontWeight800Mask, FontWeight900Mask, FontWeight500Mask, FontWeight400Mask, FontWeight300Mask, FontWeight200Mask, FontWeight100Mask }, { FontWeight800Mask, FontWeight900Mask, FontWeight600Mask, FontWeight500Mask, FontWeight400Mask, FontWeight300Mask, FontWeight200Mask, FontWeight100Mask }, { FontWeight900Mask, FontWeight700Mask, FontWeight600Mask, FontWeight500Mask, FontWeight400Mask, FontWeight300Mask, FontWeight200Mask, FontWeight100Mask }, { FontWeight800Mask, FontWeight700Mask, FontWeight600Mask, FontWeight500Mask, FontWeight400Mask, FontWeight300Mask, FontWeight200Mask, FontWeight100Mask } }; unsigned ruleSetIndex = 0; unsigned w = FontWeight100Bit; while (!(desiredTraitsMaskForComparison & (1 << w))) { w++; ruleSetIndex++; } ASSERT(ruleSetIndex < fallbackRuleSets); const FontTraitsMask* weightFallbackRule = weightFallbackRuleSets[ruleSetIndex]; for (unsigned i = 0; i < rulesPerSet; ++i) { if (secondTraitsMask & weightFallbackRule[i]) return false; if (firstTraitsMask & weightFallbackRule[i]) return true; } return false; } FontData* CSSFontSelector::getFontData(const FontDescription& fontDescription, const AtomicString& familyName) { if (m_fontFaces.isEmpty()) { if (familyName.startsWith("-webkit-")) return fontDataForGenericFamily(m_document, fontDescription, familyName); return 0; } String family = familyName.string(); Vector >* familyFontFaces = m_fontFaces.get(family); // If no face was found, then return 0 and let the OS come up with its best match for the name. if (!familyFontFaces || familyFontFaces->isEmpty()) { // If we were handed a generic family, but there was no match, go ahead and return the correct font based off our // settings. return fontDataForGenericFamily(m_document, fontDescription, familyName); } HashMap >* segmentedFontFaceCache = m_fonts.get(family); if (!segmentedFontFaceCache) { segmentedFontFaceCache = new HashMap >; m_fonts.set(family, segmentedFontFaceCache); } FontTraitsMask traitsMask = fontDescription.traitsMask(); RefPtr face = segmentedFontFaceCache->get(traitsMask); if (!face) { face = CSSSegmentedFontFace::create(this); segmentedFontFaceCache->set(traitsMask, face); // Collect all matching faces and sort them in order of preference. Vector candidateFontFaces; for (int i = familyFontFaces->size() - 1; i >= 0; --i) { CSSFontFace* candidate = familyFontFaces->at(i).get(); unsigned candidateTraitsMask = candidate->traitsMask(); if ((traitsMask & FontStyleNormalMask) && !(candidateTraitsMask & FontStyleNormalMask)) continue; if ((traitsMask & FontVariantNormalMask) && !(candidateTraitsMask & FontVariantNormalMask)) continue; #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 as a candidate. if (candidate->hasSVGFontFaceSource() && (traitsMask & FontVariantSmallCapsMask) && !(candidateTraitsMask & FontVariantSmallCapsMask)) continue; #endif candidateFontFaces.append(candidate); } if (Vector >* familyLocallyInstalledFontFaces = m_locallyInstalledFontFaces.get(family)) { unsigned numLocallyInstalledFontFaces = familyLocallyInstalledFontFaces->size(); for (unsigned i = 0; i < numLocallyInstalledFontFaces; ++i) { CSSFontFace* candidate = familyLocallyInstalledFontFaces->at(i).get(); unsigned candidateTraitsMask = candidate->traitsMask(); if ((traitsMask & FontStyleNormalMask) && !(candidateTraitsMask & FontStyleNormalMask)) continue; if ((traitsMask & FontVariantNormalMask) && !(candidateTraitsMask & FontVariantNormalMask)) continue; candidateFontFaces.append(candidate); } } desiredTraitsMaskForComparison = traitsMask; std::stable_sort(candidateFontFaces.begin(), candidateFontFaces.end(), compareFontFaces); unsigned numCandidates = candidateFontFaces.size(); for (unsigned i = 0; i < numCandidates; ++i) face->appendFontFace(candidateFontFaces[i]); } // We have a face. Ask it for a font data. If it cannot produce one, it will fail, and the OS will take over. return face->getFontData(fontDescription); } }