xref: /aosp_15_r20/external/skia/modules/skottie/src/text/TextAdapter.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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