Update CSSFontSelector's matching algorithm to understand ranges
authormmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 5 Mar 2017 20:14:02 +0000 (20:14 +0000)
committermmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 5 Mar 2017 20:14:02 +0000 (20:14 +0000)
https://bugs.webkit.org/show_bug.cgi?id=168892

Reviewed by Jon Lee.

Source/WebCore:

This patch migrates the font selection algorithm out of FontCacheCoreText and into its own file which can be shared
among all ports. It then migrates our web font selection algorithm to use it.

This patch doesn't actually change the parsing rules; it just changes the internal machinery for how fonts get
selected. Therefore, this patch simply makes zero-length ranges from the discrete values the parser emits, and passes
those zero-length ranges to the range-based font selection routine. This means that this patch doesn't actually
change the results of the font selection algorithm.

One of the inputs to the new font selection algorithm is font-stretch, which previously was not parsed inside
@font-face blocks or the CSS Font Loading API. This patch therefore adds parsing support and modifies the existing
tests for these pieces to expect parsing to work. Because the font selection algorithm itself is shared between
installed fonts and webfonts, this patch doesn't add any additional tests for it (because it is already covered under
existing tests).

No new tests because there is no behavior change.

* CMakeLists.txt:
* WebCore.xcodeproj/project.pbxproj: Add new file for the font selection algorithm.
* css/CSSFontFace.cpp:
(WebCore::CSSFontFace::calculateStretch): Used on @font-face blocks and the CSS Font Loading API.
(WebCore::CSSFontFace::setStretch): Fill out the previously stubbed function.
* css/CSSFontFace.h: Add the range member variable to CSSFontFaces.
* css/CSSFontFaceSet.cpp:
(WebCore::CSSFontFaceSet::ensureLocalFontFacesForFamilyRegistered): Now that we care about font-stretch, we need to
look it up for local fonts too. This current approach of an extra FontSelectionValue hanging around with a
FontTraitsMask is very ugly and will be removed soon (https://bugs.webkit.org/show_bug.cgi?id=168889 and
https://bugs.webkit.org/show_bug.cgi?id=168890).
(WebCore::computeFontStretch): Used on @font-face blocks and the CSS Font Loading API.
(WebCore::CSSFontFaceSet::matchingFaces): Educate about font-stretch.
(WebCore::CSSFontFaceSet::fontFace): Migrate to the new font-selection algorithm.
(WebCore::fontFaceComparator): Deleted.
* css/CSSFontFaceSet.h:
* css/CSSFontSelector.cpp:
(WebCore::CSSFontSelector::addFontFaceRule): Educate about font-stretch.
(WebCore::CSSFontSelector::fontRangesForFamily): Ditto.
* css/FontFace.cpp:
(WebCore::FontFace::setStretch): Ditto.
(WebCore::FontFace::stretch): Ditto.
* css/parser/CSSPropertyParser.cpp:
(WebCore::CSSPropertyParser::parseFontFaceDescriptor): Ditto.
* platform/graphics/FontCache.h: Ditto.
* platform/graphics/FontDescription.h:
* platform/graphics/FontSelectionAlgorithm.cpp: Added.
(WebCore::FontSelectionAlgorithm::filterCapability):
(WebCore::FontSelectionAlgorithm::indexOfBestCapabilities):
(WebCore::fontSelectionRequestForTraitsMask):
(WebCore::initialFontSelectionCapabilitiesForTraitsMask):
(WebCore::fontSelectionCapabilitiesForTraitsMask):
* platform/graphics/FontSelectionAlgorithm.h: Added. Taken from FontCacheCoreText.cpp.
(WebCore::FontSelectionValue::FontSelectionValue):
(WebCore::FontSelectionValue::operator float):
(WebCore::FontSelectionValue::rawValue):
(WebCore::FontSelectionValue::maximumValue):
(WebCore::FontSelectionValue::minimumValue):
(WebCore::FontSelectionValue::operator+):
(WebCore::FontSelectionValue::operator-):
(WebCore::FontSelectionValue::operator*):
(WebCore::FontSelectionValue::operator/):
(WebCore::FontSelectionValue::operator==):
(WebCore::FontSelectionValue::operator!=):
(WebCore::FontSelectionValue::operator<):
(WebCore::FontSelectionValue::operator<=):
(WebCore::FontSelectionValue::operator>):
(WebCore::FontSelectionValue::operator>=):
(WebCore::FontSelectionRange::isValid):
(WebCore::FontSelectionRange::expand):
(WebCore::FontSelectionRange::includes):
(WebCore::FontSelectionRequest::operator==):
(WebCore::FontSelectionRequest::operator!=):
(WebCore::FontSelectionRequestKey::FontSelectionRequestKey):
(WebCore::FontSelectionRequestKey::isHashTableDeletedValue):
(WebCore::FontSelectionRequestKey::operator==):
(WebCore::FontSelectionRequestKeyHash::hash):
(WebCore::FontSelectionRequestKeyHash::equal):
(WebCore::FontSelectionCapabilities::expand):
(WebCore::FontSelectionAlgorithm::FontSelectionAlgorithm):
(WebCore::FontSelectionAlgorithm::iterateActiveCapabilitiesWithReturn):
(WebCore::FontSelectionAlgorithm::iterateActiveCapabilities):
* platform/graphics/cocoa/FontCacheCoreText.cpp: Moved to FontSelectionAlgorithm.
(WebCore::stretchFromCoreTextTraits):
(WebCore::FontDatabase::capabilitiesForFontDescriptor):
(WebCore::findClosestFont):
(WebCore::calculateFontSelectionRequest):
(WebCore::platformFontLookupWithFamily):
(WebCore::FontCache::getTraitsInFamily): Deleted.
(WebCore::iterateActiveFontsWithReturn): Deleted.
(WebCore::iterateActiveFonts): Deleted.
(WebCore::findClosestStretch): Deleted.
(WebCore::filterStretch): Deleted.
(WebCore::findClosestStyle): Deleted.
(WebCore::filterStyle): Deleted.
(WebCore::findClosestWeight): Deleted.
(WebCore::filterWeight): Deleted.
(WebCore::computeTargetWeight): Deleted.
* platform/text/TextFlags.h: Moved to FontSelectionAlgorithm.
(WebCore::FontSelectionValue::FontSelectionValue): Deleted.
(WebCore::FontSelectionValue::operator float): Deleted.
(WebCore::FontSelectionValue::operator+): Deleted.
(WebCore::FontSelectionValue::operator-): Deleted.
(WebCore::FontSelectionValue::operator*): Deleted.
(WebCore::FontSelectionValue::operator/): Deleted.
(WebCore::FontSelectionValue::operator==): Deleted.
(WebCore::FontSelectionValue::operator!=): Deleted.
(WebCore::FontSelectionValue::operator<): Deleted.
(WebCore::FontSelectionValue::operator<=): Deleted.
(WebCore::FontSelectionValue::operator>): Deleted.
(WebCore::FontSelectionValue::operator>=): Deleted.
(WebCore::FontSelectionValue::rawValue): Deleted.
(WebCore::FontSelectionValue::maximumValue): Deleted.
(WebCore::FontSelectionValue::minimumValue): Deleted.
(WebCore::FontSelectionRange::isValid): Deleted.
(WebCore::FontSelectionRange::expand): Deleted.
(WebCore::FontSelectionRange::includes): Deleted.
(WebCore::FontSelectionCapabilities::expand): Deleted.

LayoutTests:

Update CSS Font Loading API test to accept font-stretch values.

* fast/text/font-face-javascript-expected.txt:
* fast/text/font-face-javascript.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@213436 268f45cc-cd09-0410-ab3c-d52691b4dbfc

21 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/text/font-face-javascript-expected.txt
LayoutTests/fast/text/font-face-javascript.html
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/css/CSSFontFace.cpp
Source/WebCore/css/CSSFontFace.h
Source/WebCore/css/CSSFontFaceSet.cpp
Source/WebCore/css/CSSFontFaceSet.h
Source/WebCore/css/CSSFontSelector.cpp
Source/WebCore/css/FontFace.cpp
Source/WebCore/css/parser/CSSPropertyParser.cpp
Source/WebCore/platform/graphics/FontCache.h
Source/WebCore/platform/graphics/FontDescription.h
Source/WebCore/platform/graphics/FontSelectionAlgorithm.cpp [new file with mode: 0644]
Source/WebCore/platform/graphics/FontSelectionAlgorithm.h [new file with mode: 0644]
Source/WebCore/platform/graphics/cocoa/FontCacheCoreText.cpp
Source/WebCore/platform/graphics/freetype/FontCacheFreeType.cpp
Source/WebCore/platform/graphics/win/FontCacheWin.cpp
Source/WebCore/platform/text/TextFlags.h

index 618675c..5fa0140 100644 (file)
@@ -1,3 +1,15 @@
+2017-03-04  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        Update CSSFontSelector's matching algorithm to understand ranges
+        https://bugs.webkit.org/show_bug.cgi?id=168892
+
+        Reviewed by Jon Lee.
+
+        Update CSS Font Loading API test to accept font-stretch values.
+
+        * fast/text/font-face-javascript-expected.txt:
+        * fast/text/font-face-javascript.html:
+
 2017-03-05  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         [GTK] Two file reset tests are failing in the bots since they were added in r213042
index 58e33c7..a6f3280 100644 (file)
@@ -16,7 +16,7 @@ PASS new FontFace('family_name', 'url(\'asdf\')', {'weight': 'bold'}).weight is
 PASS new FontFace('family_name', 'url(\'asdf\')', {'weight': 'bolder'}).weight is "bold"
 PASS new FontFace('family_name', 'url(\'asdf\')', {'weight': 'lighter'}).weight is "200"
 PASS new FontFace('family_name', 'url(\'asdf\')', {'weight': 'inherit'}).weight threw exception SyntaxError (DOM Exception 12): The string did not match the expected pattern..
-PASS new FontFace('family_name', 'url(\'asdf\')', {'stretch': 'stretch_name'}).stretch is "normal"
+PASS new FontFace('family_name', 'url(\'asdf\')', {'stretch': 'ultra-expanded'}).stretch is "ultra-expanded"
 PASS new FontFace('family_name', 'url(\'asdf\')', {'unicodeRange': 'U+26'}).unicodeRange is "U+26-26"
 PASS new FontFace('family_name', 'url(\'asdf\')', {'unicodeRange': 'U+0-7F'}).unicodeRange is "U+0-7f"
 PASS new FontFace('family_name', 'url(\'asdf\')', {'variant': 'variant_name'}).variant threw exception SyntaxError (DOM Exception 12): The string did not match the expected pattern..
@@ -25,14 +25,14 @@ PASS new FontFace('family_name', 'url(\'asdf\')', {'variant': 'small-caps common
 PASS new FontFace('family_name', 'url(\'asdf\')', {'featureSettings': '\'titl\''}).featureSettings is "'titl' 1"
 PASS everything.style is "italic"
 PASS everything.weight is "bold"
-PASS everything.stretch is "normal"
+PASS everything.stretch is "extra-expanded"
 PASS everything.unicodeRange is "U+26-26"
 PASS everything.variant is "small-caps"
 PASS everything.featureSettings is "'titl' 1"
 PASS everything.family is "other_family_name"
 PASS everything.style is "normal"
 PASS everything.weight is "300"
-PASS everything.stretch is "normal"
+PASS everything.stretch is "condensed"
 PASS everything.unicodeRange is "U+0-7f"
 PASS everything.variant is "stacked-fractions"
 PASS everything.featureSettings is "'smcp' 1"
index 0e6c7b7..46f547c 100644 (file)
@@ -24,7 +24,7 @@ shouldBeEqualToString("new FontFace('family_name', 'url(\\'asdf\\')', {'weight':
 shouldBeEqualToString("new FontFace('family_name', 'url(\\'asdf\\')', {'weight': 'bolder'}).weight", "bold");
 shouldBeEqualToString("new FontFace('family_name', 'url(\\'asdf\\')', {'weight': 'lighter'}).weight", "200");
 shouldThrow("new FontFace('family_name', 'url(\\'asdf\\')', {'weight': 'inherit'}).weight");
-shouldBeEqualToString("new FontFace('family_name', 'url(\\'asdf\\')', {'stretch': 'stretch_name'}).stretch", "normal");
+shouldBeEqualToString("new FontFace('family_name', 'url(\\'asdf\\')', {'stretch': 'ultra-expanded'}).stretch", "ultra-expanded");
 shouldBeEqualToString("new FontFace('family_name', 'url(\\'asdf\\')', {'unicodeRange': 'U+26'}).unicodeRange", "U+26-26");
 shouldBeEqualToString("new FontFace('family_name', 'url(\\'asdf\\')', {'unicodeRange': 'U+0-7F'}).unicodeRange", "U+0-7f");
 shouldThrow("new FontFace('family_name', 'url(\\'asdf\\')', {'variant': 'variant_name'}).variant");
@@ -32,10 +32,10 @@ shouldBeEqualToString("new FontFace('family_name', 'url(\\'asdf\\')', {'variant'
 shouldBeEqualToString("new FontFace('family_name', 'url(\\'asdf\\')', {'variant': 'small-caps common-ligatures'}).variant", "common-ligatures small-caps");
 shouldBeEqualToString("new FontFace('family_name', 'url(\\'asdf\\')', {'featureSettings': '\\'titl\\''}).featureSettings", "'titl' 1");
 
-var everything = new FontFace('family_name', 'url(\'asdf\')', {'style': 'italic', 'weight': 'bold', 'stretch': 'stretch_name', 'unicodeRange': 'U+26', 'variant': 'small-caps', 'featureSettings': '\'titl\''});
+var everything = new FontFace('family_name', 'url(\'asdf\')', {'style': 'italic', 'weight': 'bold', 'stretch': 'extra-expanded', 'unicodeRange': 'U+26', 'variant': 'small-caps', 'featureSettings': '\'titl\''});
 shouldBeEqualToString("everything.style", "italic");
 shouldBeEqualToString("everything.weight", "bold");
-shouldBeEqualToString("everything.stretch", "normal");
+shouldBeEqualToString("everything.stretch", "extra-expanded");
 shouldBeEqualToString("everything.unicodeRange", "U+26-26");
 shouldBeEqualToString("everything.variant", "small-caps");
 shouldBeEqualToString("everything.featureSettings", "'titl' 1");
@@ -46,8 +46,8 @@ everything.style = "normal";
 shouldBeEqualToString("everything.style", "normal");
 everything.weight = "300";
 shouldBeEqualToString("everything.weight", "300");
-everything.stretch = "other_stretch_name";
-shouldBeEqualToString("everything.stretch", "normal");
+everything.stretch = "condensed";
+shouldBeEqualToString("everything.stretch", "condensed");
 everything.unicodeRange = "U+0-7F";
 shouldBeEqualToString("everything.unicodeRange", "U+0-7f");
 everything.variant = "stacked-fractions";
index b44b204..2aa9905 100644 (file)
@@ -2200,6 +2200,7 @@ set(WebCore_SOURCES
     platform/graphics/FontCascade.cpp
     platform/graphics/FontCascadeFonts.cpp
     platform/graphics/FontDescription.cpp
+    platform/graphics/FontSelectionAlgorithm.cpp
     platform/graphics/FontTaggedSettings.cpp
     platform/graphics/FontGenericFamilies.cpp
     platform/graphics/FontPlatformData.cpp
index eaf9c89..8af4ec8 100644 (file)
@@ -1,3 +1,125 @@
+2017-03-04  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        Update CSSFontSelector's matching algorithm to understand ranges
+        https://bugs.webkit.org/show_bug.cgi?id=168892
+
+        Reviewed by Jon Lee.
+
+        This patch migrates the font selection algorithm out of FontCacheCoreText and into its own file which can be shared
+        among all ports. It then migrates our web font selection algorithm to use it.
+
+        This patch doesn't actually change the parsing rules; it just changes the internal machinery for how fonts get
+        selected. Therefore, this patch simply makes zero-length ranges from the discrete values the parser emits, and passes
+        those zero-length ranges to the range-based font selection routine. This means that this patch doesn't actually
+        change the results of the font selection algorithm.
+
+        One of the inputs to the new font selection algorithm is font-stretch, which previously was not parsed inside
+        @font-face blocks or the CSS Font Loading API. This patch therefore adds parsing support and modifies the existing
+        tests for these pieces to expect parsing to work. Because the font selection algorithm itself is shared between
+        installed fonts and webfonts, this patch doesn't add any additional tests for it (because it is already covered under
+        existing tests).
+
+        No new tests because there is no behavior change.
+
+        * CMakeLists.txt:
+        * WebCore.xcodeproj/project.pbxproj: Add new file for the font selection algorithm.
+        * css/CSSFontFace.cpp:
+        (WebCore::CSSFontFace::calculateStretch): Used on @font-face blocks and the CSS Font Loading API.
+        (WebCore::CSSFontFace::setStretch): Fill out the previously stubbed function.
+        * css/CSSFontFace.h: Add the range member variable to CSSFontFaces.
+        * css/CSSFontFaceSet.cpp:
+        (WebCore::CSSFontFaceSet::ensureLocalFontFacesForFamilyRegistered): Now that we care about font-stretch, we need to
+        look it up for local fonts too. This current approach of an extra FontSelectionValue hanging around with a
+        FontTraitsMask is very ugly and will be removed soon (https://bugs.webkit.org/show_bug.cgi?id=168889 and
+        https://bugs.webkit.org/show_bug.cgi?id=168890).
+        (WebCore::computeFontStretch): Used on @font-face blocks and the CSS Font Loading API.
+        (WebCore::CSSFontFaceSet::matchingFaces): Educate about font-stretch.
+        (WebCore::CSSFontFaceSet::fontFace): Migrate to the new font-selection algorithm.
+        (WebCore::fontFaceComparator): Deleted.
+        * css/CSSFontFaceSet.h:
+        * css/CSSFontSelector.cpp:
+        (WebCore::CSSFontSelector::addFontFaceRule): Educate about font-stretch.
+        (WebCore::CSSFontSelector::fontRangesForFamily): Ditto.
+        * css/FontFace.cpp:
+        (WebCore::FontFace::setStretch): Ditto.
+        (WebCore::FontFace::stretch): Ditto.
+        * css/parser/CSSPropertyParser.cpp:
+        (WebCore::CSSPropertyParser::parseFontFaceDescriptor): Ditto.
+        * platform/graphics/FontCache.h: Ditto.
+        * platform/graphics/FontDescription.h:
+        * platform/graphics/FontSelectionAlgorithm.cpp: Added.
+        (WebCore::FontSelectionAlgorithm::filterCapability):
+        (WebCore::FontSelectionAlgorithm::indexOfBestCapabilities):
+        (WebCore::fontSelectionRequestForTraitsMask):
+        (WebCore::initialFontSelectionCapabilitiesForTraitsMask):
+        (WebCore::fontSelectionCapabilitiesForTraitsMask):
+        * platform/graphics/FontSelectionAlgorithm.h: Added. Taken from FontCacheCoreText.cpp.
+        (WebCore::FontSelectionValue::FontSelectionValue):
+        (WebCore::FontSelectionValue::operator float):
+        (WebCore::FontSelectionValue::rawValue):
+        (WebCore::FontSelectionValue::maximumValue):
+        (WebCore::FontSelectionValue::minimumValue):
+        (WebCore::FontSelectionValue::operator+):
+        (WebCore::FontSelectionValue::operator-):
+        (WebCore::FontSelectionValue::operator*):
+        (WebCore::FontSelectionValue::operator/):
+        (WebCore::FontSelectionValue::operator==):
+        (WebCore::FontSelectionValue::operator!=):
+        (WebCore::FontSelectionValue::operator<):
+        (WebCore::FontSelectionValue::operator<=):
+        (WebCore::FontSelectionValue::operator>):
+        (WebCore::FontSelectionValue::operator>=):
+        (WebCore::FontSelectionRange::isValid):
+        (WebCore::FontSelectionRange::expand):
+        (WebCore::FontSelectionRange::includes):
+        (WebCore::FontSelectionRequest::operator==):
+        (WebCore::FontSelectionRequest::operator!=):
+        (WebCore::FontSelectionRequestKey::FontSelectionRequestKey):
+        (WebCore::FontSelectionRequestKey::isHashTableDeletedValue):
+        (WebCore::FontSelectionRequestKey::operator==):
+        (WebCore::FontSelectionRequestKeyHash::hash):
+        (WebCore::FontSelectionRequestKeyHash::equal):
+        (WebCore::FontSelectionCapabilities::expand):
+        (WebCore::FontSelectionAlgorithm::FontSelectionAlgorithm):
+        (WebCore::FontSelectionAlgorithm::iterateActiveCapabilitiesWithReturn):
+        (WebCore::FontSelectionAlgorithm::iterateActiveCapabilities):
+        * platform/graphics/cocoa/FontCacheCoreText.cpp: Moved to FontSelectionAlgorithm.
+        (WebCore::stretchFromCoreTextTraits):
+        (WebCore::FontDatabase::capabilitiesForFontDescriptor):
+        (WebCore::findClosestFont):
+        (WebCore::calculateFontSelectionRequest):
+        (WebCore::platformFontLookupWithFamily):
+        (WebCore::FontCache::getTraitsInFamily): Deleted.
+        (WebCore::iterateActiveFontsWithReturn): Deleted.
+        (WebCore::iterateActiveFonts): Deleted.
+        (WebCore::findClosestStretch): Deleted.
+        (WebCore::filterStretch): Deleted.
+        (WebCore::findClosestStyle): Deleted.
+        (WebCore::filterStyle): Deleted.
+        (WebCore::findClosestWeight): Deleted.
+        (WebCore::filterWeight): Deleted.
+        (WebCore::computeTargetWeight): Deleted.
+        * platform/text/TextFlags.h: Moved to FontSelectionAlgorithm.
+        (WebCore::FontSelectionValue::FontSelectionValue): Deleted.
+        (WebCore::FontSelectionValue::operator float): Deleted.
+        (WebCore::FontSelectionValue::operator+): Deleted.
+        (WebCore::FontSelectionValue::operator-): Deleted.
+        (WebCore::FontSelectionValue::operator*): Deleted.
+        (WebCore::FontSelectionValue::operator/): Deleted.
+        (WebCore::FontSelectionValue::operator==): Deleted.
+        (WebCore::FontSelectionValue::operator!=): Deleted.
+        (WebCore::FontSelectionValue::operator<): Deleted.
+        (WebCore::FontSelectionValue::operator<=): Deleted.
+        (WebCore::FontSelectionValue::operator>): Deleted.
+        (WebCore::FontSelectionValue::operator>=): Deleted.
+        (WebCore::FontSelectionValue::rawValue): Deleted.
+        (WebCore::FontSelectionValue::maximumValue): Deleted.
+        (WebCore::FontSelectionValue::minimumValue): Deleted.
+        (WebCore::FontSelectionRange::isValid): Deleted.
+        (WebCore::FontSelectionRange::expand): Deleted.
+        (WebCore::FontSelectionRange::includes): Deleted.
+        (WebCore::FontSelectionCapabilities::expand): Deleted.
+
 2017-03-05  Simon Fraser  <simon.fraser@apple.com>
 
         Make some RenderLayer tree traversal in RenderLayerBacking more generic
index ed3f4a7..b0e668d 100644 (file)
                C280833F1C6DC26F001451B6 /* JSFontFace.h in Headers */ = {isa = PBXBuildFile; fileRef = C280833E1C6DC22C001451B6 /* JSFontFace.h */; };
                C28083401C6DC275001451B6 /* JSFontFace.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C280833D1C6DC22C001451B6 /* JSFontFace.cpp */; };
                C28083421C6DC96A001451B6 /* JSFontFaceCustom.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C28083411C6DC96A001451B6 /* JSFontFaceCustom.cpp */; };
+               C2AB0AF61E6B3C6C001348C5 /* FontSelectionAlgorithm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2AB0AF41E6B3C6C001348C5 /* FontSelectionAlgorithm.cpp */; };
+               C2AB0AF71E6B3C6C001348C5 /* FontSelectionAlgorithm.h in Headers */ = {isa = PBXBuildFile; fileRef = C2AB0AF51E6B3C6C001348C5 /* FontSelectionAlgorithm.h */; settings = {ATTRIBUTES = (Private, ); }; };
                C2E1F43F1D6254E10094625C /* BreakLines.h in Headers */ = {isa = PBXBuildFile; fileRef = BCEA4816097D93020094C9E4 /* BreakLines.h */; settings = {ATTRIBUTES = (Private, ); }; };
                C2F4E78A1E45BEA1006D7105 /* ComplexTextController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2F4E7881E45AEDF006D7105 /* ComplexTextController.cpp */; };
                C2F4E78C1E45C3EF006D7105 /* ComplexTextController.h in Headers */ = {isa = PBXBuildFile; fileRef = C2F4E7891E45AEDF006D7105 /* ComplexTextController.h */; settings = {ATTRIBUTES = (Private, ); }; };
                C280833D1C6DC22C001451B6 /* JSFontFace.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSFontFace.cpp; sourceTree = "<group>"; };
                C280833E1C6DC22C001451B6 /* JSFontFace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSFontFace.h; sourceTree = "<group>"; };
                C28083411C6DC96A001451B6 /* JSFontFaceCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSFontFaceCustom.cpp; sourceTree = "<group>"; };
+               C2AB0AF41E6B3C6C001348C5 /* FontSelectionAlgorithm.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FontSelectionAlgorithm.cpp; sourceTree = "<group>"; };
+               C2AB0AF51E6B3C6C001348C5 /* FontSelectionAlgorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FontSelectionAlgorithm.h; sourceTree = "<group>"; };
                C2F4E7881E45AEDF006D7105 /* ComplexTextController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ComplexTextController.cpp; sourceTree = "<group>"; };
                C2F4E7891E45AEDF006D7105 /* ComplexTextController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ComplexTextController.h; sourceTree = "<group>"; };
                C330A22113EC196B0000B45B /* ColorChooser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ColorChooser.h; sourceTree = "<group>"; };
                                501BAAA813950E2C00F7ACEB /* WindRule.h */,
                                379919941200DDF400EA041C /* WOFFFileFormat.cpp */,
                                379919951200DDF400EA041C /* WOFFFileFormat.h */,
+                               C2AB0AF41E6B3C6C001348C5 /* FontSelectionAlgorithm.cpp */,
+                               C2AB0AF51E6B3C6C001348C5 /* FontSelectionAlgorithm.h */,
                        );
                        path = graphics;
                        sourceTree = "<group>";
                                854FE7350A2297BE0058D7AD /* NodeIterator.h in Headers */,
                                A818721B0977D3C0005826D9 /* NodeList.h in Headers */,
                                63189AE30E83A33300012E41 /* NodeRareData.h in Headers */,
+                               C2AB0AF71E6B3C6C001348C5 /* FontSelectionAlgorithm.h in Headers */,
                                63D7B32D0E78CD3F00F7617C /* NodeRenderStyle.h in Headers */,
                                E43105BB16750F1600DB2FB8 /* NodeTraversal.h in Headers */,
                                9382AAB40D8C386100F357A6 /* NodeWithIndex.h in Headers */,
                                1A8A645B1D19FCFC00D0E00F /* ApplePayShippingContactSelectedEvent.cpp in Sources */,
                                1A8A645F1D19FCFC00D0E00F /* ApplePayShippingMethodSelectedEvent.cpp in Sources */,
                                1A8A64621D19FCFC00D0E00F /* ApplePayValidateMerchantEvent.cpp in Sources */,
+                               C2AB0AF61E6B3C6C001348C5 /* FontSelectionAlgorithm.cpp in Sources */,
                                1A8F6BBC0DB55CDC001DB794 /* ApplicationCache.cpp in Sources */,
                                1A8F6BBE0DB55CDC001DB794 /* ApplicationCacheGroup.cpp in Sources */,
                                24F54EAC101FE914000AE741 /* ApplicationCacheHost.cpp in Sources */,
index 9bae31c..0348cf9 100644 (file)
@@ -194,6 +194,46 @@ std::optional<FontTraitsMask> CSSFontFace::calculateWeightMask(CSSValue& weight)
     return FontWeight400Mask;
 }
 
+std::optional<FontSelectionValue> CSSFontFace::calculateStretch(CSSValue& stretch)
+{
+    if (!is<CSSPrimitiveValue>(stretch))
+        return std::nullopt;
+
+    const auto& primitiveValue = downcast<CSSPrimitiveValue>(stretch);
+    if (primitiveValue.isPercentage() || primitiveValue.isNumber()) {
+        auto value = primitiveValue.floatValue();
+        if (value < static_cast<float>(FontSelectionValue::minimumValue()))
+            return FontSelectionValue::minimumValue();
+        if (value > static_cast<float>(FontSelectionValue::maximumValue()))
+            return FontSelectionValue::maximumValue();
+        return FontSelectionValue(value);
+    }
+
+    switch (primitiveValue.valueID()) {
+    case CSSValueUltraCondensed:
+        return FontSelectionValue(50);
+    case CSSValueExtraCondensed:
+        return FontSelectionValue(62.5f);
+    case CSSValueCondensed:
+        return FontSelectionValue(75);
+    case CSSValueSemiCondensed:
+        return FontSelectionValue(87.5f);
+    case CSSValueNormal:
+        return FontSelectionValue(100);
+    case CSSValueSemiExpanded:
+        return FontSelectionValue(112.5f);
+    case CSSValueExpanded:
+        return FontSelectionValue(125);
+    case CSSValueExtraExpanded:
+        return FontSelectionValue(150);
+    case CSSValueUltraExpanded:
+        return FontSelectionValue(200);
+    default:
+        ASSERT_NOT_REACHED();
+        return std::nullopt;
+    }
+}
+
 bool CSSFontFace::setWeight(CSSValue& weight)
 {
     if (auto mask = calculateWeightMask(weight)) {
@@ -212,6 +252,24 @@ bool CSSFontFace::setWeight(CSSValue& weight)
     return false;
 }
 
+bool CSSFontFace::setStretch(CSSValue& stretch)
+{
+    if (auto parsedStretch = calculateStretch(stretch)) {
+        m_stretch = FontSelectionRange(parsedStretch.value(), parsedStretch.value());
+
+        if (m_cssConnection)
+            m_cssConnection->mutableProperties().setProperty(CSSPropertyFontStretch, &stretch);
+
+        iterateClients(m_clients, [&](Client& client) {
+            client.fontPropertyChanged(*this);
+        });
+
+        return true;
+    }
+
+    return false;
+}
+
 bool CSSFontFace::setUnicodeRange(CSSValue& unicodeRange)
 {
     if (!is<CSSValueList>(unicodeRange))
index b11dfd0..b0c2b2e 100644 (file)
@@ -26,6 +26,7 @@
 #pragma once
 
 #include "CSSFontFaceRule.h"
+#include "FontSelectionAlgorithm.h"
 #include "FontTaggedSettings.h"
 #include "TextFlags.h"
 #include "Timer.h"
@@ -65,6 +66,7 @@ public:
     bool setFamilies(CSSValue&);
     bool setStyle(CSSValue&);
     bool setWeight(CSSValue&);
+    bool setStretch(CSSValue&);
     bool setUnicodeRange(CSSValue&);
     bool setVariantLigatures(CSSValue&);
     bool setVariantPosition(CSSValue&);
@@ -78,17 +80,21 @@ public:
     struct UnicodeRange;
     const CSSValueList* families() const { return m_families.get(); }
     FontTraitsMask traitsMask() const { return m_traitsMask; }
+    FontSelectionRange stretch() const { return m_stretch; }
     const Vector<UnicodeRange>& ranges() const { return m_ranges; }
     const FontFeatureSettings& featureSettings() const { return m_featureSettings; }
     const FontVariantSettings& variantSettings() const { return m_variantSettings; }
     void setVariantSettings(const FontVariantSettings& variantSettings) { m_variantSettings = variantSettings; }
     void setTraitsMask(FontTraitsMask traitsMask) { m_traitsMask = traitsMask; }
+    void setStretch(FontSelectionRange stretch) { m_stretch = stretch; }
     bool isLocalFallback() const { return m_isLocalFallback; }
     Status status() const { return m_status; }
     StyleRuleFontFace* cssConnection() const { return m_cssConnection.get(); }
+    FontSelectionCapabilities fontSelectionCapabilities() const { return fontSelectionCapabilitiesForTraitsMask(m_traitsMask, m_stretch); }
 
     static std::optional<FontTraitsMask> calculateStyleMask(CSSValue& style);
     static std::optional<FontTraitsMask> calculateWeightMask(CSSValue& weight);
+    static std::optional<FontSelectionValue> calculateStretch(CSSValue& stretch);
 
     class Client;
     void addClient(Client&);
@@ -173,6 +179,7 @@ private:
     HashSet<Client*> m_clients;
     WeakPtr<FontFace> m_wrapper;
     Status m_status { Status::Pending };
+    FontSelectionRange m_stretch { FontSelectionValue(100), FontSelectionValue(100) };
     bool m_isLocalFallback { false };
     bool m_sourcesPopulated { false };
     bool m_mayBePurged { true };
index 9f30dd7..83f6e57 100644 (file)
@@ -101,18 +101,19 @@ void CSSFontFaceSet::ensureLocalFontFacesForFamilyRegistered(const String& famil
     if (m_locallyInstalledFacesLookupTable.contains(familyName))
         return;
 
-    Vector<FontTraitsMask> traitsMasks = FontCache::singleton().getTraitsInFamily(familyName);
-    if (traitsMasks.isEmpty())
+    Vector<FontCache::TraitsAndStretch> traitsAndStretch = FontCache::singleton().getTraitsAndStretchInFamily(familyName);
+    if (traitsAndStretch.isEmpty())
         return;
 
     Vector<Ref<CSSFontFace>> faces;
-    for (auto mask : traitsMasks) {
+    for (auto item : traitsAndStretch) {
         Ref<CSSFontFace> face = CSSFontFace::create(nullptr, nullptr, nullptr, true);
         
         Ref<CSSValueList> familyList = CSSValueList::createCommaSeparated();
         familyList->append(CSSValuePool::singleton().createFontFamilyValue(familyName));
         face->setFamilies(familyList.get());
-        face->setTraitsMask(mask);
+        face->setTraitsMask(item.traits);
+        face->setStretch(item.stretch);
         face->adoptSource(std::make_unique<CSSFontFaceSource>(face.get(), familyName));
         ASSERT(!face->allSourcesFailed());
         faces.append(WTFMove(face));
@@ -311,6 +312,17 @@ static std::optional<FontTraitsMask> computeFontTraitsMask(MutableStylePropertie
     return static_cast<FontTraitsMask>(static_cast<unsigned>(styleMask) | static_cast<unsigned>(weightMask));
 }
 
+static std::optional<FontSelectionValue> computeFontStretch(MutableStyleProperties& style)
+{
+    RefPtr<CSSValue> stretchValue = style.getPropertyCSSValue(CSSPropertyFontStretch).get();
+    if (!stretchValue)
+        stretchValue = CSSValuePool::singleton().createIdentifierValue(CSSValueNormal).ptr();
+
+    if (auto stretchOptional = CSSFontFace::calculateStretch(*stretchValue))
+        return stretchOptional.value();
+    return std::nullopt;
+}
+
 static HashSet<UChar32> codePointsFromString(StringView stringView)
 {
     HashSet<UChar32> result;
@@ -340,6 +352,12 @@ ExceptionOr<Vector<std::reference_wrapper<CSSFontFace>>> CSSFontFaceSet::matchin
     else
         return Exception { SYNTAX_ERR };
 
+    FontSelectionValue stretch;
+    if (auto stretchOptional = computeFontStretch(style.get()))
+        stretch = stretchOptional.value();
+    else
+        return Exception { SYNTAX_ERR };
+
     auto family = style->getPropertyCSSValue(CSSPropertyFontFamily);
     if (!is<CSSValueList>(family.get()))
         return Exception { SYNTAX_ERR };
@@ -359,7 +377,7 @@ ExceptionOr<Vector<std::reference_wrapper<CSSFontFace>>> CSSFontFaceSet::matchin
     for (auto codePoint : codePointsFromString(string)) {
         bool found = false;
         for (auto& family : familyOrder) {
-            auto* faces = fontFace(fontTraitsMask, family);
+            auto* faces = fontFace(fontTraitsMask, stretch, family);
             if (!faces)
                 continue;
             for (auto& constituentFace : faces->constituentFaces()) {
@@ -394,66 +412,7 @@ ExceptionOr<bool> CSSFontFaceSet::check(const String& font, const String& text)
     return true;
 }
 
-static bool fontFaceComparator(FontTraitsMask desiredTraitsMaskForComparison, const CSSFontFace& first, const CSSFontFace& second)
-{
-    FontTraitsMask firstTraitsMask = first.traitsMask();
-    FontTraitsMask secondTraitsMask = second.traitsMask();
-
-    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/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 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, FontWeight200Mask, FontWeight100Mask, FontWeight600Mask, FontWeight700Mask, FontWeight800Mask, FontWeight900Mask },
-        { FontWeight400Mask, FontWeight300Mask, FontWeight200Mask, FontWeight100Mask, FontWeight600Mask, FontWeight700Mask, 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;
-    for (; !(desiredTraitsMaskForComparison & (1 << (FontWeight100Bit + ruleSetIndex))); ruleSetIndex++) { }
-
-    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;
-}
-
-CSSSegmentedFontFace* CSSFontFaceSet::fontFace(FontTraitsMask traitsMask, const AtomicString& family)
+CSSSegmentedFontFace* CSSFontFaceSet::fontFace(FontTraitsMask traitsMask, FontSelectionValue stretch, const AtomicString& family)
 {
     auto iterator = m_facesLookupTable.find(family);
     if (iterator == m_facesLookupTable.end())
@@ -487,11 +446,39 @@ CSSSegmentedFontFace* CSSFontFaceSet::fontFace(FontTraitsMask traitsMask, const
         }
     }
 
-    std::stable_sort(candidateFontFaces.begin(), candidateFontFaces.end(), [traitsMask](const CSSFontFace& first, const CSSFontFace& second) {
-        return fontFaceComparator(traitsMask, first, second);
-    });
-    for (auto& candidate : candidateFontFaces)
-        face->appendFontFace(candidate.get());
+    if (!candidateFontFaces.isEmpty()) {
+        Vector<FontSelectionCapabilities> capabilities;
+        capabilities.reserveInitialCapacity(candidateFontFaces.size());
+        for (auto& face : candidateFontFaces)
+            capabilities.uncheckedAppend(face.get().fontSelectionCapabilities());
+        FontSelectionAlgorithm fontSelectionAlgorithm(fontSelectionRequestForTraitsMask(traitsMask, stretch), capabilities);
+        std::stable_sort(candidateFontFaces.begin(), candidateFontFaces.end(), [&fontSelectionAlgorithm](const CSSFontFace& first, const CSSFontFace& second) {
+            auto firstCapabilities = first.fontSelectionCapabilities();
+            auto secondCapabilities = second.fontSelectionCapabilities();
+
+            auto stretchDistanceFirst = fontSelectionAlgorithm.stretchDistance(firstCapabilities).distance;
+            auto stretchDistanceSecond = fontSelectionAlgorithm.stretchDistance(secondCapabilities).distance;
+            if (stretchDistanceFirst < stretchDistanceSecond)
+                return true;
+            if (stretchDistanceFirst > stretchDistanceSecond)
+                return false;
+
+            auto styleDistanceFirst = fontSelectionAlgorithm.styleDistance(firstCapabilities).distance;
+            auto styleDistanceSecond = fontSelectionAlgorithm.styleDistance(secondCapabilities).distance;
+            if (styleDistanceFirst < styleDistanceSecond)
+                return true;
+            if (styleDistanceFirst > styleDistanceSecond)
+                return false;
+
+            auto weightDistanceFirst = fontSelectionAlgorithm.weightDistance(firstCapabilities).distance;
+            auto weightDistanceSecond = fontSelectionAlgorithm.weightDistance(secondCapabilities).distance;
+            if (weightDistanceFirst < weightDistanceSecond)
+                return true;
+            return false;
+        });
+        for (auto& candidate : candidateFontFaces)
+            face->appendFontFace(candidate.get());
+    }
 
     return face.get();
 }
index 7913402..e374e06 100644 (file)
@@ -67,7 +67,7 @@ public:
 
     ExceptionOr<bool> check(const String& font, const String& text);
 
-    CSSSegmentedFontFace* fontFace(FontTraitsMask, const AtomicString& family);
+    CSSSegmentedFontFace* fontFace(FontTraitsMask, FontSelectionValue stretch, const AtomicString& family);
 
     enum class Status { Loading, Loaded };
     Status status() const { return m_status; }
index 44d84b0..49be912 100644 (file)
@@ -145,6 +145,7 @@ void CSSFontSelector::addFontFaceRule(StyleRuleFontFace& fontFaceRule, bool isIn
     RefPtr<CSSValue> fontFamily = style.getPropertyCSSValue(CSSPropertyFontFamily);
     RefPtr<CSSValue> fontStyle = style.getPropertyCSSValue(CSSPropertyFontStyle);
     RefPtr<CSSValue> fontWeight = style.getPropertyCSSValue(CSSPropertyFontWeight);
+    RefPtr<CSSValue> fontStretch = style.getPropertyCSSValue(CSSPropertyFontStretch);
     RefPtr<CSSValue> src = style.getPropertyCSSValue(CSSPropertySrc);
     RefPtr<CSSValue> unicodeRange = style.getPropertyCSSValue(CSSPropertyUnicodeRange);
     RefPtr<CSSValue> featureSettings = style.getPropertyCSSValue(CSSPropertyFontFeatureSettings);
@@ -167,6 +168,9 @@ void CSSFontSelector::addFontFaceRule(StyleRuleFontFace& fontFaceRule, bool isIn
     if (!fontWeight)
         fontWeight = CSSValuePool::singleton().createIdentifierValue(CSSValueNormal);
 
+    if (!fontStretch)
+        fontStretch = CSSValuePool::singleton().createIdentifierValue(CSSValueNormal);
+
     CSSValueList* rangeList = downcast<CSSValueList>(unicodeRange.get());
 
     CSSValueList& srcList = downcast<CSSValueList>(*src);
@@ -182,6 +186,8 @@ void CSSFontSelector::addFontFaceRule(StyleRuleFontFace& fontFaceRule, bool isIn
         return;
     if (!fontFace->setWeight(*fontWeight))
         return;
+    if (!fontFace->setStretch(*fontStretch))
+        return;
     if (rangeList && !fontFace->setUnicodeRange(*rangeList))
         return;
     if (variantLigatures && !fontFace->setVariantLigatures(*variantLigatures))
@@ -295,7 +301,7 @@ FontRanges CSSFontSelector::fontRangesForFamily(const FontDescription& fontDescr
     bool resolveGenericFamilyFirst = familyName == standardFamily;
 
     AtomicString familyForLookup = resolveGenericFamilyFirst ? resolveGenericFamily(m_document, fontDescription, familyName) : familyName;
-    auto* face = m_cssFontFaceSet->fontFace(fontDescription.traitsMask(), familyForLookup);
+    auto* face = m_cssFontFaceSet->fontFace(fontDescription.traitsMask(), fontDescription.stretch(), familyForLookup);
     if (!face) {
         if (!resolveGenericFamilyFirst)
             familyForLookup = resolveGenericFamily(m_document, fontDescription, familyName);
index 6fb77c0..4c2fc59 100644 (file)
@@ -184,9 +184,16 @@ ExceptionOr<void> FontFace::setWeight(const String& weight)
     return { };
 }
 
-ExceptionOr<void> FontFace::setStretch(const String&)
+ExceptionOr<void> FontFace::setStretch(const String& stretch)
 {
-    // We don't support font-stretch. Swallow the call.
+    if (stretch.isEmpty())
+        return Exception { SYNTAX_ERR };
+
+    bool success = false;
+    if (auto value = parseString(stretch, CSSPropertyFontStretch))
+        success = m_backing->setStretch(*value);
+    if (!success)
+        return Exception { SYNTAX_ERR };
     return { };
 }
 
@@ -318,7 +325,33 @@ String FontFace::weight() const
 
 String FontFace::stretch() const
 {
-    return ASCIILiteral("normal");
+    m_backing->updateStyleIfNeeded();
+    auto stretch = m_backing->stretch();
+
+    auto rangeIsSingleValue = [](FontSelectionRange range, FontSelectionValue value) -> bool {
+        return range.minimum == value && range.maximum == value;
+    };
+
+    if (rangeIsSingleValue(stretch, FontSelectionValue(50)))
+        return ASCIILiteral("ultra-condensed");
+    if (rangeIsSingleValue(stretch, FontSelectionValue(62.5f)))
+        return ASCIILiteral("extra-condensed");
+    if (rangeIsSingleValue(stretch, FontSelectionValue(75)))
+        return ASCIILiteral("condensed");
+    if (rangeIsSingleValue(stretch, FontSelectionValue(87.5f)))
+        return ASCIILiteral("semi-condensed");
+    if (rangeIsSingleValue(stretch, FontSelectionValue(100)))
+        return ASCIILiteral("normal");
+    if (rangeIsSingleValue(stretch, FontSelectionValue(112.5f)))
+        return ASCIILiteral("semi-expanded");
+    if (rangeIsSingleValue(stretch, FontSelectionValue(125)))
+        return ASCIILiteral("expanded");
+    if (rangeIsSingleValue(stretch, FontSelectionValue(150)))
+        return ASCIILiteral("extra-expanded");
+    if (rangeIsSingleValue(stretch, FontSelectionValue(200)))
+        return ASCIILiteral("ultra-expanded");
+
+    return String::format("%f-%f", static_cast<float>(stretch.minimum), static_cast<float>(stretch.maximum));
 }
 
 String FontFace::unicodeRange() const
index 965e2b0..f2eabc7 100644 (file)
@@ -4185,9 +4185,7 @@ bool CSSPropertyParser::parseFontFaceDescriptor(CSSPropertyID propId)
         parsedValue = consumeFontFaceUnicodeRange(m_range);
         break;
     case CSSPropertyFontStretch:
-        // FIXME: Implement this.
-        m_range.consumeIncludingWhitespace();
-        parsedValue = CSSValuePool::singleton().createIdentifierValue(CSSValueNormal);
+        parsedValue = consumeFontStretch(m_range);
         break;
     case CSSPropertyFontStyle: {
         CSSValueID id = m_range.consumeIncludingWhitespace().id();
index dc650d2..5dc3d8d 100644 (file)
@@ -203,7 +203,11 @@ public:
 
     // This function exists so CSSFontSelector can have a unified notion of preinstalled fonts and @font-face.
     // It comes into play when you create an @font-face which shares a family name as a preinstalled font.
-    Vector<FontTraitsMask> getTraitsInFamily(const AtomicString&);
+    struct TraitsAndStretch {
+        FontTraitsMask traits;
+        FontSelectionRange stretch;
+    };
+    Vector<TraitsAndStretch> getTraitsAndStretchInFamily(const AtomicString&);
 
     WEBCORE_EXPORT RefPtr<Font> fontForFamily(const FontDescription&, const AtomicString&, const FontFeatureSettings* fontFaceFeatures = nullptr, const FontVariantSettings* fontFaceVariantSettings = nullptr, bool checkingAlternateName = false);
     WEBCORE_EXPORT Ref<Font> lastResortFallbackFont(const FontDescription&);
index 8988ee1..9a9464c 100644 (file)
@@ -26,6 +26,7 @@
 #define FontDescription_h
 
 #include "CSSValueKeywords.h"
+#include "FontSelectionAlgorithm.h"
 #include "FontTaggedSettings.h"
 #include "TextFlags.h"
 #include "WebKitFontFamilyNames.h"
diff --git a/Source/WebCore/platform/graphics/FontSelectionAlgorithm.cpp b/Source/WebCore/platform/graphics/FontSelectionAlgorithm.cpp
new file mode 100644 (file)
index 0000000..643dec7
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2017 Apple 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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 "FontSelectionAlgorithm.h"
+
+namespace WebCore {
+
+auto FontSelectionAlgorithm::stretchDistance(FontSelectionCapabilities capabilities) const -> DistanceResult
+{
+    auto width = capabilities.width;
+    ASSERT(width.isValid());
+    if (width.includes(m_request.width))
+        return { FontSelectionValue(), m_request.width };
+
+    if (m_request.width >= FontSelectionValue(100)) {
+        if (width.minimum > m_request.width)
+            return { width.minimum - m_request.width, width.minimum };
+        ASSERT(width.maximum < m_request.width);
+        auto threshold = std::max(m_request.width, m_capabilitiesBounds.width.maximum);
+        return { threshold - width.maximum, width.maximum };
+    }
+
+    if (width.maximum < m_request.width)
+        return { m_request.width - width.maximum, width.maximum };
+    ASSERT(width.minimum > m_request.width);
+    auto threshold = std::min(m_request.width, m_capabilitiesBounds.width.minimum);
+    return { width.minimum - threshold, width.minimum };
+}
+
+auto FontSelectionAlgorithm::styleDistance(FontSelectionCapabilities capabilities) const -> DistanceResult
+{
+    auto slope = capabilities.slope;
+    ASSERT(slope.isValid());
+    if (slope.includes(m_request.slope))
+        return { FontSelectionValue(), m_request.slope };
+
+    if (m_request.slope >= italicThreshold()) {
+        if (slope.minimum > m_request.slope)
+            return { slope.minimum - m_request.slope, slope.minimum };
+        ASSERT(m_request.slope > slope.maximum);
+        auto threshold = std::max(m_request.slope, m_capabilitiesBounds.slope.maximum);
+        return { threshold - slope.maximum, slope.maximum };
+    }
+
+    if (m_request.slope >= FontSelectionValue()) {
+        if (slope.maximum >= FontSelectionValue() && slope.maximum < m_request.slope)
+            return { m_request.slope - slope.maximum, slope.maximum };
+        if (slope.minimum > m_request.slope)
+            return { slope.minimum, slope.minimum };
+        ASSERT(slope.maximum < FontSelectionValue());
+        auto threshold = std::max(m_request.slope, m_capabilitiesBounds.slope.maximum);
+        return { threshold - slope.maximum, slope.maximum };
+    }
+
+    if (m_request.slope > -italicThreshold()) {
+        if (slope.minimum > m_request.slope && slope.minimum <= FontSelectionValue())
+            return { slope.minimum - m_request.slope, slope.minimum };
+        if (slope.maximum < m_request.slope)
+            return { -slope.maximum, slope.maximum };
+        ASSERT(slope.minimum > FontSelectionValue());
+        auto threshold = std::min(m_request.slope, m_capabilitiesBounds.slope.minimum);
+        return { slope.minimum - threshold, slope.minimum };
+    }
+
+    if (slope.maximum < m_request.slope)
+        return { m_request.slope - slope.maximum, slope.maximum };
+    ASSERT(slope.minimum > m_request.slope);
+    auto threshold = std::min(m_request.slope, m_capabilitiesBounds.slope.minimum);
+    return { slope.minimum - threshold, slope.minimum };
+}
+
+auto FontSelectionAlgorithm::weightDistance(FontSelectionCapabilities capabilities) const -> DistanceResult
+{
+    auto weight = capabilities.weight;
+    ASSERT(weight.isValid());
+    if (weight.includes(m_request.weight))
+        return { FontSelectionValue(), m_request.weight };
+
+    // The spec states: "If the desired weight is 400, 500 is checked first ... If the desired weight is 500, 400 is checked first"
+    FontSelectionValue offset(1);
+    if (m_request.weight == FontSelectionValue(400) && weight.includes(FontSelectionValue(500)))
+        return { offset, FontSelectionValue(500) };
+    if (m_request.weight == FontSelectionValue(500) && weight.includes(FontSelectionValue(400)))
+        return { offset, FontSelectionValue(400) };
+
+    if (m_request.weight <= weightSearchThreshold()) {
+        if (weight.maximum < m_request.weight)
+            return { m_request.weight - weight.maximum + offset, weight.maximum };
+        ASSERT(weight.minimum > m_request.weight);
+        auto threshold = std::min(m_request.weight, m_capabilitiesBounds.weight.minimum);
+        return { weight.minimum - threshold + offset, weight.minimum };
+    }
+
+    if (weight.minimum > m_request.weight)
+        return { weight.minimum - m_request.weight + offset, weight.minimum };
+    ASSERT(weight.maximum < m_request.weight);
+    auto threshold = std::max(m_request.weight, m_capabilitiesBounds.weight.maximum);
+    return { threshold - weight.maximum + offset, weight.maximum };
+}
+
+void FontSelectionAlgorithm::filterCapability(DistanceResult(FontSelectionAlgorithm::*computeDistance)(FontSelectionCapabilities) const, FontSelectionRange FontSelectionCapabilities::*inclusionRange)
+{
+    std::optional<FontSelectionValue> smallestDistance;
+    FontSelectionValue closestValue;
+    iterateActiveCapabilities([&](FontSelectionCapabilities capabilities, size_t) {
+        auto distanceResult = (this->*computeDistance)(capabilities);
+        if (!smallestDistance || distanceResult.distance < smallestDistance.value()) {
+            smallestDistance = distanceResult.distance;
+            closestValue = distanceResult.value;
+        }
+    });
+    ASSERT(smallestDistance);
+    iterateActiveCapabilities([&](auto& capabilities, size_t i) {
+        if (!(capabilities.*inclusionRange).includes(closestValue))
+            m_filter[i] = false;
+    });
+}
+
+size_t FontSelectionAlgorithm::indexOfBestCapabilities()
+{
+    filterCapability(&FontSelectionAlgorithm::stretchDistance, &FontSelectionCapabilities::width);
+    filterCapability(&FontSelectionAlgorithm::styleDistance, &FontSelectionCapabilities::slope);
+    filterCapability(&FontSelectionAlgorithm::weightDistance, &FontSelectionCapabilities::weight);
+
+    auto result = iterateActiveCapabilitiesWithReturn<size_t>([](FontSelectionCapabilities, size_t i) {
+        return i;
+    });
+    ASSERT(result);
+    return result.value_or(0);
+}
+
+FontSelectionRequest fontSelectionRequestForTraitsMask(FontTraitsMask traitsMask, FontSelectionValue stretch)
+{
+    FontSelectionRequest result;
+    if (traitsMask & FontWeight100Mask)
+        result.weight = FontSelectionValue(100);
+    else if (traitsMask & FontWeight200Mask)
+        result.weight = FontSelectionValue(200);
+    else if (traitsMask & FontWeight300Mask)
+        result.weight = FontSelectionValue(300);
+    else if (traitsMask & FontWeight400Mask)
+        result.weight = FontSelectionValue(400);
+    else if (traitsMask & FontWeight500Mask)
+        result.weight = FontSelectionValue(500);
+    else if (traitsMask & FontWeight600Mask)
+        result.weight = FontSelectionValue(600);
+    else if (traitsMask & FontWeight700Mask)
+        result.weight = FontSelectionValue(700);
+    else if (traitsMask & FontWeight800Mask)
+        result.weight = FontSelectionValue(800);
+    else {
+        ASSERT(traitsMask & FontWeight900Mask);
+        result.weight = FontSelectionValue(900);
+    }
+
+    result.width = stretch;
+
+    if (traitsMask & FontStyleNormalMask)
+        result.slope = FontSelectionValue();
+    else {
+        ASSERT(traitsMask & FontStyleItalicMask);
+        result.slope = italicThreshold();
+    }
+
+    return result;
+}
+
+static FontSelectionCapabilities initialFontSelectionCapabilitiesForTraitsMask(FontTraitsMask traitsMask)
+{
+    FontSelectionCapabilities result;
+    if (traitsMask & FontWeight100Mask)
+        result.weight = { FontSelectionValue(100), FontSelectionValue(100) };
+    else if (traitsMask & FontWeight200Mask)
+        result.weight = { FontSelectionValue(200), FontSelectionValue(200) };
+    else if (traitsMask & FontWeight300Mask)
+        result.weight = { FontSelectionValue(300), FontSelectionValue(300) };
+    else if (traitsMask & FontWeight400Mask)
+        result.weight = { FontSelectionValue(400), FontSelectionValue(400) };
+    else if (traitsMask & FontWeight500Mask)
+        result.weight = { FontSelectionValue(500), FontSelectionValue(500) };
+    else if (traitsMask & FontWeight600Mask)
+        result.weight = { FontSelectionValue(600), FontSelectionValue(600) };
+    else if (traitsMask & FontWeight700Mask)
+        result.weight = { FontSelectionValue(700), FontSelectionValue(700) };
+    else if (traitsMask & FontWeight800Mask)
+        result.weight = { FontSelectionValue(800), FontSelectionValue(800) };
+    else {
+        ASSERT(traitsMask & FontWeight900Mask);
+        result.weight = { FontSelectionValue(900), FontSelectionValue(900) };
+    }
+
+    if (traitsMask & FontStyleNormalMask)
+        result.slope = { FontSelectionValue(), FontSelectionValue() };
+    else {
+        ASSERT(traitsMask & FontStyleItalicMask);
+        result.slope = { italicThreshold(), italicThreshold() };
+    }
+
+    return result;
+}
+
+FontSelectionCapabilities fontSelectionCapabilitiesForTraitsMask(FontTraitsMask traitsMask, FontSelectionValue stretch)
+{
+    FontSelectionCapabilities result = initialFontSelectionCapabilitiesForTraitsMask(traitsMask);
+    result.width = { stretch, stretch };
+    return result;
+}
+
+FontSelectionCapabilities fontSelectionCapabilitiesForTraitsMask(FontTraitsMask traitsMask, FontSelectionRange stretch)
+{
+    FontSelectionCapabilities result = initialFontSelectionCapabilitiesForTraitsMask(traitsMask);
+    result.width = stretch;
+    return result;
+}
+
+}
diff --git a/Source/WebCore/platform/graphics/FontSelectionAlgorithm.h b/Source/WebCore/platform/graphics/FontSelectionAlgorithm.h
new file mode 100644 (file)
index 0000000..8d6afb1
--- /dev/null
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2017 Apple 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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.
+ */
+
+#pragma once
+
+#include "TextFlags.h"
+#include <wtf/GetPtr.h>
+#include <wtf/Hasher.h>
+#include <wtf/NeverDestroyed.h>
+#include <wtf/Optional.h>
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+// Unclamped, unchecked, signed fixed-point number representing a value used for font variations.
+// Sixteen bits in total, one sign bit, two fractional bits, means the smallest positive representable value is 0.25,
+// the maximum representable value is 8191.75, and the minimum representable value is -8192.
+class FontSelectionValue {
+public:
+    FontSelectionValue() = default;
+
+    // Explicit because it is lossy.
+    explicit FontSelectionValue(int x)
+        : m_backing(x * fractionalEntropy)
+    {
+    }
+
+    // Explicit because it is lossy.
+    explicit FontSelectionValue(float x)
+        : m_backing(x * fractionalEntropy)
+    {
+    }
+
+    operator float() const
+    {
+        // floats have 23 fractional bits, but only 14 fractional bits are necessary, so every value can be represented losslessly.
+        return m_backing / static_cast<float>(fractionalEntropy);
+    }
+
+    FontSelectionValue operator+(const FontSelectionValue other) const;
+    FontSelectionValue operator-(const FontSelectionValue other) const;
+    FontSelectionValue operator*(const FontSelectionValue other) const;
+    FontSelectionValue operator/(const FontSelectionValue other) const;
+    FontSelectionValue operator-() const;
+    bool operator==(const FontSelectionValue other) const;
+    bool operator!=(const FontSelectionValue other) const;
+    bool operator<(const FontSelectionValue other) const;
+    bool operator<=(const FontSelectionValue other) const;
+    bool operator>(const FontSelectionValue other) const;
+    bool operator>=(const FontSelectionValue other) const;
+
+    int16_t rawValue() const
+    {
+        return m_backing;
+    }
+
+    static FontSelectionValue maximumValue()
+    {
+        static NeverDestroyed<FontSelectionValue> result = FontSelectionValue(std::numeric_limits<int16_t>::max(), RawTag::RawTag);
+        return result.get();
+    }
+
+    static FontSelectionValue minimumValue()
+    {
+        static NeverDestroyed<FontSelectionValue> result = FontSelectionValue(std::numeric_limits<int16_t>::min(), RawTag::RawTag);
+        return result.get();
+    }
+
+private:
+    enum class RawTag { RawTag };
+
+    FontSelectionValue(int16_t rawValue, RawTag)
+        : m_backing(rawValue)
+    {
+    }
+
+    static constexpr int fractionalEntropy = 4;
+    int16_t m_backing { 0 };
+};
+
+inline FontSelectionValue FontSelectionValue::operator+(const FontSelectionValue other) const
+{
+    return FontSelectionValue(m_backing + other.m_backing, RawTag::RawTag);
+}
+
+inline FontSelectionValue FontSelectionValue::operator-(const FontSelectionValue other) const
+{
+    return FontSelectionValue(m_backing - other.m_backing, RawTag::RawTag);
+}
+
+inline FontSelectionValue FontSelectionValue::operator*(const FontSelectionValue other) const
+{
+    return FontSelectionValue(static_cast<int32_t>(m_backing) * other.m_backing / fractionalEntropy, RawTag::RawTag);
+}
+
+inline FontSelectionValue FontSelectionValue::operator/(const FontSelectionValue other) const
+{
+    return FontSelectionValue(static_cast<int32_t>(m_backing) / other.m_backing * fractionalEntropy, RawTag::RawTag);
+}
+
+inline FontSelectionValue FontSelectionValue::operator-() const
+{
+    return FontSelectionValue(-m_backing, RawTag::RawTag);
+}
+
+inline bool FontSelectionValue::operator==(const FontSelectionValue other) const
+{
+    return m_backing == other.m_backing;
+}
+
+inline bool FontSelectionValue::operator!=(const FontSelectionValue other) const
+{
+    return !operator==(other);
+}
+
+inline bool FontSelectionValue::operator<(const FontSelectionValue other) const
+{
+    return m_backing < other.m_backing;
+}
+
+inline bool FontSelectionValue::operator<=(const FontSelectionValue other) const
+{
+    return m_backing <= other.m_backing;
+}
+
+inline bool FontSelectionValue::operator>(const FontSelectionValue other) const
+{
+    return m_backing > other.m_backing;
+}
+
+inline bool FontSelectionValue::operator>=(const FontSelectionValue other) const
+{
+    return m_backing >= other.m_backing;
+}
+
+static inline FontSelectionValue italicThreshold()
+{
+    static NeverDestroyed<FontSelectionValue> result = FontSelectionValue(20);
+    return result.get();
+}
+
+static inline FontSelectionValue boldThreshold()
+{
+    static NeverDestroyed<FontSelectionValue> result = FontSelectionValue(600);
+    return result.get();
+}
+
+static inline FontSelectionValue weightSearchThreshold()
+{
+    static NeverDestroyed<FontSelectionValue> result = FontSelectionValue(500);
+    return result.get();
+}
+
+// [Inclusive, Inclusive]
+struct FontSelectionRange {
+    FontSelectionRange(FontSelectionValue minimum, FontSelectionValue maximum)
+        : minimum(minimum)
+        , maximum(maximum)
+    {
+    }
+
+    bool operator==(const FontSelectionRange& other) const
+    {
+        return minimum == other.minimum
+            && maximum == other.maximum;
+    }
+
+    bool isValid() const
+    {
+        return minimum <= maximum;
+    }
+
+    void expand(const FontSelectionRange& other)
+    {
+        ASSERT(other.isValid());
+        if (!isValid())
+            *this = other;
+        else {
+            minimum = std::min(minimum, other.minimum);
+            maximum = std::max(maximum, other.maximum);
+        }
+        ASSERT(isValid());
+    }
+
+    bool includes(FontSelectionValue target) const
+    {
+        return target >= minimum && target <= maximum;
+    }
+
+    FontSelectionValue minimum { FontSelectionValue(1) };
+    FontSelectionValue maximum { FontSelectionValue(0) };
+};
+
+struct FontSelectionRequest {
+    bool operator==(const FontSelectionRequest& other) const
+    {
+        return weight == other.weight
+            && width == other.width
+            && slope == other.slope;
+    }
+
+    bool operator!=(const FontSelectionRequest& other) const
+    {
+        return !operator==(other);
+    }
+
+    FontSelectionValue weight;
+    FontSelectionValue width;
+    FontSelectionValue slope;
+};
+
+// Only used for HashMaps. We don't want to put the bool into FontSelectionRequest
+// because FontSelectionRequest needs to be as small as possible because it's inside
+// every FontDescription.
+struct FontSelectionRequestKey {
+    FontSelectionRequestKey() = default;
+
+    FontSelectionRequestKey(FontSelectionRequest request)
+        : request(request)
+    {
+    }
+
+    explicit FontSelectionRequestKey(WTF::HashTableDeletedValueType)
+        : isDeletedValue(true)
+    {
+    }
+
+    bool isHashTableDeletedValue() const
+    {
+        return isDeletedValue;
+    }
+
+    bool operator==(const FontSelectionRequestKey& other) const
+    {
+        return request == other.request
+            && isDeletedValue == other.isDeletedValue;
+    }
+
+    FontSelectionRequest request;
+    bool isDeletedValue { false };
+};
+
+struct FontSelectionRequestKeyHash {
+    static unsigned hash(const FontSelectionRequestKey& key)
+    {
+        IntegerHasher hasher;
+        hasher.add(key.request.weight.rawValue());
+        hasher.add(key.request.width.rawValue());
+        hasher.add(key.request.slope.rawValue());
+        hasher.add(key.isDeletedValue);
+        return hasher.hash();
+    }
+
+    static bool equal(const FontSelectionRequestKey& a, const FontSelectionRequestKey& b)
+    {
+        return a == b;
+    }
+
+    static const bool safeToCompareToEmptyOrDeleted = true;
+};
+
+struct FontSelectionCapabilities {
+    void expand(const FontSelectionCapabilities& capabilities)
+    {
+        weight.expand(capabilities.weight);
+        width.expand(capabilities.width);
+        slope.expand(capabilities.slope);
+    }
+
+    FontSelectionRange weight { FontSelectionValue(400), FontSelectionValue(400) };
+    FontSelectionRange width { FontSelectionValue(100), FontSelectionValue(100) };
+    FontSelectionRange slope { FontSelectionValue(), FontSelectionValue() };
+};
+
+class FontSelectionAlgorithm {
+public:
+    FontSelectionAlgorithm() = delete;
+
+    FontSelectionAlgorithm(FontSelectionRequest request, const Vector<FontSelectionCapabilities>& capabilities, std::optional<FontSelectionCapabilities> capabilitiesBounds = std::nullopt)
+        : m_request(request)
+        , m_capabilities(capabilities)
+        , m_filter(new bool[m_capabilities.size()])
+    {
+        ASSERT(!m_capabilities.isEmpty());
+        if (capabilitiesBounds)
+            m_capabilitiesBounds = capabilitiesBounds.value();
+        else {
+            for (auto capabilities : m_capabilities)
+                m_capabilitiesBounds.expand(capabilities);
+        }
+        for (size_t i = 0; i < m_capabilities.size(); ++i)
+            m_filter[i] = true;
+    }
+
+    struct DistanceResult {
+        FontSelectionValue distance;
+        FontSelectionValue value;
+    };
+
+    DistanceResult stretchDistance(FontSelectionCapabilities) const;
+    DistanceResult styleDistance(FontSelectionCapabilities) const;
+    DistanceResult weightDistance(FontSelectionCapabilities) const;
+
+    size_t indexOfBestCapabilities();
+
+private:
+    template <typename T>
+    using IterateActiveCapabilitiesWithReturnCallback = std::function<std::optional<T>(FontSelectionCapabilities, size_t)>;
+
+    template <typename T>
+    inline std::optional<T> iterateActiveCapabilitiesWithReturn(IterateActiveCapabilitiesWithReturnCallback<T> callback)
+    {
+        for (size_t i = 0; i < m_capabilities.size(); ++i) {
+            if (!m_filter[i])
+                continue;
+            if (auto result = callback(m_capabilities[i], i))
+                return result;
+        }
+        return std::nullopt;
+    }
+
+    template <typename T>
+    inline void iterateActiveCapabilities(T callback)
+    {
+        iterateActiveCapabilitiesWithReturn<int>([&](FontSelectionCapabilities capabilities, size_t i) -> std::optional<int> {
+            callback(capabilities, i);
+            return std::nullopt;
+        });
+    }
+
+    void filterCapability(DistanceResult(FontSelectionAlgorithm::*computeDistance)(FontSelectionCapabilities) const, FontSelectionRange FontSelectionCapabilities::*inclusionRange);
+
+    FontSelectionRequest m_request;
+    FontSelectionCapabilities m_capabilitiesBounds;
+    const Vector<FontSelectionCapabilities>& m_capabilities;
+    std::unique_ptr<bool[]> m_filter;
+};
+
+FontSelectionRequest fontSelectionRequestForTraitsMask(FontTraitsMask, FontSelectionValue stretch);
+FontSelectionCapabilities fontSelectionCapabilitiesForTraitsMask(FontTraitsMask, FontSelectionValue stretch);
+FontSelectionCapabilities fontSelectionCapabilitiesForTraitsMask(FontTraitsMask, FontSelectionRange stretch);
+
+}
index 7d53671..ad0918f 100644 (file)
@@ -662,7 +662,20 @@ RefPtr<Font> FontCache::similarFont(const FontDescription& description, const At
     return nullptr;
 }
 
-Vector<FontTraitsMask> FontCache::getTraitsInFamily(const AtomicString& familyName)
+static FontSelectionValue stretchFromCoreTextTraits(CFDictionaryRef traits)
+{
+    auto widthNumber = static_cast<CFNumberRef>(CFDictionaryGetValue(traits, kCTFontWidthTrait));
+    if (widthNumber) {
+        // FIXME: The normalization from Core Text's [-1, 1] range to CSS's [50%, 200%] range isn't perfect.
+        float ctWidth;
+        auto success = CFNumberGetValue(widthNumber, kCFNumberFloatType, &ctWidth);
+        ASSERT_UNUSED(success, success);
+        return FontSelectionValue(ctWidth < 0.5 ? ctWidth * 50 + 100 : ctWidth * 150 + 50);
+    }
+    return FontSelectionValue(100);
+}
+
+auto FontCache::getTraitsAndStretchInFamily(const AtomicString& familyName) -> Vector<TraitsAndStretch>
 {
     auto familyNameStr = familyName.string().createCFString();
     CFTypeRef keys[] = { kCTFontFamilyNameAttribute };
@@ -677,8 +690,8 @@ Vector<FontTraitsMask> FontCache::getTraitsInFamily(const AtomicString& familyNa
     if (!numMatches)
         return { };
 
-    Vector<FontTraitsMask> traitsMasks;
-    traitsMasks.reserveInitialCapacity(numMatches);
+    Vector<TraitsAndStretch> result;
+    result.reserveInitialCapacity(numMatches);
     for (CFIndex i = 0; i < numMatches; ++i) {
         auto traits = adoptCF((CFDictionaryRef)CTFontDescriptorCopyAttribute((CTFontDescriptorRef)CFArrayGetValueAtIndex(matchedDescriptors.get(), i), kCTFontTraitsAttribute));
         CFNumberRef resultRef = (CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontSymbolicTrait);
@@ -688,10 +701,11 @@ Vector<FontTraitsMask> FontCache::getTraitsInFamily(const AtomicString& familyNa
             CFNumberGetValue(resultRef, kCFNumberIntType, &symbolicTraits);
             CGFloat weight = 0;
             CFNumberGetValue(weightRef, kCFNumberCGFloatType, &weight);
-            traitsMasks.uncheckedAppend(toTraitsMask(symbolicTraits, weight));
+            auto stretch = stretchFromCoreTextTraits(traits.get());
+            result.uncheckedAppend({ toTraitsMask(symbolicTraits, weight), { stretch, stretch } });
         }
     }
-    return traitsMasks;
+    return result;
 }
 
 static void invalidateFontCache();
@@ -895,14 +909,7 @@ public:
         FontSelectionValue slant;
         FontSelectionValue weight;
         if (traits) {
-            auto widthNumber = static_cast<CFNumberRef>(CFDictionaryGetValue(traits.get(), kCTFontWidthTrait));
-            if (widthNumber) {
-                // FIXME: The normalization from Core Text's [-1, 1] range to CSS's [50%, 200%] range isn't perfect.
-                float ctWidth;
-                auto success = CFNumberGetValue(widthNumber, kCFNumberFloatType, &ctWidth);
-                ASSERT_UNUSED(success, success);
-                width = FontSelectionValue(ctWidth < 0.5 ? ctWidth * 50 + 100 : ctWidth * 150 + 50);
-            }
+            width = stretchFromCoreTextTraits(traits.get());
 
             auto symbolicTraitsNumber = static_cast<CFNumberRef>(CFDictionaryGetValue(traits.get(), kCTFontSymbolicTrait));
             if (symbolicTraitsNumber) {
@@ -926,24 +933,6 @@ public:
         return { { weight, weight }, { width, width }, { slant, slant } };
     }
 
-    static const FontSelectionValue stretchThreshold()
-    {
-        static NeverDestroyed<FontSelectionValue> threshold(100);
-        return threshold.get();
-    }
-
-    static const FontSelectionValue italicThreshold()
-    {
-        static NeverDestroyed<FontSelectionValue> threshold(20);
-        return threshold.get();
-    }
-
-    static const FontSelectionValue weightThreshold()
-    {
-        static NeverDestroyed<FontSelectionValue> threshold(500);
-        return threshold.get();
-    }
-
 private:
     friend class NeverDestroyed<FontDatabase>;
 
@@ -953,326 +942,61 @@ private:
     HashMap<String, InstalledFont> m_postScriptNameToFontDescriptors;
 };
 
-template <typename T>
-using IterateActiveFontsWithReturnCallback = std::function<std::optional<T>(const FontDatabase::InstalledFont&, size_t)>;
-
-template <typename T>
-inline std::optional<T> iterateActiveFontsWithReturn(const FontDatabase::InstalledFontFamily& installedFonts, const std::unique_ptr<bool[]>& filter, IterateActiveFontsWithReturnCallback<T> callback)
-{
-    for (size_t i = 0; i < installedFonts.size(); ++i) {
-        if (!filter[i])
-            continue;
-        if (auto result = callback(installedFonts.installedFonts[i], i))
-            return result;
-    }
-    return std::nullopt;
-}
-
-template <typename T>
-inline void iterateActiveFonts(const FontDatabase::InstalledFontFamily& installedFonts, const std::unique_ptr<bool[]>& filter, T callback)
-{
-    iterateActiveFontsWithReturn<int>(installedFonts, filter, [&](const FontDatabase::InstalledFont& font, size_t i) -> std::optional<int> {
-        callback(font, i);
-        return std::nullopt;
-    });
-}
-
-static inline std::optional<FontSelectionValue> findClosestStretch(FontSelectionValue targetStretch, const FontDatabase::InstalledFontFamily& installedFonts, const std::unique_ptr<bool[]>& filter)
-{
-    std::function<float(const FontDatabase::InstalledFont&)> computeScore;
-
-    if (targetStretch >= FontDatabase::stretchThreshold()) {
-        auto threshold = std::max(targetStretch, installedFonts.capabilities.width.maximum);
-        computeScore = [&, threshold](const FontDatabase::InstalledFont& font) -> float {
-            auto width = font.capabilities.width;
-            ASSERT(width.isValid());
-            if (width.includes(targetStretch))
-                return 0;
-            ASSERT(width.minimum > targetStretch || width.maximum < targetStretch);
-            if (width.minimum > targetStretch)
-                return width.minimum - targetStretch;
-            ASSERT(width.maximum < targetStretch);
-            return threshold - width.maximum;
-        };
-    } else {
-        ASSERT(targetStretch < FontDatabase::stretchThreshold());
-        auto threshold = std::min(targetStretch, installedFonts.capabilities.width.minimum);
-        computeScore = [&, threshold](const FontDatabase::InstalledFont& font) -> float {
-            auto width = font.capabilities.width;
-            if (width.includes(targetStretch))
-                return 0;
-            ASSERT(width.minimum > targetStretch || width.maximum < targetStretch);
-            if (width.maximum < targetStretch)
-                return targetStretch - width.maximum;
-            ASSERT(width.minimum > targetStretch);
-            return width.minimum - threshold;
-        };
-    }
-
-    size_t closestIndex = 0;
-    std::optional<float> minimumScore;
-    iterateActiveFonts(installedFonts, filter, [&](auto& installedFont, size_t i) {
-        auto score = computeScore(installedFont);
-        if (!minimumScore || score < minimumScore.value()) {
-            minimumScore = score;
-            closestIndex = i;
-        }
-    });
-
-    if (!minimumScore)
-        return std::nullopt;
-    auto& winner = installedFonts.installedFonts[closestIndex];
-    auto width = winner.capabilities.width;
-    if (width.includes(targetStretch))
-        return targetStretch;
-    if (width.minimum > targetStretch)
-        return width.minimum;
-    ASSERT(width.maximum < targetStretch);
-    return width.maximum;
-}
-
-static inline void filterStretch(FontSelectionValue target, const FontDatabase::InstalledFontFamily& installedFonts, std::unique_ptr<bool[]>& filter)
-{
-    iterateActiveFonts(installedFonts, filter, [&](auto& installedFont, size_t i) {
-        if (!installedFont.capabilities.width.includes(target))
-            filter[i] = false;
-    });
-}
-
-static inline std::optional<FontSelectionValue> findClosestStyle(FontSelectionValue targetStyle, const FontDatabase::InstalledFontFamily& installedFonts, const std::unique_ptr<bool[]>& filter)
-{
-    std::function<float(const FontDatabase::InstalledFont&)> computeScore;
-
-    if (targetStyle >= FontDatabase::italicThreshold()) {
-        auto threshold = std::max(targetStyle, installedFonts.capabilities.slope.maximum);
-        computeScore = [&, threshold](const FontDatabase::InstalledFont& font) -> float {
-            auto slope = font.capabilities.slope;
-            ASSERT(slope.isValid());
-            if (slope.includes(targetStyle))
-                return 0;
-            ASSERT(slope.minimum > targetStyle || slope.maximum < targetStyle);
-            if (slope.minimum > targetStyle)
-                return slope.minimum - targetStyle;
-            ASSERT(targetStyle > slope.maximum);
-            return threshold - slope.maximum;
-        };
-    } else if (targetStyle >= FontSelectionValue()) {
-        auto threshold = std::max(targetStyle, installedFonts.capabilities.slope.maximum);
-        computeScore = [&, threshold](const FontDatabase::InstalledFont& font) -> float {
-            auto slope = font.capabilities.slope;
-            ASSERT(slope.isValid());
-            if (slope.includes(targetStyle))
-                return 0;
-            ASSERT(slope.minimum > targetStyle || slope.maximum < targetStyle);
-            if (slope.maximum >= FontSelectionValue() && slope.maximum < targetStyle)
-                return targetStyle - slope.maximum;
-            if (slope.minimum > targetStyle)
-                return slope.minimum;
-            ASSERT(slope.maximum < FontSelectionValue());
-            return threshold - slope.maximum;
-        };
-    } else if (targetStyle > -FontDatabase::italicThreshold()) {
-        auto threshold = std::min(targetStyle, installedFonts.capabilities.slope.minimum);
-        computeScore = [&, threshold](const FontDatabase::InstalledFont& font) -> float {
-            auto slope = font.capabilities.slope;
-            ASSERT(slope.isValid());
-            if (slope.includes(targetStyle))
-                return 0;
-            ASSERT(slope.minimum > targetStyle || slope.maximum < targetStyle);
-            if (slope.minimum > targetStyle && slope.minimum <= FontSelectionValue())
-                return slope.minimum - targetStyle;
-            if (slope.maximum < targetStyle)
-                return -slope.maximum;
-            ASSERT(slope.minimum > FontSelectionValue());
-            return slope.minimum - threshold;
-        };
-    } else {
-        ASSERT(targetStyle <= -FontDatabase::italicThreshold());
-        auto threshold = std::min(targetStyle, installedFonts.capabilities.slope.minimum);
-        computeScore = [&, threshold](const FontDatabase::InstalledFont& font) -> float {
-            auto slope = font.capabilities.slope;
-            ASSERT(slope.isValid());
-            if (slope.includes(targetStyle))
-                return 0;
-            ASSERT(slope.minimum > targetStyle || slope.maximum < targetStyle);
-            if (slope.maximum < targetStyle)
-                return targetStyle - slope.maximum;
-            ASSERT(slope.minimum > targetStyle);
-            return slope.minimum - threshold;
-        };
-    }
-
-    size_t closestIndex = 0;
-    std::optional<float> minimumScore;
-    iterateActiveFonts(installedFonts, filter, [&](auto& installedFont, size_t i) {
-        auto score = computeScore(installedFont);
-        if (!minimumScore || score < minimumScore.value()) {
-            minimumScore = score;
-            closestIndex = i;
-        }
-    });
-
-    if (!minimumScore)
-        return std::nullopt;
-    auto& winner = installedFonts.installedFonts[closestIndex];
-    auto slope = winner.capabilities.slope;
-    if (slope.includes(targetStyle))
-        return targetStyle;
-    if (slope.minimum > targetStyle)
-        return slope.minimum;
-    ASSERT(slope.maximum < targetStyle);
-    return slope.maximum;
-}
-
-static inline void filterStyle(FontSelectionValue target, const FontDatabase::InstalledFontFamily& installedFonts, std::unique_ptr<bool[]>& filter)
+static const FontDatabase::InstalledFont* findClosestFont(const FontDatabase::InstalledFontFamily& familyFonts, FontSelectionRequest fontSelectionRequest)
 {
-    iterateActiveFonts(installedFonts, filter, [&](auto& installedFont, size_t i) {
-        if (!installedFont.capabilities.slope.includes(target))
-            filter[i] = false;
-    });
+    Vector<FontSelectionCapabilities> capabilities;
+    capabilities.reserveInitialCapacity(familyFonts.size());
+    for (auto& font : familyFonts.installedFonts)
+        capabilities.uncheckedAppend(font.capabilities);
+    FontSelectionAlgorithm fontSelectionAlgorithm(fontSelectionRequest, capabilities, familyFonts.capabilities);
+    return &familyFonts.installedFonts[fontSelectionAlgorithm.indexOfBestCapabilities()];
 }
 
-static inline std::optional<FontSelectionValue> findClosestWeight(FontSelectionValue targetWeight, const FontDatabase::InstalledFontFamily& installedFonts, const std::unique_ptr<bool[]>& filter)
-{
-    {
-        // The spec states: "If the desired weight is 400, 500 is checked first ... If the desired weight is 500, 400 is checked first"
-        IterateActiveFontsWithReturnCallback<FontSelectionValue> searchFor400 = [&](const FontDatabase::InstalledFont& font, size_t) -> std::optional<FontSelectionValue> {
-            if (font.capabilities.weight.includes(FontSelectionValue(400)))
-                return FontSelectionValue(400);
-            return std::nullopt;
-        };
-        IterateActiveFontsWithReturnCallback<FontSelectionValue> searchFor500 = [&](const FontDatabase::InstalledFont& font, size_t) -> std::optional<FontSelectionValue> {
-            if (font.capabilities.weight.includes(FontSelectionValue(500)))
-                return FontSelectionValue(500);
-            return std::nullopt;
-        };
-        if (targetWeight == FontSelectionValue(400)) {
-            if (auto result = iterateActiveFontsWithReturn(installedFonts, filter, searchFor400))
-                return result;
-            if (auto result = iterateActiveFontsWithReturn(installedFonts, filter, searchFor500))
-                return result;
-        } else if (targetWeight == FontSelectionValue(500)) {
-            if (auto result = iterateActiveFontsWithReturn(installedFonts, filter, searchFor500))
-                return result;
-            if (auto result = iterateActiveFontsWithReturn(installedFonts, filter, searchFor400))
-                return result;
-        }
-    }
-
-    std::function<float(const FontDatabase::InstalledFont&)> computeScore;
-    if (targetWeight <= FontDatabase::weightThreshold()) {
-        auto threshold = std::min(targetWeight, installedFonts.capabilities.weight.minimum);
-        computeScore = [&, threshold](const FontDatabase::InstalledFont& font) -> FontSelectionValue {
-            auto weight = font.capabilities.weight;
-            if (weight.includes(targetWeight))
-                return FontSelectionValue();
-            ASSERT(weight.minimum > targetWeight || weight.maximum < targetWeight);
-            if (weight.maximum < targetWeight)
-                return targetWeight - weight.maximum;
-            ASSERT(weight.minimum > targetWeight);
-            return weight.minimum - threshold;
-        };
-    } else {
-        ASSERT(targetWeight > FontDatabase::weightThreshold());
-        auto threshold = std::max(targetWeight, installedFonts.capabilities.weight.maximum);
-        computeScore = [&, threshold](const FontDatabase::InstalledFont& font) -> FontSelectionValue {
-            auto weight = font.capabilities.weight;
-            if (weight.includes(targetWeight))
-                return FontSelectionValue();
-            ASSERT(weight.minimum > targetWeight || weight.maximum < targetWeight);
-            if (weight.minimum > targetWeight)
-                return weight.minimum - targetWeight;
-            ASSERT(weight.maximum < targetWeight);
-            return threshold - weight.maximum;
-        };
-    }
-
-    size_t closestIndex = 0;
-    std::optional<float> minimumScore;
-    iterateActiveFonts(installedFonts, filter, [&](auto& installedFont, size_t i) {
-        auto score = computeScore(installedFont);
-        if (!minimumScore || score < minimumScore.value()) {
-            minimumScore = score;
-            closestIndex = i;
-        }
-    });
-
-    if (!minimumScore)
-        return std::nullopt;
-    auto& winner = installedFonts.installedFonts[closestIndex];
-    auto weight = winner.capabilities.weight;
-    if (weight.includes(targetWeight))
-        return targetWeight;
-    if (weight.minimum > targetWeight)
-        return weight.minimum;
-    ASSERT(weight.maximum < targetWeight);
-    return weight.maximum;
-}
-
-static inline void filterWeight(FontSelectionValue target, const FontDatabase::InstalledFontFamily& installedFonts, std::unique_ptr<bool[]>& filter)
-{
-    iterateActiveFonts(installedFonts, filter, [&](auto& installedFont, size_t i) {
-        if (!installedFont.capabilities.weight.includes(target))
-            filter[i] = false;
-    });
-}
-
-static inline FontSelectionValue computeTargetWeight(FontWeight weight)
+static FontSelectionRequest calculateFontSelectionRequest(CTFontSymbolicTraits requestedTraits, FontWeight weight, FontSelectionValue stretch)
 {
+    FontSelectionRequest result;
     switch (weight) {
     case FontWeight100:
-        return FontSelectionValue(100);
+        result.weight = FontSelectionValue(100);
+        break;
     case FontWeight200:
-        return FontSelectionValue(200);
+        result.weight = FontSelectionValue(200);
+        break;
     case FontWeight300:
-        return FontSelectionValue(300);
+        result.weight = FontSelectionValue(300);
+        break;
     case FontWeight400:
-        return FontSelectionValue(400);
+        result.weight = FontSelectionValue(400);
+        break;
     case FontWeight500:
-        return FontSelectionValue(500);
+        result.weight = FontSelectionValue(500);
+        break;
     case FontWeight600:
-        return FontSelectionValue(600);
+        result.weight = FontSelectionValue(600);
+        break;
     case FontWeight700:
-        return FontSelectionValue(700);
+        result.weight = FontSelectionValue(700);
+        break;
     case FontWeight800:
-        return FontSelectionValue(800);
+        result.weight = FontSelectionValue(800);
+        break;
     case FontWeight900:
-        return FontSelectionValue(900);
+        result.weight = FontSelectionValue(900);
+        break;
     default:
         ASSERT_NOT_REACHED();
-        return FontSelectionValue(400);
+        result.weight = FontSelectionValue(400);
+        break;
     }
-}
-
-static const FontDatabase::InstalledFont* findClosestFont(const FontDatabase::InstalledFontFamily& familyFonts, CTFontSymbolicTraits requestedTraits, FontWeight weight, FontSelectionValue stretch)
-{
-    ASSERT(!familyFonts.isEmpty());
 
-    // Parallel to familyFonts.
-    std::unique_ptr<bool[]> filter { new bool[familyFonts.size()] };
-    for (size_t i = 0; i < familyFonts.size(); ++i)
-        filter[i] = true;
+    result.width = stretch;
 
-    if (auto closestStretch = findClosestStretch(stretch, familyFonts, filter))
-        filterStretch(closestStretch.value(), familyFonts, filter);
+    if (requestedTraits & kCTFontTraitItalic)
+        result.slope = italicThreshold();
     else
-        return nullptr;
+        result.slope = FontSelectionValue();
 
-    FontSelectionValue targetStyle = requestedTraits & kCTFontTraitItalic ? FontDatabase::italicThreshold() : FontSelectionValue();
-    if (auto closestStyle = findClosestStyle(targetStyle, familyFonts, filter))
-        filterStyle(closestStyle.value(), familyFonts, filter);
-    else
-        return nullptr;
-
-    FontSelectionValue targetWeight = computeTargetWeight(weight);
-    if (auto closestWeight = findClosestWeight(targetWeight, familyFonts, filter))
-        filterWeight(closestWeight.value(), familyFonts, filter);
-    else
-        return nullptr;
-
-    return iterateActiveFontsWithReturn<const FontDatabase::InstalledFont*>(familyFonts, filter, [](const FontDatabase::InstalledFont& font, size_t) {
-        return &font;
-    }).value_or(nullptr);
+    return result;
 }
 
 #endif // !SHOULD_USE_CORE_TEXT_FONT_LOOKUP
@@ -1283,7 +1007,6 @@ static RetainPtr<CTFontRef> platformFontLookupWithFamily(const AtomicString& fam
     if (!isSystemFont(family) && whitelist.size() && !whitelist.contains(family))
         return nullptr;
 
-
 #if SHOULD_USE_CORE_TEXT_FONT_LOOKUP
     UNUSED_PARAM(stretch);
     return adoptCF(CTFontCreateForCSS(family.string().createCFString().get(), toCoreTextFontWeight(weight), requestedTraits, size));
@@ -1300,15 +1023,15 @@ static RetainPtr<CTFontRef> platformFontLookupWithFamily(const AtomicString& fam
         const auto& postScriptFont = FontDatabase::singleton().fontForPostScriptName(family);
         if (!postScriptFont.fontDescriptor)
             return nullptr;
-        if (((requestedTraits & kCTFontTraitItalic) && postScriptFont.capabilities.slope.maximum < FontDatabase::italicThreshold())
-            || (weight >= FontWeight600 && postScriptFont.capabilities.weight.maximum < FontSelectionValue(600))) {
+        if (((requestedTraits & kCTFontTraitItalic) && postScriptFont.capabilities.slope.maximum < italicThreshold())
+            || (weight >= FontWeight600 && postScriptFont.capabilities.weight.maximum < boldThreshold())) {
             auto postScriptFamilyName = adoptCF(static_cast<CFStringRef>(CTFontDescriptorCopyAttribute(postScriptFont.fontDescriptor.get(), kCTFontFamilyNameAttribute)));
             if (!postScriptFamilyName)
                 return nullptr;
             const auto& familyFonts = FontDatabase::singleton().collectionForFamily(String(postScriptFamilyName.get()));
             if (familyFonts.isEmpty())
                 return nullptr;
-            if (const auto* installedFont = findClosestFont(familyFonts, requestedTraits, weight, stretch)) {
+            if (const auto* installedFont = findClosestFont(familyFonts, calculateFontSelectionRequest(requestedTraits, weight, stretch))) {
                 if (!installedFont->fontDescriptor)
                     return nullptr;
                 return adoptCF(CTFontCreateWithFontDescriptor(installedFont->fontDescriptor.get(), size, nullptr));
@@ -1318,7 +1041,7 @@ static RetainPtr<CTFontRef> platformFontLookupWithFamily(const AtomicString& fam
         return adoptCF(CTFontCreateWithFontDescriptor(postScriptFont.fontDescriptor.get(), size, nullptr));
     }
 
-    if (const auto* installedFont = findClosestFont(familyFonts, requestedTraits, weight, stretch))
+    if (const auto* installedFont = findClosestFont(familyFonts, calculateFontSelectionRequest(requestedTraits, weight, stretch)))
         return adoptCF(CTFontCreateWithFontDescriptor(installedFont->fontDescriptor.get(), size, nullptr));
 
     return nullptr;
index 428d095..bd5ad64 100644 (file)
@@ -138,7 +138,7 @@ Ref<Font> FontCache::lastResortFallbackFont(const FontDescription& fontDescripti
     RELEASE_ASSERT_NOT_REACHED();
 }
 
-Vector<FontTraitsMask> FontCache::getTraitsInFamily(const AtomicString&)
+auto FontCache::getTraitsAndStretchInFamily(const AtomicString& familyName) -> Vector<TraitsAndStretch>
 {
     return { };
 }
index 9f5733c..4ac60ac 100644 (file)
@@ -559,7 +559,7 @@ static int CALLBACK traitsInFamilyEnumProc(CONST LOGFONT* logFont, CONST TEXTMET
     return 1;
 }
 
-Vector<FontTraitsMask> FontCache::getTraitsInFamily(const AtomicString& familyName)
+auto FontCache::getTraitsAndStretchInFamily(const AtomicString& familyName) -> Vector<TraitsAndStretch>
 {
     HWndDC hdc(0);
 
@@ -572,10 +572,10 @@ Vector<FontTraitsMask> FontCache::getTraitsInFamily(const AtomicString& familyNa
 
     TraitsInFamilyProcData procData(familyName);
     EnumFontFamiliesEx(hdc, &logFont, traitsInFamilyEnumProc, reinterpret_cast<LPARAM>(&procData), 0);
-    Vector<FontTraitsMask> result;
+    Vector<TraitsAndStretch> result;
     result.reserveInitialCapacity(procData.m_traitsMasks.size());
     for (unsigned mask : procData.m_traitsMasks)
-        result.uncheckedAppend(static_cast<FontTraitsMask>(mask));
+        result.uncheckedAppend({ static_cast<FontTraitsMask>(mask), FontSelectionRange(FontSelectionValue(), FontSelectionValue()) });
     return result;
 }
 
index a729a97..554c8d9 100644 (file)
@@ -25,8 +25,6 @@
 
 #pragma once
 
-#include <wtf/NeverDestroyed.h>
-
 namespace WebCore {
 
 enum TextRenderingMode { AutoTextRendering, OptimizeSpeed, OptimizeLegibility, GeometricPrecision };
@@ -398,162 +396,6 @@ enum FontTraitsMask {
     FontWeightMask = FontWeight100Mask | FontWeight200Mask | FontWeight300Mask | FontWeight400Mask | FontWeight500Mask | FontWeight600Mask | FontWeight700Mask | FontWeight800Mask | FontWeight900Mask
 };
 
-// Unclamped, unchecked, signed fixed-point number representing a value used for font variations.
-// Sixteen bits in total, one sign bit, two fractional bits, means the smallest positive representable value is 0.25,
-// the maximum representable value is 8191.75, and the minimum representable value is -8192.
-class FontSelectionValue {
-public:
-    FontSelectionValue() = default;
-
-    // Explicit because it is lossy.
-    explicit FontSelectionValue(int x)
-        : m_backing(x * fractionalEntropy)
-    {
-    }
-
-    // Explicit because it is lossy.
-    explicit FontSelectionValue(float x)
-        : m_backing(x * fractionalEntropy)
-    {
-    }
-
-    operator float() const
-    {
-        // floats have 23 fractional bits, but only 14 fractional bits are necessary, so every value can be represented losslessly.
-        return m_backing / static_cast<float>(fractionalEntropy);
-    }
-
-    FontSelectionValue operator+(const FontSelectionValue other) const
-    {
-        return FontSelectionValue(m_backing + other.m_backing, RawTag::RawTag);
-    }
-
-    FontSelectionValue operator-(const FontSelectionValue other) const
-    {
-        return FontSelectionValue(m_backing - other.m_backing, RawTag::RawTag);
-    }
-
-    FontSelectionValue operator*(const FontSelectionValue other) const
-    {
-        return FontSelectionValue(static_cast<int32_t>(m_backing) * other.m_backing / fractionalEntropy, RawTag::RawTag);
-    }
-
-    FontSelectionValue operator/(const FontSelectionValue other) const
-    {
-        return FontSelectionValue(static_cast<int32_t>(m_backing) / other.m_backing * fractionalEntropy, RawTag::RawTag);
-    }
-
-    FontSelectionValue operator-() const
-    {
-        return FontSelectionValue(-m_backing, RawTag::RawTag);
-    }
-
-    bool operator==(const FontSelectionValue other) const
-    {
-        return m_backing == other.m_backing;
-    }
-
-    bool operator!=(const FontSelectionValue other) const
-    {
-        return !operator==(other);
-    }
-
-    bool operator<(const FontSelectionValue other) const
-    {
-        return m_backing < other.m_backing;
-    }
-
-    bool operator<=(const FontSelectionValue other) const
-    {
-        return m_backing <= other.m_backing;
-    }
-
-    bool operator>(const FontSelectionValue other) const
-    {
-        return m_backing > other.m_backing;
-    }
-
-    bool operator>=(const FontSelectionValue other) const
-    {
-        return m_backing >= other.m_backing;
-    }
-
-    int16_t rawValue() const
-    {
-        return m_backing;
-    }
-
-    static FontSelectionValue maximumValue()
-    {
-        static NeverDestroyed<FontSelectionValue> result = FontSelectionValue(std::numeric_limits<int16_t>::max(), RawTag::RawTag);
-        return result.get();
-    }
-
-    static FontSelectionValue minimumValue()
-    {
-        static NeverDestroyed<FontSelectionValue> result = FontSelectionValue(std::numeric_limits<int16_t>::min(), RawTag::RawTag);
-        return result.get();
-    }
-
-private:
-    enum class RawTag { RawTag };
-
-    FontSelectionValue(int16_t rawValue, RawTag)
-        : m_backing(rawValue)
-    {
-    }
-
-    static constexpr int fractionalEntropy = 4;
-    int16_t m_backing { 0 };
-};
-
-// [Inclusive, Inclusive]
-struct FontSelectionRange {
-    bool isValid() const
-    {
-        return minimum <= maximum;
-    }
-
-    void expand(const FontSelectionRange& other)
-    {
-        ASSERT(other.isValid());
-        if (!isValid())
-            *this = other;
-        else {
-            minimum = std::min(minimum, other.minimum);
-            maximum = std::max(maximum, other.maximum);
-        }
-        ASSERT(isValid());
-    }
-
-    bool includes(FontSelectionValue target) const
-    {
-        return target >= minimum && target <= maximum;
-    }
-
-    FontSelectionValue minimum { FontSelectionValue(1) };
-    FontSelectionValue maximum { FontSelectionValue(0) };
-};
-
-struct FontSelectionRequest {
-    FontSelectionValue weight;
-    FontSelectionValue width;
-    FontSelectionValue slope;
-};
-
-struct FontSelectionCapabilities {
-    void expand(const FontSelectionCapabilities& capabilities)
-    {
-        weight.expand(capabilities.weight);
-        width.expand(capabilities.width);
-        slope.expand(capabilities.slope);
-    }
-
-    FontSelectionRange weight;
-    FontSelectionRange width;
-    FontSelectionRange slope;
-};
-
 enum class Kerning {
     Auto,
     Normal,