Add test for typeface style round trip.
authorbungeman <bungeman@google.com>
Mon, 25 Jul 2016 22:11:49 +0000 (15:11 -0700)
committerCommit bot <commit-bot@chromium.org>
Mon, 25 Jul 2016 22:11:49 +0000 (15:11 -0700)
This also fixes the CG and GDI ports so they pass the test.

GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2171163002

Review-Url: https://codereview.chromium.org/2171163002

include/core/SkFontStyle.h
include/core/SkTypeface.h
src/core/SkFontStyle.cpp
src/ports/SkFontConfigInterface_direct.cpp
src/ports/SkFontHost_FreeType.cpp
src/ports/SkFontHost_mac.cpp
src/ports/SkFontHost_win.cpp
src/ports/SkFontMgr_fontconfig.cpp
tests/TypefaceTest.cpp

index f4ffbb9..643b69b 100644 (file)
 class SK_API SkFontStyle {
 public:
     enum Weight {
-        kThin_Weight        = 100,
-        kExtraLight_Weight  = 200,
-        kLight_Weight       = 300,
-        kNormal_Weight      = 400,
-        kMedium_Weight      = 500,
-        kSemiBold_Weight    = 600,
-        kBold_Weight        = 700,
-        kExtraBold_Weight   = 800,
-        kBlack_Weight       = 900
+        kInvisible_Weight   =    0,
+        kThin_Weight        =  100,
+        kExtraLight_Weight  =  200,
+        kLight_Weight       =  300,
+        kNormal_Weight      =  400,
+        kMedium_Weight      =  500,
+        kSemiBold_Weight    =  600,
+        kBold_Weight        =  700,
+        kExtraBold_Weight   =  800,
+        kBlack_Weight       =  900,
+        kExtraBlack_Weight  = 1000
     };
 
     enum Width {
index 4f3879c..83008d3 100644 (file)
@@ -346,6 +346,8 @@ protected:
 
     /** Sets the fixedPitch bit. If used, must be called in the constructor. */
     void setIsFixedPitch(bool isFixedPitch) { fIsFixedPitch = isFixedPitch; }
+    /** Sets the font style. If used, must be called in the constructor. */
+    void setFontStyle(SkFontStyle style) { fStyle = style; }
 
     friend class SkScalerContext;
     static SkTypeface* GetDefaultTypeface(Style style = SkTypeface::kNormal);
index 01628ae..fc50f1f 100644 (file)
@@ -18,7 +18,7 @@ SkFontStyle::SkFontStyle() {
 
 SkFontStyle::SkFontStyle(int weight, int width, Slant slant) {
     fUnion.fU32 = 0;
-    fUnion.fR.fWeight = SkTPin<int>(weight, kThin_Weight, kBlack_Weight);
+    fUnion.fR.fWeight = SkTPin<int>(weight, kInvisible_Weight, kExtraBlack_Weight);
     fUnion.fR.fWidth = SkTPin<int>(width, kUltraCondensed_Width, kUltaExpanded_Width);
     fUnion.fR.fSlant = SkTPin<int>(slant, kUpright_Slant, kOblique_Slant);
 }
index ccea794..6e133bd 100644 (file)
@@ -418,7 +418,7 @@ static SkFontStyle skfontstyle_from_fcpattern(FcPattern* pattern) {
         { SkTFixed<FC_WEIGHT_BOLD>::value,       SkTFixed<SkFS::kBold_Weight>::value },
         { SkTFixed<FC_WEIGHT_EXTRABOLD>::value,  SkTFixed<SkFS::kExtraBold_Weight>::value },
         { SkTFixed<FC_WEIGHT_BLACK>::value,      SkTFixed<SkFS::kBlack_Weight>::value },
-        { SkTFixed<FC_WEIGHT_EXTRABLACK>::value, SkTFixed<1000>::value },
+        { SkTFixed<FC_WEIGHT_EXTRABLACK>::value, SkTFixed<SkFS::kExtraBlack_Weight>::value },
     };
     int weight = map_ranges(get_int(pattern, FC_WEIGHT, FC_WEIGHT_REGULAR),
                             weightRanges, SK_ARRAY_COUNT(weightRanges));
@@ -461,7 +461,7 @@ static void fcpattern_from_skfontstyle(SkFontStyle style, FcPattern* pattern) {
         { SkTFixed<SkFS::kBold_Weight>::value,       SkTFixed<FC_WEIGHT_BOLD>::value },
         { SkTFixed<SkFS::kExtraBold_Weight>::value,  SkTFixed<FC_WEIGHT_EXTRABOLD>::value },
         { SkTFixed<SkFS::kBlack_Weight>::value,      SkTFixed<FC_WEIGHT_BLACK>::value },
-        { SkTFixed<1000>::value,                     SkTFixed<FC_WEIGHT_EXTRABLACK>::value },
+        { SkTFixed<SkFS::kExtraBlack_Weight>::value, SkTFixed<FC_WEIGHT_EXTRABLACK>::value },
     };
     int weight = map_ranges(style.weight(), weightRanges, SK_ARRAY_COUNT(weightRanges));
 
index 4a1f12b..e093bf0 100644 (file)
@@ -1696,9 +1696,9 @@ bool SkTypeface_FreeType::Scanner::scanFont(
             { "standard", SkFontStyle::kNormal_Weight },
             { "thin", SkFontStyle::kThin_Weight },
             { "ultra", SkFontStyle::kExtraBold_Weight },
-            { "ultrablack", 1000 },
+            { "ultrablack", SkFontStyle::kExtraBlack_Weight },
             { "ultrabold", SkFontStyle::kExtraBold_Weight },
-            { "ultraheavy", 1000 },
+            { "ultraheavy", SkFontStyle::kExtraBlack_Weight },
             { "ultralight", SkFontStyle::kExtraLight_Weight },
         };
         int const index = SkStrLCSearch(&commonWeights[0].name, SK_ARRAY_COUNT(commonWeights),
index 68668c2..41cb52b 100644 (file)
@@ -370,31 +370,90 @@ private:
 
 ///////////////////////////////////////////////////////////////////////////////
 
-static bool find_dict_float(CFDictionaryRef dict, CFStringRef name, float* value) {
+static bool find_dict_CGFloat(CFDictionaryRef dict, CFStringRef name, CGFloat* value) {
     CFNumberRef num;
     return CFDictionaryGetValueIfPresent(dict, name, (const void**)&num)
     && CFNumberIsFloatType(num)
-    && CFNumberGetValue(num, kCFNumberFloatType, value);
+    && CFNumberGetValue(num, kCFNumberCGFloatType, value);
 }
 
-static int unit_weight_to_fontstyle(float unit) {
-    float value;
-    if (unit < 0) {
-        value = 100 + (1 + unit) * 300;
-    } else {
-        value = 400 + unit * 500;
+template <typename S, typename D, typename C> struct LinearInterpolater {
+    struct Mapping {
+        S src_val;
+        D dst_val;
+    };
+    constexpr LinearInterpolater(Mapping const mapping[], int mappingCount)
+        : fMapping(mapping), fMappingCount(mappingCount) {}
+
+    static D map(S value, S src_min, S src_max, D dst_min, D dst_max) {
+        SkASSERT(src_min < src_max);
+        SkASSERT(dst_min <= dst_max);
+        return C()(dst_min + (((value - src_min) * (dst_max - dst_min)) / (src_max - src_min)));
     }
-    return sk_float_round2int(value);
-}
 
-static int unit_width_to_fontstyle(float unit) {
-    float value;
-    if (unit < 0) {
-        value = 1 + (1 + unit) * 4;
-    } else {
-        value = 5 + unit * 4;
+    int map(S val) const {
+        // -Inf to [0]
+        if (val < fMapping[0].src_val) {
+            return fMapping[0].dst_val;
+        }
+
+        // Linear from [i] to [i+1]
+        for (int i = 0; i < fMappingCount - 1; ++i) {
+            if (val < fMapping[i+1].src_val) {
+                return map(val, fMapping[i].src_val, fMapping[i+1].src_val,
+                                fMapping[i].dst_val, fMapping[i+1].dst_val);
+            }
+        }
+
+        // From [n] to +Inf
+        // if (fcweight < Inf)
+        return fMapping[fMappingCount - 1].dst_val;
     }
-    return sk_float_round2int(value);
+
+    Mapping const * fMapping;
+    int fMappingCount;
+};
+
+struct RoundCGFloatToInt {
+    int operator()(CGFloat s) { return s + 0.5; }
+};
+
+static int ct_weight_to_fontstyle(CGFloat cgWeight) {
+    using Interpolator = LinearInterpolater<CGFloat, int, RoundCGFloatToInt>;
+
+    // Values determined by creating font data with every weight, creating a CTFont,
+    // and asking the CTFont for its weight. See TypefaceStyle test for basics.
+
+    // Note that Mac supports the old OS2 version A so 0 through 10 are as if multiplied by 100.
+    // However, on this end we can't tell.
+    static constexpr Interpolator::Mapping weightMappings[] = {
+        { -1.00,    0 },
+        { -0.70,  100 },
+        { -0.50,  200 },
+        { -0.23,  300 },
+        {  0.00,  400 },
+        {  0.20,  500 },
+        {  0.30,  600 },
+        {  0.40,  700 },
+        {  0.60,  800 },
+        {  0.80,  900 },
+        {  1.00, 1000 },
+    };
+    static constexpr Interpolator interpolater(weightMappings, SK_ARRAY_COUNT(weightMappings));
+    return interpolater.map(cgWeight);
+}
+
+static int ct_width_to_fontstyle(CGFloat cgWidth) {
+    using Interpolator = LinearInterpolater<CGFloat, int, RoundCGFloatToInt>;
+
+    // Values determined by creating font data with every width, creating a CTFont,
+    // and asking the CTFont for its width. See TypefaceStyle test for basics.
+    static constexpr Interpolator::Mapping widthMappings[] = {
+        { -0.5,  0 },
+        {  0.5, 10 },
+    };
+    static constexpr Interpolator interpolater(widthMappings, SK_ARRAY_COUNT(widthMappings));
+    return interpolater.map(cgWidth);
 }
 
 static SkFontStyle fontstyle_from_descriptor(CTFontDescriptorRef desc) {
@@ -404,19 +463,19 @@ static SkFontStyle fontstyle_from_descriptor(CTFontDescriptorRef desc) {
         return SkFontStyle();
     }
 
-    float weight, width, slant;
-    if (!find_dict_float(dict, kCTFontWeightTrait, &weight)) {
+    CGFloat weight, width, slant;
+    if (!find_dict_CGFloat(dict, kCTFontWeightTrait, &weight)) {
         weight = 0;
     }
-    if (!find_dict_float(dict, kCTFontWidthTrait, &width)) {
+    if (!find_dict_CGFloat(dict, kCTFontWidthTrait, &width)) {
         width = 0;
     }
-    if (!find_dict_float(dict, kCTFontSlantTrait, &slant)) {
+    if (!find_dict_CGFloat(dict, kCTFontSlantTrait, &slant)) {
         slant = 0;
     }
 
-    return SkFontStyle(unit_weight_to_fontstyle(weight),
-                       unit_width_to_fontstyle(width),
+    return SkFontStyle(ct_weight_to_fontstyle(weight),
+                       ct_width_to_fontstyle(width),
                        slant ? SkFontStyle::kItalic_Slant
                              : SkFontStyle::kUpright_Slant);
 }
index 22262a5..226f219 100644 (file)
@@ -210,8 +210,6 @@ public:
         , fLogFont(lf)
         , fSerializeAsStream(serializeAsStream)
     {
-
-        // If the font has cubic outlines, it will not be rendered with ClearType.
         HFONT font = CreateFontIndirect(&lf);
 
         HDC deviceContext = ::CreateCompatibleDC(nullptr);
@@ -234,9 +232,11 @@ public:
 
         // The fixed pitch bit is set if the font is *not* fixed pitch.
         this->setIsFixedPitch((textMetric.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0);
+        this->setFontStyle(SkFontStyle(textMetric.tmWeight, style.width(), style.slant()));
 
         // Used a logfont on a memory context, should never get a device font.
         // Therefore all TMPF_DEVICE will be PostScript (cubic) fonts.
+        // If the font has cubic outlines, it will not be rendered with ClearType.
         fCanBeLCD = !((textMetric.tmPitchAndFamily & TMPF_VECTOR) &&
                       (textMetric.tmPitchAndFamily & TMPF_DEVICE));
     }
index 0876fb6..d4d8737 100644 (file)
@@ -326,7 +326,7 @@ static SkFontStyle skfontstyle_from_fcpattern(FcPattern* pattern) {
         { SkTFixed<FC_WEIGHT_BOLD>::value,       SkTFixed<SkFS::kBold_Weight>::value },
         { SkTFixed<FC_WEIGHT_EXTRABOLD>::value,  SkTFixed<SkFS::kExtraBold_Weight>::value },
         { SkTFixed<FC_WEIGHT_BLACK>::value,      SkTFixed<SkFS::kBlack_Weight>::value },
-        { SkTFixed<FC_WEIGHT_EXTRABLACK>::value, SkTFixed<1000>::value },
+        { SkTFixed<FC_WEIGHT_EXTRABLACK>::value, SkTFixed<SkFS::kExtraBlack_Weight>::value },
     };
     int weight = map_ranges(get_int(pattern, FC_WEIGHT, FC_WEIGHT_REGULAR),
                             weightRanges, SK_ARRAY_COUNT(weightRanges));
@@ -371,7 +371,7 @@ static void fcpattern_from_skfontstyle(SkFontStyle style, FcPattern* pattern) {
         { SkTFixed<SkFS::kBold_Weight>::value,       SkTFixed<FC_WEIGHT_BOLD>::value },
         { SkTFixed<SkFS::kExtraBold_Weight>::value,  SkTFixed<FC_WEIGHT_EXTRABOLD>::value },
         { SkTFixed<SkFS::kBlack_Weight>::value,      SkTFixed<FC_WEIGHT_BLACK>::value },
-        { SkTFixed<1000>::value,                     SkTFixed<FC_WEIGHT_EXTRABLACK>::value },
+        { SkTFixed<SkFS::kExtraBlack_Weight>::value, SkTFixed<FC_WEIGHT_EXTRABLACK>::value },
     };
     int weight = map_ranges(style.weight(), weightRanges, SK_ARRAY_COUNT(weightRanges));
 
index 4c65fa3..33fa887 100644 (file)
@@ -5,11 +5,89 @@
  * found in the LICENSE file.
  */
 
+#include "SkData.h"
+#include "SkOTTable_OS_2.h"
+#include "SkSFNTHeader.h"
+#include "SkStream.h"
 #include "SkRefCnt.h"
 #include "SkTypeface.h"
 #include "SkTypefaceCache.h"
+#include "Resources.h"
 #include "Test.h"
 
+#include <memory>
+
+static void TypefaceStyle_test(skiatest::Reporter* reporter,
+                               uint16_t weight, uint16_t width, SkData* data)
+{
+    sk_sp<SkData> dataCopy;
+    SkData* dataToUse = data;
+    if (!dataToUse->unique()) {
+        dataCopy = SkData::MakeWithCopy(data->data(), data->size());
+        dataToUse = dataCopy.get();
+    }
+    SkSFNTHeader* sfntHeader = static_cast<SkSFNTHeader*>(dataToUse->writable_data());
+
+    SkSFNTHeader::TableDirectoryEntry* tableEntry =
+        SkTAfter<SkSFNTHeader::TableDirectoryEntry>(sfntHeader);
+    SkSFNTHeader::TableDirectoryEntry* os2TableEntry = nullptr;
+    int numTables = SkEndian_SwapBE16(sfntHeader->numTables);
+    for (int tableEntryIndex = 0; tableEntryIndex < numTables; ++tableEntryIndex) {
+        if (SkOTTableOS2::TAG == tableEntry[tableEntryIndex].tag) {
+            os2TableEntry = tableEntry + tableEntryIndex;
+            break;
+        }
+    }
+    SkASSERT_RELEASE(os2TableEntry);
+
+    size_t os2TableOffset = SkEndian_SwapBE32(os2TableEntry->offset);
+    SkOTTableOS2_V0* os2Table = SkTAddOffset<SkOTTableOS2_V0>(sfntHeader, os2TableOffset);
+    os2Table->usWeightClass.value = SkEndian_SwapBE16(weight);
+    using WidthType = SkOTTableOS2_V0::WidthClass::Value;
+    os2Table->usWidthClass.value = static_cast<WidthType>(SkEndian_SwapBE16(width));
+
+    sk_sp<SkTypeface> newTypeface(SkTypeface::MakeFromStream(new SkMemoryStream(dataToUse)));
+    SkASSERT_RELEASE(newTypeface);
+
+    SkFontStyle newStyle = newTypeface->fontStyle();
+
+    //printf("%d, %f\n", weight, (newStyle.weight() - (float)0x7FFF) / (float)0x7FFF);
+    //printf("%d, %f\n", width , (newStyle.width()  - (float)0x7F)   / (float)0x7F);
+    //printf("%d, %d\n", weight, newStyle.weight());
+    //printf("%d, %d\n", width , newStyle.width());
+
+    // Some back-ends (CG, GDI, DW) support OS/2 version A which uses 0 - 10 (but all differently).
+    REPORTER_ASSERT(reporter,
+                    newStyle.weight() == weight ||
+                    (weight <=   10 && newStyle.weight() == 100 * weight) ||
+                    (weight ==    4 && newStyle.weight() == 350) ||  // GDI weirdness
+                    (weight ==    5 && newStyle.weight() == 400) ||  // GDI weirdness
+                    (weight ==    0 && newStyle.weight() ==   1) ||  // DW weirdness
+                    (weight == 1000 && newStyle.weight() == 999)     // DW weirdness
+    );
+
+    // Some back-ends (GDI) don't support width, ensure these always report 'medium'.
+    REPORTER_ASSERT(reporter,
+                    newStyle.width() == width ||
+                    newStyle.width() == 5);
+}
+DEF_TEST(TypefaceStyle, reporter) {
+    std::unique_ptr<SkStreamAsset> stream(GetResourceAsStream("/fonts/Em.ttf"));
+    if (!stream) {
+        REPORT_FAILURE(reporter, "/fonts/Em.ttf", SkString("Cannot load resource"));
+        return;
+    }
+    sk_sp<SkData> data(SkData::MakeFromStream(stream.get(), stream->getLength()));
+
+    using SkFS = SkFontStyle;
+    for (int weight = SkFS::kInvisible_Weight; weight <= SkFS::kExtraBlack_Weight; ++weight) {
+        TypefaceStyle_test(reporter, weight, 5, data.get());
+    }
+    for (int width = SkFS::kUltraCondensed_Width; width <= SkFS::kUltaExpanded_Width; ++width) {
+        TypefaceStyle_test(reporter, 400, width, data.get());
+    }
+}
+
 DEF_TEST(Typeface, reporter) {
 
     sk_sp<SkTypeface> t1(SkTypeface::MakeFromName(nullptr, SkFontStyle()));