xref: /third_party/skia/modules/svg/src/SkSVGText.cpp (revision cb93a386)
1/*
2 * Copyright 2019 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "modules/svg/include/SkSVGText.h"
9
10#include <limits>
11
12#include "include/core/SkCanvas.h"
13#include "include/core/SkContourMeasure.h"
14#include "include/core/SkFont.h"
15#include "include/core/SkFontMgr.h"
16#include "include/core/SkFontStyle.h"
17#include "include/core/SkPathBuilder.h"
18#include "include/core/SkRSXform.h"
19#include "include/core/SkString.h"
20#include "modules/skshaper/include/SkShaper.h"
21#include "modules/svg/include/SkSVGRenderContext.h"
22#include "modules/svg/include/SkSVGValue.h"
23#include "modules/svg/src/SkSVGTextPriv.h"
24#include "src/core/SkTextBlobPriv.h"
25#include "src/utils/SkUTF.h"
26
27namespace {
28
29static SkFont ResolveFont(const SkSVGRenderContext& ctx) {
30    auto weight = [](const SkSVGFontWeight& w) {
31        switch (w.type()) {
32            case SkSVGFontWeight::Type::k100:     return SkFontStyle::kThin_Weight;
33            case SkSVGFontWeight::Type::k200:     return SkFontStyle::kExtraLight_Weight;
34            case SkSVGFontWeight::Type::k300:     return SkFontStyle::kLight_Weight;
35            case SkSVGFontWeight::Type::k400:     return SkFontStyle::kNormal_Weight;
36            case SkSVGFontWeight::Type::k500:     return SkFontStyle::kMedium_Weight;
37            case SkSVGFontWeight::Type::k600:     return SkFontStyle::kSemiBold_Weight;
38            case SkSVGFontWeight::Type::k700:     return SkFontStyle::kBold_Weight;
39            case SkSVGFontWeight::Type::k800:     return SkFontStyle::kExtraBold_Weight;
40            case SkSVGFontWeight::Type::k900:     return SkFontStyle::kBlack_Weight;
41            case SkSVGFontWeight::Type::kNormal:  return SkFontStyle::kNormal_Weight;
42            case SkSVGFontWeight::Type::kBold:    return SkFontStyle::kBold_Weight;
43            case SkSVGFontWeight::Type::kBolder:  return SkFontStyle::kExtraBold_Weight;
44            case SkSVGFontWeight::Type::kLighter: return SkFontStyle::kLight_Weight;
45            case SkSVGFontWeight::Type::kInherit: {
46                SkASSERT(false);
47                return SkFontStyle::kNormal_Weight;
48            }
49        }
50        SkUNREACHABLE;
51    };
52
53    auto slant = [](const SkSVGFontStyle& s) {
54        switch (s.type()) {
55            case SkSVGFontStyle::Type::kNormal:  return SkFontStyle::kUpright_Slant;
56            case SkSVGFontStyle::Type::kItalic:  return SkFontStyle::kItalic_Slant;
57            case SkSVGFontStyle::Type::kOblique: return SkFontStyle::kOblique_Slant;
58            case SkSVGFontStyle::Type::kInherit: {
59                SkASSERT(false);
60                return SkFontStyle::kUpright_Slant;
61            }
62        }
63        SkUNREACHABLE;
64    };
65
66    const auto& family = ctx.presentationContext().fInherited.fFontFamily->family();
67    const SkFontStyle style(weight(*ctx.presentationContext().fInherited.fFontWeight),
68                            SkFontStyle::kNormal_Width,
69                            slant(*ctx.presentationContext().fInherited.fFontStyle));
70
71    const auto size =
72            ctx.lengthContext().resolve(ctx.presentationContext().fInherited.fFontSize->size(),
73                                        SkSVGLengthContext::LengthType::kVertical);
74
75    // TODO: we likely want matchFamilyStyle here, but switching away from legacyMakeTypeface
76    // changes all the results when using the default fontmgr.
77    auto tf = ctx.fontMgr()->legacyMakeTypeface(family.c_str(), style);
78
79    SkFont font(std::move(tf), size);
80    font.setHinting(SkFontHinting::kNone);
81    font.setSubpixel(true);
82    font.setLinearMetrics(true);
83    font.setBaselineSnap(false);
84    font.setEdging(SkFont::Edging::kAntiAlias);
85
86    return font;
87}
88
89static std::vector<float> ResolveLengths(const SkSVGLengthContext& lctx,
90                                         const std::vector<SkSVGLength>& lengths,
91                                         SkSVGLengthContext::LengthType lt) {
92    std::vector<float> resolved;
93    resolved.reserve(lengths.size());
94
95    for (const auto& l : lengths) {
96        resolved.push_back(lctx.resolve(l, lt));
97    }
98
99    return resolved;
100}
101
102static float ComputeAlignmentFactor(const SkSVGPresentationContext& pctx) {
103    switch (pctx.fInherited.fTextAnchor->type()) {
104    case SkSVGTextAnchor::Type::kStart : return  0.0f;
105    case SkSVGTextAnchor::Type::kMiddle: return -0.5f;
106    case SkSVGTextAnchor::Type::kEnd   : return -1.0f;
107    case SkSVGTextAnchor::Type::kInherit:
108        SkASSERT(false);
109        return 0.0f;
110    }
111    SkUNREACHABLE;
112}
113
114} // namespace
115
116SkSVGTextContext::ScopedPosResolver::ScopedPosResolver(const SkSVGTextContainer& txt,
117                                                       const SkSVGLengthContext& lctx,
118                                                       SkSVGTextContext* tctx,
119                                                       size_t charIndexOffset)
120    : fTextContext(tctx)
121    , fParent(tctx->fPosResolver)
122    , fCharIndexOffset(charIndexOffset)
123    , fX(ResolveLengths(lctx, txt.getX(), SkSVGLengthContext::LengthType::kHorizontal))
124    , fY(ResolveLengths(lctx, txt.getY(), SkSVGLengthContext::LengthType::kVertical))
125    , fDx(ResolveLengths(lctx, txt.getDx(), SkSVGLengthContext::LengthType::kHorizontal))
126    , fDy(ResolveLengths(lctx, txt.getDy(), SkSVGLengthContext::LengthType::kVertical))
127    , fRotate(txt.getRotate())
128{
129    fTextContext->fPosResolver = this;
130}
131
132SkSVGTextContext::ScopedPosResolver::ScopedPosResolver(const SkSVGTextContainer& txt,
133                                                       const SkSVGLengthContext& lctx,
134                                                       SkSVGTextContext* tctx)
135    : ScopedPosResolver(txt, lctx, tctx, tctx->fCurrentCharIndex) {}
136
137SkSVGTextContext::ScopedPosResolver::~ScopedPosResolver() {
138    fTextContext->fPosResolver = fParent;
139}
140
141SkSVGTextContext::PosAttrs SkSVGTextContext::ScopedPosResolver::resolve(size_t charIndex) const {
142    PosAttrs attrs;
143
144    if (charIndex < fLastPosIndex) {
145        SkASSERT(charIndex >= fCharIndexOffset);
146        const auto localCharIndex = charIndex - fCharIndexOffset;
147
148        const auto hasAllLocal = localCharIndex < fX.size() &&
149                                 localCharIndex < fY.size() &&
150                                 localCharIndex < fDx.size() &&
151                                 localCharIndex < fDy.size() &&
152                                 localCharIndex < fRotate.size();
153        if (!hasAllLocal && fParent) {
154            attrs = fParent->resolve(charIndex);
155        }
156
157        if (localCharIndex < fX.size()) {
158            attrs[PosAttrs::kX] = fX[localCharIndex];
159        }
160        if (localCharIndex < fY.size()) {
161            attrs[PosAttrs::kY] = fY[localCharIndex];
162        }
163        if (localCharIndex < fDx.size()) {
164            attrs[PosAttrs::kDx] = fDx[localCharIndex];
165        }
166        if (localCharIndex < fDy.size()) {
167            attrs[PosAttrs::kDy] = fDy[localCharIndex];
168        }
169
170        // Rotation semantics are interestingly different [1]:
171        //
172        //   - values are not cumulative
173        //   - if explicit values are present at any level in the ancestor chain, those take
174        //     precedence (closest ancestor)
175        //   - last specified value applies to all remaining chars (closest ancestor)
176        //   - these rules apply at node scope (not chunk scope)
177        //
178        // This means we need to discriminate between explicit rotation (rotate value provided for
179        // current char) and implicit rotation (ancestor has some values - but not for the requested
180        // char - we use the last specified value).
181        //
182        // [1] https://www.w3.org/TR/SVG11/text.html#TSpanElementRotateAttribute
183        if (!fRotate.empty()) {
184            if (localCharIndex < fRotate.size()) {
185                // Explicit rotation value overrides anything in the ancestor chain.
186                attrs[PosAttrs::kRotate] = fRotate[localCharIndex];
187                attrs.setImplicitRotate(false);
188            } else if (!attrs.has(PosAttrs::kRotate) || attrs.isImplicitRotate()){
189                // Local implicit rotation (last specified value) overrides ancestor implicit
190                // rotation.
191                attrs[PosAttrs::kRotate] = fRotate.back();
192                attrs.setImplicitRotate(true);
193            }
194        }
195
196        if (!attrs.hasAny()) {
197            // Once we stop producing explicit position data, there is no reason to
198            // continue trying for higher indices.  We can suppress future lookups.
199            fLastPosIndex = charIndex;
200        }
201    }
202
203    return attrs;
204}
205
206void SkSVGTextContext::ShapeBuffer::append(SkUnichar ch, PositionAdjustment pos) {
207    // relative pos adjustments are cumulative
208    if (!fUtf8PosAdjust.empty()) {
209        pos.offset += fUtf8PosAdjust.back().offset;
210    }
211
212    char utf8_buf[SkUTF::kMaxBytesInUTF8Sequence];
213    const auto utf8_len = SkToInt(SkUTF::ToUTF8(ch, utf8_buf));
214    fUtf8         .push_back_n(utf8_len, utf8_buf);
215    fUtf8PosAdjust.push_back_n(utf8_len, pos);
216}
217
218void SkSVGTextContext::shapePendingBuffer(const SkFont& font) {
219    // TODO: directionality hints?
220    const auto LTR  = true;
221
222    // Initiate shaping: this will generate a series of runs via callbacks.
223    fShaper->shape(fShapeBuffer.fUtf8.data(), fShapeBuffer.fUtf8.size(),
224                   font, LTR, SK_ScalarMax, this);
225    fShapeBuffer.reset();
226}
227
228SkSVGTextContext::SkSVGTextContext(const SkSVGRenderContext& ctx, const ShapedTextCallback& cb,
229                                   const SkSVGTextPath* tpath)
230    : fRenderContext(ctx)
231    , fCallback(cb)
232    , fShaper(SkShaper::Make(ctx.fontMgr()))
233    , fChunkAlignmentFactor(ComputeAlignmentFactor(ctx.presentationContext()))
234{
235    if (tpath) {
236        fPathData = std::make_unique<PathData>(ctx, *tpath);
237
238        // https://www.w3.org/TR/SVG11/text.html#TextPathElementStartOffsetAttribute
239        auto resolve_offset = [this](const SkSVGLength& offset) {
240            if (offset.unit() != SkSVGLength::Unit::kPercentage) {
241                // "If a <length> other than a percentage is given, then the ‘startOffset’
242                // represents a distance along the path measured in the current user coordinate
243                // system."
244                return fRenderContext.lengthContext()
245                                     .resolve(offset, SkSVGLengthContext::LengthType::kHorizontal);
246            }
247
248            // "If a percentage is given, then the ‘startOffset’ represents a percentage distance
249            // along the entire path."
250            return offset.value() * fPathData->length() / 100;
251        };
252
253        // startOffset acts as an initial absolute position
254        fChunkPos.fX = resolve_offset(tpath->getStartOffset());
255    }
256}
257
258SkSVGTextContext::~SkSVGTextContext() {
259    this->flushChunk(fRenderContext);
260}
261
262void SkSVGTextContext::shapeFragment(const SkString& txt, const SkSVGRenderContext& ctx,
263                                     SkSVGXmlSpace xs) {
264    // https://www.w3.org/TR/SVG11/text.html#WhiteSpace
265    // https://www.w3.org/TR/2008/REC-xml-20081126/#NT-S
266    auto filterWSDefault = [this](SkUnichar ch) -> SkUnichar {
267        // Remove all newline chars.
268        if (ch == '\n') {
269            return -1;
270        }
271
272        // Convert tab chars to space.
273        if (ch == '\t') {
274            ch = ' ';
275        }
276
277        // Consolidate contiguous space chars and strip leading spaces (fPrevCharSpace
278        // starts off as true).
279        if (fPrevCharSpace && ch == ' ') {
280            return -1;
281        }
282
283        // TODO: Strip trailing WS?  Doing this across chunks would require another buffering
284        //   layer.  In general, trailing WS should have no rendering side effects. Skipping
285        //   for now.
286        return ch;
287    };
288    auto filterWSPreserve = [](SkUnichar ch) -> SkUnichar {
289        // Convert newline and tab chars to space.
290        if (ch == '\n' || ch == '\t') {
291            ch = ' ';
292        }
293        return ch;
294    };
295
296    // Stash paints for access from SkShaper callbacks.
297    fCurrentFill   = ctx.fillPaint();
298    fCurrentStroke = ctx.strokePaint();
299
300    const auto font = ResolveFont(ctx);
301    fShapeBuffer.reserve(txt.size());
302
303    const char* ch_ptr = txt.c_str();
304    const char* ch_end = ch_ptr + txt.size();
305
306    while (ch_ptr < ch_end) {
307        auto ch = SkUTF::NextUTF8(&ch_ptr, ch_end);
308        ch = (xs == SkSVGXmlSpace::kDefault)
309                ? filterWSDefault(ch)
310                : filterWSPreserve(ch);
311
312        if (ch < 0) {
313            // invalid utf or char filtered out
314            continue;
315        }
316
317        SkASSERT(fPosResolver);
318        const auto pos = fPosResolver->resolve(fCurrentCharIndex++);
319
320        // Absolute position adjustments define a new chunk.
321        // (https://www.w3.org/TR/SVG11/text.html#TextLayoutIntroduction)
322        if (pos.has(PosAttrs::kX) || pos.has(PosAttrs::kY)) {
323            this->shapePendingBuffer(font);
324            this->flushChunk(ctx);
325
326            // New chunk position.
327            if (pos.has(PosAttrs::kX)) {
328                fChunkPos.fX = pos[PosAttrs::kX];
329            }
330            if (pos.has(PosAttrs::kY)) {
331                fChunkPos.fY = pos[PosAttrs::kY];
332            }
333        }
334
335        fShapeBuffer.append(ch, {
336            {
337                pos.has(PosAttrs::kDx) ? pos[PosAttrs::kDx] : 0,
338                pos.has(PosAttrs::kDy) ? pos[PosAttrs::kDy] : 0,
339            },
340            pos.has(PosAttrs::kRotate) ? SkDegreesToRadians(pos[PosAttrs::kRotate]) : 0,
341        });
342
343        fPrevCharSpace = (ch == ' ');
344    }
345
346    this->shapePendingBuffer(font);
347
348    // Note: at this point we have shaped and buffered RunRecs for the current fragment.
349    // The active text chunk continues until an explicit or implicit flush.
350}
351
352SkSVGTextContext::PathData::PathData(const SkSVGRenderContext& ctx, const SkSVGTextPath& tpath)
353{
354    const auto ref = ctx.findNodeById(tpath.getHref());
355    if (!ref) {
356        return;
357    }
358
359    SkContourMeasureIter cmi(ref->asPath(ctx), false);
360    while (sk_sp<SkContourMeasure> contour = cmi.next()) {
361        fLength += contour->length();
362        fContours.push_back(std::move(contour));
363    }
364}
365
366SkMatrix SkSVGTextContext::PathData::getMatrixAt(float offset) const {
367    if (offset >= 0) {
368        for (const auto& contour : fContours) {
369            const auto contour_len = contour->length();
370            if (offset < contour_len) {
371                SkMatrix m;
372                return contour->getMatrix(offset, &m) ? m : SkMatrix::I();
373            }
374            offset -= contour_len;
375        }
376    }
377
378    // Quick & dirty way to "skip" rendering of glyphs off path.
379    return SkMatrix::Translate(std::numeric_limits<float>::infinity(),
380                               std::numeric_limits<float>::infinity());
381}
382
383SkRSXform SkSVGTextContext::computeGlyphXform(SkGlyphID glyph, const SkFont& font,
384                                              const SkPoint& glyph_pos,
385                                              const PositionAdjustment& pos_adjust) const {
386    SkPoint pos = fChunkPos + glyph_pos + pos_adjust.offset + fChunkAdvance * fChunkAlignmentFactor;
387    if (!fPathData) {
388        return SkRSXform::MakeFromRadians(/*scale=*/ 1, pos_adjust.rotation, pos.fX, pos.fY, 0, 0);
389    }
390
391    // We're in a textPath scope, reposition the glyph on path.
392    // (https://www.w3.org/TR/SVG11/text.html#TextpathLayoutRules)
393
394    // Path positioning is based on the glyph center (horizontal component).
395    float glyph_width;
396    font.getWidths(&glyph, 1, &glyph_width);
397    auto path_offset = pos.fX + glyph_width * .5f;
398
399    // In addition to the path matrix, the final glyph matrix also includes:
400    //
401    //   -- vertical position adjustment "dy" ("dx" is factored into path_offset)
402    //   -- glyph origin adjustment (undoing the glyph center offset above)
403    //   -- explicit rotation adjustment (composing with the path glyph rotation)
404    const auto m = fPathData->getMatrixAt(path_offset) *
405            SkMatrix::Translate(-glyph_width * .5f, pos_adjust.offset.fY) *
406            SkMatrix::RotateRad(pos_adjust.rotation);
407
408    return SkRSXform::Make(m.getScaleX(), m.getSkewY(), m.getTranslateX(), m.getTranslateY());
409}
410
411void SkSVGTextContext::flushChunk(const SkSVGRenderContext& ctx) {
412    SkTextBlobBuilder blobBuilder;
413
414    for (const auto& run : fRuns) {
415        const auto& buf = blobBuilder.allocRunRSXform(run.font, SkToInt(run.glyphCount));
416        std::copy(run.glyphs.get(), run.glyphs.get() + run.glyphCount, buf.glyphs);
417        for (size_t i = 0; i < run.glyphCount; ++i) {
418            buf.xforms()[i] = this->computeGlyphXform(run.glyphs[i],
419                                                      run.font,
420                                                      run.glyphPos[i],
421                                                      run.glyhPosAdjust[i]);
422        }
423
424        fCallback(ctx, blobBuilder.make(), run.fillPaint.get(), run.strokePaint.get());
425    }
426
427    fChunkPos += fChunkAdvance;
428    fChunkAdvance = {0,0};
429    fChunkAlignmentFactor = ComputeAlignmentFactor(ctx.presentationContext());
430
431    fRuns.clear();
432}
433
434SkShaper::RunHandler::Buffer SkSVGTextContext::runBuffer(const RunInfo& ri) {
435    SkASSERT(ri.glyphCount);
436
437    fRuns.push_back({
438        ri.fFont,
439        fCurrentFill.isValid()   ? std::make_unique<SkPaint>(*fCurrentFill)   : nullptr,
440        fCurrentStroke.isValid() ? std::make_unique<SkPaint>(*fCurrentStroke) : nullptr,
441        std::make_unique<SkGlyphID[]         >(ri.glyphCount),
442        std::make_unique<SkPoint[]           >(ri.glyphCount),
443        std::make_unique<PositionAdjustment[]>(ri.glyphCount),
444        ri.glyphCount,
445        ri.fAdvance,
446    });
447
448    // Ensure sufficient space to temporarily fetch cluster information.
449    fShapeClusterBuffer.resize(std::max(fShapeClusterBuffer.size(), ri.glyphCount));
450
451    return {
452        fRuns.back().glyphs.get(),
453        fRuns.back().glyphPos.get(),
454        nullptr,
455        fShapeClusterBuffer.data(),
456        fChunkAdvance,
457    };
458}
459
460void SkSVGTextContext::commitRunBuffer(const RunInfo& ri) {
461    const auto& current_run = fRuns.back();
462
463    // stash position adjustments
464    for (size_t i = 0; i < ri.glyphCount; ++i) {
465        const auto utf8_index = fShapeClusterBuffer[i];
466        current_run.glyhPosAdjust[i] = fShapeBuffer.fUtf8PosAdjust[SkToInt(utf8_index)];
467    }
468
469    // Offset adjustments are cumulative - we only need to advance the current chunk
470    // with the last value.
471    fChunkAdvance += ri.fAdvance + fShapeBuffer.fUtf8PosAdjust.back().offset;
472}
473
474void SkSVGTextFragment::renderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
475                                   SkSVGXmlSpace xs) const {
476    // N.B.: unlike regular elements, text fragments do not establish a new OBB scope -- they
477    // always defer to the root <text> element for OBB resolution.
478    SkSVGRenderContext localContext(ctx);
479
480    if (this->onPrepareToRender(&localContext)) {
481        this->onShapeText(localContext, tctx, xs);
482    }
483}
484
485SkPath SkSVGTextFragment::onAsPath(const SkSVGRenderContext&) const {
486    // TODO
487    return SkPath();
488}
489
490void SkSVGTextContainer::appendChild(sk_sp<SkSVGNode> child) {
491    // Only allow text content child nodes.
492    switch (child->tag()) {
493    case SkSVGTag::kTextLiteral:
494    case SkSVGTag::kTextPath:
495    case SkSVGTag::kTSpan:
496        fChildren.push_back(
497            sk_sp<SkSVGTextFragment>(static_cast<SkSVGTextFragment*>(child.release())));
498        break;
499    default:
500        break;
501    }
502}
503
504
505std::vector<sk_sp<SkSVGNode>> SkSVGTextContainer::getChild() {
506    std::vector<sk_sp<SkSVGNode>> res;
507    res.assign(fChildren.begin(),fChildren.end());
508    return res;
509}
510
511void SkSVGTextContainer::onShapeText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
512                                     SkSVGXmlSpace) const {
513    SkASSERT(tctx);
514
515    const SkSVGTextContext::ScopedPosResolver resolver(*this, ctx.lengthContext(), tctx);
516
517    for (const auto& frag : fChildren) {
518        // Containers always override xml:space with the local value.
519        frag->renderText(ctx, tctx, this->getXmlSpace());
520    }
521}
522
523// https://www.w3.org/TR/SVG11/text.html#WhiteSpace
524template <>
525bool SkSVGAttributeParser::parse(SkSVGXmlSpace* xs) {
526    static constexpr std::tuple<const char*, SkSVGXmlSpace> gXmlSpaceMap[] = {
527            {"default" , SkSVGXmlSpace::kDefault },
528            {"preserve", SkSVGXmlSpace::kPreserve},
529    };
530
531    return this->parseEnumMap(gXmlSpaceMap, xs) && this->parseEOSToken();
532}
533
534bool SkSVGTextContainer::parseAndSetAttribute(const char* name, const char* value) {
535    return INHERITED::parseAndSetAttribute(name, value) ||
536           this->setX(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("x", name, value)) ||
537           this->setY(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("y", name, value)) ||
538           this->setDx(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("dx", name, value)) ||
539           this->setDy(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("dy", name, value)) ||
540           this->setRotate(SkSVGAttributeParser::parse<std::vector<SkSVGNumberType>>("rotate",
541                                                                                     name,
542                                                                                     value)) ||
543           this->setXmlSpace(SkSVGAttributeParser::parse<SkSVGXmlSpace>("xml:space", name, value));
544}
545
546void SkSVGTextLiteral::onShapeText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
547                                   SkSVGXmlSpace xs) const {
548    SkASSERT(tctx);
549
550    tctx->shapeFragment(this->getText(), ctx, xs);
551}
552
553void SkSVGText::onRender(const SkSVGRenderContext& ctx) const {
554    const SkSVGTextContext::ShapedTextCallback render_text = [](const SkSVGRenderContext& ctx,
555                                                                const sk_sp<SkTextBlob>& blob,
556                                                                const SkPaint* fill,
557                                                                const SkPaint* stroke) {
558        if (fill) {
559            ctx.canvas()->drawTextBlob(blob, 0, 0, *fill);
560        }
561        if (stroke) {
562            ctx.canvas()->drawTextBlob(blob, 0, 0, *stroke);
563        }
564    };
565
566    // Root <text> nodes establish a text layout context.
567    SkSVGTextContext tctx(ctx, render_text);
568
569    this->onShapeText(ctx, &tctx, this->getXmlSpace());
570}
571
572SkRect SkSVGText::onObjectBoundingBox(const SkSVGRenderContext& ctx) const {
573    SkRect bounds = SkRect::MakeEmpty();
574
575    const SkSVGTextContext::ShapedTextCallback compute_bounds =
576        [&bounds](const SkSVGRenderContext& ctx, const sk_sp<SkTextBlob>& blob, const SkPaint*,
577                  const SkPaint*) {
578            if (!blob) {
579                return;
580            }
581
582            SkAutoSTArray<64, SkRect> glyphBounds;
583
584            for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
585                glyphBounds.reset(SkToInt(it.glyphCount()));
586                it.font().getBounds(it.glyphs(), it.glyphCount(), glyphBounds.get(), nullptr);
587
588                SkASSERT(it.positioning() == SkTextBlobRunIterator::kRSXform_Positioning);
589                SkMatrix m;
590                for (uint32_t i = 0; i < it.glyphCount(); ++i) {
591                    m.setRSXform(it.xforms()[i]);
592                    bounds.join(m.mapRect(glyphBounds[i]));
593                }
594            }
595        };
596
597    {
598        SkSVGTextContext tctx(ctx, compute_bounds);
599        this->onShapeText(ctx, &tctx, this->getXmlSpace());
600    }
601
602    return bounds;
603}
604
605SkPath SkSVGText::onAsPath(const SkSVGRenderContext& ctx) const {
606    SkPathBuilder builder;
607
608    const SkSVGTextContext::ShapedTextCallback as_path =
609        [&builder](const SkSVGRenderContext& ctx, const sk_sp<SkTextBlob>& blob, const SkPaint*,
610                   const SkPaint*) {
611            if (!blob) {
612                return;
613            }
614
615            for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
616                struct GetPathsCtx {
617                    SkPathBuilder&   builder;
618                    const SkRSXform* xform;
619                } get_paths_ctx {builder, it.xforms()};
620
621                it.font().getPaths(it.glyphs(), it.glyphCount(), [](const SkPath* path,
622                                                                    const SkMatrix& matrix,
623                                                                    void* raw_ctx) {
624                    auto* get_paths_ctx = static_cast<GetPathsCtx*>(raw_ctx);
625                    const auto& glyph_rsx = *get_paths_ctx->xform++;
626
627                    if (!path) {
628                        return;
629                    }
630
631                    SkMatrix glyph_matrix;
632                    glyph_matrix.setRSXform(glyph_rsx);
633                    glyph_matrix.preConcat(matrix);
634
635                    get_paths_ctx->builder.addPath(path->makeTransform(glyph_matrix));
636                }, &get_paths_ctx);
637            }
638        };
639
640    {
641        SkSVGTextContext tctx(ctx, as_path);
642        this->onShapeText(ctx, &tctx, this->getXmlSpace());
643    }
644
645    auto path = builder.detach();
646    this->mapToParent(&path);
647
648    return path;
649}
650
651void SkSVGTextPath::onShapeText(const SkSVGRenderContext& ctx, SkSVGTextContext* parent_tctx,
652                                 SkSVGXmlSpace xs) const {
653    SkASSERT(parent_tctx);
654
655    // textPath nodes establish a new text layout context.
656    SkSVGTextContext tctx(ctx, parent_tctx->getCallback(), this);
657
658    this->INHERITED::onShapeText(ctx, &tctx, xs);
659}
660
661bool SkSVGTextPath::parseAndSetAttribute(const char* name, const char* value) {
662    return INHERITED::parseAndSetAttribute(name, value) ||
663        this->setHref(SkSVGAttributeParser::parse<SkSVGIRI>("xlink:href", name, value)) ||
664        this->setStartOffset(SkSVGAttributeParser::parse<SkSVGLength>("startOffset", name, value));
665}
666