Determine weight, width and slant from variable axes

Let variable axes override existing or determine style information
with priority over FreeType face flags or information
from the OS/2 table.

Add test case for matching a variable fonts through SkFontMgr_FCI to
ensure the weight determination from variable axes is exercise.
The test does not exercise width or slant.

Add a small (~8.5k) subsetted Noto Sans CJK collection as a test font
(made using [1]), and ensure that matching, reported font style and axis
configuration are correct after matching.

[1] https://github.com/drott/noto-cjk/blob/subsetVFttv/subsetvf.py

Bug: skia:12864, skia:12881
Change-Id: I1fb05d88f68eda308b8864d32d98400c68e46834
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/500516
Commit-Queue: Dominik Röttsches <drott@chromium.org>
Reviewed-by: Ben Wagner <bungeman@google.com>
Auto-Submit: Dominik Röttsches <drott@chromium.org>
Commit-Queue: Dominik Röttsches <drott@google.com>
This commit is contained in:
Dominik Röttsches 2022-02-01 20:53:07 +02:00 committed by SkCQ
parent ea161cde89
commit 7646dffe58
3 changed files with 183 additions and 8 deletions

Binary file not shown.

View File

@ -33,6 +33,7 @@
#include "src/utils/SkMatrix22.h" #include "src/utils/SkMatrix22.h"
#include <memory> #include <memory>
#include <optional>
#include <tuple> #include <tuple>
#include <ft2build.h> #include <ft2build.h>
@ -1880,9 +1881,13 @@ bool SkTypeface_FreeType::Scanner::scanFont(
slant = SkFontStyle::kItalic_Slant; slant = SkFontStyle::kItalic_Slant;
} }
PS_FontInfoRec psFontInfo; bool hasAxes = face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS;
TT_OS2* os2 = static_cast<TT_OS2*>(FT_Get_Sfnt_Table(face.get(), ft_sfnt_os2)); TT_OS2* os2 = static_cast<TT_OS2*>(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; weight = os2->usWeightClass;
width = os2->usWidthClass; width = os2->usWidthClass;
@ -1890,7 +1895,68 @@ bool SkTypeface_FreeType::Scanner::scanFont(
if (SkToBool(os2->fsSelection & (1u << 9))) { if (SkToBool(os2->fsSelection & (1u << 9))) {
slant = SkFontStyle::kOblique_Slant; 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<size_t> wghtIndex;
std::optional<size_t> wdthIndex;
std::optional<size_t> 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 { static const struct {
char const * const name; char const * const name;
int const weight; int const weight;

View File

@ -10,13 +10,17 @@
#include "include/core/SkFont.h" #include "include/core/SkFont.h"
#include "include/core/SkFontMgr.h" #include "include/core/SkFontMgr.h"
#include "include/core/SkTypeface.h" #include "include/core/SkTypeface.h"
#include "include/ports/SkFontMgr_FontConfigInterface.h"
#include "include/ports/SkFontMgr_fontconfig.h" #include "include/ports/SkFontMgr_fontconfig.h"
#include "src/ports/SkFontConfigInterface_direct.h"
#include "tests/Test.h" #include "tests/Test.h"
#include "tools/Resources.h" #include "tools/Resources.h"
#include <fontconfig/fontconfig.h> #include <fontconfig/fontconfig.h>
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 y = 0; y < test.height(); ++y) {
for (int x = 0; x < test.width(); ++x) { for (int x = 0; x < test.width(); ++x) {
SkColor testColor = test.getColor(x, y); SkColor testColor = test.getColor(x, y);
@ -29,17 +33,38 @@ static bool bitmap_compare(const SkBitmap& ref, const SkBitmap& test) {
return true; return true;
} }
DEF_TEST(FontMgrFontConfig, reporter) { FcConfig* build_fontconfig_with_fontfile(const char* fontFilename) {
FcConfig* config = FcConfigCreate(); FcConfig* config = FcConfigCreate();
// FontConfig may modify the passed path (make absolute or other). // FontConfig may modify the passed path (make absolute or other).
FcConfigSetSysRoot(config, reinterpret_cast<const FcChar8*>(GetResourcePath("").c_str())); FcConfigSetSysRoot(config, reinterpret_cast<const FcChar8*>(GetResourcePath("").c_str()));
// FontConfig will lexically compare paths against its version of the sysroot. // FontConfig will lexically compare paths against its version of the sysroot.
SkString distortablePath(reinterpret_cast<const char*>(FcConfigGetSysRoot(config))); SkString fontFilePath(reinterpret_cast<const char*>(FcConfigGetSysRoot(config)));
distortablePath += "/fonts/Distortable.ttf"; fontFilePath += fontFilename;
FcConfigAppFontAddFile(config, reinterpret_cast<const FcChar8*>(distortablePath.c_str())); FcConfigAppFontAddFile(config, reinterpret_cast<const FcChar8*>(fontFilePath.c_str()));
FcConfigBuildFonts(config); FcConfigBuildFonts(config);
return config;
}
bool fontmgr_understands_ft_named_instance_bits() {
std::unique_ptr<SkStreamAsset> distortable(GetResourceAsStream("fonts/Distortable.ttf"));
if (!distortable) {
return false;
}
sk_sp<SkFontMgr> fm = SkFontMgr::RefDefault();
SkFontArguments params;
// The first named variation position in Distortable is 'Thin'.
params.setCollectionIndex(0x00010000);
sk_sp<SkTypeface> 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<SkFontMgr> fontMgr(SkFontMgr_New_FontConfig(config)); sk_sp<SkFontMgr> fontMgr(SkFontMgr_New_FontConfig(config));
sk_sp<SkTypeface> typeface(fontMgr->legacyMakeTypeface("Distortable", SkFontStyle())); sk_sp<SkTypeface> typeface(fontMgr->legacyMakeTypeface("Distortable", SkFontStyle()));
@ -102,3 +127,87 @@ DEF_TEST(FontMgrFontConfig, reporter) {
REPORTER_ASSERT(reporter, success); 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<SkFontConfigInterfaceDirect> fciDirect(new SkFontConfigInterfaceDirect());
std::vector<std::string> family_names{{"Noto Sans CJK JP",
"Noto Sans CJK HK",
"Noto Sans CJK SC",
"Noto Sans CJK TC",
"Noto Sans CJK KR"}};
std::vector<int> weights = {100, 300, 350, 400, 500, 700, 900};
std::vector<bool> 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<SkTypeface> 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<SkFontArguments::VariationPosition::Coordinate> 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<int>(coords[0].value) == weight,
"The weight axis must match the weight from the request.");
}
}
}