[skottie,skshaper] Plumb an optional SkFontMgr in SkShaper/SkottieShaper

Skottie already takes an optional client fontmgr at load time, but
SkShaper(HB) currently uses the default fontmgr for fallback.

Plumb the Skottie font manager all the way to SkShaper.

This should give clients more control over font fallback, instead of
relying on the default SkFontMgr.

Change-Id: I3df16b3924a68d232573e25f9e526f523fc1dc08
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/230122
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: Ben Wagner aka dogben <benjaminwagner@google.com>
This commit is contained in:
Florin Malita 2019-07-26 14:54:40 -04:00 committed by Skia Commit-Bot
parent c561189af9
commit 426843323f
10 changed files with 171 additions and 48 deletions

View File

@ -65,6 +65,7 @@ if (skia_enable_skottie) {
":skottie",
"../..:gpu_tool_utils",
"../..:skia",
"../skshaper",
]
}

View File

@ -5,6 +5,7 @@
* found in the LICENSE file.
*/
#include "include/core/SkFontMgr.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkStream.h"
#include "include/core/SkTextBlob.h"
@ -12,9 +13,10 @@
#include "modules/skottie/include/Skottie.h"
#include "modules/skottie/include/SkottieProperty.h"
#include "modules/skottie/src/text/SkottieShaper.h"
#include "src/core/SkFontDescriptor.h"
#include "src/core/SkTextBlobPriv.h"
#include "tests/Test.h"
#include "tools/ToolUtils.h"
#include <cmath>
#include <tuple>
@ -300,7 +302,8 @@ DEF_TEST(Skottie_Shaper_HAlign, reporter) {
Shaper::Flags::kNone
};
const auto shape_result = skottie::Shaper::Shape(text, desc, text_point);
const auto shape_result = skottie::Shaper::Shape(text, desc, text_point,
SkFontMgr::RefDefault());
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
@ -364,7 +367,8 @@ DEF_TEST(Skottie_Shaper_VAlign, reporter) {
Shaper::Flags::kNone
};
const auto shape_result = skottie::Shaper::Shape(text, desc, text_box);
const auto shape_result = skottie::Shaper::Shape(text, desc, text_box,
SkFontMgr::RefDefault());
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
@ -403,7 +407,8 @@ DEF_TEST(Skottie_Shaper_FragmentGlyphs, reporter) {
const auto text_box = SkRect::MakeWH(100, 100);
{
const auto shape_result = skottie::Shaper::Shape(text, desc, text_box);
const auto shape_result = skottie::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].fBlob);
@ -411,7 +416,8 @@ DEF_TEST(Skottie_Shaper_FragmentGlyphs, reporter) {
{
desc.fFlags = Shaper::Flags::kFragmentGlyphs;
const auto shape_result = skottie::Shaper::Shape(text, desc, text_box);
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);
@ -420,3 +426,97 @@ DEF_TEST(Skottie_Shaper_FragmentGlyphs, reporter) {
}
}
}
#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;
}
SkTypeface* onMatchFaceStyle(const SkTypeface*, const SkFontStyle&) const override {
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> onMakeFromFontData(std::unique_ptr<SkFontData>) 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,
18,
0,
SkTextUtils::Align::kCenter_Align,
Shaper::VAlign::kTop,
Shaper::Flags::kNone
};
const auto text_box = SkRect::MakeWH(100, 100);
{
const auto shape_result = skottie::Shaper::Shape(SkString("foo bar"),
desc, text_box, fontmgr);
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
REPORTER_ASSERT(reporter, fontmgr->fallbackCount() == 0ul);
}
{
// 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].fBlob);
REPORTER_ASSERT(reporter, fontmgr->fallbackCount() == 1ul);
}
}
#endif

View File

@ -291,7 +291,7 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectVa
}
auto text_root = sksg::Group::Make();
auto adapter = sk_make_sp<TextAdapter>(text_root, has_animators);
auto adapter = sk_make_sp<TextAdapter>(text_root, fLazyFontMgr.getMaybeNull(), has_animators);
this->bindProperty<TextValue>(*jd,
[adapter] (const TextValue& txt) {

View File

@ -8,6 +8,7 @@
#include "modules/skottie/src/text/SkottieShaper.h"
#include "include/core/SkFontMetrics.h"
#include "include/core/SkFontMgr.h"
#include "include/core/SkTextBlob.h"
#include "include/private/SkTemplates.h"
#include "modules/skshaper/include/SkShaper.h"
@ -48,12 +49,12 @@ SkRect ComputeBlobBounds(const sk_sp<SkTextBlob>& blob) {
// per-line position adjustments (for external line breaking, horizontal alignment, etc).
class BlobMaker final : public SkShaper::RunHandler {
public:
BlobMaker(const Shaper::TextDesc& desc, const SkRect& box)
BlobMaker(const Shaper::TextDesc& desc, const SkRect& box, const sk_sp<SkFontMgr>& fontmgr)
: fDesc(desc)
, fBox(box)
, fHAlignFactor(HAlignFactor(fDesc.fHAlign))
, fFont(fDesc.fTypeface, fDesc.fTextSize)
, fShaper(SkShaper::Make()) {
, fShaper(SkShaper::Make(fontmgr)) {
fFont.setHinting(SkFontHinting::kNone);
fFont.setSubpixel(true);
fFont.setLinearMetrics(true);
@ -314,7 +315,8 @@ private:
};
Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc,
const SkRect& box, float* shaped_height = nullptr) {
const SkRect& box, const sk_sp<SkFontMgr>& fontmgr,
float* shaped_height = nullptr) {
SkASSERT(desc.fVAlign != Shaper::VAlign::kVisualResizeToFit);
const auto& is_line_break = [](SkUnichar uch) {
@ -326,7 +328,7 @@ Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc,
const char* line_start = ptr;
const char* end = ptr + txt.size();
BlobMaker blobMaker(desc, box);
BlobMaker blobMaker(desc, box, fontmgr);
while (ptr < end) {
if (is_line_break(SkUTF::NextUTF8(&ptr, end))) {
blobMaker.shapeLine(line_start, ptr - 1);
@ -339,7 +341,7 @@ Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc,
}
Shaper::Result ShapeToFit(const SkString& txt, const Shaper::TextDesc& orig_desc,
const SkRect& box) {
const SkRect& box, const sk_sp<SkFontMgr>& fontmgr) {
SkASSERT(orig_desc.fVAlign == Shaper::VAlign::kVisualResizeToFit);
Shaper::Result best_result;
@ -368,7 +370,7 @@ Shaper::Result ShapeToFit(const SkString& txt, const Shaper::TextDesc& orig_desc
desc.fAscent = try_scale * orig_desc.fAscent;
float res_height = 0;
auto res = ShapeImpl(txt, desc, box, &res_height);
auto res = ShapeImpl(txt, desc, box, fontmgr, &res_height);
if (res_height > box.height()) {
out_scale = try_scale;
@ -396,16 +398,18 @@ Shaper::Result ShapeToFit(const SkString& txt, const Shaper::TextDesc& orig_desc
} // namespace
Shaper::Result Shaper::Shape(const SkString& txt, const TextDesc& desc, const SkPoint& point) {
Shaper::Result Shaper::Shape(const SkString& txt, const TextDesc& desc, const SkPoint& point,
const sk_sp<SkFontMgr>& fontmgr) {
return (desc.fVAlign == VAlign::kVisualResizeToFit) // makes no sense in point mode
? Result()
: ShapeImpl(txt, desc, SkRect::MakeEmpty().makeOffset(point.x(), point.y()));
: ShapeImpl(txt, desc, SkRect::MakeEmpty().makeOffset(point.x(), point.y()), fontmgr);
}
Shaper::Result Shaper::Shape(const SkString& txt, const TextDesc& desc, const SkRect& box) {
Shaper::Result Shaper::Shape(const SkString& txt, const TextDesc& desc, const SkRect& box,
const sk_sp<SkFontMgr>& fontmgr) {
return (desc.fVAlign == VAlign::kVisualResizeToFit)
? ShapeToFit(txt, desc, box)
: ShapeImpl(txt, desc, box);
? ShapeToFit(txt, desc, box, fontmgr)
: ShapeImpl(txt, desc, box, fontmgr);
}
SkRect Shaper::Result::computeVisualBounds() const {

View File

@ -13,6 +13,7 @@
#include <vector>
class SkFontMgr;
class SkTextBlob;
namespace skottie {
@ -83,12 +84,14 @@ public:
// Performs text layout along an infinite horizontal line, starting at |textPoint|.
// Only explicit line breaks (\r) are observed.
static Result Shape(const SkString& text, const TextDesc& desc, const SkPoint& textPoint);
static Result Shape(const SkString& text, const TextDesc& desc, const SkPoint& textPoint,
const sk_sp<SkFontMgr>&);
// Performs text layout within |textBox|, injecting line breaks as needed to ensure
// horizontal fitting. The result is *not* guaranteed to fit vertically (it may extend
// below the box bottom).
static Result Shape(const SkString& text, const TextDesc& desc, const SkRect& textBox);
static Result Shape(const SkString& text, const TextDesc& desc, const SkRect& textBox,
const sk_sp<SkFontMgr>&);
private:
Shaper() = delete;

View File

@ -7,6 +7,7 @@
#include "modules/skottie/src/text/TextAdapter.h"
#include "include/core/SkFontMgr.h"
#include "modules/skottie/src/text/TextAnimator.h"
#include "modules/sksg/include/SkSGDraw.h"
#include "modules/sksg/include/SkSGGroup.h"
@ -18,8 +19,9 @@
namespace skottie {
namespace internal {
TextAdapter::TextAdapter(sk_sp<sksg::Group> root, bool hasAnimators)
TextAdapter::TextAdapter(sk_sp<sksg::Group> root, sk_sp<SkFontMgr> fontmgr, bool hasAnimators)
: fRoot(std::move(root))
, fFontMgr(std::move(fontmgr))
, fHasAnimators(hasAnimators) {}
TextAdapter::~TextAdapter() = default;
@ -128,7 +130,7 @@ void TextAdapter::apply() {
fText.fVAlign,
fHasAnimators ? Shaper::Flags::kFragmentGlyphs : Shaper::Flags::kNone,
};
const auto shape_result = Shaper::Shape(fText.fText, text_desc, fText.fBox);
const auto shape_result = Shaper::Shape(fText.fText, text_desc, fText.fBox, fFontMgr);
// Rebuild all fragments.
// TODO: we can be smarter here and try to reuse the existing SG structure if needed.

View File

@ -15,12 +15,14 @@
#include <vector>
class SkFontMgr;
namespace skottie {
namespace internal {
class TextAdapter final : public SkNVRefCnt<TextAdapter> {
public:
TextAdapter(sk_sp<sksg::Group> root, bool hasAnimators);
TextAdapter(sk_sp<sksg::Group> root, sk_sp<SkFontMgr>, bool hasAnimators);
~TextAdapter();
ADAPTER_PROPERTY(Text, TextValue, TextValue())
@ -49,11 +51,12 @@ private:
const TextAnimator::DomainSpan&,
float line_tracking) const;
sk_sp<sksg::Group> fRoot;
const sk_sp<sksg::Group> fRoot;
const sk_sp<SkFontMgr> fFontMgr;
const bool fHasAnimators;
std::vector<FragmentRec> fFragments;
TextAnimator::DomainMaps fMaps;
const bool fHasAnimators;
};
} // namespace internal

View File

@ -8,6 +8,7 @@
#ifndef SkShaper_DEFINED
#define SkShaper_DEFINED
#include "include/core/SkFontMgr.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
@ -29,12 +30,12 @@ class SkShaper {
public:
static std::unique_ptr<SkShaper> MakePrimitive();
#ifdef SK_SHAPER_HARFBUZZ_AVAILABLE
static std::unique_ptr<SkShaper> MakeShaperDrivenWrapper();
static std::unique_ptr<SkShaper> MakeShapeThenWrap();
static std::unique_ptr<SkShaper> MakeShapeDontWrapOrReorder();
static std::unique_ptr<SkShaper> MakeShaperDrivenWrapper(sk_sp<SkFontMgr> = nullptr);
static std::unique_ptr<SkShaper> MakeShapeThenWrap(sk_sp<SkFontMgr> = nullptr);
static std::unique_ptr<SkShaper> MakeShapeDontWrapOrReorder(sk_sp<SkFontMgr> = nullptr);
#endif
static std::unique_ptr<SkShaper> Make();
static std::unique_ptr<SkShaper> Make(sk_sp<SkFontMgr> = nullptr);
SkShaper();
virtual ~SkShaper();

View File

@ -22,9 +22,9 @@
#include <string>
#include <utility>
std::unique_ptr<SkShaper> SkShaper::Make() {
std::unique_ptr<SkShaper> SkShaper::Make(sk_sp<SkFontMgr> fontmgr) {
#ifdef SK_SHAPER_HARFBUZZ_AVAILABLE
std::unique_ptr<SkShaper> shaper = SkShaper::MakeShaperDrivenWrapper();
std::unique_ptr<SkShaper> shaper = SkShaper::MakeShaperDrivenWrapper(std::move(fontmgr));
if (shaper) {
return shaper;
}

View File

@ -618,7 +618,8 @@ struct ShapedRunGlyphIterator {
class ShaperHarfBuzz : public SkShaper {
public:
ShaperHarfBuzz(HBBuffer, ICUBrk line, ICUBrk grapheme);
ShaperHarfBuzz(HBBuffer, ICUBrk line, ICUBrk grapheme, sk_sp<SkFontMgr>);
protected:
ICUBrk fLineBreakIterator;
ICUBrk fGraphemeBreakIterator;
@ -631,7 +632,8 @@ protected:
const ScriptRunIterator&,
const FontRunIterator&) const;
private:
HBBuffer fBuffer;
const sk_sp<SkFontMgr> fFontMgr;
HBBuffer fBuffer;
void shape(const char* utf8, size_t utf8Bytes,
const SkFont&,
@ -699,7 +701,7 @@ private:
RunHandler*) const override;
};
static std::unique_ptr<SkShaper> MakeHarfBuzz(bool correct) {
static std::unique_ptr<SkShaper> MakeHarfBuzz(sk_sp<SkFontMgr> fontmgr, bool correct) {
#if defined(SK_USING_THIRD_PARTY_ICU)
if (!SkLoadICU()) {
SkDEBUGF("SkLoadICU() failed!\n");
@ -726,17 +728,23 @@ static std::unique_ptr<SkShaper> MakeHarfBuzz(bool correct) {
}
if (correct) {
return skstd::make_unique<ShaperDrivenWrapper>(
std::move(buffer), std::move(lineBreakIterator), std::move(graphemeBreakIterator));
return skstd::make_unique<ShaperDrivenWrapper>(std::move(buffer),
std::move(lineBreakIterator),
std::move(graphemeBreakIterator),
std::move(fontmgr));
} else {
return skstd::make_unique<ShapeThenWrap>(
std::move(buffer), std::move(lineBreakIterator), std::move(graphemeBreakIterator));
return skstd::make_unique<ShapeThenWrap>(std::move(buffer),
std::move(lineBreakIterator),
std::move(graphemeBreakIterator),
std::move(fontmgr));
}
}
ShaperHarfBuzz::ShaperHarfBuzz(HBBuffer buffer, ICUBrk line, ICUBrk grapheme)
ShaperHarfBuzz::ShaperHarfBuzz(HBBuffer buffer, ICUBrk line, ICUBrk grapheme,
sk_sp<SkFontMgr> fontmgr)
: fLineBreakIterator(std::move(line))
, fGraphemeBreakIterator(std::move(grapheme))
, fFontMgr(std::move(fontmgr))
, fBuffer(std::move(buffer))
{}
@ -747,7 +755,6 @@ void ShaperHarfBuzz::shape(const char* utf8, size_t utf8Bytes,
RunHandler* handler) const
{
SkASSERT(handler);
sk_sp<SkFontMgr> fontMgr = SkFontMgr::RefDefault();
UBiDiLevel defaultLevel = leftToRight ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL;
std::unique_ptr<BiDiRunIterator> bidi(MakeIcuBiDiRunIterator(utf8, utf8Bytes, defaultLevel));
@ -765,8 +772,9 @@ void ShaperHarfBuzz::shape(const char* utf8, size_t utf8Bytes,
return;
}
std::unique_ptr<FontRunIterator> font(MakeFontMgrRunIterator(utf8, utf8Bytes,
srcFont, std::move(fontMgr)));
std::unique_ptr<FontRunIterator> font(
MakeFontMgrRunIterator(utf8, utf8Bytes, srcFont,
fFontMgr ? fFontMgr : SkFontMgr::RefDefault()));
if (!font) {
return;
}
@ -1354,13 +1362,13 @@ SkShaper::MakeHbIcuScriptRunIterator(const char* utf8, size_t utf8Bytes) {
return skstd::make_unique<HbIcuScriptRunIterator>(utf8, utf8Bytes);
}
std::unique_ptr<SkShaper> SkShaper::MakeShaperDrivenWrapper() {
return MakeHarfBuzz(true);
std::unique_ptr<SkShaper> SkShaper::MakeShaperDrivenWrapper(sk_sp<SkFontMgr> fontmgr) {
return MakeHarfBuzz(std::move(fontmgr), true);
}
std::unique_ptr<SkShaper> SkShaper::MakeShapeThenWrap() {
return MakeHarfBuzz(false);
std::unique_ptr<SkShaper> SkShaper::MakeShapeThenWrap(sk_sp<SkFontMgr> fontmgr) {
return MakeHarfBuzz(std::move(fontmgr), false);
}
std::unique_ptr<SkShaper> SkShaper::MakeShapeDontWrapOrReorder() {
std::unique_ptr<SkShaper> SkShaper::MakeShapeDontWrapOrReorder(sk_sp<SkFontMgr> fontmgr) {
#if defined(SK_USING_THIRD_PARTY_ICU)
if (!SkLoadICU()) {
SkDEBUGF("SkLoadICU() failed!\n");
@ -1373,5 +1381,6 @@ std::unique_ptr<SkShaper> SkShaper::MakeShapeDontWrapOrReorder() {
return nullptr;
}
return skstd::make_unique<ShapeDontWrapOrReorder>(std::move(buffer), nullptr, nullptr);
return skstd::make_unique<ShapeDontWrapOrReorder>(std::move(buffer), nullptr, nullptr,
std::move(fontmgr));
}