1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2019 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/text/TextAdapter.h"
8*c8dee2aaSAndroid Build Coastguard Worker
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkCanvas.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColor.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkContourMeasure.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkFont.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkFontMgr.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkM44.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkMatrix.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPaint.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPath.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRect.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkScalar.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSpan.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkString.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTypes.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTPin.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTo.h"
25*c8dee2aaSAndroid Build Coastguard Worker #include "include/utils/SkTextUtils.h"
26*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/include/Skottie.h"
27*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/include/SkottieProperty.h"
28*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/SkottieJson.h"
29*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/SkottiePriv.h"
30*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/text/RangeSelector.h" // IWYU pragma: keep
31*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/text/TextAnimator.h"
32*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGDraw.h"
33*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGGeometryNode.h"
34*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGGroup.h"
35*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGPaint.h"
36*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGPath.h"
37*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGRect.h"
38*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGRenderEffect.h"
39*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGRenderNode.h"
40*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGTransform.h"
41*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/src/SkSGTransformPriv.h"
42*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skshaper/include/SkShaper_factory.h"
43*c8dee2aaSAndroid Build Coastguard Worker #include "src/utils/SkJSON.h"
44*c8dee2aaSAndroid Build Coastguard Worker
45*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
46*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
47*c8dee2aaSAndroid Build Coastguard Worker #include <cstddef>
48*c8dee2aaSAndroid Build Coastguard Worker #include <limits>
49*c8dee2aaSAndroid Build Coastguard Worker #include <tuple>
50*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
51*c8dee2aaSAndroid Build Coastguard Worker
52*c8dee2aaSAndroid Build Coastguard Worker namespace sksg {
53*c8dee2aaSAndroid Build Coastguard Worker class InvalidationController;
54*c8dee2aaSAndroid Build Coastguard Worker }
55*c8dee2aaSAndroid Build Coastguard Worker
56*c8dee2aaSAndroid Build Coastguard Worker // Enable for text layout debugging.
57*c8dee2aaSAndroid Build Coastguard Worker #define SHOW_LAYOUT_BOXES 0
58*c8dee2aaSAndroid Build Coastguard Worker
59*c8dee2aaSAndroid Build Coastguard Worker namespace skottie::internal {
60*c8dee2aaSAndroid Build Coastguard Worker
61*c8dee2aaSAndroid Build Coastguard Worker namespace {
62*c8dee2aaSAndroid Build Coastguard Worker
63*c8dee2aaSAndroid Build Coastguard Worker class GlyphTextNode final : public sksg::GeometryNode {
64*c8dee2aaSAndroid Build Coastguard Worker public:
GlyphTextNode(Shaper::ShapedGlyphs && glyphs)65*c8dee2aaSAndroid Build Coastguard Worker explicit GlyphTextNode(Shaper::ShapedGlyphs&& glyphs) : fGlyphs(std::move(glyphs)) {}
66*c8dee2aaSAndroid Build Coastguard Worker
67*c8dee2aaSAndroid Build Coastguard Worker ~GlyphTextNode() override = default;
68*c8dee2aaSAndroid Build Coastguard Worker
glyphs() const69*c8dee2aaSAndroid Build Coastguard Worker const Shaper::ShapedGlyphs* glyphs() const { return &fGlyphs; }
70*c8dee2aaSAndroid Build Coastguard Worker
71*c8dee2aaSAndroid Build Coastguard Worker protected:
onRevalidate(sksg::InvalidationController *,const SkMatrix &)72*c8dee2aaSAndroid Build Coastguard Worker SkRect onRevalidate(sksg::InvalidationController*, const SkMatrix&) override {
73*c8dee2aaSAndroid Build Coastguard Worker return fGlyphs.computeBounds(Shaper::ShapedGlyphs::BoundsType::kTight);
74*c8dee2aaSAndroid Build Coastguard Worker }
75*c8dee2aaSAndroid Build Coastguard Worker
onDraw(SkCanvas * canvas,const SkPaint & paint) const76*c8dee2aaSAndroid Build Coastguard Worker void onDraw(SkCanvas* canvas, const SkPaint& paint) const override {
77*c8dee2aaSAndroid Build Coastguard Worker fGlyphs.draw(canvas, {0,0}, paint);
78*c8dee2aaSAndroid Build Coastguard Worker }
79*c8dee2aaSAndroid Build Coastguard Worker
onClip(SkCanvas * canvas,bool antiAlias) const80*c8dee2aaSAndroid Build Coastguard Worker void onClip(SkCanvas* canvas, bool antiAlias) const override {
81*c8dee2aaSAndroid Build Coastguard Worker canvas->clipPath(this->asPath(), antiAlias);
82*c8dee2aaSAndroid Build Coastguard Worker }
83*c8dee2aaSAndroid Build Coastguard Worker
onContains(const SkPoint & p) const84*c8dee2aaSAndroid Build Coastguard Worker bool onContains(const SkPoint& p) const override {
85*c8dee2aaSAndroid Build Coastguard Worker return this->asPath().contains(p.x(), p.y());
86*c8dee2aaSAndroid Build Coastguard Worker }
87*c8dee2aaSAndroid Build Coastguard Worker
onAsPath() const88*c8dee2aaSAndroid Build Coastguard Worker SkPath onAsPath() const override {
89*c8dee2aaSAndroid Build Coastguard Worker // TODO
90*c8dee2aaSAndroid Build Coastguard Worker return SkPath();
91*c8dee2aaSAndroid Build Coastguard Worker }
92*c8dee2aaSAndroid Build Coastguard Worker
93*c8dee2aaSAndroid Build Coastguard Worker private:
94*c8dee2aaSAndroid Build Coastguard Worker const Shaper::ShapedGlyphs fGlyphs;
95*c8dee2aaSAndroid Build Coastguard Worker };
96*c8dee2aaSAndroid Build Coastguard Worker
align_factor(SkTextUtils::Align a)97*c8dee2aaSAndroid Build Coastguard Worker static float align_factor(SkTextUtils::Align a) {
98*c8dee2aaSAndroid Build Coastguard Worker switch (a) {
99*c8dee2aaSAndroid Build Coastguard Worker case SkTextUtils::kLeft_Align : return 0.0f;
100*c8dee2aaSAndroid Build Coastguard Worker case SkTextUtils::kCenter_Align: return 0.5f;
101*c8dee2aaSAndroid Build Coastguard Worker case SkTextUtils::kRight_Align : return 1.0f;
102*c8dee2aaSAndroid Build Coastguard Worker }
103*c8dee2aaSAndroid Build Coastguard Worker
104*c8dee2aaSAndroid Build Coastguard Worker SkUNREACHABLE;
105*c8dee2aaSAndroid Build Coastguard Worker }
106*c8dee2aaSAndroid Build Coastguard Worker
107*c8dee2aaSAndroid Build Coastguard Worker } // namespace
108*c8dee2aaSAndroid Build Coastguard Worker
109*c8dee2aaSAndroid Build Coastguard Worker class TextAdapter::GlyphDecoratorNode final : public sksg::Group {
110*c8dee2aaSAndroid Build Coastguard Worker public:
GlyphDecoratorNode(sk_sp<GlyphDecorator> decorator,float scale)111*c8dee2aaSAndroid Build Coastguard Worker GlyphDecoratorNode(sk_sp<GlyphDecorator> decorator, float scale)
112*c8dee2aaSAndroid Build Coastguard Worker : fDecorator(std::move(decorator))
113*c8dee2aaSAndroid Build Coastguard Worker , fScale(scale)
114*c8dee2aaSAndroid Build Coastguard Worker {}
115*c8dee2aaSAndroid Build Coastguard Worker
116*c8dee2aaSAndroid Build Coastguard Worker ~GlyphDecoratorNode() override = default;
117*c8dee2aaSAndroid Build Coastguard Worker
updateFragmentData(const std::vector<TextAdapter::FragmentRec> & recs)118*c8dee2aaSAndroid Build Coastguard Worker void updateFragmentData(const std::vector<TextAdapter::FragmentRec>& recs) {
119*c8dee2aaSAndroid Build Coastguard Worker fFragCount = recs.size();
120*c8dee2aaSAndroid Build Coastguard Worker
121*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(!fFragInfo);
122*c8dee2aaSAndroid Build Coastguard Worker fFragInfo = std::make_unique<FragmentInfo[]>(recs.size());
123*c8dee2aaSAndroid Build Coastguard Worker
124*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < recs.size(); ++i) {
125*c8dee2aaSAndroid Build Coastguard Worker const auto& rec = recs[i];
126*c8dee2aaSAndroid Build Coastguard Worker fFragInfo[i] = {rec.fGlyphs, rec.fMatrixNode, rec.fAdvance};
127*c8dee2aaSAndroid Build Coastguard Worker }
128*c8dee2aaSAndroid Build Coastguard Worker
129*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(!fDecoratorInfo);
130*c8dee2aaSAndroid Build Coastguard Worker fDecoratorInfo = std::make_unique<GlyphDecorator::GlyphInfo[]>(recs.size());
131*c8dee2aaSAndroid Build Coastguard Worker }
132*c8dee2aaSAndroid Build Coastguard Worker
onRevalidate(sksg::InvalidationController * ic,const SkMatrix & ctm)133*c8dee2aaSAndroid Build Coastguard Worker SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
134*c8dee2aaSAndroid Build Coastguard Worker const auto child_bounds = INHERITED::onRevalidate(ic, ctm);
135*c8dee2aaSAndroid Build Coastguard Worker
136*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < fFragCount; ++i) {
137*c8dee2aaSAndroid Build Coastguard Worker const auto* glyphs = fFragInfo[i].fGlyphs;
138*c8dee2aaSAndroid Build Coastguard Worker fDecoratorInfo[i].fBounds =
139*c8dee2aaSAndroid Build Coastguard Worker glyphs->computeBounds(Shaper::ShapedGlyphs::BoundsType::kTight);
140*c8dee2aaSAndroid Build Coastguard Worker fDecoratorInfo[i].fMatrix = sksg::TransformPriv::As<SkMatrix>(fFragInfo[i].fMatrixNode);
141*c8dee2aaSAndroid Build Coastguard Worker
142*c8dee2aaSAndroid Build Coastguard Worker fDecoratorInfo[i].fCluster = glyphs->fClusters.empty() ? 0 : glyphs->fClusters.front();
143*c8dee2aaSAndroid Build Coastguard Worker fDecoratorInfo[i].fAdvance = fFragInfo[i].fAdvance;
144*c8dee2aaSAndroid Build Coastguard Worker }
145*c8dee2aaSAndroid Build Coastguard Worker
146*c8dee2aaSAndroid Build Coastguard Worker return child_bounds;
147*c8dee2aaSAndroid Build Coastguard Worker }
148*c8dee2aaSAndroid Build Coastguard Worker
onRender(SkCanvas * canvas,const RenderContext * ctx) const149*c8dee2aaSAndroid Build Coastguard Worker void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
150*c8dee2aaSAndroid Build Coastguard Worker auto local_ctx = ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(),
151*c8dee2aaSAndroid Build Coastguard Worker canvas->getTotalMatrix(),
152*c8dee2aaSAndroid Build Coastguard Worker true);
153*c8dee2aaSAndroid Build Coastguard Worker this->INHERITED::onRender(canvas, local_ctx);
154*c8dee2aaSAndroid Build Coastguard Worker
155*c8dee2aaSAndroid Build Coastguard Worker fDecorator->onDecorate(canvas, {
156*c8dee2aaSAndroid Build Coastguard Worker SkSpan(fDecoratorInfo.get(), fFragCount),
157*c8dee2aaSAndroid Build Coastguard Worker fScale
158*c8dee2aaSAndroid Build Coastguard Worker });
159*c8dee2aaSAndroid Build Coastguard Worker }
160*c8dee2aaSAndroid Build Coastguard Worker
161*c8dee2aaSAndroid Build Coastguard Worker private:
162*c8dee2aaSAndroid Build Coastguard Worker struct FragmentInfo {
163*c8dee2aaSAndroid Build Coastguard Worker const Shaper::ShapedGlyphs* fGlyphs;
164*c8dee2aaSAndroid Build Coastguard Worker sk_sp<sksg::Matrix<SkM44>> fMatrixNode;
165*c8dee2aaSAndroid Build Coastguard Worker float fAdvance;
166*c8dee2aaSAndroid Build Coastguard Worker };
167*c8dee2aaSAndroid Build Coastguard Worker
168*c8dee2aaSAndroid Build Coastguard Worker const sk_sp<GlyphDecorator> fDecorator;
169*c8dee2aaSAndroid Build Coastguard Worker const float fScale;
170*c8dee2aaSAndroid Build Coastguard Worker
171*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<FragmentInfo[]> fFragInfo;
172*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<GlyphDecorator::GlyphInfo[]> fDecoratorInfo;
173*c8dee2aaSAndroid Build Coastguard Worker size_t fFragCount;
174*c8dee2aaSAndroid Build Coastguard Worker
175*c8dee2aaSAndroid Build Coastguard Worker using INHERITED = Group;
176*c8dee2aaSAndroid Build Coastguard Worker };
177*c8dee2aaSAndroid Build Coastguard Worker
178*c8dee2aaSAndroid Build Coastguard Worker // Text path semantics
179*c8dee2aaSAndroid Build Coastguard Worker //
180*c8dee2aaSAndroid Build Coastguard Worker // * glyphs are positioned on the path based on their horizontal/x anchor point, interpreted as
181*c8dee2aaSAndroid Build Coastguard Worker // a distance along the path
182*c8dee2aaSAndroid Build Coastguard Worker //
183*c8dee2aaSAndroid Build Coastguard Worker // * horizontal alignment is applied relative to the path start/end points
184*c8dee2aaSAndroid Build Coastguard Worker //
185*c8dee2aaSAndroid Build Coastguard Worker // * "Reverse Path" allows reversing the path direction
186*c8dee2aaSAndroid Build Coastguard Worker //
187*c8dee2aaSAndroid Build Coastguard Worker // * "Perpendicular To Path" determines whether glyphs are rotated to be perpendicular
188*c8dee2aaSAndroid Build Coastguard Worker // to the path tangent, or not (just positioned).
189*c8dee2aaSAndroid Build Coastguard Worker //
190*c8dee2aaSAndroid Build Coastguard Worker // * two controls ("First Margin" and "Last Margin") allow arbitrary offseting along the path,
191*c8dee2aaSAndroid Build Coastguard Worker // depending on horizontal alignement:
192*c8dee2aaSAndroid Build Coastguard Worker // - left: offset = first margin
193*c8dee2aaSAndroid Build Coastguard Worker // - center: offset = first margin + last margin
194*c8dee2aaSAndroid Build Coastguard Worker // - right: offset = last margin
195*c8dee2aaSAndroid Build Coastguard Worker //
196*c8dee2aaSAndroid Build Coastguard Worker // * extranormal path positions (d < 0, d > path len) are allowed
197*c8dee2aaSAndroid Build Coastguard Worker // - closed path: the position wraps around in both directions
198*c8dee2aaSAndroid Build Coastguard Worker // - open path: extrapolates from extremes' positions/slopes
199*c8dee2aaSAndroid Build Coastguard Worker //
200*c8dee2aaSAndroid Build Coastguard Worker struct TextAdapter::PathInfo {
201*c8dee2aaSAndroid Build Coastguard Worker ShapeValue fPath;
202*c8dee2aaSAndroid Build Coastguard Worker ScalarValue fPathFMargin = 0,
203*c8dee2aaSAndroid Build Coastguard Worker fPathLMargin = 0,
204*c8dee2aaSAndroid Build Coastguard Worker fPathPerpendicular = 0,
205*c8dee2aaSAndroid Build Coastguard Worker fPathReverse = 0;
206*c8dee2aaSAndroid Build Coastguard Worker
updateContourDataskottie::internal::TextAdapter::PathInfo207*c8dee2aaSAndroid Build Coastguard Worker void updateContourData() {
208*c8dee2aaSAndroid Build Coastguard Worker const auto reverse = fPathReverse != 0;
209*c8dee2aaSAndroid Build Coastguard Worker
210*c8dee2aaSAndroid Build Coastguard Worker if (fPath != fCurrentPath || reverse != fCurrentReversed) {
211*c8dee2aaSAndroid Build Coastguard Worker // reinitialize cached contour data
212*c8dee2aaSAndroid Build Coastguard Worker auto path = static_cast<SkPath>(fPath);
213*c8dee2aaSAndroid Build Coastguard Worker if (reverse) {
214*c8dee2aaSAndroid Build Coastguard Worker SkPath reversed;
215*c8dee2aaSAndroid Build Coastguard Worker reversed.reverseAddPath(path);
216*c8dee2aaSAndroid Build Coastguard Worker path = reversed;
217*c8dee2aaSAndroid Build Coastguard Worker }
218*c8dee2aaSAndroid Build Coastguard Worker
219*c8dee2aaSAndroid Build Coastguard Worker SkContourMeasureIter iter(path, /*forceClosed = */false);
220*c8dee2aaSAndroid Build Coastguard Worker fCurrentMeasure = iter.next();
221*c8dee2aaSAndroid Build Coastguard Worker fCurrentClosed = path.isLastContourClosed();
222*c8dee2aaSAndroid Build Coastguard Worker fCurrentReversed = reverse;
223*c8dee2aaSAndroid Build Coastguard Worker fCurrentPath = fPath;
224*c8dee2aaSAndroid Build Coastguard Worker
225*c8dee2aaSAndroid Build Coastguard Worker // AE paths are always single-contour (no moves allowed).
226*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(!iter.next());
227*c8dee2aaSAndroid Build Coastguard Worker }
228*c8dee2aaSAndroid Build Coastguard Worker }
229*c8dee2aaSAndroid Build Coastguard Worker
pathLengthskottie::internal::TextAdapter::PathInfo230*c8dee2aaSAndroid Build Coastguard Worker float pathLength() const {
231*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(fPath == fCurrentPath);
232*c8dee2aaSAndroid Build Coastguard Worker SkASSERT((fPathReverse != 0) == fCurrentReversed);
233*c8dee2aaSAndroid Build Coastguard Worker
234*c8dee2aaSAndroid Build Coastguard Worker return fCurrentMeasure ? fCurrentMeasure->length() : 0;
235*c8dee2aaSAndroid Build Coastguard Worker }
236*c8dee2aaSAndroid Build Coastguard Worker
getMatrixskottie::internal::TextAdapter::PathInfo237*c8dee2aaSAndroid Build Coastguard Worker SkM44 getMatrix(float distance, SkTextUtils::Align alignment) const {
238*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(fPath == fCurrentPath);
239*c8dee2aaSAndroid Build Coastguard Worker SkASSERT((fPathReverse != 0) == fCurrentReversed);
240*c8dee2aaSAndroid Build Coastguard Worker
241*c8dee2aaSAndroid Build Coastguard Worker if (!fCurrentMeasure) {
242*c8dee2aaSAndroid Build Coastguard Worker return SkM44();
243*c8dee2aaSAndroid Build Coastguard Worker }
244*c8dee2aaSAndroid Build Coastguard Worker
245*c8dee2aaSAndroid Build Coastguard Worker const auto path_len = fCurrentMeasure->length();
246*c8dee2aaSAndroid Build Coastguard Worker
247*c8dee2aaSAndroid Build Coastguard Worker // First/last margin adjustment also depends on alignment.
248*c8dee2aaSAndroid Build Coastguard Worker switch (alignment) {
249*c8dee2aaSAndroid Build Coastguard Worker case SkTextUtils::Align::kLeft_Align: distance += fPathFMargin; break;
250*c8dee2aaSAndroid Build Coastguard Worker case SkTextUtils::Align::kCenter_Align: distance += fPathFMargin +
251*c8dee2aaSAndroid Build Coastguard Worker fPathLMargin; break;
252*c8dee2aaSAndroid Build Coastguard Worker case SkTextUtils::Align::kRight_Align: distance += fPathLMargin; break;
253*c8dee2aaSAndroid Build Coastguard Worker }
254*c8dee2aaSAndroid Build Coastguard Worker
255*c8dee2aaSAndroid Build Coastguard Worker // For closed paths, extranormal distances wrap around the contour.
256*c8dee2aaSAndroid Build Coastguard Worker if (fCurrentClosed) {
257*c8dee2aaSAndroid Build Coastguard Worker distance = std::fmod(distance, path_len);
258*c8dee2aaSAndroid Build Coastguard Worker if (distance < 0) {
259*c8dee2aaSAndroid Build Coastguard Worker distance += path_len;
260*c8dee2aaSAndroid Build Coastguard Worker }
261*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(0 <= distance && distance <= path_len);
262*c8dee2aaSAndroid Build Coastguard Worker }
263*c8dee2aaSAndroid Build Coastguard Worker
264*c8dee2aaSAndroid Build Coastguard Worker SkPoint pos;
265*c8dee2aaSAndroid Build Coastguard Worker SkVector tan;
266*c8dee2aaSAndroid Build Coastguard Worker if (!fCurrentMeasure->getPosTan(distance, &pos, &tan)) {
267*c8dee2aaSAndroid Build Coastguard Worker return SkM44();
268*c8dee2aaSAndroid Build Coastguard Worker }
269*c8dee2aaSAndroid Build Coastguard Worker
270*c8dee2aaSAndroid Build Coastguard Worker // For open paths, extranormal distances are extrapolated from extremes.
271*c8dee2aaSAndroid Build Coastguard Worker // Note:
272*c8dee2aaSAndroid Build Coastguard Worker // - getPosTan above clamps to the extremes
273*c8dee2aaSAndroid Build Coastguard Worker // - the extrapolation below only kicks in for extranormal values
274*c8dee2aaSAndroid Build Coastguard Worker const auto underflow = std::min(0.0f, distance),
275*c8dee2aaSAndroid Build Coastguard Worker overflow = std::max(0.0f, distance - path_len);
276*c8dee2aaSAndroid Build Coastguard Worker pos += tan*(underflow + overflow);
277*c8dee2aaSAndroid Build Coastguard Worker
278*c8dee2aaSAndroid Build Coastguard Worker auto m = SkM44::Translate(pos.x(), pos.y());
279*c8dee2aaSAndroid Build Coastguard Worker
280*c8dee2aaSAndroid Build Coastguard Worker // The "perpendicular" flag controls whether fragments are positioned and rotated,
281*c8dee2aaSAndroid Build Coastguard Worker // or just positioned.
282*c8dee2aaSAndroid Build Coastguard Worker if (fPathPerpendicular != 0) {
283*c8dee2aaSAndroid Build Coastguard Worker m = m * SkM44::Rotate({0,0,1}, std::atan2(tan.y(), tan.x()));
284*c8dee2aaSAndroid Build Coastguard Worker }
285*c8dee2aaSAndroid Build Coastguard Worker
286*c8dee2aaSAndroid Build Coastguard Worker return m;
287*c8dee2aaSAndroid Build Coastguard Worker }
288*c8dee2aaSAndroid Build Coastguard Worker
289*c8dee2aaSAndroid Build Coastguard Worker private:
290*c8dee2aaSAndroid Build Coastguard Worker // Cached contour data.
291*c8dee2aaSAndroid Build Coastguard Worker ShapeValue fCurrentPath;
292*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkContourMeasure> fCurrentMeasure;
293*c8dee2aaSAndroid Build Coastguard Worker bool fCurrentReversed = false,
294*c8dee2aaSAndroid Build Coastguard Worker fCurrentClosed = false;
295*c8dee2aaSAndroid Build Coastguard Worker };
296*c8dee2aaSAndroid Build Coastguard Worker
Make(const skjson::ObjectValue & jlayer,const AnimationBuilder * abuilder,sk_sp<SkFontMgr> fontmgr,sk_sp<CustomFont::GlyphCompMapper> custom_glyph_mapper,sk_sp<Logger> logger,sk_sp<::SkShapers::Factory> factory)297*c8dee2aaSAndroid Build Coastguard Worker sk_sp<TextAdapter> TextAdapter::Make(const skjson::ObjectValue& jlayer,
298*c8dee2aaSAndroid Build Coastguard Worker const AnimationBuilder* abuilder,
299*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkFontMgr> fontmgr,
300*c8dee2aaSAndroid Build Coastguard Worker sk_sp<CustomFont::GlyphCompMapper> custom_glyph_mapper,
301*c8dee2aaSAndroid Build Coastguard Worker sk_sp<Logger> logger,
302*c8dee2aaSAndroid Build Coastguard Worker sk_sp<::SkShapers::Factory> factory) {
303*c8dee2aaSAndroid Build Coastguard Worker // General text node format:
304*c8dee2aaSAndroid Build Coastguard Worker // "t": {
305*c8dee2aaSAndroid Build Coastguard Worker // "a": [], // animators (see TextAnimator)
306*c8dee2aaSAndroid Build Coastguard Worker // "d": {
307*c8dee2aaSAndroid Build Coastguard Worker // "k": [
308*c8dee2aaSAndroid Build Coastguard Worker // {
309*c8dee2aaSAndroid Build Coastguard Worker // "s": {
310*c8dee2aaSAndroid Build Coastguard Worker // "f": "Roboto-Regular",
311*c8dee2aaSAndroid Build Coastguard Worker // "fc": [
312*c8dee2aaSAndroid Build Coastguard Worker // 0.42,
313*c8dee2aaSAndroid Build Coastguard Worker // 0.15,
314*c8dee2aaSAndroid Build Coastguard Worker // 0.15
315*c8dee2aaSAndroid Build Coastguard Worker // ],
316*c8dee2aaSAndroid Build Coastguard Worker // "j": 1,
317*c8dee2aaSAndroid Build Coastguard Worker // "lh": 60,
318*c8dee2aaSAndroid Build Coastguard Worker // "ls": 0,
319*c8dee2aaSAndroid Build Coastguard Worker // "s": 50,
320*c8dee2aaSAndroid Build Coastguard Worker // "t": "text align right",
321*c8dee2aaSAndroid Build Coastguard Worker // "tr": 0
322*c8dee2aaSAndroid Build Coastguard Worker // },
323*c8dee2aaSAndroid Build Coastguard Worker // "t": 0
324*c8dee2aaSAndroid Build Coastguard Worker // }
325*c8dee2aaSAndroid Build Coastguard Worker // ],
326*c8dee2aaSAndroid Build Coastguard Worker // "sid": "optionalSlotID"
327*c8dee2aaSAndroid Build Coastguard Worker // },
328*c8dee2aaSAndroid Build Coastguard Worker // "m": { // more options
329*c8dee2aaSAndroid Build Coastguard Worker // "g": 1, // Anchor Point Grouping
330*c8dee2aaSAndroid Build Coastguard Worker // "a": {...} // Grouping Alignment
331*c8dee2aaSAndroid Build Coastguard Worker // },
332*c8dee2aaSAndroid Build Coastguard Worker // "p": { // path options
333*c8dee2aaSAndroid Build Coastguard Worker // "a": 0, // force alignment
334*c8dee2aaSAndroid Build Coastguard Worker // "f": {}, // first margin
335*c8dee2aaSAndroid Build Coastguard Worker // "l": {}, // last margin
336*c8dee2aaSAndroid Build Coastguard Worker // "m": 1, // mask index
337*c8dee2aaSAndroid Build Coastguard Worker // "p": 1, // perpendicular
338*c8dee2aaSAndroid Build Coastguard Worker // "r": 0 // reverse path
339*c8dee2aaSAndroid Build Coastguard Worker // }
340*c8dee2aaSAndroid Build Coastguard Worker
341*c8dee2aaSAndroid Build Coastguard Worker // },
342*c8dee2aaSAndroid Build Coastguard Worker
343*c8dee2aaSAndroid Build Coastguard Worker const skjson::ObjectValue* jt = jlayer["t"];
344*c8dee2aaSAndroid Build Coastguard Worker const skjson::ObjectValue* jd = jt ? static_cast<const skjson::ObjectValue*>((*jt)["d"])
345*c8dee2aaSAndroid Build Coastguard Worker : nullptr;
346*c8dee2aaSAndroid Build Coastguard Worker if (!jd) {
347*c8dee2aaSAndroid Build Coastguard Worker abuilder->log(Logger::Level::kError, &jlayer, "Invalid text layer.");
348*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
349*c8dee2aaSAndroid Build Coastguard Worker }
350*c8dee2aaSAndroid Build Coastguard Worker
351*c8dee2aaSAndroid Build Coastguard Worker // "More options"
352*c8dee2aaSAndroid Build Coastguard Worker const skjson::ObjectValue* jm = (*jt)["m"];
353*c8dee2aaSAndroid Build Coastguard Worker static constexpr AnchorPointGrouping gGroupingMap[] = {
354*c8dee2aaSAndroid Build Coastguard Worker AnchorPointGrouping::kCharacter, // 'g': 1
355*c8dee2aaSAndroid Build Coastguard Worker AnchorPointGrouping::kWord, // 'g': 2
356*c8dee2aaSAndroid Build Coastguard Worker AnchorPointGrouping::kLine, // 'g': 3
357*c8dee2aaSAndroid Build Coastguard Worker AnchorPointGrouping::kAll, // 'g': 4
358*c8dee2aaSAndroid Build Coastguard Worker };
359*c8dee2aaSAndroid Build Coastguard Worker const auto apg = jm
360*c8dee2aaSAndroid Build Coastguard Worker ? SkTPin<int>(ParseDefault<int>((*jm)["g"], 1), 1, std::size(gGroupingMap))
361*c8dee2aaSAndroid Build Coastguard Worker : 1;
362*c8dee2aaSAndroid Build Coastguard Worker
363*c8dee2aaSAndroid Build Coastguard Worker auto adapter = sk_sp<TextAdapter>(new TextAdapter(std::move(fontmgr),
364*c8dee2aaSAndroid Build Coastguard Worker std::move(custom_glyph_mapper),
365*c8dee2aaSAndroid Build Coastguard Worker std::move(logger),
366*c8dee2aaSAndroid Build Coastguard Worker std::move(factory),
367*c8dee2aaSAndroid Build Coastguard Worker gGroupingMap[SkToSizeT(apg - 1)]));
368*c8dee2aaSAndroid Build Coastguard Worker
369*c8dee2aaSAndroid Build Coastguard Worker adapter->bind(*abuilder, jd, adapter->fText.fCurrentValue);
370*c8dee2aaSAndroid Build Coastguard Worker if (jm) {
371*c8dee2aaSAndroid Build Coastguard Worker adapter->bind(*abuilder, (*jm)["a"], adapter->fGroupingAlignment);
372*c8dee2aaSAndroid Build Coastguard Worker }
373*c8dee2aaSAndroid Build Coastguard Worker
374*c8dee2aaSAndroid Build Coastguard Worker // Animators
375*c8dee2aaSAndroid Build Coastguard Worker if (const skjson::ArrayValue* janimators = (*jt)["a"]) {
376*c8dee2aaSAndroid Build Coastguard Worker adapter->fAnimators.reserve(janimators->size());
377*c8dee2aaSAndroid Build Coastguard Worker
378*c8dee2aaSAndroid Build Coastguard Worker for (const skjson::ObjectValue* janimator : *janimators) {
379*c8dee2aaSAndroid Build Coastguard Worker if (auto animator = TextAnimator::Make(janimator, abuilder, adapter.get())) {
380*c8dee2aaSAndroid Build Coastguard Worker adapter->fHasBlurAnimator |= animator->hasBlur();
381*c8dee2aaSAndroid Build Coastguard Worker adapter->fRequiresAnchorPoint |= animator->requiresAnchorPoint();
382*c8dee2aaSAndroid Build Coastguard Worker adapter->fRequiresLineAdjustments |= animator->requiresLineAdjustments();
383*c8dee2aaSAndroid Build Coastguard Worker
384*c8dee2aaSAndroid Build Coastguard Worker adapter->fAnimators.push_back(std::move(animator));
385*c8dee2aaSAndroid Build Coastguard Worker }
386*c8dee2aaSAndroid Build Coastguard Worker }
387*c8dee2aaSAndroid Build Coastguard Worker }
388*c8dee2aaSAndroid Build Coastguard Worker
389*c8dee2aaSAndroid Build Coastguard Worker // Optional text path
390*c8dee2aaSAndroid Build Coastguard Worker const auto attach_path = [&](const skjson::ObjectValue* jpath) -> std::unique_ptr<PathInfo> {
391*c8dee2aaSAndroid Build Coastguard Worker if (!jpath) {
392*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
393*c8dee2aaSAndroid Build Coastguard Worker }
394*c8dee2aaSAndroid Build Coastguard Worker
395*c8dee2aaSAndroid Build Coastguard Worker // the actual path is identified as an index in the layer mask stack
396*c8dee2aaSAndroid Build Coastguard Worker const auto mask_index =
397*c8dee2aaSAndroid Build Coastguard Worker ParseDefault<size_t>((*jpath)["m"], std::numeric_limits<size_t>::max());
398*c8dee2aaSAndroid Build Coastguard Worker const skjson::ArrayValue* jmasks = jlayer["masksProperties"];
399*c8dee2aaSAndroid Build Coastguard Worker if (!jmasks || mask_index >= jmasks->size()) {
400*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
401*c8dee2aaSAndroid Build Coastguard Worker }
402*c8dee2aaSAndroid Build Coastguard Worker
403*c8dee2aaSAndroid Build Coastguard Worker const skjson::ObjectValue* mask = (*jmasks)[mask_index];
404*c8dee2aaSAndroid Build Coastguard Worker if (!mask) {
405*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
406*c8dee2aaSAndroid Build Coastguard Worker }
407*c8dee2aaSAndroid Build Coastguard Worker
408*c8dee2aaSAndroid Build Coastguard Worker auto pinfo = std::make_unique<PathInfo>();
409*c8dee2aaSAndroid Build Coastguard Worker adapter->bind(*abuilder, (*mask)["pt"], &pinfo->fPath);
410*c8dee2aaSAndroid Build Coastguard Worker adapter->bind(*abuilder, (*jpath)["f"], &pinfo->fPathFMargin);
411*c8dee2aaSAndroid Build Coastguard Worker adapter->bind(*abuilder, (*jpath)["l"], &pinfo->fPathLMargin);
412*c8dee2aaSAndroid Build Coastguard Worker adapter->bind(*abuilder, (*jpath)["p"], &pinfo->fPathPerpendicular);
413*c8dee2aaSAndroid Build Coastguard Worker adapter->bind(*abuilder, (*jpath)["r"], &pinfo->fPathReverse);
414*c8dee2aaSAndroid Build Coastguard Worker
415*c8dee2aaSAndroid Build Coastguard Worker // TODO: force align support
416*c8dee2aaSAndroid Build Coastguard Worker
417*c8dee2aaSAndroid Build Coastguard Worker // Historically, these used to be exported as static properties.
418*c8dee2aaSAndroid Build Coastguard Worker // Attempt parsing both ways, for backward compat.
419*c8dee2aaSAndroid Build Coastguard Worker skottie::Parse((*jpath)["p"], &pinfo->fPathPerpendicular);
420*c8dee2aaSAndroid Build Coastguard Worker skottie::Parse((*jpath)["r"], &pinfo->fPathReverse);
421*c8dee2aaSAndroid Build Coastguard Worker
422*c8dee2aaSAndroid Build Coastguard Worker // Path positioning requires anchor point info.
423*c8dee2aaSAndroid Build Coastguard Worker adapter->fRequiresAnchorPoint = true;
424*c8dee2aaSAndroid Build Coastguard Worker
425*c8dee2aaSAndroid Build Coastguard Worker return pinfo;
426*c8dee2aaSAndroid Build Coastguard Worker };
427*c8dee2aaSAndroid Build Coastguard Worker
428*c8dee2aaSAndroid Build Coastguard Worker adapter->fPathInfo = attach_path((*jt)["p"]);
429*c8dee2aaSAndroid Build Coastguard Worker abuilder->dispatchTextProperty(adapter, jd);
430*c8dee2aaSAndroid Build Coastguard Worker
431*c8dee2aaSAndroid Build Coastguard Worker return adapter;
432*c8dee2aaSAndroid Build Coastguard Worker }
433*c8dee2aaSAndroid Build Coastguard Worker
TextAdapter(sk_sp<SkFontMgr> fontmgr,sk_sp<CustomFont::GlyphCompMapper> custom_glyph_mapper,sk_sp<Logger> logger,sk_sp<SkShapers::Factory> factory,AnchorPointGrouping apg)434*c8dee2aaSAndroid Build Coastguard Worker TextAdapter::TextAdapter(sk_sp<SkFontMgr> fontmgr,
435*c8dee2aaSAndroid Build Coastguard Worker sk_sp<CustomFont::GlyphCompMapper> custom_glyph_mapper,
436*c8dee2aaSAndroid Build Coastguard Worker sk_sp<Logger> logger,
437*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkShapers::Factory> factory,
438*c8dee2aaSAndroid Build Coastguard Worker AnchorPointGrouping apg)
439*c8dee2aaSAndroid Build Coastguard Worker : fRoot(sksg::Group::Make())
440*c8dee2aaSAndroid Build Coastguard Worker , fFontMgr(std::move(fontmgr))
441*c8dee2aaSAndroid Build Coastguard Worker , fCustomGlyphMapper(std::move(custom_glyph_mapper))
442*c8dee2aaSAndroid Build Coastguard Worker , fLogger(std::move(logger))
443*c8dee2aaSAndroid Build Coastguard Worker , fShapingFactory(std::move(factory))
444*c8dee2aaSAndroid Build Coastguard Worker , fAnchorPointGrouping(apg)
445*c8dee2aaSAndroid Build Coastguard Worker , fHasBlurAnimator(false)
446*c8dee2aaSAndroid Build Coastguard Worker , fRequiresAnchorPoint(false)
447*c8dee2aaSAndroid Build Coastguard Worker , fRequiresLineAdjustments(false) {}
448*c8dee2aaSAndroid Build Coastguard Worker
449*c8dee2aaSAndroid Build Coastguard Worker TextAdapter::~TextAdapter() = default;
450*c8dee2aaSAndroid Build Coastguard Worker
451*c8dee2aaSAndroid Build Coastguard Worker std::vector<sk_sp<sksg::RenderNode>>
buildGlyphCompNodes(Shaper::ShapedGlyphs & glyphs) const452*c8dee2aaSAndroid Build Coastguard Worker TextAdapter::buildGlyphCompNodes(Shaper::ShapedGlyphs& glyphs) const {
453*c8dee2aaSAndroid Build Coastguard Worker std::vector<sk_sp<sksg::RenderNode>> draws;
454*c8dee2aaSAndroid Build Coastguard Worker
455*c8dee2aaSAndroid Build Coastguard Worker if (fCustomGlyphMapper) {
456*c8dee2aaSAndroid Build Coastguard Worker size_t run_offset = 0;
457*c8dee2aaSAndroid Build Coastguard Worker for (auto& run : glyphs.fRuns) {
458*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < run.fSize; ++i) {
459*c8dee2aaSAndroid Build Coastguard Worker const size_t goffset = run_offset + i;
460*c8dee2aaSAndroid Build Coastguard Worker const SkGlyphID gid = glyphs.fGlyphIDs[goffset];
461*c8dee2aaSAndroid Build Coastguard Worker
462*c8dee2aaSAndroid Build Coastguard Worker if (auto gcomp = fCustomGlyphMapper->getGlyphComp(run.fFont.getTypeface(), gid)) {
463*c8dee2aaSAndroid Build Coastguard Worker // Position and scale the "glyph".
464*c8dee2aaSAndroid Build Coastguard Worker const auto m = SkMatrix::Translate(glyphs.fGlyphPos[goffset])
465*c8dee2aaSAndroid Build Coastguard Worker * SkMatrix::Scale(fText->fTextSize*fTextShapingScale,
466*c8dee2aaSAndroid Build Coastguard Worker fText->fTextSize*fTextShapingScale);
467*c8dee2aaSAndroid Build Coastguard Worker
468*c8dee2aaSAndroid Build Coastguard Worker draws.push_back(sksg::TransformEffect::Make(std::move(gcomp), m));
469*c8dee2aaSAndroid Build Coastguard Worker
470*c8dee2aaSAndroid Build Coastguard Worker // Remove all related data from the fragment, so we don't attempt to render
471*c8dee2aaSAndroid Build Coastguard Worker // this as a regular glyph.
472*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(glyphs.fGlyphIDs.size() > goffset);
473*c8dee2aaSAndroid Build Coastguard Worker glyphs.fGlyphIDs.erase(glyphs.fGlyphIDs.begin() + goffset);
474*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(glyphs.fGlyphPos.size() > goffset);
475*c8dee2aaSAndroid Build Coastguard Worker glyphs.fGlyphPos.erase(glyphs.fGlyphPos.begin() + goffset);
476*c8dee2aaSAndroid Build Coastguard Worker if (!glyphs.fClusters.empty()) {
477*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(glyphs.fClusters.size() > goffset);
478*c8dee2aaSAndroid Build Coastguard Worker glyphs.fClusters.erase(glyphs.fClusters.begin() + goffset);
479*c8dee2aaSAndroid Build Coastguard Worker }
480*c8dee2aaSAndroid Build Coastguard Worker i -= 1;
481*c8dee2aaSAndroid Build Coastguard Worker run.fSize -= 1;
482*c8dee2aaSAndroid Build Coastguard Worker }
483*c8dee2aaSAndroid Build Coastguard Worker }
484*c8dee2aaSAndroid Build Coastguard Worker run_offset += run.fSize;
485*c8dee2aaSAndroid Build Coastguard Worker }
486*c8dee2aaSAndroid Build Coastguard Worker }
487*c8dee2aaSAndroid Build Coastguard Worker
488*c8dee2aaSAndroid Build Coastguard Worker return draws;
489*c8dee2aaSAndroid Build Coastguard Worker }
490*c8dee2aaSAndroid Build Coastguard Worker
addFragment(Shaper::Fragment & frag,sksg::Group * container)491*c8dee2aaSAndroid Build Coastguard Worker void TextAdapter::addFragment(Shaper::Fragment& frag, sksg::Group* container) {
492*c8dee2aaSAndroid Build Coastguard Worker // For a given shaped fragment, build a corresponding SG fragment:
493*c8dee2aaSAndroid Build Coastguard Worker //
494*c8dee2aaSAndroid Build Coastguard Worker // [TransformEffect] -> [Transform]
495*c8dee2aaSAndroid Build Coastguard Worker // [Group]
496*c8dee2aaSAndroid Build Coastguard Worker // [Draw] -> [GlyphTextNode*] [FillPaint] // SkTypeface-based glyph.
497*c8dee2aaSAndroid Build Coastguard Worker // [Draw] -> [GlyphTextNode*] [StrokePaint] // SkTypeface-based glyph.
498*c8dee2aaSAndroid Build Coastguard Worker // [CompRenderTree] // Comp glyph.
499*c8dee2aaSAndroid Build Coastguard Worker // ...
500*c8dee2aaSAndroid Build Coastguard Worker //
501*c8dee2aaSAndroid Build Coastguard Worker
502*c8dee2aaSAndroid Build Coastguard Worker FragmentRec rec;
503*c8dee2aaSAndroid Build Coastguard Worker rec.fOrigin = frag.fOrigin;
504*c8dee2aaSAndroid Build Coastguard Worker rec.fAdvance = frag.fAdvance;
505*c8dee2aaSAndroid Build Coastguard Worker rec.fAscent = frag.fAscent;
506*c8dee2aaSAndroid Build Coastguard Worker rec.fMatrixNode = sksg::Matrix<SkM44>::Make(SkM44::Translate(frag.fOrigin.x(),
507*c8dee2aaSAndroid Build Coastguard Worker frag.fOrigin.y()));
508*c8dee2aaSAndroid Build Coastguard Worker
509*c8dee2aaSAndroid Build Coastguard Worker // Start off substituting existing comp nodes for all composition-based glyphs.
510*c8dee2aaSAndroid Build Coastguard Worker std::vector<sk_sp<sksg::RenderNode>> draws = this->buildGlyphCompNodes(frag.fGlyphs);
511*c8dee2aaSAndroid Build Coastguard Worker
512*c8dee2aaSAndroid Build Coastguard Worker // Use a regular GlyphTextNode for the remaining glyphs (backed by a real SkTypeface).
513*c8dee2aaSAndroid Build Coastguard Worker auto text_node = sk_make_sp<GlyphTextNode>(std::move(frag.fGlyphs));
514*c8dee2aaSAndroid Build Coastguard Worker rec.fGlyphs = text_node->glyphs();
515*c8dee2aaSAndroid Build Coastguard Worker
516*c8dee2aaSAndroid Build Coastguard Worker draws.reserve(draws.size() +
517*c8dee2aaSAndroid Build Coastguard Worker static_cast<size_t>(fText->fHasFill) +
518*c8dee2aaSAndroid Build Coastguard Worker static_cast<size_t>(fText->fHasStroke));
519*c8dee2aaSAndroid Build Coastguard Worker
520*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(fText->fHasFill || fText->fHasStroke);
521*c8dee2aaSAndroid Build Coastguard Worker
522*c8dee2aaSAndroid Build Coastguard Worker auto add_fill = [&]() {
523*c8dee2aaSAndroid Build Coastguard Worker if (fText->fHasFill) {
524*c8dee2aaSAndroid Build Coastguard Worker rec.fFillColorNode = sksg::Color::Make(fText->fFillColor);
525*c8dee2aaSAndroid Build Coastguard Worker rec.fFillColorNode->setAntiAlias(true);
526*c8dee2aaSAndroid Build Coastguard Worker draws.push_back(sksg::Draw::Make(text_node, rec.fFillColorNode));
527*c8dee2aaSAndroid Build Coastguard Worker }
528*c8dee2aaSAndroid Build Coastguard Worker };
529*c8dee2aaSAndroid Build Coastguard Worker auto add_stroke = [&] {
530*c8dee2aaSAndroid Build Coastguard Worker if (fText->fHasStroke) {
531*c8dee2aaSAndroid Build Coastguard Worker rec.fStrokeColorNode = sksg::Color::Make(fText->fStrokeColor);
532*c8dee2aaSAndroid Build Coastguard Worker rec.fStrokeColorNode->setAntiAlias(true);
533*c8dee2aaSAndroid Build Coastguard Worker rec.fStrokeColorNode->setStyle(SkPaint::kStroke_Style);
534*c8dee2aaSAndroid Build Coastguard Worker rec.fStrokeColorNode->setStrokeWidth(fText->fStrokeWidth * fTextShapingScale);
535*c8dee2aaSAndroid Build Coastguard Worker rec.fStrokeColorNode->setStrokeJoin(fText->fStrokeJoin);
536*c8dee2aaSAndroid Build Coastguard Worker draws.push_back(sksg::Draw::Make(text_node, rec.fStrokeColorNode));
537*c8dee2aaSAndroid Build Coastguard Worker }
538*c8dee2aaSAndroid Build Coastguard Worker };
539*c8dee2aaSAndroid Build Coastguard Worker
540*c8dee2aaSAndroid Build Coastguard Worker if (fText->fPaintOrder == TextPaintOrder::kFillStroke) {
541*c8dee2aaSAndroid Build Coastguard Worker add_fill();
542*c8dee2aaSAndroid Build Coastguard Worker add_stroke();
543*c8dee2aaSAndroid Build Coastguard Worker } else {
544*c8dee2aaSAndroid Build Coastguard Worker add_stroke();
545*c8dee2aaSAndroid Build Coastguard Worker add_fill();
546*c8dee2aaSAndroid Build Coastguard Worker }
547*c8dee2aaSAndroid Build Coastguard Worker
548*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(!draws.empty());
549*c8dee2aaSAndroid Build Coastguard Worker
550*c8dee2aaSAndroid Build Coastguard Worker if (SHOW_LAYOUT_BOXES) {
551*c8dee2aaSAndroid Build Coastguard Worker // visualize fragment ascent boxes
552*c8dee2aaSAndroid Build Coastguard Worker auto box_color = sksg::Color::Make(0xff0000ff);
553*c8dee2aaSAndroid Build Coastguard Worker box_color->setStyle(SkPaint::kStroke_Style);
554*c8dee2aaSAndroid Build Coastguard Worker box_color->setStrokeWidth(1);
555*c8dee2aaSAndroid Build Coastguard Worker box_color->setAntiAlias(true);
556*c8dee2aaSAndroid Build Coastguard Worker auto box = SkRect::MakeLTRB(0, rec.fAscent, rec.fAdvance, 0);
557*c8dee2aaSAndroid Build Coastguard Worker draws.push_back(sksg::Draw::Make(sksg::Rect::Make(box), std::move(box_color)));
558*c8dee2aaSAndroid Build Coastguard Worker }
559*c8dee2aaSAndroid Build Coastguard Worker
560*c8dee2aaSAndroid Build Coastguard Worker draws.shrink_to_fit();
561*c8dee2aaSAndroid Build Coastguard Worker
562*c8dee2aaSAndroid Build Coastguard Worker auto draws_node = (draws.size() > 1)
563*c8dee2aaSAndroid Build Coastguard Worker ? sksg::Group::Make(std::move(draws))
564*c8dee2aaSAndroid Build Coastguard Worker : std::move(draws[0]);
565*c8dee2aaSAndroid Build Coastguard Worker
566*c8dee2aaSAndroid Build Coastguard Worker if (fHasBlurAnimator) {
567*c8dee2aaSAndroid Build Coastguard Worker // Optional blur effect.
568*c8dee2aaSAndroid Build Coastguard Worker rec.fBlur = sksg::BlurImageFilter::Make();
569*c8dee2aaSAndroid Build Coastguard Worker draws_node = sksg::ImageFilterEffect::Make(std::move(draws_node), rec.fBlur);
570*c8dee2aaSAndroid Build Coastguard Worker }
571*c8dee2aaSAndroid Build Coastguard Worker
572*c8dee2aaSAndroid Build Coastguard Worker container->addChild(sksg::TransformEffect::Make(std::move(draws_node), rec.fMatrixNode));
573*c8dee2aaSAndroid Build Coastguard Worker fFragments.push_back(std::move(rec));
574*c8dee2aaSAndroid Build Coastguard Worker }
575*c8dee2aaSAndroid Build Coastguard Worker
buildDomainMaps(const Shaper::Result & shape_result)576*c8dee2aaSAndroid Build Coastguard Worker void TextAdapter::buildDomainMaps(const Shaper::Result& shape_result) {
577*c8dee2aaSAndroid Build Coastguard Worker fMaps.fNonWhitespaceMap.clear();
578*c8dee2aaSAndroid Build Coastguard Worker fMaps.fWordsMap.clear();
579*c8dee2aaSAndroid Build Coastguard Worker fMaps.fLinesMap.clear();
580*c8dee2aaSAndroid Build Coastguard Worker
581*c8dee2aaSAndroid Build Coastguard Worker size_t i = 0,
582*c8dee2aaSAndroid Build Coastguard Worker line = 0,
583*c8dee2aaSAndroid Build Coastguard Worker line_start = 0,
584*c8dee2aaSAndroid Build Coastguard Worker word_start = 0;
585*c8dee2aaSAndroid Build Coastguard Worker
586*c8dee2aaSAndroid Build Coastguard Worker float word_advance = 0,
587*c8dee2aaSAndroid Build Coastguard Worker word_ascent = 0,
588*c8dee2aaSAndroid Build Coastguard Worker line_advance = 0,
589*c8dee2aaSAndroid Build Coastguard Worker line_ascent = 0;
590*c8dee2aaSAndroid Build Coastguard Worker
591*c8dee2aaSAndroid Build Coastguard Worker bool in_word = false;
592*c8dee2aaSAndroid Build Coastguard Worker
593*c8dee2aaSAndroid Build Coastguard Worker // TODO: use ICU for building the word map?
594*c8dee2aaSAndroid Build Coastguard Worker for (; i < shape_result.fFragments.size(); ++i) {
595*c8dee2aaSAndroid Build Coastguard Worker const auto& frag = shape_result.fFragments[i];
596*c8dee2aaSAndroid Build Coastguard Worker const bool is_new_line = frag.fLineIndex != line;
597*c8dee2aaSAndroid Build Coastguard Worker
598*c8dee2aaSAndroid Build Coastguard Worker if (frag.fIsWhitespace || is_new_line) {
599*c8dee2aaSAndroid Build Coastguard Worker // Both whitespace and new lines break words.
600*c8dee2aaSAndroid Build Coastguard Worker if (in_word) {
601*c8dee2aaSAndroid Build Coastguard Worker in_word = false;
602*c8dee2aaSAndroid Build Coastguard Worker fMaps.fWordsMap.push_back({word_start, i - word_start, word_advance, word_ascent});
603*c8dee2aaSAndroid Build Coastguard Worker }
604*c8dee2aaSAndroid Build Coastguard Worker }
605*c8dee2aaSAndroid Build Coastguard Worker
606*c8dee2aaSAndroid Build Coastguard Worker if (!frag.fIsWhitespace) {
607*c8dee2aaSAndroid Build Coastguard Worker fMaps.fNonWhitespaceMap.push_back({i, 1, 0, 0});
608*c8dee2aaSAndroid Build Coastguard Worker
609*c8dee2aaSAndroid Build Coastguard Worker if (!in_word) {
610*c8dee2aaSAndroid Build Coastguard Worker in_word = true;
611*c8dee2aaSAndroid Build Coastguard Worker word_start = i;
612*c8dee2aaSAndroid Build Coastguard Worker word_advance = word_ascent = 0;
613*c8dee2aaSAndroid Build Coastguard Worker }
614*c8dee2aaSAndroid Build Coastguard Worker
615*c8dee2aaSAndroid Build Coastguard Worker word_advance += frag.fAdvance;
616*c8dee2aaSAndroid Build Coastguard Worker word_ascent = std::min(word_ascent, frag.fAscent); // negative ascent
617*c8dee2aaSAndroid Build Coastguard Worker }
618*c8dee2aaSAndroid Build Coastguard Worker
619*c8dee2aaSAndroid Build Coastguard Worker if (is_new_line) {
620*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(frag.fLineIndex == line + 1);
621*c8dee2aaSAndroid Build Coastguard Worker fMaps.fLinesMap.push_back({line_start, i - line_start, line_advance, line_ascent});
622*c8dee2aaSAndroid Build Coastguard Worker line = frag.fLineIndex;
623*c8dee2aaSAndroid Build Coastguard Worker line_start = i;
624*c8dee2aaSAndroid Build Coastguard Worker line_advance = line_ascent = 0;
625*c8dee2aaSAndroid Build Coastguard Worker }
626*c8dee2aaSAndroid Build Coastguard Worker
627*c8dee2aaSAndroid Build Coastguard Worker line_advance += frag.fAdvance;
628*c8dee2aaSAndroid Build Coastguard Worker line_ascent = std::min(line_ascent, frag.fAscent); // negative ascent
629*c8dee2aaSAndroid Build Coastguard Worker }
630*c8dee2aaSAndroid Build Coastguard Worker
631*c8dee2aaSAndroid Build Coastguard Worker if (i > word_start) {
632*c8dee2aaSAndroid Build Coastguard Worker fMaps.fWordsMap.push_back({word_start, i - word_start, word_advance, word_ascent});
633*c8dee2aaSAndroid Build Coastguard Worker }
634*c8dee2aaSAndroid Build Coastguard Worker
635*c8dee2aaSAndroid Build Coastguard Worker if (i > line_start) {
636*c8dee2aaSAndroid Build Coastguard Worker fMaps.fLinesMap.push_back({line_start, i - line_start, line_advance, line_ascent});
637*c8dee2aaSAndroid Build Coastguard Worker }
638*c8dee2aaSAndroid Build Coastguard Worker }
639*c8dee2aaSAndroid Build Coastguard Worker
setText(const TextValue & txt)640*c8dee2aaSAndroid Build Coastguard Worker void TextAdapter::setText(const TextValue& txt) {
641*c8dee2aaSAndroid Build Coastguard Worker fText.fCurrentValue = txt;
642*c8dee2aaSAndroid Build Coastguard Worker this->onSync();
643*c8dee2aaSAndroid Build Coastguard Worker }
644*c8dee2aaSAndroid Build Coastguard Worker
shaperFlags() const645*c8dee2aaSAndroid Build Coastguard Worker uint32_t TextAdapter::shaperFlags() const {
646*c8dee2aaSAndroid Build Coastguard Worker uint32_t flags = Shaper::Flags::kNone;
647*c8dee2aaSAndroid Build Coastguard Worker
648*c8dee2aaSAndroid Build Coastguard Worker // We need granular fragments (as opposed to consolidated blobs):
649*c8dee2aaSAndroid Build Coastguard Worker // - when animating
650*c8dee2aaSAndroid Build Coastguard Worker // - when positioning on a path
651*c8dee2aaSAndroid Build Coastguard Worker // - when clamping the number or lines (for accurate line count)
652*c8dee2aaSAndroid Build Coastguard Worker // - when a text decorator is present
653*c8dee2aaSAndroid Build Coastguard Worker if (!fAnimators.empty() || fPathInfo || fText->fMaxLines || fText->fDecorator) {
654*c8dee2aaSAndroid Build Coastguard Worker flags |= Shaper::Flags::kFragmentGlyphs;
655*c8dee2aaSAndroid Build Coastguard Worker }
656*c8dee2aaSAndroid Build Coastguard Worker
657*c8dee2aaSAndroid Build Coastguard Worker if (fRequiresAnchorPoint || fText->fDecorator) {
658*c8dee2aaSAndroid Build Coastguard Worker flags |= Shaper::Flags::kTrackFragmentAdvanceAscent;
659*c8dee2aaSAndroid Build Coastguard Worker }
660*c8dee2aaSAndroid Build Coastguard Worker
661*c8dee2aaSAndroid Build Coastguard Worker if (fText->fDecorator) {
662*c8dee2aaSAndroid Build Coastguard Worker flags |= Shaper::Flags::kClusters;
663*c8dee2aaSAndroid Build Coastguard Worker }
664*c8dee2aaSAndroid Build Coastguard Worker
665*c8dee2aaSAndroid Build Coastguard Worker return flags;
666*c8dee2aaSAndroid Build Coastguard Worker }
667*c8dee2aaSAndroid Build Coastguard Worker
reshape()668*c8dee2aaSAndroid Build Coastguard Worker void TextAdapter::reshape() {
669*c8dee2aaSAndroid Build Coastguard Worker // AE clamps the font size to a reasonable range.
670*c8dee2aaSAndroid Build Coastguard Worker // We do the same, since HB is susceptible to int overflows for degenerate values.
671*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kMinSize = 0.1f,
672*c8dee2aaSAndroid Build Coastguard Worker kMaxSize = 1296.0f;
673*c8dee2aaSAndroid Build Coastguard Worker const Shaper::TextDesc text_desc = {
674*c8dee2aaSAndroid Build Coastguard Worker fText->fTypeface,
675*c8dee2aaSAndroid Build Coastguard Worker SkTPin(fText->fTextSize, kMinSize, kMaxSize),
676*c8dee2aaSAndroid Build Coastguard Worker SkTPin(fText->fMinTextSize, kMinSize, kMaxSize),
677*c8dee2aaSAndroid Build Coastguard Worker SkTPin(fText->fMaxTextSize, kMinSize, kMaxSize),
678*c8dee2aaSAndroid Build Coastguard Worker fText->fLineHeight,
679*c8dee2aaSAndroid Build Coastguard Worker fText->fLineShift,
680*c8dee2aaSAndroid Build Coastguard Worker fText->fAscent,
681*c8dee2aaSAndroid Build Coastguard Worker fText->fHAlign,
682*c8dee2aaSAndroid Build Coastguard Worker fText->fVAlign,
683*c8dee2aaSAndroid Build Coastguard Worker fText->fResize,
684*c8dee2aaSAndroid Build Coastguard Worker fText->fLineBreak,
685*c8dee2aaSAndroid Build Coastguard Worker fText->fDirection,
686*c8dee2aaSAndroid Build Coastguard Worker fText->fCapitalization,
687*c8dee2aaSAndroid Build Coastguard Worker fText->fMaxLines,
688*c8dee2aaSAndroid Build Coastguard Worker this->shaperFlags(),
689*c8dee2aaSAndroid Build Coastguard Worker fText->fLocale.isEmpty() ? nullptr : fText->fLocale.c_str(),
690*c8dee2aaSAndroid Build Coastguard Worker fText->fFontFamily.isEmpty() ? nullptr : fText->fFontFamily.c_str(),
691*c8dee2aaSAndroid Build Coastguard Worker };
692*c8dee2aaSAndroid Build Coastguard Worker auto shape_result = Shaper::Shape(fText->fText, text_desc, fText->fBox, fFontMgr,
693*c8dee2aaSAndroid Build Coastguard Worker fShapingFactory);
694*c8dee2aaSAndroid Build Coastguard Worker
695*c8dee2aaSAndroid Build Coastguard Worker if (fLogger) {
696*c8dee2aaSAndroid Build Coastguard Worker if (shape_result.fFragments.empty() && fText->fText.size() > 0) {
697*c8dee2aaSAndroid Build Coastguard Worker const auto msg = SkStringPrintf("Text layout failed for '%s'.",
698*c8dee2aaSAndroid Build Coastguard Worker fText->fText.c_str());
699*c8dee2aaSAndroid Build Coastguard Worker fLogger->log(Logger::Level::kError, msg.c_str());
700*c8dee2aaSAndroid Build Coastguard Worker
701*c8dee2aaSAndroid Build Coastguard Worker // These may trigger repeatedly when the text is animating.
702*c8dee2aaSAndroid Build Coastguard Worker // To avoid spamming, only log once.
703*c8dee2aaSAndroid Build Coastguard Worker fLogger = nullptr;
704*c8dee2aaSAndroid Build Coastguard Worker }
705*c8dee2aaSAndroid Build Coastguard Worker
706*c8dee2aaSAndroid Build Coastguard Worker if (shape_result.fMissingGlyphCount > 0) {
707*c8dee2aaSAndroid Build Coastguard Worker const auto msg = SkStringPrintf("Missing %zu glyphs for '%s'.",
708*c8dee2aaSAndroid Build Coastguard Worker shape_result.fMissingGlyphCount,
709*c8dee2aaSAndroid Build Coastguard Worker fText->fText.c_str());
710*c8dee2aaSAndroid Build Coastguard Worker fLogger->log(Logger::Level::kWarning, msg.c_str());
711*c8dee2aaSAndroid Build Coastguard Worker fLogger = nullptr;
712*c8dee2aaSAndroid Build Coastguard Worker }
713*c8dee2aaSAndroid Build Coastguard Worker }
714*c8dee2aaSAndroid Build Coastguard Worker
715*c8dee2aaSAndroid Build Coastguard Worker // Save the text shaping scale for later adjustments.
716*c8dee2aaSAndroid Build Coastguard Worker fTextShapingScale = shape_result.fScale;
717*c8dee2aaSAndroid Build Coastguard Worker
718*c8dee2aaSAndroid Build Coastguard Worker // Rebuild all fragments.
719*c8dee2aaSAndroid Build Coastguard Worker // TODO: we can be smarter here and try to reuse the existing SG structure if needed.
720*c8dee2aaSAndroid Build Coastguard Worker
721*c8dee2aaSAndroid Build Coastguard Worker fRoot->clear();
722*c8dee2aaSAndroid Build Coastguard Worker fFragments.clear();
723*c8dee2aaSAndroid Build Coastguard Worker
724*c8dee2aaSAndroid Build Coastguard Worker if (SHOW_LAYOUT_BOXES) {
725*c8dee2aaSAndroid Build Coastguard Worker auto box_color = sksg::Color::Make(0xffff0000);
726*c8dee2aaSAndroid Build Coastguard Worker box_color->setStyle(SkPaint::kStroke_Style);
727*c8dee2aaSAndroid Build Coastguard Worker box_color->setStrokeWidth(1);
728*c8dee2aaSAndroid Build Coastguard Worker box_color->setAntiAlias(true);
729*c8dee2aaSAndroid Build Coastguard Worker
730*c8dee2aaSAndroid Build Coastguard Worker auto bounds_color = sksg::Color::Make(0xff00ff00);
731*c8dee2aaSAndroid Build Coastguard Worker bounds_color->setStyle(SkPaint::kStroke_Style);
732*c8dee2aaSAndroid Build Coastguard Worker bounds_color->setStrokeWidth(1);
733*c8dee2aaSAndroid Build Coastguard Worker bounds_color->setAntiAlias(true);
734*c8dee2aaSAndroid Build Coastguard Worker
735*c8dee2aaSAndroid Build Coastguard Worker fRoot->addChild(sksg::Draw::Make(sksg::Rect::Make(fText->fBox),
736*c8dee2aaSAndroid Build Coastguard Worker std::move(box_color)));
737*c8dee2aaSAndroid Build Coastguard Worker fRoot->addChild(sksg::Draw::Make(sksg::Rect::Make(shape_result.computeVisualBounds()),
738*c8dee2aaSAndroid Build Coastguard Worker std::move(bounds_color)));
739*c8dee2aaSAndroid Build Coastguard Worker
740*c8dee2aaSAndroid Build Coastguard Worker if (fPathInfo) {
741*c8dee2aaSAndroid Build Coastguard Worker auto path_color = sksg::Color::Make(0xffffff00);
742*c8dee2aaSAndroid Build Coastguard Worker path_color->setStyle(SkPaint::kStroke_Style);
743*c8dee2aaSAndroid Build Coastguard Worker path_color->setStrokeWidth(1);
744*c8dee2aaSAndroid Build Coastguard Worker path_color->setAntiAlias(true);
745*c8dee2aaSAndroid Build Coastguard Worker
746*c8dee2aaSAndroid Build Coastguard Worker fRoot->addChild(
747*c8dee2aaSAndroid Build Coastguard Worker sksg::Draw::Make(sksg::Path::Make(static_cast<SkPath>(fPathInfo->fPath)),
748*c8dee2aaSAndroid Build Coastguard Worker std::move(path_color)));
749*c8dee2aaSAndroid Build Coastguard Worker }
750*c8dee2aaSAndroid Build Coastguard Worker }
751*c8dee2aaSAndroid Build Coastguard Worker
752*c8dee2aaSAndroid Build Coastguard Worker // Depending on whether a GlyphDecorator is present, we either add the glyph render nodes
753*c8dee2aaSAndroid Build Coastguard Worker // directly to the root group, or to an intermediate GlyphDecoratorNode container.
754*c8dee2aaSAndroid Build Coastguard Worker sksg::Group* container = fRoot.get();
755*c8dee2aaSAndroid Build Coastguard Worker sk_sp<GlyphDecoratorNode> decorator_node;
756*c8dee2aaSAndroid Build Coastguard Worker if (fText->fDecorator) {
757*c8dee2aaSAndroid Build Coastguard Worker decorator_node = sk_make_sp<GlyphDecoratorNode>(fText->fDecorator, fTextShapingScale);
758*c8dee2aaSAndroid Build Coastguard Worker container = decorator_node.get();
759*c8dee2aaSAndroid Build Coastguard Worker }
760*c8dee2aaSAndroid Build Coastguard Worker
761*c8dee2aaSAndroid Build Coastguard Worker // N.B. addFragment moves shaped glyph data out of the fragment, so only the fragment
762*c8dee2aaSAndroid Build Coastguard Worker // metrics are valid after this block.
763*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < shape_result.fFragments.size(); ++i) {
764*c8dee2aaSAndroid Build Coastguard Worker this->addFragment(shape_result.fFragments[i], container);
765*c8dee2aaSAndroid Build Coastguard Worker }
766*c8dee2aaSAndroid Build Coastguard Worker
767*c8dee2aaSAndroid Build Coastguard Worker if (decorator_node) {
768*c8dee2aaSAndroid Build Coastguard Worker decorator_node->updateFragmentData(fFragments);
769*c8dee2aaSAndroid Build Coastguard Worker fRoot->addChild(std::move(decorator_node));
770*c8dee2aaSAndroid Build Coastguard Worker }
771*c8dee2aaSAndroid Build Coastguard Worker
772*c8dee2aaSAndroid Build Coastguard Worker if (!fAnimators.empty() || fPathInfo) {
773*c8dee2aaSAndroid Build Coastguard Worker // Range selectors and text paths require fragment domain maps.
774*c8dee2aaSAndroid Build Coastguard Worker this->buildDomainMaps(shape_result);
775*c8dee2aaSAndroid Build Coastguard Worker }
776*c8dee2aaSAndroid Build Coastguard Worker }
777*c8dee2aaSAndroid Build Coastguard Worker
onSync()778*c8dee2aaSAndroid Build Coastguard Worker void TextAdapter::onSync() {
779*c8dee2aaSAndroid Build Coastguard Worker if (!fText->fHasFill && !fText->fHasStroke) {
780*c8dee2aaSAndroid Build Coastguard Worker return;
781*c8dee2aaSAndroid Build Coastguard Worker }
782*c8dee2aaSAndroid Build Coastguard Worker
783*c8dee2aaSAndroid Build Coastguard Worker if (fText.hasChanged()) {
784*c8dee2aaSAndroid Build Coastguard Worker this->reshape();
785*c8dee2aaSAndroid Build Coastguard Worker }
786*c8dee2aaSAndroid Build Coastguard Worker
787*c8dee2aaSAndroid Build Coastguard Worker if (fFragments.empty()) {
788*c8dee2aaSAndroid Build Coastguard Worker return;
789*c8dee2aaSAndroid Build Coastguard Worker }
790*c8dee2aaSAndroid Build Coastguard Worker
791*c8dee2aaSAndroid Build Coastguard Worker // Update the path contour measure, if needed.
792*c8dee2aaSAndroid Build Coastguard Worker if (fPathInfo) {
793*c8dee2aaSAndroid Build Coastguard Worker fPathInfo->updateContourData();
794*c8dee2aaSAndroid Build Coastguard Worker }
795*c8dee2aaSAndroid Build Coastguard Worker
796*c8dee2aaSAndroid Build Coastguard Worker // Seed props from the current text value.
797*c8dee2aaSAndroid Build Coastguard Worker TextAnimator::ResolvedProps seed_props;
798*c8dee2aaSAndroid Build Coastguard Worker seed_props.fill_color = fText->fFillColor;
799*c8dee2aaSAndroid Build Coastguard Worker seed_props.stroke_color = fText->fStrokeColor;
800*c8dee2aaSAndroid Build Coastguard Worker seed_props.stroke_width = fText->fStrokeWidth;
801*c8dee2aaSAndroid Build Coastguard Worker
802*c8dee2aaSAndroid Build Coastguard Worker TextAnimator::ModulatorBuffer buf;
803*c8dee2aaSAndroid Build Coastguard Worker buf.resize(fFragments.size(), { seed_props, 0 });
804*c8dee2aaSAndroid Build Coastguard Worker
805*c8dee2aaSAndroid Build Coastguard Worker // Apply all animators to the modulator buffer.
806*c8dee2aaSAndroid Build Coastguard Worker for (const auto& animator : fAnimators) {
807*c8dee2aaSAndroid Build Coastguard Worker animator->modulateProps(fMaps, buf);
808*c8dee2aaSAndroid Build Coastguard Worker }
809*c8dee2aaSAndroid Build Coastguard Worker
810*c8dee2aaSAndroid Build Coastguard Worker const TextAnimator::DomainMap* grouping_domain = nullptr;
811*c8dee2aaSAndroid Build Coastguard Worker switch (fAnchorPointGrouping) {
812*c8dee2aaSAndroid Build Coastguard Worker // for word/line grouping, we rely on domain map info
813*c8dee2aaSAndroid Build Coastguard Worker case AnchorPointGrouping::kWord: grouping_domain = &fMaps.fWordsMap; break;
814*c8dee2aaSAndroid Build Coastguard Worker case AnchorPointGrouping::kLine: grouping_domain = &fMaps.fLinesMap; break;
815*c8dee2aaSAndroid Build Coastguard Worker // remaining grouping modes (character/all) do not need (or have) domain map data
816*c8dee2aaSAndroid Build Coastguard Worker default: break;
817*c8dee2aaSAndroid Build Coastguard Worker }
818*c8dee2aaSAndroid Build Coastguard Worker
819*c8dee2aaSAndroid Build Coastguard Worker size_t grouping_span_index = 0;
820*c8dee2aaSAndroid Build Coastguard Worker SkV2 current_line_offset = { 0, 0 }; // cumulative line spacing
821*c8dee2aaSAndroid Build Coastguard Worker
822*c8dee2aaSAndroid Build Coastguard Worker auto compute_linewide_props = [this](const TextAnimator::ModulatorBuffer& buf,
823*c8dee2aaSAndroid Build Coastguard Worker const TextAnimator::DomainSpan& line_span) {
824*c8dee2aaSAndroid Build Coastguard Worker SkV2 total_spacing = {0,0};
825*c8dee2aaSAndroid Build Coastguard Worker float total_tracking = 0;
826*c8dee2aaSAndroid Build Coastguard Worker
827*c8dee2aaSAndroid Build Coastguard Worker // Only compute these when needed.
828*c8dee2aaSAndroid Build Coastguard Worker if (fRequiresLineAdjustments && line_span.fCount) {
829*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = line_span.fOffset; i < line_span.fOffset + line_span.fCount; ++i) {
830*c8dee2aaSAndroid Build Coastguard Worker const auto& props = buf[i].props;
831*c8dee2aaSAndroid Build Coastguard Worker total_spacing += props.line_spacing;
832*c8dee2aaSAndroid Build Coastguard Worker total_tracking += props.tracking;
833*c8dee2aaSAndroid Build Coastguard Worker }
834*c8dee2aaSAndroid Build Coastguard Worker
835*c8dee2aaSAndroid Build Coastguard Worker // The first glyph does not contribute |before| tracking, and the last one does not
836*c8dee2aaSAndroid Build Coastguard Worker // contribute |after| tracking.
837*c8dee2aaSAndroid Build Coastguard Worker total_tracking -= 0.5f * (buf[line_span.fOffset].props.tracking +
838*c8dee2aaSAndroid Build Coastguard Worker buf[line_span.fOffset + line_span.fCount - 1].props.tracking);
839*c8dee2aaSAndroid Build Coastguard Worker }
840*c8dee2aaSAndroid Build Coastguard Worker
841*c8dee2aaSAndroid Build Coastguard Worker return std::make_tuple(total_spacing, total_tracking);
842*c8dee2aaSAndroid Build Coastguard Worker };
843*c8dee2aaSAndroid Build Coastguard Worker
844*c8dee2aaSAndroid Build Coastguard Worker // Finally, push all props to their corresponding fragment.
845*c8dee2aaSAndroid Build Coastguard Worker for (const auto& line_span : fMaps.fLinesMap) {
846*c8dee2aaSAndroid Build Coastguard Worker const auto [line_spacing, line_tracking] = compute_linewide_props(buf, line_span);
847*c8dee2aaSAndroid Build Coastguard Worker const auto align_offset = -line_tracking * align_factor(fText->fHAlign);
848*c8dee2aaSAndroid Build Coastguard Worker
849*c8dee2aaSAndroid Build Coastguard Worker // line spacing of the first line is ignored (nothing to "space" against)
850*c8dee2aaSAndroid Build Coastguard Worker if (&line_span != &fMaps.fLinesMap.front() && line_span.fCount) {
851*c8dee2aaSAndroid Build Coastguard Worker // For each line, the actual spacing is an average of individual fragment spacing
852*c8dee2aaSAndroid Build Coastguard Worker // (to preserve the "line").
853*c8dee2aaSAndroid Build Coastguard Worker current_line_offset += line_spacing / line_span.fCount;
854*c8dee2aaSAndroid Build Coastguard Worker }
855*c8dee2aaSAndroid Build Coastguard Worker
856*c8dee2aaSAndroid Build Coastguard Worker float tracking_acc = 0;
857*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = line_span.fOffset; i < line_span.fOffset + line_span.fCount; ++i) {
858*c8dee2aaSAndroid Build Coastguard Worker // Track the grouping domain span in parallel.
859*c8dee2aaSAndroid Build Coastguard Worker if (grouping_domain && i >= (*grouping_domain)[grouping_span_index].fOffset +
860*c8dee2aaSAndroid Build Coastguard Worker (*grouping_domain)[grouping_span_index].fCount) {
861*c8dee2aaSAndroid Build Coastguard Worker grouping_span_index += 1;
862*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(i < (*grouping_domain)[grouping_span_index].fOffset +
863*c8dee2aaSAndroid Build Coastguard Worker (*grouping_domain)[grouping_span_index].fCount);
864*c8dee2aaSAndroid Build Coastguard Worker }
865*c8dee2aaSAndroid Build Coastguard Worker
866*c8dee2aaSAndroid Build Coastguard Worker const auto& props = buf[i].props;
867*c8dee2aaSAndroid Build Coastguard Worker const auto& frag = fFragments[i];
868*c8dee2aaSAndroid Build Coastguard Worker
869*c8dee2aaSAndroid Build Coastguard Worker // AE tracking is defined per glyph, based on two components: |before| and |after|.
870*c8dee2aaSAndroid Build Coastguard Worker // BodyMovin only exports "balanced" tracking values, where before = after = tracking/2.
871*c8dee2aaSAndroid Build Coastguard Worker //
872*c8dee2aaSAndroid Build Coastguard Worker // Tracking is applied as a local glyph offset, and contributes to the line width for
873*c8dee2aaSAndroid Build Coastguard Worker // alignment purposes.
874*c8dee2aaSAndroid Build Coastguard Worker //
875*c8dee2aaSAndroid Build Coastguard Worker // No |before| tracking for the first glyph, nor |after| tracking for the last one.
876*c8dee2aaSAndroid Build Coastguard Worker const auto track_before = i > line_span.fOffset
877*c8dee2aaSAndroid Build Coastguard Worker ? props.tracking * 0.5f : 0.0f,
878*c8dee2aaSAndroid Build Coastguard Worker track_after = i < line_span.fOffset + line_span.fCount - 1
879*c8dee2aaSAndroid Build Coastguard Worker ? props.tracking * 0.5f : 0.0f;
880*c8dee2aaSAndroid Build Coastguard Worker
881*c8dee2aaSAndroid Build Coastguard Worker const auto frag_offset = current_line_offset +
882*c8dee2aaSAndroid Build Coastguard Worker SkV2{align_offset + tracking_acc + track_before, 0};
883*c8dee2aaSAndroid Build Coastguard Worker
884*c8dee2aaSAndroid Build Coastguard Worker tracking_acc += track_before + track_after;
885*c8dee2aaSAndroid Build Coastguard Worker
886*c8dee2aaSAndroid Build Coastguard Worker this->pushPropsToFragment(props, frag, frag_offset, fGroupingAlignment * .01f, // %
887*c8dee2aaSAndroid Build Coastguard Worker grouping_domain ? &(*grouping_domain)[grouping_span_index]
888*c8dee2aaSAndroid Build Coastguard Worker : nullptr);
889*c8dee2aaSAndroid Build Coastguard Worker }
890*c8dee2aaSAndroid Build Coastguard Worker }
891*c8dee2aaSAndroid Build Coastguard Worker }
892*c8dee2aaSAndroid Build Coastguard Worker
fragmentAnchorPoint(const FragmentRec & rec,const SkV2 & grouping_alignment,const TextAnimator::DomainSpan * grouping_span) const893*c8dee2aaSAndroid Build Coastguard Worker SkV2 TextAdapter::fragmentAnchorPoint(const FragmentRec& rec,
894*c8dee2aaSAndroid Build Coastguard Worker const SkV2& grouping_alignment,
895*c8dee2aaSAndroid Build Coastguard Worker const TextAnimator::DomainSpan* grouping_span) const {
896*c8dee2aaSAndroid Build Coastguard Worker // Construct the following 2x ascent box:
897*c8dee2aaSAndroid Build Coastguard Worker //
898*c8dee2aaSAndroid Build Coastguard Worker // -------------
899*c8dee2aaSAndroid Build Coastguard Worker // | |
900*c8dee2aaSAndroid Build Coastguard Worker // | | ascent
901*c8dee2aaSAndroid Build Coastguard Worker // | |
902*c8dee2aaSAndroid Build Coastguard Worker // ----+-------------+---------- baseline
903*c8dee2aaSAndroid Build Coastguard Worker // (pos) |
904*c8dee2aaSAndroid Build Coastguard Worker // | | ascent
905*c8dee2aaSAndroid Build Coastguard Worker // | |
906*c8dee2aaSAndroid Build Coastguard Worker // -------------
907*c8dee2aaSAndroid Build Coastguard Worker // advance
908*c8dee2aaSAndroid Build Coastguard Worker
909*c8dee2aaSAndroid Build Coastguard Worker auto make_box = [](const SkPoint& pos, float advance, float ascent) {
910*c8dee2aaSAndroid Build Coastguard Worker // note: negative ascent
911*c8dee2aaSAndroid Build Coastguard Worker return SkRect::MakeXYWH(pos.fX, pos.fY + ascent, advance, -2 * ascent);
912*c8dee2aaSAndroid Build Coastguard Worker };
913*c8dee2aaSAndroid Build Coastguard Worker
914*c8dee2aaSAndroid Build Coastguard Worker // Compute a grouping-dependent anchor point box.
915*c8dee2aaSAndroid Build Coastguard Worker // The default anchor point is at the center, and gets adjusted relative to the bounds
916*c8dee2aaSAndroid Build Coastguard Worker // based on |grouping_alignment|.
917*c8dee2aaSAndroid Build Coastguard Worker auto anchor_box = [&]() -> SkRect {
918*c8dee2aaSAndroid Build Coastguard Worker switch (fAnchorPointGrouping) {
919*c8dee2aaSAndroid Build Coastguard Worker case AnchorPointGrouping::kCharacter:
920*c8dee2aaSAndroid Build Coastguard Worker // Anchor box relative to each individual fragment.
921*c8dee2aaSAndroid Build Coastguard Worker return make_box(rec.fOrigin, rec.fAdvance, rec.fAscent);
922*c8dee2aaSAndroid Build Coastguard Worker case AnchorPointGrouping::kWord:
923*c8dee2aaSAndroid Build Coastguard Worker // Fall through
924*c8dee2aaSAndroid Build Coastguard Worker case AnchorPointGrouping::kLine: {
925*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(grouping_span);
926*c8dee2aaSAndroid Build Coastguard Worker // Anchor box relative to the first fragment in the word/line.
927*c8dee2aaSAndroid Build Coastguard Worker const auto& first_span_fragment = fFragments[grouping_span->fOffset];
928*c8dee2aaSAndroid Build Coastguard Worker return make_box(first_span_fragment.fOrigin,
929*c8dee2aaSAndroid Build Coastguard Worker grouping_span->fAdvance,
930*c8dee2aaSAndroid Build Coastguard Worker grouping_span->fAscent);
931*c8dee2aaSAndroid Build Coastguard Worker }
932*c8dee2aaSAndroid Build Coastguard Worker case AnchorPointGrouping::kAll:
933*c8dee2aaSAndroid Build Coastguard Worker // Anchor box is the same as the text box.
934*c8dee2aaSAndroid Build Coastguard Worker return fText->fBox;
935*c8dee2aaSAndroid Build Coastguard Worker }
936*c8dee2aaSAndroid Build Coastguard Worker SkUNREACHABLE;
937*c8dee2aaSAndroid Build Coastguard Worker };
938*c8dee2aaSAndroid Build Coastguard Worker
939*c8dee2aaSAndroid Build Coastguard Worker const auto ab = anchor_box();
940*c8dee2aaSAndroid Build Coastguard Worker
941*c8dee2aaSAndroid Build Coastguard Worker // Apply grouping alignment.
942*c8dee2aaSAndroid Build Coastguard Worker const auto ap = SkV2 { ab.centerX() + ab.width() * 0.5f * grouping_alignment.x,
943*c8dee2aaSAndroid Build Coastguard Worker ab.centerY() + ab.height() * 0.5f * grouping_alignment.y };
944*c8dee2aaSAndroid Build Coastguard Worker
945*c8dee2aaSAndroid Build Coastguard Worker // The anchor point is relative to the fragment position.
946*c8dee2aaSAndroid Build Coastguard Worker return ap - SkV2 { rec.fOrigin.fX, rec.fOrigin.fY };
947*c8dee2aaSAndroid Build Coastguard Worker }
948*c8dee2aaSAndroid Build Coastguard Worker
fragmentMatrix(const TextAnimator::ResolvedProps & props,const FragmentRec & rec,const SkV2 & frag_offset) const949*c8dee2aaSAndroid Build Coastguard Worker SkM44 TextAdapter::fragmentMatrix(const TextAnimator::ResolvedProps& props,
950*c8dee2aaSAndroid Build Coastguard Worker const FragmentRec& rec, const SkV2& frag_offset) const {
951*c8dee2aaSAndroid Build Coastguard Worker const SkV3 pos = {
952*c8dee2aaSAndroid Build Coastguard Worker props.position.x + rec.fOrigin.fX + frag_offset.x,
953*c8dee2aaSAndroid Build Coastguard Worker props.position.y + rec.fOrigin.fY + frag_offset.y,
954*c8dee2aaSAndroid Build Coastguard Worker props.position.z
955*c8dee2aaSAndroid Build Coastguard Worker };
956*c8dee2aaSAndroid Build Coastguard Worker
957*c8dee2aaSAndroid Build Coastguard Worker if (!fPathInfo) {
958*c8dee2aaSAndroid Build Coastguard Worker return SkM44::Translate(pos.x, pos.y, pos.z);
959*c8dee2aaSAndroid Build Coastguard Worker }
960*c8dee2aaSAndroid Build Coastguard Worker
961*c8dee2aaSAndroid Build Coastguard Worker // "Align" the paragraph box left/center/right to path start/mid/end, respectively.
962*c8dee2aaSAndroid Build Coastguard Worker const auto align_offset =
963*c8dee2aaSAndroid Build Coastguard Worker align_factor(fText->fHAlign)*(fPathInfo->pathLength() - fText->fBox.width());
964*c8dee2aaSAndroid Build Coastguard Worker
965*c8dee2aaSAndroid Build Coastguard Worker // Path positioning is based on the fragment position relative to the paragraph box
966*c8dee2aaSAndroid Build Coastguard Worker // upper-left corner:
967*c8dee2aaSAndroid Build Coastguard Worker //
968*c8dee2aaSAndroid Build Coastguard Worker // - the horizontal component determines the distance on path
969*c8dee2aaSAndroid Build Coastguard Worker //
970*c8dee2aaSAndroid Build Coastguard Worker // - the vertical component is post-applied after orienting on path
971*c8dee2aaSAndroid Build Coastguard Worker //
972*c8dee2aaSAndroid Build Coastguard Worker // Note: in point-text mode, the box adjustments have no effect as fBox is {0,0,0,0}.
973*c8dee2aaSAndroid Build Coastguard Worker //
974*c8dee2aaSAndroid Build Coastguard Worker const auto rel_pos = SkV2{pos.x, pos.y} - SkV2{fText->fBox.fLeft, fText->fBox.fTop};
975*c8dee2aaSAndroid Build Coastguard Worker const auto path_distance = rel_pos.x + align_offset;
976*c8dee2aaSAndroid Build Coastguard Worker
977*c8dee2aaSAndroid Build Coastguard Worker return fPathInfo->getMatrix(path_distance, fText->fHAlign)
978*c8dee2aaSAndroid Build Coastguard Worker * SkM44::Translate(0, rel_pos.y, pos.z);
979*c8dee2aaSAndroid Build Coastguard Worker }
980*c8dee2aaSAndroid Build Coastguard Worker
pushPropsToFragment(const TextAnimator::ResolvedProps & props,const FragmentRec & rec,const SkV2 & frag_offset,const SkV2 & grouping_alignment,const TextAnimator::DomainSpan * grouping_span) const981*c8dee2aaSAndroid Build Coastguard Worker void TextAdapter::pushPropsToFragment(const TextAnimator::ResolvedProps& props,
982*c8dee2aaSAndroid Build Coastguard Worker const FragmentRec& rec,
983*c8dee2aaSAndroid Build Coastguard Worker const SkV2& frag_offset,
984*c8dee2aaSAndroid Build Coastguard Worker const SkV2& grouping_alignment,
985*c8dee2aaSAndroid Build Coastguard Worker const TextAnimator::DomainSpan* grouping_span) const {
986*c8dee2aaSAndroid Build Coastguard Worker const auto anchor_point = this->fragmentAnchorPoint(rec, grouping_alignment, grouping_span);
987*c8dee2aaSAndroid Build Coastguard Worker
988*c8dee2aaSAndroid Build Coastguard Worker rec.fMatrixNode->setMatrix(
989*c8dee2aaSAndroid Build Coastguard Worker this->fragmentMatrix(props, rec, anchor_point + frag_offset)
990*c8dee2aaSAndroid Build Coastguard Worker * SkM44::Rotate({ 1, 0, 0 }, SkDegreesToRadians(props.rotation.x))
991*c8dee2aaSAndroid Build Coastguard Worker * SkM44::Rotate({ 0, 1, 0 }, SkDegreesToRadians(props.rotation.y))
992*c8dee2aaSAndroid Build Coastguard Worker * SkM44::Rotate({ 0, 0, 1 }, SkDegreesToRadians(props.rotation.z))
993*c8dee2aaSAndroid Build Coastguard Worker * SkM44::Scale(props.scale.x, props.scale.y, props.scale.z)
994*c8dee2aaSAndroid Build Coastguard Worker * SkM44::Translate(-anchor_point.x, -anchor_point.y, 0));
995*c8dee2aaSAndroid Build Coastguard Worker
996*c8dee2aaSAndroid Build Coastguard Worker const auto scale_alpha = [](SkColor c, float o) {
997*c8dee2aaSAndroid Build Coastguard Worker return SkColorSetA(c, SkScalarRoundToInt(o * SkColorGetA(c)));
998*c8dee2aaSAndroid Build Coastguard Worker };
999*c8dee2aaSAndroid Build Coastguard Worker
1000*c8dee2aaSAndroid Build Coastguard Worker if (rec.fFillColorNode) {
1001*c8dee2aaSAndroid Build Coastguard Worker rec.fFillColorNode->setColor(scale_alpha(props.fill_color, props.opacity));
1002*c8dee2aaSAndroid Build Coastguard Worker }
1003*c8dee2aaSAndroid Build Coastguard Worker if (rec.fStrokeColorNode) {
1004*c8dee2aaSAndroid Build Coastguard Worker rec.fStrokeColorNode->setColor(scale_alpha(props.stroke_color, props.opacity));
1005*c8dee2aaSAndroid Build Coastguard Worker rec.fStrokeColorNode->setStrokeWidth(props.stroke_width * fTextShapingScale);
1006*c8dee2aaSAndroid Build Coastguard Worker }
1007*c8dee2aaSAndroid Build Coastguard Worker if (rec.fBlur) {
1008*c8dee2aaSAndroid Build Coastguard Worker rec.fBlur->setSigma({ props.blur.x * kBlurSizeToSigma,
1009*c8dee2aaSAndroid Build Coastguard Worker props.blur.y * kBlurSizeToSigma });
1010*c8dee2aaSAndroid Build Coastguard Worker }
1011*c8dee2aaSAndroid Build Coastguard Worker }
1012*c8dee2aaSAndroid Build Coastguard Worker
1013*c8dee2aaSAndroid Build Coastguard Worker } // namespace skottie::internal
1014