diff --git a/modules/skottie/src/SkottieTest.cpp b/modules/skottie/src/SkottieTest.cpp index 173ee0743b..61b08ad205 100644 --- a/modules/skottie/src/SkottieTest.cpp +++ b/modules/skottie/src/SkottieTest.cpp @@ -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); + } + } +} diff --git a/modules/skottie/src/text/SkottieShaper.cpp b/modules/skottie/src/text/SkottieShaper.cpp index aaeb8e7758..2c7b2ee2e6 100644 --- a/modules/skottie/src/text/SkottieShaper.cpp +++ b/modules/skottie/src/text/SkottieShaper.cpp @@ -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, - fLineGlyphs.get() + run_offset, - rec.fGlyphCount * sizeof(SkGlyphID)); - sk_careful_memcpy(blob_buffer.pos, - fLinePos.get() + run_offset, - rec.fGlyphCount * sizeof(SkPoint)); + commit_proc(rec, fBox, + fLineGlyphs.get() + run_offset, + fLinePos.get() + run_offset, + &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(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, diff --git a/modules/skottie/src/text/SkottieShaper.h b/modules/skottie/src/text/SkottieShaper.h index 8b60f281d9..8705c7a8b9 100644 --- a/modules/skottie/src/text/SkottieShaper.h +++ b/modules/skottie/src/text/SkottieShaper.h @@ -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& fTypeface; SkScalar fTextSize, fLineHeight; SkTextUtils::Align fHAlign; VAlign fVAlign; + uint32_t fFlags; }; // Performs text layout along an infinite horizontal line, starting at |textPoint|. diff --git a/modules/skottie/src/text/TextAdapter.cpp b/modules/skottie/src/text/TextAdapter.cpp index bdf9fd13e3..2bfb23815b 100644 --- a/modules/skottie/src/text/TextAdapter.cpp +++ b/modules/skottie/src/text/TextAdapter.cpp @@ -15,7 +15,9 @@ namespace skottie { -TextAdapter::TextAdapter(sk_sp root) : fRoot(std::move(root)) {} +TextAdapter::TextAdapter(sk_sp 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); diff --git a/modules/skottie/src/text/TextAdapter.h b/modules/skottie/src/text/TextAdapter.h index 0385a371b4..8fcdf14ac6 100644 --- a/modules/skottie/src/text/TextAdapter.h +++ b/modules/skottie/src/text/TextAdapter.h @@ -22,7 +22,7 @@ namespace skottie { class TextAdapter final : public SkNVRefCnt { public: - explicit TextAdapter(sk_sp root); + TextAdapter(sk_sp root, bool hasAnimators); ~TextAdapter(); ADAPTER_PROPERTY(Text, TextValue, TextValue()) @@ -38,6 +38,8 @@ private: sk_sp fRoot; std::vector fFragments; + + const bool fHasAnimators; }; } // namespace skottie diff --git a/modules/skottie/src/text/TextLayer.cpp b/modules/skottie/src/text/TextLayer.cpp index 1684802269..4b9037502d 100644 --- a/modules/skottie/src/text/TextLayer.cpp +++ b/modules/skottie/src/text/TextLayer.cpp @@ -289,9 +289,8 @@ sk_sp 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 AnimationBuilder::attachTextLayer(const skjson::ObjectVa } auto text_root = sksg::Group::Make(); - auto adapter = sk_make_sp(text_root); + auto adapter = sk_make_sp(text_root, has_animators); this->bindProperty(*jd, ascope, [adapter] (const TextValue& txt) { adapter->setText(txt);