blob: aff0ebaf870df5d406e935a0fa075d8ec79f9335 [file] [log] [blame]
/*
* Copyright 2022 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkFontMgr.h"
#include "modules/skottie/src/text/SkottieShaper.h"
#include "tests/Test.h"
#include "tools/ToolUtils.h"
using namespace skottie;
DEF_TEST(Skottie_Shaper_Clusters, r) {
const SkString text("Foo \rbar \rBaz.");
auto check_clusters = [](skiatest::Reporter* r, const SkString& text, Shaper::Flags flags,
const std::vector<size_t>& expected_clusters) {
const Shaper::TextDesc desc = {
ToolUtils::create_portable_typeface("Serif", SkFontStyle()),
18,
0, 18,
18,
0,
0,
SkTextUtils::Align::kCenter_Align,
Shaper::VAlign::kTop,
Shaper::ResizePolicy::kNone,
Shaper::LinebreakPolicy::kParagraph,
Shaper::Direction::kLTR,
Shaper::Capitalization::kNone,
0,
flags
};
const auto result = Shaper::Shape(text, desc, SkRect::MakeWH(1000, 1000),
SkFontMgr::RefDefault());
REPORTER_ASSERT(r, !result.fFragments.empty());
size_t i = 0;
for (const auto& frag : result.fFragments) {
const auto& glyphs = frag.fGlyphs;
if (flags & Shaper::kClusters) {
REPORTER_ASSERT(r, glyphs.fClusters.size() == glyphs.fGlyphIDs.size());
}
for (const auto& utf_cluster : glyphs.fClusters) {
REPORTER_ASSERT(r, i < expected_clusters.size());
REPORTER_ASSERT(r, utf_cluster == expected_clusters[i++]);
}
}
REPORTER_ASSERT(r, i == expected_clusters.size());
};
check_clusters(r, text, Shaper::kNone, {});
check_clusters(r, text, Shaper::kFragmentGlyphs, {});
check_clusters(r, text, Shaper::kClusters,
{0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13});
check_clusters(r, text, (Shaper::Flags)(Shaper::kClusters | Shaper::kFragmentGlyphs),
{0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13});
}
DEF_TEST(Skottie_Shaper_HAlign, reporter) {
auto typeface = SkTypeface::MakeDefault();
REPORTER_ASSERT(reporter, typeface);
static constexpr struct {
SkScalar text_size,
tolerance;
} kTestSizes[] = {
// These gross tolerances are required for the test to pass on NativeFonts bots.
// Might be worth investigating why we need so much slack.
{ 5, 2.0f },
{ 10, 2.0f },
{ 15, 2.4f },
{ 25, 4.4f },
};
static constexpr struct {
SkTextUtils::Align align;
SkScalar l_selector,
r_selector;
} kTestAligns[] = {
{ SkTextUtils:: kLeft_Align, 0.0f, 1.0f },
{ SkTextUtils::kCenter_Align, 0.5f, 0.5f },
{ SkTextUtils:: kRight_Align, 1.0f, 0.0f },
};
const SkString text("Foo, bar.\rBaz.");
const SkPoint text_point = SkPoint::Make(100, 100);
for (const auto& tsize : kTestSizes) {
for (const auto& talign : kTestAligns) {
const skottie::Shaper::TextDesc desc = {
typeface,
tsize.text_size,
0, tsize.text_size,
tsize.text_size,
0,
0,
talign.align,
Shaper::VAlign::kTopBaseline,
Shaper::ResizePolicy::kNone,
Shaper::LinebreakPolicy::kExplicit,
Shaper::Direction::kLTR,
Shaper::Capitalization::kNone,
};
const auto shape_result = Shaper::Shape(text, desc, text_point,
SkFontMgr::RefDefault());
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
const auto shape_bounds = shape_result.computeVisualBounds();
REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
const auto expected_l = text_point.x() - shape_bounds.width() * talign.l_selector;
REPORTER_ASSERT(reporter,
std::fabs(shape_bounds.left() - expected_l) < tsize.tolerance,
"%f %f %f %f %d", shape_bounds.left(), expected_l, tsize.tolerance,
tsize.text_size, talign.align);
const auto expected_r = text_point.x() + shape_bounds.width() * talign.r_selector;
REPORTER_ASSERT(reporter,
std::fabs(shape_bounds.right() - expected_r) < tsize.tolerance,
"%f %f %f %f %d", shape_bounds.right(), expected_r, tsize.tolerance,
tsize.text_size, talign.align);
}
}
}
DEF_TEST(Skottie_Shaper_VAlign, reporter) {
auto typeface = SkTypeface::MakeDefault();
REPORTER_ASSERT(reporter, typeface);
static constexpr struct {
SkScalar text_size,
tolerance;
} kTestSizes[] = {
// These gross tolerances are required for the test to pass on NativeFonts bots.
// Might be worth investigating why we need so much slack.
{ 5, 2.0f },
{ 10, 4.0f },
{ 15, 5.5f },
{ 25, 8.0f },
};
struct {
skottie::Shaper::VAlign align;
SkScalar topFactor;
} kTestAligns[] = {
{ skottie::Shaper::VAlign::kVisualTop , 0.0f },
{ skottie::Shaper::VAlign::kVisualCenter, 0.5f },
// TODO: any way to test kTopBaseline?
};
const SkString text("Foo, bar.\rBaz.");
const auto text_box = SkRect::MakeXYWH(100, 100, 1000, 1000); // large-enough to avoid breaks.
for (const auto& tsize : kTestSizes) {
for (const auto& talign : kTestAligns) {
const skottie::Shaper::TextDesc desc = {
typeface,
tsize.text_size,
0, tsize.text_size,
tsize.text_size,
0,
0,
SkTextUtils::Align::kCenter_Align,
talign.align,
Shaper::ResizePolicy::kNone,
Shaper::LinebreakPolicy::kParagraph,
Shaper::Direction::kLTR,
Shaper::Capitalization::kNone,
};
const auto shape_result = Shaper::Shape(text, desc, text_box, SkFontMgr::RefDefault());
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
const auto shape_bounds = shape_result.computeVisualBounds();
REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
const auto v_diff = text_box.height() - shape_bounds.height();
const auto expected_t = text_box.top() + v_diff * talign.topFactor;
REPORTER_ASSERT(reporter,
std::fabs(shape_bounds.top() - expected_t) < tsize.tolerance,
"%f %f %f %f %d", shape_bounds.top(), expected_t, tsize.tolerance,
tsize.text_size, SkToU32(talign.align));
const auto expected_b = text_box.bottom() - v_diff * (1 - talign.topFactor);
REPORTER_ASSERT(reporter,
std::fabs(shape_bounds.bottom() - expected_b) < tsize.tolerance,
"%f %f %f %f %d", shape_bounds.bottom(), expected_b, tsize.tolerance,
tsize.text_size, SkToU32(talign.align));
}
}
}
DEF_TEST(Skottie_Shaper_FragmentGlyphs, reporter) {
skottie::Shaper::TextDesc desc = {
SkTypeface::MakeDefault(),
18,
0, 18,
18,
0,
0,
SkTextUtils::Align::kCenter_Align,
Shaper::VAlign::kTop,
Shaper::ResizePolicy::kNone,
Shaper::LinebreakPolicy::kParagraph,
Shaper::Direction::kLTR,
Shaper::Capitalization::kNone,
};
const SkString text("Foo bar baz");
const auto text_box = SkRect::MakeWH(100, 100);
{
const auto shape_result = Shaper::Shape(text, desc, text_box, SkFontMgr::RefDefault());
// Default/consolidated mode => single blob result.
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
}
{
desc.fFlags = Shaper::Flags::kFragmentGlyphs;
const auto shape_result = skottie::Shaper::Shape(text, desc, text_box,
SkFontMgr::RefDefault());
// Fragmented mode => one blob per glyph.
const size_t expectedSize = text.size();
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == expectedSize);
for (size_t i = 0; i < expectedSize; ++i) {
REPORTER_ASSERT(reporter, !shape_result.fFragments[i].fGlyphs.fRuns.empty());
}
}
}
#if defined(SK_SHAPER_HARFBUZZ_AVAILABLE) && !defined(SK_BUILD_FOR_WIN)
DEF_TEST(Skottie_Shaper_ExplicitFontMgr, reporter) {
class CountingFontMgr : public SkFontMgr {
public:
size_t fallbackCount() const { return fFallbackCount; }
protected:
int onCountFamilies() const override { return 0; }
void onGetFamilyName(int index, SkString* familyName) const override {
SkDEBUGFAIL("onGetFamilyName called with bad index");
}
SkFontStyleSet* onCreateStyleSet(int index) const override {
SkDEBUGFAIL("onCreateStyleSet called with bad index");
return nullptr;
}
SkFontStyleSet* onMatchFamily(const char[]) const override {
return SkFontStyleSet::CreateEmpty();
}
SkTypeface* onMatchFamilyStyle(const char[], const SkFontStyle&) const override {
return nullptr;
}
SkTypeface* onMatchFamilyStyleCharacter(const char familyName[],
const SkFontStyle& style,
const char* bcp47[],
int bcp47Count,
SkUnichar character) const override {
fFallbackCount++;
return nullptr;
}
sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int) const override {
return nullptr;
}
sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>, int) const override {
return nullptr;
}
sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
const SkFontArguments&) const override {
return nullptr;
}
sk_sp<SkTypeface> onMakeFromFile(const char[], int) const override {
return nullptr;
}
sk_sp<SkTypeface> onLegacyMakeTypeface(const char [], SkFontStyle) const override {
return nullptr;
}
private:
mutable size_t fFallbackCount = 0;
};
auto fontmgr = sk_make_sp<CountingFontMgr>();
skottie::Shaper::TextDesc desc = {
ToolUtils::create_portable_typeface(),
18,
0, 18,
18,
0,
0,
SkTextUtils::Align::kCenter_Align,
Shaper::VAlign::kTop,
Shaper::ResizePolicy::kNone,
Shaper::LinebreakPolicy::kParagraph,
Shaper::Direction::kLTR,
Shaper::Capitalization::kNone,
};
const auto text_box = SkRect::MakeWH(100, 100);
{
const auto shape_result = Shaper::Shape(SkString("foo bar"), desc, text_box, fontmgr);
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
REPORTER_ASSERT(reporter, fontmgr->fallbackCount() == 0ul);
REPORTER_ASSERT(reporter, shape_result.fMissingGlyphCount == 0);
}
{
// An unassigned codepoint should trigger fallback.
const auto shape_result = skottie::Shaper::Shape(SkString("foo\U000DFFFFbar"),
desc, text_box, fontmgr);
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
REPORTER_ASSERT(reporter, fontmgr->fallbackCount() == 1ul);
REPORTER_ASSERT(reporter, shape_result.fMissingGlyphCount == 1ul);
}
}
#endif