[skottie] Shaper: fission glyphs into separate fragments when needed
In the presence of animated text properties, we want the Shaper result glyphs to be split into separate fragments. Add a Shaper flag to request this mode, and upate the logic to emit one fragment per glyph, when active. Otherwise fall back to the current/consolidated blob mode. Change-Id: If7440e5fa1ae2f8855984d3ae4d6852b10b2316c Reviewed-on: https://skia-review.googlesource.com/c/skia/+/216879 Reviewed-by: Ben Wagner <bungeman@google.com> Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
parent
f62e575bab
commit
0fe004c14c
@ -259,6 +259,7 @@ DEF_TEST(Skottie_Shaper_HAlign, reporter) {
|
||||
tsize.text_size,
|
||||
talign.align,
|
||||
skottie::Shaper::VAlign::kTopBaseline,
|
||||
Shaper::Flags::kNone
|
||||
};
|
||||
|
||||
const auto shape_result = skottie::Shaper::Shape(text, desc, text_point);
|
||||
@ -321,6 +322,7 @@ DEF_TEST(Skottie_Shaper_VAlign, reporter) {
|
||||
tsize.text_size,
|
||||
SkTextUtils::Align::kCenter_Align,
|
||||
talign.align,
|
||||
Shaper::Flags::kNone
|
||||
};
|
||||
|
||||
const auto shape_result = skottie::Shaper::Shape(text, desc, text_box);
|
||||
@ -346,3 +348,35 @@ DEF_TEST(Skottie_Shaper_VAlign, reporter) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEF_TEST(Skottie_Shaper_FragmentGlyphs, reporter) {
|
||||
skottie::Shaper::TextDesc desc = {
|
||||
SkTypeface::MakeDefault(),
|
||||
18,
|
||||
18,
|
||||
SkTextUtils::Align::kCenter_Align,
|
||||
Shaper::VAlign::kTop,
|
||||
Shaper::Flags::kNone
|
||||
};
|
||||
|
||||
const SkString text("Foo bar baz");
|
||||
const auto text_box = SkRect::MakeWH(100, 100);
|
||||
|
||||
{
|
||||
const auto shape_result = skottie::Shaper::Shape(text, desc, text_box);
|
||||
// Default/consolidated mode => single blob result.
|
||||
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
|
||||
REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
|
||||
}
|
||||
|
||||
{
|
||||
desc.fFlags = Shaper::Flags::kFragmentGlyphs;
|
||||
const auto shape_result = skottie::Shaper::Shape(text, desc, text_box);
|
||||
// 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].fBlob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,53 +101,97 @@ public:
|
||||
void commitLine() override {
|
||||
fOffset.fY += fDesc.fLineHeight;
|
||||
|
||||
// Commit all accumulated runs to the blob.
|
||||
// TODO: justification adjustments
|
||||
// TODO: multi-blob/fragmented results
|
||||
|
||||
using CommitProc = void(*)(const RunRec&,
|
||||
const SkRect&,
|
||||
const SkGlyphID*,
|
||||
const SkPoint*,
|
||||
SkTextBlobBuilder*,
|
||||
Shaper::Result*);
|
||||
|
||||
static const CommitProc fragment_commit_proc = [](const RunRec& rec,
|
||||
const SkRect& box,
|
||||
const SkGlyphID* glyphs,
|
||||
const SkPoint* pos,
|
||||
SkTextBlobBuilder* builder,
|
||||
Shaper::Result* result) {
|
||||
// In fragmented mode we immediately push the glyphs to fResult,
|
||||
// one fragment per glyph.
|
||||
for (size_t i = 0; i < rec.fGlyphCount; ++i) {
|
||||
const auto& blob_buffer = builder->allocRunPos(rec.fFont, 1);
|
||||
blob_buffer.glyphs[0] = glyphs[i];
|
||||
blob_buffer.pos[0] = pos[i].fX;
|
||||
blob_buffer.pos[1] = pos[i].fY;
|
||||
|
||||
result->fFragments.push_back({builder->make(), {box.x(), box.y()}});
|
||||
}
|
||||
};
|
||||
|
||||
static const CommitProc consolidated_commit_proc = [](const RunRec& rec,
|
||||
const SkRect& box,
|
||||
const SkGlyphID* glyphs,
|
||||
const SkPoint* pos,
|
||||
SkTextBlobBuilder* builder,
|
||||
Shaper::Result*) {
|
||||
// In consolidated mode we just accumulate glyphs to the blob builder, then push
|
||||
// to fResult as a single blob in finalize().
|
||||
const auto& blob_buffer = builder->allocRunPos(rec.fFont, rec.fGlyphCount);
|
||||
sk_careful_memcpy(blob_buffer.glyphs, glyphs, rec.fGlyphCount * sizeof(SkGlyphID));
|
||||
sk_careful_memcpy(blob_buffer.pos , pos , rec.fGlyphCount * sizeof(SkPoint));
|
||||
};
|
||||
|
||||
const auto commit_proc = (fDesc.fFlags & Shaper::Flags::kFragmentGlyphs)
|
||||
? fragment_commit_proc : consolidated_commit_proc;
|
||||
|
||||
size_t run_offset = 0;
|
||||
for (const auto& rec : fLineRuns) {
|
||||
SkASSERT(run_offset < fLineGlyphCount);
|
||||
|
||||
const auto& blob_buffer = fBuilder.allocRunPos(rec.fFont, rec.fGlyphCount);
|
||||
sk_careful_memcpy(blob_buffer.glyphs,
|
||||
commit_proc(rec, fBox,
|
||||
fLineGlyphs.get() + run_offset,
|
||||
rec.fGlyphCount * sizeof(SkGlyphID));
|
||||
sk_careful_memcpy(blob_buffer.pos,
|
||||
fLinePos.get() + run_offset,
|
||||
rec.fGlyphCount * sizeof(SkPoint));
|
||||
&fBuilder, &fResult);
|
||||
|
||||
run_offset += rec.fGlyphCount;
|
||||
}
|
||||
}
|
||||
|
||||
Shaper::Result makeBlob() {
|
||||
auto blob = fBuilder.make();
|
||||
|
||||
SkPoint pos {fBox.x(), fBox.y()};
|
||||
Shaper::Result finalize() {
|
||||
if (!(fDesc.fFlags & Shaper::Flags::kFragmentGlyphs)) {
|
||||
// All glyphs are pending in a single blob.
|
||||
SkASSERT(fResult.fFragments.empty());
|
||||
fResult.fFragments.reserve(1);
|
||||
fResult.fFragments.push_back({fBuilder.make(), {fBox.x(), fBox.y()}});
|
||||
}
|
||||
|
||||
// By default, first line is vertical-aligned on a baseline of 0.
|
||||
// Perform additional adjustments based on VAlign.
|
||||
float v_offset = 0;
|
||||
switch (fDesc.fVAlign) {
|
||||
case Shaper::VAlign::kTop:
|
||||
pos.fY -= ComputeBlobBounds(blob).fTop;
|
||||
v_offset = fBox.fTop - fResult.computeBounds().fTop;
|
||||
break;
|
||||
case Shaper::VAlign::kTopBaseline:
|
||||
// Default behavior.
|
||||
break;
|
||||
case Shaper::VAlign::kCenter: {
|
||||
const auto bounds = ComputeBlobBounds(blob).makeOffset(pos.x(), pos.y());
|
||||
pos.fY += fBox.centerY() - bounds.centerY();
|
||||
} break;
|
||||
case Shaper::VAlign::kCenter:
|
||||
v_offset = fBox.centerY() - fResult.computeBounds().centerY();
|
||||
break;
|
||||
case Shaper::VAlign::kBottom:
|
||||
pos.fY += fBox.height() - ComputeBlobBounds(blob).fBottom;
|
||||
v_offset = fBox.fBottom - fResult.computeBounds().fBottom;
|
||||
break;
|
||||
case Shaper::VAlign::kResizeToFit:
|
||||
SkASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// single blob for now
|
||||
return { std::vector<Shaper::Fragment>(1ul, { std::move(blob), pos })};
|
||||
if (v_offset) {
|
||||
for (auto& fragment : fResult.fFragments) {
|
||||
fragment.fPos.fY += v_offset;
|
||||
}
|
||||
}
|
||||
|
||||
return std::move(fResult);
|
||||
}
|
||||
|
||||
void shapeLine(const char* start, const char* end) {
|
||||
@ -194,6 +238,8 @@ private:
|
||||
SkPoint fCurrentPosition{ 0, 0 };
|
||||
SkPoint fOffset{ 0, 0 };
|
||||
SkVector fPendingLineAdvance{ 0, 0 };
|
||||
|
||||
Shaper::Result fResult;
|
||||
};
|
||||
|
||||
Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc, const SkRect& box) {
|
||||
@ -217,7 +263,7 @@ Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc, cons
|
||||
}
|
||||
blobMaker.shapeLine(line_start, ptr);
|
||||
|
||||
return blobMaker.makeBlob();
|
||||
return blobMaker.finalize();
|
||||
}
|
||||
|
||||
Shaper::Result ShapeToFit(const SkString& txt, const Shaper::TextDesc& orig_desc,
|
||||
|
@ -45,12 +45,21 @@ public:
|
||||
kResizeToFit,
|
||||
};
|
||||
|
||||
enum Flags : uint32_t {
|
||||
kNone = 0x00,
|
||||
|
||||
// Split out individual glyphs into separate Fragments
|
||||
// (useful when the caller intends to manipulate glyphs independently).
|
||||
kFragmentGlyphs = 0x01,
|
||||
};
|
||||
|
||||
struct TextDesc {
|
||||
const sk_sp<SkTypeface>& fTypeface;
|
||||
SkScalar fTextSize,
|
||||
fLineHeight;
|
||||
SkTextUtils::Align fHAlign;
|
||||
VAlign fVAlign;
|
||||
uint32_t fFlags;
|
||||
};
|
||||
|
||||
// Performs text layout along an infinite horizontal line, starting at |textPoint|.
|
||||
|
@ -15,7 +15,9 @@
|
||||
|
||||
namespace skottie {
|
||||
|
||||
TextAdapter::TextAdapter(sk_sp<sksg::Group> root) : fRoot(std::move(root)) {}
|
||||
TextAdapter::TextAdapter(sk_sp<sksg::Group> root, bool hasAnimators)
|
||||
: fRoot(std::move(root))
|
||||
, fHasAnimators(hasAnimators) {}
|
||||
|
||||
TextAdapter::~TextAdapter() = default;
|
||||
|
||||
@ -76,6 +78,7 @@ void TextAdapter::apply() {
|
||||
fText.fLineHeight,
|
||||
fText.fHAlign,
|
||||
fText.fVAlign,
|
||||
fHasAnimators ? Shaper::Flags::kFragmentGlyphs : Shaper::Flags::kNone,
|
||||
};
|
||||
const auto shape_result = Shaper::Shape(fText.fText, text_desc, fText.fBox);
|
||||
|
||||
|
@ -22,7 +22,7 @@ namespace skottie {
|
||||
|
||||
class TextAdapter final : public SkNVRefCnt<TextAdapter> {
|
||||
public:
|
||||
explicit TextAdapter(sk_sp<sksg::Group> root);
|
||||
TextAdapter(sk_sp<sksg::Group> root, bool hasAnimators);
|
||||
~TextAdapter();
|
||||
|
||||
ADAPTER_PROPERTY(Text, TextValue, TextValue())
|
||||
@ -38,6 +38,8 @@ private:
|
||||
|
||||
sk_sp<sksg::Group> fRoot;
|
||||
std::vector<FragmentRec> fFragments;
|
||||
|
||||
const bool fHasAnimators;
|
||||
};
|
||||
|
||||
} // namespace skottie
|
||||
|
@ -289,9 +289,8 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectVa
|
||||
}
|
||||
|
||||
const skjson::ArrayValue* animated_props = (*jt)["a"];
|
||||
if (animated_props && animated_props->size() > 0) {
|
||||
this->log(Logger::Level::kWarning, nullptr, "Unsupported animated text properties.");
|
||||
}
|
||||
const auto has_animators = (animated_props && animated_props->size() > 0);
|
||||
// TODO: actually parse/implement animators.
|
||||
|
||||
const skjson::ObjectValue* jd = (*jt)["d"];
|
||||
if (!jd) {
|
||||
@ -299,7 +298,7 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectVa
|
||||
}
|
||||
|
||||
auto text_root = sksg::Group::Make();
|
||||
auto adapter = sk_make_sp<TextAdapter>(text_root);
|
||||
auto adapter = sk_make_sp<TextAdapter>(text_root, has_animators);
|
||||
|
||||
this->bindProperty<TextValue>(*jd, ascope, [adapter] (const TextValue& txt) {
|
||||
adapter->setText(txt);
|
||||
|
Loading…
Reference in New Issue
Block a user