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