Changeset 214572 in webkit


Ignore:
Timestamp:
Mar 29, 2017 4:12:08 PM (7 years ago)
Author:
mmaxfield@apple.com
Message:

Try to normalize variation ranges
https://bugs.webkit.org/show_bug.cgi?id=170119

Reviewed by Simon Fraser.

Source/WebCore:

TrueType GX-style variation fonts use one particular scale for values on their
weight/width/slope axes - usually the values lie between -1 and 1 on that scale.
However, OpenType 1.8-style fonts use the CSS scale for values on these axes.
For the purposes of font selection, these values need to lie on the same scale.
However, when font selection is completed and the variation values are actually
being applied to the fonts, values which lie on the font's actual scale need to
be applied. This patch adds normalize*() and denormalize*() functions to perform
both of these operations.

The conversion itself between the two scales isn't an exact mapping. Mapping
slope is just a linear relationship with 0deg <=> 0 and 20deg <=> 1 (as per the
CSS Fonts spec). Mapping widths is similar, it uses a 2-component piecewise
linear relationship which includes the values given in the Microsoft OpenType
spec for the OS/2 table's usWidthClass field. Weights are more difficult, so I
plotted the CSS weights and the GX-style weights for every style of San
Francisco, saw that the relationship appears to be linear, and ran a linear
regression to compute the line equation.

As for the actual discrimination of determining whether a font is a GX-style
font or not, we can use the presence of the 'STAT' table. This table didn't
exist when GX fonts were being created, and OpenType 1.8 variable fonts are
required to have this table.

Facebook uses the string ".SFNSText" in their @font-face blocks. This font is
a variation font, but uses the GX-style values. Facebook asks us to create
this font with a weight of 700, and because the values in the font are around
1.0, we were erroneously thinking that the font wasn't bold, so we were then
applying synthetic bold. This was causing text on facebook to look fuzzy and
ugly.

Test: fast/text/variations/font-selection-properties-expected.html

  • platform/graphics/cocoa/FontCacheCoreText.cpp:

(WebCore::isGXVariableFont):
(WebCore::normalizeWeight):
(WebCore::normalizeSlope):
(WebCore::denormalizeWeight):
(WebCore::denormalizeWidth):
(WebCore::denormalizeSlope):
(WebCore::normalizeWidth):
(WebCore::preparePlatformFont): Instead of using FontSelectionValues for the
intermediate values, we should use floats instead. This is because
FontSelectionValues are fixed-point numbers with the denominator having 2 bits.
When using this data type to represent values on the GX scale, which are usually
between 0 and 1, you lose a lot of fidelity. Instead, our intermediate
calculations should be done with floats, and converted to FontSelectionValues at
the end when they are representative of values on the CSS scale.
(WebCore::stretchFromCoreTextTraits):
(WebCore::fontWeightFromCoreText):
(WebCore::extractVariationBounds):
(WebCore::variationCapabilitiesForFontDescriptor):
(WebCore::capabilitiesForFontDescriptor):

LayoutTests:

  • fast/text/variations/font-selection-properties-expected.html:
Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r214570 r214572  
     12017-03-29  Myles C. Maxfield  <mmaxfield@apple.com>
     2
     3        Try to normalize variation ranges
     4        https://bugs.webkit.org/show_bug.cgi?id=170119
     5
     6        Reviewed by Simon Fraser.
     7
     8        * fast/text/variations/font-selection-properties-expected.html:
     9
    1102017-03-29  Ryan Haddad  <ryanhaddad@apple.com>
    211
  • trunk/LayoutTests/fast/text/variations/font-selection-properties-expected.html

    r213505 r214572  
    1111<body>
    1212This test makes sure that the font selection properties affect font variations.
    13 <div style="font-family: Boxis; font-variation-settings: 'wdth' 900;">Hello</div>
     13<div style="font-family: Boxis; font-variation-settings: 'wdth' 5.6666;">Hello</div>
    1414<div style="font-family: Boxis; font-variation-settings: 'wdth' 100;">Hello</div>
    1515</body>
  • trunk/Source/WebCore/ChangeLog

    r214571 r214572  
     12017-03-29  Myles C. Maxfield  <mmaxfield@apple.com>
     2
     3        Try to normalize variation ranges
     4        https://bugs.webkit.org/show_bug.cgi?id=170119
     5
     6        Reviewed by Simon Fraser.
     7
     8        TrueType GX-style variation fonts use one particular scale for values on their
     9        weight/width/slope axes - usually the values lie between -1 and 1 on that scale.
     10        However, OpenType 1.8-style fonts use the CSS scale for values on these axes.
     11        For the purposes of font selection, these values need to lie on the same scale.
     12        However, when font selection is completed and the variation values are actually
     13        being applied to the fonts, values which lie on the font's actual scale need to
     14        be applied. This patch adds normalize*() and denormalize*() functions to perform
     15        both of these operations.
     16
     17        The conversion itself between the two scales isn't an exact mapping. Mapping
     18        slope is just a linear relationship with 0deg <=> 0 and 20deg <=> 1 (as per the
     19        CSS Fonts spec). Mapping widths is similar, it uses a 2-component piecewise
     20        linear relationship which includes the values given in the Microsoft OpenType
     21        spec for the OS/2 table's usWidthClass field. Weights are more difficult, so I
     22        plotted the CSS weights and the GX-style weights for every style of San
     23        Francisco, saw that the relationship appears to be linear, and ran a linear
     24        regression to compute the line equation.
     25
     26        As for the actual discrimination of determining whether a font is a GX-style
     27        font or not, we can use the presence of the 'STAT' table. This table didn't
     28        exist when GX fonts were being created, and OpenType 1.8 variable fonts are
     29        required to have this table.
     30
     31        Facebook uses the string ".SFNSText" in their @font-face blocks. This font is
     32        a variation font, but uses the GX-style values. Facebook asks us to create
     33        this font with a weight of 700, and because the values in the font are around
     34        1.0, we were erroneously thinking that the font wasn't bold, so we were then
     35        applying synthetic bold. This was causing text on facebook to look fuzzy and
     36        ugly.
     37
     38        Test: fast/text/variations/font-selection-properties-expected.html
     39
     40        * platform/graphics/cocoa/FontCacheCoreText.cpp:
     41        (WebCore::isGXVariableFont):
     42        (WebCore::normalizeWeight):
     43        (WebCore::normalizeSlope):
     44        (WebCore::denormalizeWeight):
     45        (WebCore::denormalizeWidth):
     46        (WebCore::denormalizeSlope):
     47        (WebCore::normalizeWidth):
     48        (WebCore::preparePlatformFont): Instead of using FontSelectionValues for the
     49        intermediate values, we should use floats instead. This is because
     50        FontSelectionValues are fixed-point numbers with the denominator having 2 bits.
     51        When using this data type to represent values on the GX scale, which are usually
     52        between 0 and 1, you lose a lot of fidelity. Instead, our intermediate
     53        calculations should be done with floats, and converted to FontSelectionValues at
     54        the end when they are representative of values on the CSS scale.
     55        (WebCore::stretchFromCoreTextTraits):
     56        (WebCore::fontWeightFromCoreText):
     57        (WebCore::extractVariationBounds):
     58        (WebCore::variationCapabilitiesForFontDescriptor):
     59        (WebCore::capabilitiesForFontDescriptor):
     60
    1612017-03-29  Saam Barati  <sbarati@apple.com>
    262
  • trunk/Source/WebCore/platform/graphics/cocoa/FontCacheCoreText.cpp

    r214364 r214572  
    429429    return CFStringGetLength(name.get()) > 0 && CFStringGetCharacterAtIndex(name.get(), 0) == '.';
    430430}
    431 #endif
     431
     432static inline bool isGXVariableFont(CTFontRef font)
     433{
     434    auto tables = adoptCF(CTFontCopyAvailableTables(font, kCTFontTableOptionNoOptions));
     435    if (!tables)
     436        return false;
     437    auto size = CFArrayGetCount(tables.get());
     438    for (CFIndex i = 0; i < size; ++i) {
     439        // This is so yucky.
     440        // https://developer.apple.com/reference/coretext/1510774-ctfontcopyavailabletables
     441        // "The returned set will contain unboxed values, which can be extracted like so:"
     442        // "CTFontTableTag tag = (CTFontTableTag)(uintptr_t)CFArrayGetValueAtIndex(tags, index);"
     443        CTFontTableTag tableTag = static_cast<CTFontTableTag>(reinterpret_cast<uintptr_t>(CFArrayGetValueAtIndex(tables.get(), i)));
     444        if (tableTag == 'stat')
     445            return false;
     446    }
     447    return true;
     448}
     449
     450// These values were calculated by performing a linear regression on the CSS weights/widths/slopes and Core Text weights/widths/slopes of San Francisco.
     451// FIXME: <rdar://problem/31312602> Get the real values from Core Text.
     452static inline float normalizeWeight(float value)
     453{
     454    return 523.7 * value - 109.3;
     455}
     456
     457static inline float normalizeSlope(float value)
     458{
     459    return value * 300;
     460}
     461
     462static inline float denormalizeWeight(float value)
     463{
     464    return (value + 109.3) / 523.7;
     465}
     466
     467static inline float denormalizeWidth(float value)
     468{
     469    if (value < 125)
     470        return (value - 100) / 50;
     471    return (value - 50) / 150;
     472}
     473
     474static inline float denormalizeSlope(float value)
     475{
     476    return value / 300;
     477}
     478#endif
     479
     480static inline float normalizeWidth(float value)
     481{
     482    if (value < 0.5)
     483        return value * 50 + 100;
     484    return value * 150 + 50;
     485}
    432486
    433487RetainPtr<CTFontRef> preparePlatformFont(CTFontRef originalFont, TextRenderingMode textRenderingMode, const FontFeatureSettings* fontFaceFeatures, const FontVariantSettings* fontFaceVariantSettings, const FontFeatureSettings& features, const FontVariantSettings& variantSettings, FontSelectionRequest fontSelectionRequest, const FontVariationSettings& variations, FontOpticalSizing fontOpticalSizing, float size)
     
    484538    VariationsMap variationsToBeApplied;
    485539
     540    bool needsConversion = isGXVariableFont(originalFont);
    486541    auto applyVariationValue = [&](const FontTag& tag, float value, bool isDefaultValue) {
    487542        // FIXME: Remove when <rdar://problem/28707822> is fixed
     
    508563    // The system font is somewhat magical. Don't mess with its variations.
    509564    if (!fontIsSystemFont(originalFont)) {
    510         applyVariation({{'w', 'g', 'h', 't'}}, static_cast<float>(fontSelectionRequest.weight));
    511         applyVariation({{'w', 'd', 't', 'h'}}, static_cast<float>(fontSelectionRequest.width));
    512         applyVariation({{'s', 'l', 'n', 't'}}, static_cast<float>(fontSelectionRequest.slope));
     565        float weight = fontSelectionRequest.weight;
     566        float width = fontSelectionRequest.width;
     567        float slope = fontSelectionRequest.slope;
     568        if (needsConversion) {
     569            weight = denormalizeWeight(weight);
     570            width = denormalizeWidth(width);
     571            slope = denormalizeSlope(slope);
     572        }
     573        applyVariation({{'w', 'g', 'h', 't'}}, weight);
     574        applyVariation({{'w', 'd', 't', 'h'}}, width);
     575        applyVariation({{'s', 'l', 'n', 't'}}, slope);
    513576    }
    514577
     
    602665}
    603666
    604 static FontSelectionValue stretchFromCoreTextTraits(CFDictionaryRef traits)
     667static float stretchFromCoreTextTraits(CFDictionaryRef traits)
    605668{
    606669    auto widthNumber = static_cast<CFNumberRef>(CFDictionaryGetValue(traits, kCTFontWidthTrait));
    607     if (widthNumber) {
    608         // FIXME: The normalization from Core Text's [-1, 1] range to CSS's [50%, 200%] range isn't perfect.
    609         float ctWidth;
    610         auto success = CFNumberGetValue(widthNumber, kCFNumberFloatType, &ctWidth);
    611         ASSERT_UNUSED(success, success);
    612         return FontSelectionValue(ctWidth < 0.5 ? ctWidth * 50 + 100 : ctWidth * 150 + 50);
    613     }
    614     return normalStretchValue();
     670    if (!widthNumber)
     671        return normalStretchValue();
     672
     673    float ctWidth;
     674    auto success = CFNumberGetValue(widthNumber, kCFNumberFloatType, &ctWidth);
     675    ASSERT_UNUSED(success, success);
     676    return normalizeWidth(ctWidth);
    615677}
    616678
     
    709771
    710772#if SHOULD_USE_CORE_TEXT_FONT_LOOKUP
    711 static FontSelectionValue fontWeightFromCoreText(CGFloat weight)
     773static float fontWeightFromCoreText(CGFloat weight)
    712774{
    713775    if (weight < -0.6)
    714         return FontSelectionValue(100);
     776        return 100;
    715777    if (weight < -0.365)
    716         return FontSelectionValue(200);
     778        return 200;
    717779    if (weight < -0.115)
    718         return FontSelectionValue(300);
     780        return 300;
    719781    if (weight <  0.130)
    720         return FontSelectionValue(400);
     782        return 400;
    721783    if (weight <  0.235)
    722         return FontSelectionValue(500);
     784        return 500;
    723785    if (weight <  0.350)
    724         return FontSelectionValue(600);
     786        return 600;
    725787    if (weight <  0.500)
    726         return FontSelectionValue(700);
     788        return 700;
    727789    if (weight <  0.700)
    728         return FontSelectionValue(800);
    729     return FontSelectionValue(900);
     790        return 800;
     791    return 900;
    730792}
    731793#endif
     
    836898};
    837899
     900// Because this struct holds intermediate values which may be in the compressed -1 - 1 GX range, we don't want to use the relatively large
     901// quantization of FontSelectionValue. Instead, do this logic with floats.
     902struct MinMax {
     903    float minimum;
     904    float maximum;
     905};
     906
    838907struct VariationCapabilities {
    839     std::optional<FontSelectionRange> weight;
    840     std::optional<FontSelectionRange> width;
    841     std::optional<FontSelectionRange> slope;
     908    std::optional<MinMax> weight;
     909    std::optional<MinMax> width;
     910    std::optional<MinMax> slope;
    842911};
    843912
    844913#if ENABLE(VARIATION_FONTS)
    845 static std::optional<FontSelectionRange> extractVariationBounds(CFDictionaryRef axis)
     914static std::optional<MinMax> extractVariationBounds(CFDictionaryRef axis)
    846915{
    847916    CFNumberRef minimumValue = static_cast<CFNumberRef>(CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey));
     
    852921    CFNumberGetValue(maximumValue, kCFNumberFloatType, &rawMaximumValue);
    853922    if (rawMinimumValue < rawMaximumValue)
    854         return {{ FontSelectionValue(rawMinimumValue), FontSelectionValue(rawMaximumValue) }};
     923        return {{ rawMinimumValue, rawMaximumValue }};
    855924    return std::nullopt;
    856925}
     
    865934        return result;
    866935
    867     auto variations = adoptCF(CTFontCopyVariationAxes(adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor, 0, nullptr)).get()));
     936    auto font = adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor, 0, nullptr));
     937    auto variations = adoptCF(CTFontCopyVariationAxes(font.get()));
    868938    if (!variations)
    869939        return result;
     
    886956            result.slope = extractVariationBounds(axis);
    887957    }
     958
     959    if (isGXVariableFont(font.get())) {
     960        if (result.weight)
     961            result.weight = {{ normalizeWeight(result.weight.value().minimum), normalizeWeight(result.weight.value().maximum) }};
     962        if (result.width)
     963            result.width = {{ normalizeWidth(result.width.value().minimum), normalizeWidth(result.width.value().maximum) }};
     964        if (result.slope)
     965            result.slope = {{ normalizeSlope(result.slope.value().minimum), normalizeSlope(result.slope.value().maximum) }};
     966    }
    888967#else
    889968    UNUSED_PARAM(fontDescriptor);
     
    920999                    auto success = CFNumberGetValue(symbolicTraitsNumber, kCFNumberSInt32Type, &symbolicTraits);
    9211000                    ASSERT_UNUSED(success, success);
    922                     auto slopeValue = symbolicTraits & kCTFontTraitItalic ? italicValue() : normalItalicValue();
     1001                    auto slopeValue = static_cast<float>(symbolicTraits & kCTFontTraitItalic ? italicValue() : normalItalicValue());
    9231002                    variationCapabilities.slope = {{ slopeValue, slopeValue }};
    9241003                } else
    925                     variationCapabilities.slope = {{ normalItalicValue(), normalItalicValue() }};
     1004                    variationCapabilities.slope = {{ static_cast<float>(normalItalicValue()), static_cast<float>(normalItalicValue()) }};
    9261005            }
    9271006
     
    9361015                    variationCapabilities.weight = {{ weightValue, weightValue }};
    9371016                } else
    938                     variationCapabilities.weight = {{ normalWeightValue(), normalWeightValue() }};
     1017                    variationCapabilities.weight = {{ static_cast<float>(normalWeightValue()), static_cast<float>(normalWeightValue()) }};
    9391018            }
    9401019#endif
     
    9491028            auto success = CFNumberGetValue(weightNumber.get(), kCFNumberFloatType, &cssWeight);
    9501029            ASSERT_UNUSED(success, success);
    951             auto weightValue = FontSelectionValue(cssWeight);
    952             variationCapabilities.weight = {{ weightValue, weightValue }};
     1030            variationCapabilities.weight = {{ cssWeight, cssWeight }};
    9531031        } else
    954             variationCapabilities.weight = {{ normalWeightValue(), normalWeightValue() }};
    955     }
    956 #endif
    957 
    958     return { variationCapabilities.weight.value(), variationCapabilities.width.value(), variationCapabilities.slope.value() };
     1032            variationCapabilities.weight = {{ static_cast<float>(normalWeightValue()), static_cast<float>(normalWeightValue()) }};
     1033    }
     1034#endif
     1035
     1036    return {{ FontSelectionValue(variationCapabilities.weight.value().minimum), FontSelectionValue(variationCapabilities.weight.value().maximum) },
     1037        { FontSelectionValue(variationCapabilities.width.value().minimum), FontSelectionValue(variationCapabilities.width.value().maximum) },
     1038        { FontSelectionValue(variationCapabilities.slope.value().minimum), FontSelectionValue(variationCapabilities.slope.value().maximum) }};
    9591039}
    9601040
Note: See TracChangeset for help on using the changeset viewer.