diff --git a/resources/fonts/NotoSansCJK-VF-subset.otf.ttc b/resources/fonts/NotoSansCJK-VF-subset.otf.ttc new file mode 100644 index 0000000000..24b85d389c Binary files /dev/null and b/resources/fonts/NotoSansCJK-VF-subset.otf.ttc differ diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp index 7f35ef7b2d..ce55fde6a5 100644 --- a/src/ports/SkFontHost_FreeType.cpp +++ b/src/ports/SkFontHost_FreeType.cpp @@ -33,6 +33,7 @@ #include "src/utils/SkMatrix22.h" #include +#include #include #include @@ -1880,9 +1881,13 @@ bool SkTypeface_FreeType::Scanner::scanFont( slant = SkFontStyle::kItalic_Slant; } - PS_FontInfoRec psFontInfo; + bool hasAxes = face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS; TT_OS2* os2 = static_cast(FT_Get_Sfnt_Table(face.get(), ft_sfnt_os2)); - if (os2 && os2->version != 0xffff) { + bool hasOs2 = os2 && os2->version != 0xffff; + + PS_FontInfoRec psFontInfo; + + if (hasOs2) { weight = os2->usWeightClass; width = os2->usWidthClass; @@ -1890,7 +1895,68 @@ bool SkTypeface_FreeType::Scanner::scanFont( if (SkToBool(os2->fsSelection & (1u << 9))) { slant = SkFontStyle::kOblique_Slant; } - } else if (0 == FT_Get_PS_Font_Info(face.get(), &psFontInfo) && psFontInfo.weight) { + } + + // Let variable axes override properties from the OS/2 table. + if (hasAxes) { + AxisDefinitions axisDefinitions; + if (GetAxes(face.get(), &axisDefinitions)) { + size_t numAxes = axisDefinitions.size(); + static constexpr SkFourByteTag wghtTag = SkSetFourByteTag('w', 'g', 'h', 't'); + static constexpr SkFourByteTag wdthTag = SkSetFourByteTag('w', 'd', 't', 'h'); + static constexpr SkFourByteTag slntTag = SkSetFourByteTag('s', 'l', 'n', 't'); + std::optional wghtIndex; + std::optional wdthIndex; + std::optional slntIndex; + for(size_t i = 0; i < numAxes; ++i) { + if (axisDefinitions[i].fTag == wghtTag) { + // Rough validity check, is there sufficient spread and are ranges + // within 0-1000. + int wghtRange = SkFixedToScalar(axisDefinitions[i].fMaximum) - + SkFixedToScalar(axisDefinitions[i].fMinimum); + if (wghtRange > 5 && wghtRange <= 1000 && + SkFixedToScalar(axisDefinitions[i].fMaximum) <= 1000) { + wghtIndex = i; + } + } + if (axisDefinitions[i].fTag == wdthTag) { + // Rough validity check, is there a spread and are ranges within + // 0-500. + int widthRange = SkFixedToScalar(axisDefinitions[i].fMaximum) - + SkFixedToScalar(axisDefinitions[i].fMinimum); + if (widthRange > 0 && widthRange <= 500 && + SkFixedToScalar(axisDefinitions[i].fMaximum) <= 500) + wdthIndex = i; + } + if (axisDefinitions[i].fTag == slntTag) + slntIndex = i; + } + SkAutoSTMalloc<4, FT_Fixed> coords(numAxes); + if ((wghtIndex || wdthIndex || slntIndex) && + !FT_Get_Var_Design_Coordinates(face.get(), numAxes, coords.get())) { + if (wghtIndex) { + SkASSERT(*wghtIndex < numAxes); + weight = SkScalarRoundToInt(SkFixedToScalar(coords[*wghtIndex])); + } + if (wdthIndex) { + SkASSERT(*wdthIndex < numAxes); + width = SkScalarRoundToInt(SkFixedToScalar(coords[*wdthIndex])); + } + if (slntIndex) { + SkASSERT(*slntIndex < numAxes); + // https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_slnt + // "Scale interpretation: Values can be interpreted as the angle, + // in counter-clockwise degrees, of oblique slant from whatever + // the designer considers to be upright for that font design." + if (SkFixedToScalar(coords[*slntIndex]) < 0) { + slant = SkFontStyle::kOblique_Slant; + } + } + } + } + } + + if (!hasOs2 && !hasAxes && 0 == FT_Get_PS_Font_Info(face.get(), &psFontInfo) && psFontInfo.weight) { static const struct { char const * const name; int const weight; diff --git a/tests/FontMgrFontConfigTest.cpp b/tests/FontMgrFontConfigTest.cpp index d41596f4b4..ddda539b13 100644 --- a/tests/FontMgrFontConfigTest.cpp +++ b/tests/FontMgrFontConfigTest.cpp @@ -10,13 +10,17 @@ #include "include/core/SkFont.h" #include "include/core/SkFontMgr.h" #include "include/core/SkTypeface.h" +#include "include/ports/SkFontMgr_FontConfigInterface.h" #include "include/ports/SkFontMgr_fontconfig.h" +#include "src/ports/SkFontConfigInterface_direct.h" #include "tests/Test.h" #include "tools/Resources.h" #include -static bool bitmap_compare(const SkBitmap& ref, const SkBitmap& test) { +namespace { + +bool bitmap_compare(const SkBitmap& ref, const SkBitmap& test) { for (int y = 0; y < test.height(); ++y) { for (int x = 0; x < test.width(); ++x) { SkColor testColor = test.getColor(x, y); @@ -29,17 +33,38 @@ static bool bitmap_compare(const SkBitmap& ref, const SkBitmap& test) { return true; } -DEF_TEST(FontMgrFontConfig, reporter) { +FcConfig* build_fontconfig_with_fontfile(const char* fontFilename) { FcConfig* config = FcConfigCreate(); // FontConfig may modify the passed path (make absolute or other). FcConfigSetSysRoot(config, reinterpret_cast(GetResourcePath("").c_str())); // FontConfig will lexically compare paths against its version of the sysroot. - SkString distortablePath(reinterpret_cast(FcConfigGetSysRoot(config))); - distortablePath += "/fonts/Distortable.ttf"; - FcConfigAppFontAddFile(config, reinterpret_cast(distortablePath.c_str())); + SkString fontFilePath(reinterpret_cast(FcConfigGetSysRoot(config))); + fontFilePath += fontFilename; + FcConfigAppFontAddFile(config, reinterpret_cast(fontFilePath.c_str())); FcConfigBuildFonts(config); + return config; +} + +bool fontmgr_understands_ft_named_instance_bits() { + std::unique_ptr distortable(GetResourceAsStream("fonts/Distortable.ttf")); + if (!distortable) { + return false; + } + + sk_sp fm = SkFontMgr::RefDefault(); + SkFontArguments params; + // The first named variation position in Distortable is 'Thin'. + params.setCollectionIndex(0x00010000); + sk_sp typeface = fm->makeFromStream(std::move(distortable), params); + return !!typeface; +} + +} // namespace + +DEF_TEST(FontMgrFontConfig, reporter) { + FcConfig* config = build_fontconfig_with_fontfile("/fonts/Distortable.ttf"); sk_sp fontMgr(SkFontMgr_New_FontConfig(config)); sk_sp typeface(fontMgr->legacyMakeTypeface("Distortable", SkFontStyle())); @@ -102,3 +127,87 @@ DEF_TEST(FontMgrFontConfig, reporter) { REPORTER_ASSERT(reporter, success); } } + +DEF_TEST(FontConfigInterface_MatchStyleNamedInstance, reporter) { + if (!fontmgr_understands_ft_named_instance_bits()) { + return; + } + + FcConfig* config = build_fontconfig_with_fontfile("/fonts/NotoSansCJK-VF-subset.otf.ttc"); + FcConfigSetCurrent(config); + sk_sp fciDirect(new SkFontConfigInterfaceDirect()); + + std::vector family_names{{"Noto Sans CJK JP", + "Noto Sans CJK HK", + "Noto Sans CJK SC", + "Noto Sans CJK TC", + "Noto Sans CJK KR"}}; + std::vector weights = {100, 300, 350, 400, 500, 700, 900}; + std::vector highBitsExpectation = {false, true, true, true, true, true}; + + for (const auto& font_name : family_names) { + for (size_t i = 0; i < weights.size(); ++i) { + auto weight = weights[i]; + SkFontStyle fontStyle(weight, SkFontStyle::kNormal_Width, SkFontStyle::kUpright_Slant); + + SkFontConfigInterface::FontIdentity resultIdentity; + SkFontStyle resultStyle; + SkString resultFamily; + const bool r = fciDirect->matchFamilyName( + font_name.c_str(), fontStyle, &resultIdentity, &resultFamily, &resultStyle); + + REPORTER_ASSERT(reporter, r, "Expecting to find a match result."); + REPORTER_ASSERT( + reporter, + (resultIdentity.fTTCIndex >> 16 > 0) == highBitsExpectation[i], + "Expected to have the ttcIndex' upper 16 bits refer to a named instance."); + + // Intentionally go through manually creating the typeface so that SkFontStyle is + // derived from data inside the font, not from the FcPattern that is the FontConfig + // match result, see https://crbug.com/skia/12881 + sk_sp typeface(fciDirect->makeTypeface(resultIdentity).release()); + + if (!typeface) { + ERRORF(reporter, "Could not instantiate typeface, FcVersion: %d", FcGetVersion()); + return; + } + + SkString family_from_typeface; + typeface->getFamilyName(&family_from_typeface); + + REPORTER_ASSERT(reporter, + family_from_typeface == SkString(font_name.c_str()), + "Matched font's family name should match the request."); + + SkFontStyle intrinsic_style = typeface->fontStyle(); + REPORTER_ASSERT(reporter, + intrinsic_style.weight() == weight, + "Matched font's weight should match request."); + if (intrinsic_style.weight() != weight) { + ERRORF(reporter, + "Matched font had weight: %d, expected %d, family: %s", + intrinsic_style.weight(), + weight, + family_from_typeface.c_str()); + } + + int numAxes = typeface->getVariationDesignPosition(nullptr, 0); + std::vector coords; + coords.resize(numAxes); + typeface->getVariationDesignPosition(coords.data(), numAxes); + + REPORTER_ASSERT(reporter, + coords.size() == 1, + "The font must only have one axis, the weight axis."); + + REPORTER_ASSERT(reporter, + coords[0].axis == SkSetFourByteTag('w', 'g', 'h', 't'), + "The weight axis must be present and configured."); + + REPORTER_ASSERT(reporter, + static_cast(coords[0].value) == weight, + "The weight axis must match the weight from the request."); + } + } + +}