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