• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getChild()581 std::vector<sk_sp<SkSVGNode>> SkSVGTextContainer::getChild() {
582     std::vector<sk_sp<SkSVGNode>> res;
583     res.assign(fChildren.begin(),fChildren.end());
584     return res;
585 }
586 
onShapeText(const SkSVGRenderContext & ctx,SkSVGTextContext * tctx,SkSVGXmlSpace) const587 void SkSVGTextContainer::onShapeText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
588                                      SkSVGXmlSpace) const {
589     SkASSERT(tctx);
590 
591     const SkSVGTextContext::ScopedPosResolver resolver(*this, ctx.lengthContext(), tctx);
592 
593     for (const auto& frag : fChildren) {
594         // Containers always override xml:space with the local value.
595         frag->renderText(ctx, tctx, this->getXmlSpace());
596     }
597 }
598 
599 // https://www.w3.org/TR/SVG11/text.html#WhiteSpace
600 template <>
parse(SkSVGXmlSpace * xs)601 bool SkSVGAttributeParser::parse(SkSVGXmlSpace* xs) {
602     static constexpr std::tuple<const char*, SkSVGXmlSpace> gXmlSpaceMap[] = {
603             {"default" , SkSVGXmlSpace::kDefault },
604             {"preserve", SkSVGXmlSpace::kPreserve},
605     };
606 
607     return this->parseEnumMap(gXmlSpaceMap, xs) && this->parseEOSToken();
608 }
609 
parseAndSetAttribute(const char * name,const char * value)610 bool SkSVGTextContainer::parseAndSetAttribute(const char* name, const char* value) {
611     return INHERITED::parseAndSetAttribute(name, value) ||
612            this->setX(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("x", name, value)) ||
613            this->setY(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("y", name, value)) ||
614            this->setDx(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("dx", name, value)) ||
615            this->setDy(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("dy", name, value)) ||
616            this->setRotate(SkSVGAttributeParser::parse<std::vector<SkSVGNumberType>>("rotate",
617                                                                                      name,
618                                                                                      value)) ||
619            this->setXmlSpace(SkSVGAttributeParser::parse<SkSVGXmlSpace>("xml:space", name, value));
620 }
621 
onShapeText(const SkSVGRenderContext & ctx,SkSVGTextContext * tctx,SkSVGXmlSpace xs) const622 void SkSVGTextLiteral::onShapeText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
623                                    SkSVGXmlSpace xs) const {
624     SkASSERT(tctx);
625 
626     tctx->shapeFragment(this->getText(), ctx, xs);
627 }
628 
onRender(const SkSVGRenderContext & ctx) const629 void SkSVGText::onRender(const SkSVGRenderContext& ctx) const {
630     const SkSVGTextContext::ShapedTextCallback render_text = [](const SkSVGRenderContext& ctx,
631                                                                 const sk_sp<SkTextBlob>& blob,
632                                                                 const SkPaint* fill,
633                                                                 const SkPaint* stroke) {
634         if (fill) {
635             ctx.canvas()->drawTextBlob(blob, 0, 0, *fill);
636         }
637         if (stroke) {
638             ctx.canvas()->drawTextBlob(blob, 0, 0, *stroke);
639         }
640     };
641 
642     // Root <text> nodes establish a text layout context.
643     SkSVGTextContext tctx(ctx, render_text);
644 
645     this->onShapeText(ctx, &tctx, this->getXmlSpace());
646 }
647 
onTransformableObjectBoundingBox(const SkSVGRenderContext & ctx) const648 SkRect SkSVGText::onTransformableObjectBoundingBox(const SkSVGRenderContext& ctx) const {
649     SkRect bounds = SkRect::MakeEmpty();
650 
651     const SkSVGTextContext::ShapedTextCallback compute_bounds =
652         [&bounds](const SkSVGRenderContext& ctx, const sk_sp<SkTextBlob>& blob, const SkPaint*,
653                   const SkPaint*) {
654             if (!blob) {
655                 return;
656             }
657 
658             AutoSTArray<64, SkRect> glyphBounds;
659 
660             for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
661                 glyphBounds.reset(SkToInt(it.glyphCount()));
662                 it.font().getBounds(it.glyphs(), it.glyphCount(), glyphBounds.get(), nullptr);
663 
664                 SkASSERT(it.positioning() == SkTextBlobRunIterator::kRSXform_Positioning);
665                 SkMatrix m;
666                 for (uint32_t i = 0; i < it.glyphCount(); ++i) {
667                     m.setRSXform(it.xforms()[i]);
668                     bounds.join(m.mapRect(glyphBounds[i]));
669                 }
670             }
671         };
672 
673     {
674         SkSVGTextContext tctx(ctx, compute_bounds);
675         this->onShapeText(ctx, &tctx, this->getXmlSpace());
676     }
677 
678     return bounds;
679 }
680 
onAsPath(const SkSVGRenderContext & ctx) const681 SkPath SkSVGText::onAsPath(const SkSVGRenderContext& ctx) const {
682     SkPathBuilder builder;
683 
684     const SkSVGTextContext::ShapedTextCallback as_path =
685         [&builder](const SkSVGRenderContext& ctx, const sk_sp<SkTextBlob>& blob, const SkPaint*,
686                    const SkPaint*) {
687             if (!blob) {
688                 return;
689             }
690 
691             for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
692                 struct GetPathsCtx {
693                     SkPathBuilder&   builder;
694                     const SkRSXform* xform;
695                 } get_paths_ctx {builder, it.xforms()};
696 
697                 it.font().getPaths(it.glyphs(), it.glyphCount(), [](const SkPath* path,
698                                                                     const SkMatrix& matrix,
699                                                                     void* raw_ctx) {
700                     auto* get_paths_ctx = static_cast<GetPathsCtx*>(raw_ctx);
701                     const auto& glyph_rsx = *get_paths_ctx->xform++;
702 
703                     if (!path) {
704                         return;
705                     }
706 
707                     SkMatrix glyph_matrix;
708                     glyph_matrix.setRSXform(glyph_rsx);
709                     glyph_matrix.preConcat(matrix);
710 
711                     get_paths_ctx->builder.addPath(path->makeTransform(glyph_matrix));
712                 }, &get_paths_ctx);
713             }
714         };
715 
716     {
717         SkSVGTextContext tctx(ctx, as_path);
718         this->onShapeText(ctx, &tctx, this->getXmlSpace());
719     }
720 
721     auto path = builder.detach();
722     this->mapToParent(&path);
723 
724     return path;
725 }
726 
onShapeText(const SkSVGRenderContext & ctx,SkSVGTextContext * parent_tctx,SkSVGXmlSpace xs) const727 void SkSVGTextPath::onShapeText(const SkSVGRenderContext& ctx, SkSVGTextContext* parent_tctx,
728                                  SkSVGXmlSpace xs) const {
729     SkASSERT(parent_tctx);
730 
731     // textPath nodes establish a new text layout context.
732     SkSVGTextContext tctx(ctx, parent_tctx->getCallback(), this);
733 
734     this->INHERITED::onShapeText(ctx, &tctx, xs);
735 }
736 
parseAndSetAttribute(const char * name,const char * value)737 bool SkSVGTextPath::parseAndSetAttribute(const char* name, const char* value) {
738     return INHERITED::parseAndSetAttribute(name, value) ||
739         this->setHref(SkSVGAttributeParser::parse<SkSVGIRI>("xlink:href", name, value)) ||
740         this->setStartOffset(SkSVGAttributeParser::parse<SkSVGLength>("startOffset", name, value));
741 }
742