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:
parent
ea161cde89
commit
7646dffe58
BIN
resources/fonts/NotoSansCJK-VF-subset.otf.ttc
Normal file
BIN
resources/fonts/NotoSansCJK-VF-subset.otf.ttc
Normal file
Binary file not shown.
@ -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;
|
||||||
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user