Commit 5c29462c authored by drott@chromium.org's avatar drott@chromium.org

Improve CSS Style matching spec compliance

Bring our CSS font style matching closer to the relevant section of the
spec. [1] We previously had issues on matching correctly on font-stretch and
on font-style. These are addressed in this patch by reimplementing the
search for the correct font candidate out of the list of available
@font-face's.

[1] https://drafts.csswg.org/css-fonts/#font-style-matching

BUG=514751,513670

Review URL: https://codereview.chromium.org/1270723002

git-svn-id: svn://svn.chromium.org/blink/trunk@200895 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 6cca86ab
......@@ -700,6 +700,16 @@ crbug.com/396941 ietestcenter/css3/multicolumn/column-width-applies-to-015.htm [
crbug.com/396945 ietestcenter/css3/namespaces/syntax-021.xml [ ImageOnlyFailure ]
crbug.com/378610 [ Win7 ] svg/custom/focus-ring-text.svg [ ImageOnlyFailure ]
crbug.com/515454 css3/fonts/font-style-matching.html [ Slow ]
crbug.com/514751 fast/text/first-letter-bad-line-boxes-crash.html [ NeedsRebaseline ]
crbug.com/514751 [ Win ] css1/font_properties/font_style.html [ NeedsRebaseline ]
crbug.com/514751 [ Win ] css2.1/t1504-c523-font-style-00-b.html [ NeedsRebaseline ]
crbug.com/514751 [ Win ] css2.1/t1508-c527-font-08-b.html [ NeedsRebaseline ]
crbug.com/514751 [ Win7 Win10 ] svg/batik/text/smallFonts.svg [ NeedsRebaseline ]
crbug.com/514751 [ Win ] svg/batik/text/textFeatures.svg [ NeedsRebaseline ]
crbug.com/514751 [ Win ] svg/batik/text/textStyles.svg [ NeedsRebaseline ]
crbug.com/474987 [ Win7 Mac ] css3/flexbox/auto-margins.html [ ImageOnlyFailure ]
crbug.com/267206 [ Mac ] fast/scrolling/scrollbar-tickmarks-hittest.html [ Timeout ]
......
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<head>
<script type="text/javascript" src="../../http/tests/inspector-protocol/inspector-protocol-test.js"></script>
<script type="text/javascript" src="../../http/tests/inspector-protocol/css-protocol-test.js"></script>
<script type="text/javascript" src="../../http/tests/inspector-protocol/dom-protocol-test.js"></script>
<script type="text/javascript" src="resources/style-matching-test.js"></script>
<!-- available fonts --->
<style type="text/css">
</style>
<!-- Applied styles -->
<style type="text/css">
body {
font-size: 24px;
line-height: 110%;
text-rendering: optimizeLegibility;
font-feature-settings: "liga" 1;
}
#condensed_normal_100 {
font-family: CSSMatchingTest;
font-stretch: condensed;
font-style: normal;
font-weight: 100;
}
#condensed_normal_900 {
font-family: CSSMatchingTest;
font-stretch: condensed;
font-style: normal;
font-weight: 900;
}
#condensed_italic_100 {
font-family: CSSMatchingTest;
font-stretch: condensed;
font-style: italic;
font-weight: 100;
}
#condensed_italic_900 {
font-family: CSSMatchingTest;
font-stretch: condensed;
font-style: italic;
font-weight: 900;
}
#expanded_normal_100 {
font-family: CSSMatchingTest;
font-stretch: expanded;
font-style: normal;
font-weight: 100;
}
#expanded_normal_900 {
font-family: CSSMatchingTest;
font-stretch: expanded;
font-style: normal;
font-weight: 900;
}
#expanded_italic_100 {
font-family: CSSMatchingTest;
font-stretch: expanded;
font-style: italic;
font-weight: 100;
}
#expanded_italic_900 {
font-family: CSSMatchingTest;
font-stretch: expanded;
font-style: italic;
font-weight: 900;
}
</style>
</head>
<body>
<pre>According to the CSS3 Fonts Module, Step 4 or the Font Style Matching Algorithm
( https://drafts.csswg.org/css-fonts-3/#font-style-matching ) must narrow down the
available font faces by finding the nearest match in the following order of
precedence: stretch, style, weight.
</pre>
<div class="test">
<div id="condensed_normal_100">abcdefg</div>
<div id="condensed_normal_900">abcdefg</div>
<div id="condensed_italic_100">abcdefg</div>
<div id="condensed_italic_900">abcdefg</div>
<div id="expanded_normal_100">abcdefg</div>
<div id="expanded_normal_900">abcdefg</div>
<div id="expanded_italic_100">abcdefg</div>
<div id="expanded_italic_900">abcdefg</div>
</div>
</body>
</html>
function emptyFontFaceDeclarations()
{
var stylesheet = document.styleSheets[0];
var rules = stylesheet.cssRules;
var rulesLengthSnapshot = stylesheet.cssRules.length;
if (!rulesLengthSnapshot)
return;
for (var i = 0; i < rulesLengthSnapshot; i++) {
stylesheet.deleteRule(0);
}
}
function makeFontFaceDeclaration(stretch, style, weight)
{
var fontFaceDeclaration = '@font-face { font-family: "CSSMatchingtest"; ' +
"font-stretch: " + stretch + ";" +
"font-style: " + style + ";" +
"font-weight: " + weight + ";" +
'src: url("' +
"resources/fonts/CSSMatchingTest_" +
stretch + "_" + style + "_" + weight + '.ttf");';
return fontFaceDeclaration;
}
function notifyInspectorFontsReady() {
var cssRules = document.styleSheets[0].cssRules;
var fontsAvailable = [];
for (var i = 0; i < cssRules.length; i++) {
urlmatch = /url\(.*fonts\/CSSMatchingTest_(.*).ttf\)/.exec(
cssRules[i].cssText);
fontsAvailable.push(urlmatch[1]);
}
evaluateInFrontend('window.onFontsLoaded(' +
JSON.stringify(fontsAvailable) + ");");
}
function updateAvailableFontFaceDeclarations(available)
{
emptyFontFaceDeclarations();
for (stretch of available.stretches)
for (style of available.styles)
for (weight of available.weights) {
document.styleSheets[0].addRule(
makeFontFaceDeclaration(stretch, style, weight));
}
document.fonts.ready.then(window.notifyInspectorFontsReady);
}
function test()
{
var documentNodeId;
var documentNodeSelector;
var allTestSelectors = [];
var testSelectors = [];
var stretches = [ 'condensed', 'expanded' ];
var styles = [ 'normal', 'italic' ];
var weights = [ '100', '900' ];
var weightsHumanReadable = [ "Thin", "Black" ];
var fontSetVariations = [];
var currentFontSetVariation = {};
makeFontSetVariations();
function makeFontSetVariations() {
// For each of the three properties we have three choices:
// Restrict to the first value, to the second, or
// allow both. So we're iterating over those options
// for each dimension to generate the set of allowed
// options for each property. The fonts in the test
// page will later be generated from these possible
// choices / restrictions.
function choices(property) {
return [ property, [ property[0] ], [ property[1] ] ];
}
for (stretchChoice of choices(stretches)) {
for (styleChoice of choices(styles)) {
for (weightChoice of choices(weights)) {
var available = {};
available.stretches = stretchChoice;
available.styles = styleChoice;
available.weights = weightChoice;
fontSetVariations.push(available);
}
}
}
}
InspectorTest.requestDocumentNodeId(onDocumentNodeId);
function onDocumentNodeId(nodeId)
{
documentNodeId = nodeId;
InspectorTest.evaluateInInspectedPage(
'JSON.stringify(' +
'Array.prototype.map.call(' +
'document.querySelectorAll(".test *"),' +
'function(element) { return element.id } ));',
startTestingPage);
}
function startTestingPage(result)
{
allTestSelectors = JSON.parse(result.result.result.value);
nextTest();
}
function nextTest() {
if (testSelectors.length) {
testNextPageElement();
} else {
currentFontSetVariation = fontSetVariations.shift();
if (currentFontSetVariation) {
testSelectors = allTestSelectors.slice();
updateFontDeclarationsInPageForVariation(
currentFontSetVariation);
} else {
InspectorTest.completeTest();
}
}
}
function updateFontDeclarationsInPageForVariation(fontSetVariation)
{
window.testPageFontsReady = false;
InspectorTest.evaluateInInspectedPage(
'updateAvailableFontFaceDeclarations(' +
JSON.stringify(fontSetVariation) +
');');
// nextTest() is called once we receive the onFontsLoaded
// notification from the page.
}
// Explicitly make this available on window scope so that testing
// can resume through a signal from the page once the fonts.ready
// promise resolves.
window.onFontsLoaded = function(loadedFonts) {
InspectorTest.log("Available fonts updated: " +
JSON.stringify(loadedFonts) + "\n");
// fonts.ready event fires too early, used fonts for rendering
// have not been updated yet. Remove this wehn crbug.com/516680
// is fixed.
// https://drafts.csswg.org/css-font-loading/#font-face-set-ready
setTimeout(nextTest, 100);
}
function testNextPageElement(result)
{
var nextSelector = testSelectors.shift()
if (nextSelector) {
documentNodeSelector = "#" + nextSelector;
platformFontsForElementWithSelector(documentNodeSelector);
}
}
function platformFontsForElementWithSelector(selector)
{
InspectorTest.requestNodeId(documentNodeId, selector, onNodeId);
function onNodeId(nodeId)
{
InspectorTest.sendCommandOrDie("CSS.getPlatformFontsForNode",
{ nodeId: nodeId },
onGotComputedFonts);
}
function onGotComputedFonts(response)
{
logResults(response);
nextTest();
}
}
function logResults(response)
{
InspectorTest.log(documentNodeSelector);
logPassFailResult(
documentNodeSelector,
response.fonts[0].familyName);
}
function cssStyleMatchingExpectationForSelector(selector) {
var selectorStretchStyleWeight = selector.substr(1).split("_");
var selectorStretch = selectorStretchStyleWeight[0].toLowerCase();
var selectorStyle = selectorStretchStyleWeight[1].toLowerCase();
var selectorWeight = selectorStretchStyleWeight[2];
var expectedProperties = {};
if (currentFontSetVariation.stretches.indexOf(selectorStretch) > 0) {
expectedProperties.stretch = selectorStretch;
} else {
// If the requested property value is not availabe in the
// current font set, then it's restricted to only one value,
// which is the nearest match, and at index 0.
expectedProperties.stretch = currentFontSetVariation.stretches[0];
}
if (currentFontSetVariation.styles.indexOf(selectorStyle) > 0) {
expectedProperties.style = selectorStyle;
} else {
expectedProperties.style = currentFontSetVariation.styles[0];
}
if (currentFontSetVariation.weights.indexOf(selectorWeight) > 0) {
expectedProperties.weight = selectorWeight;
} else {
expectedProperties.weight = currentFontSetVariation.weights[0];
}
return expectedProperties;
}
function logPassFailResult(selector, usedFontName)
{
var actualStretchStyleWeight =
/CSSMatchingTest (\w*) (\w*) (.*)/.exec(usedFontName);
var actualStretch, actualStyle, actualWeight;
if (actualStretchStyleWeight && actualStretchStyleWeight.length > 3) {
actualStretch = actualStretchStyleWeight[1].toLowerCase();
actualStyle = actualStretchStyleWeight[2].toLowerCase();
// Font names have human readable weight description,
// we need to convert.
actualWeight = weights [
weightsHumanReadable.indexOf(actualStretchStyleWeight[3]) ];
} else {
actualStretch = usedFontName;
actualStyle = "";
actualWeight = "";
}
var expectedProperties = cssStyleMatchingExpectationForSelector(
selector);
InspectorTest.log("Expected: " +
expectedProperties.stretch + " " +
expectedProperties.style + " " +
expectedProperties.weight);
InspectorTest.log("Actual: " + actualStretch + " " +
actualStyle + " " +
actualWeight);
if (actualStretch != expectedProperties.stretch ||
actualStyle != expectedProperties.style ||
actualWeight != expectedProperties.weight) {
InspectorTest.log("FAIL\n");
} else {
InspectorTest.log("PASS\n");
}
}
};
window.addEventListener("DOMContentLoaded", function () {
runTest();
}, false);
......@@ -57,8 +57,3 @@
<span style="font-weight: 900" class="helvetica">Helvetica Bold</span>
<span style="font-weight: 900" class="helvetica">Helvetica Bold</span>
</div>
<p>Times followed by Ahem (normal):</p>
<div>
Times <i class="ahem" style="font-style: normal;">AHEM</i>
</div>
......@@ -25,12 +25,6 @@
src: local(Papyrus), local(Times), local('Times New Roman');
}
@font-face {
font-family: webkit-two;
font-style: italic;
src: url(../../resources/Ahem.ttf);
}
@font-face {
font-family: webkit-six;
font-weight: 400;
......@@ -118,8 +112,3 @@
<span style="font-weight: 800">Helvetica Bold</span>
<span style="font-weight: 900">Helvetica Bold</span>
</div>
<p>Times followed by Ahem (normal):</p>
<div style="font-family: webkit-two;">
Times <i>AHEM</i>
</div>
......@@ -1133,6 +1133,8 @@
'css/FontLoader.h',
'css/FontSize.cpp',
'css/FontSize.h',
'css/FontStyleMatcher.cpp',
'css/FontStyleMatcher.h'
'css/HashTools.h',
'css/KeyframeStyleRuleCSSStyleDeclaration.cpp',
'css/KeyframeStyleRuleCSSStyleDeclaration.h',
......
......@@ -55,6 +55,7 @@ PassRefPtr<SimpleFontData> CSSFontFaceSource::getFontData(const FontDescription&
}
// See if we have a mapping in our FontData cache.
// TODO(drott): Check whether losing traits information here is problematic. crbug.com/516677
FontCacheKey key = fontDescription.cacheKey(FontFaceCreationParams());
RefPtr<SimpleFontData>& fontData = m_fontDataTable.add(key.hash(), nullptr).storedValue->value;
......
......@@ -3232,6 +3232,9 @@ template<> inline CSSPrimitiveValue::CSSPrimitiveValue(FontStyle italic)
case FontStyleNormal:
m_value.valueID = CSSValueNormal;
return;
case FontStyleOblique:
m_value.valueID = CSSValueOblique;
return;
case FontStyleItalic:
m_value.valueID = CSSValueItalic;
return;
......@@ -3246,7 +3249,7 @@ template<> inline CSSPrimitiveValue::operator FontStyle() const
ASSERT(isValueID());
switch (m_value.valueID) {
case CSSValueOblique:
// FIXME: oblique is the same as italic for the moment...
return FontStyleOblique;
case CSSValueItalic:
return FontStyleItalic;
case CSSValueNormal:
......
......@@ -150,7 +150,7 @@ FontFace::FontFace(ExecutionContext* context, const AtomicString& family, const
Document* document = toDocument(context);
setPropertyFromString(document, descriptors.style(), CSSPropertyFontStyle);
setPropertyFromString(document, descriptors.weight(), CSSPropertyFontWeight);
// FIXME: we don't implement 'font-strech' property yet so we can't set the property.
setPropertyFromString(document, descriptors.stretch(), CSSPropertyFontStretch);
setPropertyFromString(document, descriptors.unicodeRange(), CSSPropertyUnicodeRange);
setPropertyFromString(document, descriptors.variant(), CSSPropertyFontVariant);
setPropertyFromString(document, descriptors.featureSettings(), CSSPropertyWebkitFontFeatureSettings);
......@@ -398,6 +398,41 @@ void FontFace::loadInternal(ExecutionContext* context)
FontTraits FontFace::traits() const
{
FontStretch stretch = FontStretchNormal;
if (m_stretch) {
if (!m_stretch->isPrimitiveValue())
return 0;
switch (toCSSPrimitiveValue(m_stretch.get())->getValueID()) {
case CSSValueUltraCondensed:
stretch = FontStretchUltraCondensed;
break;
case CSSValueExtraCondensed:
stretch = FontStretchExtraCondensed;
break;
case CSSValueCondensed:
stretch = FontStretchCondensed;
break;
case CSSValueSemiCondensed:
stretch = FontStretchSemiCondensed;
break;
case CSSValueSemiExpanded:
stretch = FontStretchSemiExpanded;
break;
case CSSValueExpanded:
stretch = FontStretchExpanded;
break;
case CSSValueExtraExpanded:
stretch = FontStretchExtraExpanded;
break;
case CSSValueUltraExpanded:
stretch = FontStretchUltraExpanded;
break;
default:
break;
}
}
FontStyle style = FontStyleNormal;
if (m_style) {
if (!m_style->isPrimitiveValue())
......@@ -407,8 +442,10 @@ FontTraits FontFace::traits() const
case CSSValueNormal:
style = FontStyleNormal;
break;
case CSSValueItalic:
case CSSValueOblique:
style = FontStyleOblique;
break;
case CSSValueItalic:
style = FontStyleItalic;
break;
default:
......@@ -492,7 +529,7 @@ FontTraits FontFace::traits() const
}
}
return FontTraits(style, variant, weight, FontStretchNormal);
return FontTraits(style, variant, weight, stretch);
}
static PassOwnPtrWillBeRawPtr<CSSFontFace> createCSSFontFace(FontFace* fontFace, CSSValue* unicodeRange)
......
......@@ -32,6 +32,7 @@
#include "core/css/CSSSegmentedFontFace.h"
#include "core/css/CSSValueList.h"
#include "core/css/FontFace.h"
#include "core/css/FontStyleMatcher.h"
#include "core/css/StyleRule.h"
#include "core/fetch/FontResource.h"
#include "core/fetch/ResourceFetcher.h"
......@@ -39,6 +40,7 @@
#include "platform/fonts/FontDescription.h"
#include "wtf/text/AtomicString.h"
namespace blink {
FontFaceCache::FontFaceCache()
......@@ -127,79 +129,6 @@ void FontFaceCache::clearAll()
++m_version;
}
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;
}
CSSSegmentedFontFace* FontFaceCache::get(const FontDescription& fontDescription, const AtomicString& family)
{
TraitsMap* familyFontFaces = m_fontFaces.get(family);
......@@ -215,12 +144,8 @@ CSSSegmentedFontFace* FontFaceCache::get(const FontDescription& fontDescription,
if (!faceResult.storedValue->value) {
for (const auto& item : *familyFontFaces) {
CSSSegmentedFontFace* candidate = item.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))
FontStyleMatcher styleMatcher(traits);
if (!faceResult.storedValue->value || styleMatcher.isCandidateBetter(candidate, faceResult.storedValue->value.get()))
faceResult.storedValue->value = candidate;
}
}
......
/*
* Copyright (C) 2007, 2008, 2011 Apple Inc. All rights reserved.
* Copyright (C) 2015 Google Inc. All rights reserved.
*
* 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 "core/css/FontStyleMatcher.h"
#include "core/css/CSSSegmentedFontFace.h"
#include "wtf/Assertions.h"
#include <stdlib.h>
namespace blink {
static inline unsigned stretchDistanceToDesired(FontTraits desired, FontTraits candidate)
{
return abs(static_cast<int>(desired.stretch() - candidate.stretch()));
}
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()];
}
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];
}
bool FontStyleMatcher::isCandidateBetter(CSSSegmentedFontFace *candidate, CSSSegmentedFontFace *current)
{
const FontTraits& candidateTraits = candidate->traits();
const FontTraits& currentTraits = current->traits();
// According to CSS3 Fonts Font Style matching, there is a precedence for matching:
// A better stretch match wins over a better style match, a better style match
// wins over a better weight match, where "better" means closer to the desired
// traits.
int stretchComparison = 0, styleComparison = 0, weightComparison = 0;
stretchComparison = stretchDistanceToDesired(m_fontTraits, candidateTraits) -
stretchDistanceToDesired(m_fontTraits, currentTraits);
if (stretchComparison > 0)
return false;
if (stretchComparison < 0)
return true;
styleComparison = styleScore(m_fontTraits, candidateTraits) -
styleScore(m_fontTraits, currentTraits);
if (styleComparison > 0)
return true;
if (styleComparison < 0)
return false;
weightComparison = weightScore(m_fontTraits, candidateTraits) -
weightScore(m_fontTraits, currentTraits);
if (weightComparison > 0)
return true;
return false;
}
}
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FontStyleMatcher_h
#define FontStyleMatcher_h
#include "platform/fonts/FontTraits.h"
namespace blink {
class CSSSegmentedFontFace;
class FontStyleMatcher final {
public:
explicit FontStyleMatcher(const FontTraits& fontTraits) : m_fontTraits(fontTraits) {};
bool isCandidateBetter(CSSSegmentedFontFace* candidate, CSSSegmentedFontFace* current);
private:
FontStyleMatcher();
const FontTraits& m_fontTraits;
};
}
#endif
......@@ -242,7 +242,7 @@ private:
unsigned m_widthVariant : 2; // FontWidthVariant
unsigned m_style : 1; // FontStyle
unsigned m_style : 2; // FontStyle
unsigned m_variant : 1; // FontVariant
unsigned m_isAbsoluteSize : 1; // Whether or not CSS specified an explicit size
// (logical sizes like "medium" don't count).
......
......@@ -32,15 +32,15 @@
namespace blink {
enum FontWeight {
FontWeight100,
FontWeight200,
FontWeight300,
FontWeight400,
FontWeight500,
FontWeight600,
FontWeight700,
FontWeight800,
FontWeight900,
FontWeight100 = 0,
FontWeight200 = 1,
FontWeight300 = 2,
FontWeight400 = 3,
FontWeight500 = 4,
FontWeight600 = 5,
FontWeight700 = 6,
FontWeight800 = 7,
FontWeight900 = 8,
FontWeightNormal = FontWeight400,
FontWeightBold = FontWeight700
};
......@@ -61,7 +61,8 @@ enum FontStretch {
enum FontStyle {
FontStyleNormal = 0,
FontStyleItalic = 1
FontStyleOblique = 1,
FontStyleItalic = 2
};
enum FontVariant {
......@@ -75,17 +76,19 @@ struct FontTraits {
FontTraits(FontStyle style, FontVariant variant, FontWeight weight, FontStretch stretch)
{
m_traits.m_style = style;
// TODO(drott): crbug.com/516673 Variant is not relevant for font selection,
// should be removed here.
m_traits.m_variant = variant;
m_traits.m_weight = weight;
m_traits.m_stretch = stretch;
m_traits.m_filler = 0;
ASSERT(!(m_bitfield >> 10));
ASSERT(!(m_bitfield >> 11));
}
FontTraits(FontTraitsBitfield bitfield)
: m_bitfield(bitfield)
{
ASSERT(!m_traits.m_filler);
ASSERT(!(m_bitfield >> 10));
ASSERT(!(m_bitfield >> 11));
}
FontStyle style() const { return static_cast<FontStyle>(m_traits.m_style); }
FontVariant variant() const { return static_cast<FontVariant>(m_traits.m_variant); }
......@@ -95,11 +98,11 @@ struct FontTraits {
union {
struct {
unsigned m_style : 1;
unsigned m_style : 2;
unsigned m_variant : 1;
unsigned m_weight : 4;
unsigned m_stretch : 4;
unsigned m_filler : 22;
unsigned m_filler : 21;
} m_traits;
FontTraitsBitfield m_bitfield;
};
......
......@@ -76,7 +76,7 @@ PassRefPtr<SimpleFontData> FontCache::fallbackOnStandardFontStyle(
if (substitutePlatformData && substitutePlatformData->fontContainsCharacter(character)) {
FontPlatformData platformData = FontPlatformData(*substitutePlatformData);
platformData.setSyntheticBold(fontDescription.weight() >= FontWeight600);
platformData.setSyntheticItalic(fontDescription.style() == FontStyleItalic);
platformData.setSyntheticItalic(fontDescription.style() == FontStyleItalic || fontDescription.style() == FontStyleOblique);
return fontDataFromFontPlatformData(&platformData, DoNotRetain);
}
......@@ -117,7 +117,7 @@ PassRefPtr<SimpleFontData> FontCache::fallbackFontForCharacter(const FontDescrip
}
if (fallbackFont.isItalic && description.style() == FontStyleNormal)
description.setStyle(FontStyleItalic);
if (!fallbackFont.isItalic && description.style() == FontStyleItalic) {
if (!fallbackFont.isItalic && (description.style() == FontStyleItalic || description.style() == FontStyleOblique)) {
shouldSetSyntheticItalic = true;
description.setStyle(FontStyleNormal);
}
......@@ -248,7 +248,7 @@ FontPlatformData* FontCache::createFontPlatformData(const FontDescription& fontD
name.data(),
fontSize,
(fontDescription.weight() >= FontWeight600 && !tf->isBold()) || fontDescription.isSyntheticBold(),
(fontDescription.style() && !tf->isItalic()) || fontDescription.isSyntheticItalic(),
((fontDescription.style() == FontStyleItalic || fontDescription.style() == FontStyleOblique) && !tf->isItalic()) || fontDescription.isSyntheticItalic(),
fontDescription.orientation(),
fontDescription.useSubpixelPositioning());
return result;
......
......@@ -372,7 +372,7 @@ FontPlatformData* FontCache::createFontPlatformData(const FontDescription& fontD
name.data(),
fontSize,
(fontDescription.weight() >= FontWeight600 && !tf->isBold()) || fontDescription.isSyntheticBold(),
(fontDescription.style() == FontStyleItalic && !tf->isItalic()) || fontDescription.isSyntheticItalic(),
((fontDescription.style() == FontStyleItalic || fontDescription.style() == FontStyleOblique) && !tf->isItalic()) || fontDescription.isSyntheticItalic(),
fontDescription.orientation(),
s_useSubpixelPositioning);
......
......@@ -68,11 +68,15 @@ const char* fontVariantToString(FontVariant variant)
return nullptr;
}
// TODO crbug.com/516675 Add stretch to serialization
const char* fontStyleToString(FontStyle style)
{
switch (style) {
case FontStyleNormal:
return "normal";
case FontStyleOblique:
return "oblique";
case FontStyleItalic:
return "italic";
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment