blob: f96ba3f5e20ee21954de547b8043ca2e152c4d84 [file] [log] [blame]
Xavier Phane29cdaf2020-03-26 16:15:14 +00001/*
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
Florin Malitab3418102020-10-15 18:10:29 -04008#include "modules/svg/include/SkSVGText.h"
Xavier Phane29cdaf2020-03-26 16:15:14 +00009
Florin Malitadec78022020-12-17 16:36:54 -050010#include <limits>
Florin Malita7dc984a2020-12-08 11:37:15 -050011
Xavier Phane29cdaf2020-03-26 16:15:14 +000012#include "include/core/SkCanvas.h"
Florin Malita512ff752020-12-06 11:50:52 -050013#include "include/core/SkFont.h"
Florin Malita39fe8c82020-10-20 10:43:03 -040014#include "include/core/SkFontMgr.h"
Tyler Freemane9663db2020-04-14 14:37:13 -070015#include "include/core/SkFontStyle.h"
Florin Malitae5a21712020-12-30 11:08:53 -050016#include "include/core/SkRSXform.h"
Tyler Freemane9663db2020-04-14 14:37:13 -070017#include "include/core/SkString.h"
Florin Malita7dc984a2020-12-08 11:37:15 -050018#include "modules/skshaper/include/SkShaper.h"
Florin Malitab3418102020-10-15 18:10:29 -040019#include "modules/svg/include/SkSVGRenderContext.h"
20#include "modules/svg/include/SkSVGValue.h"
Florin Malitadec78022020-12-17 16:36:54 -050021#include "modules/svg/src/SkSVGTextPriv.h"
Florin Malita9c1f1be2020-12-09 13:02:50 -050022#include "src/utils/SkUTF.h"
Xavier Phane29cdaf2020-03-26 16:15:14 +000023
Florin Malita512ff752020-12-06 11:50:52 -050024namespace {
Xavier Phane29cdaf2020-03-26 16:15:14 +000025
Florin Malita512ff752020-12-06 11:50:52 -050026static SkFont ResolveFont(const SkSVGRenderContext& ctx) {
Florin Malita39fe8c82020-10-20 10:43:03 -040027 auto weight = [](const SkSVGFontWeight& w) {
28 switch (w.type()) {
29 case SkSVGFontWeight::Type::k100: return SkFontStyle::kThin_Weight;
30 case SkSVGFontWeight::Type::k200: return SkFontStyle::kExtraLight_Weight;
31 case SkSVGFontWeight::Type::k300: return SkFontStyle::kLight_Weight;
32 case SkSVGFontWeight::Type::k400: return SkFontStyle::kNormal_Weight;
33 case SkSVGFontWeight::Type::k500: return SkFontStyle::kMedium_Weight;
34 case SkSVGFontWeight::Type::k600: return SkFontStyle::kSemiBold_Weight;
35 case SkSVGFontWeight::Type::k700: return SkFontStyle::kBold_Weight;
36 case SkSVGFontWeight::Type::k800: return SkFontStyle::kExtraBold_Weight;
37 case SkSVGFontWeight::Type::k900: return SkFontStyle::kBlack_Weight;
38 case SkSVGFontWeight::Type::kNormal: return SkFontStyle::kNormal_Weight;
39 case SkSVGFontWeight::Type::kBold: return SkFontStyle::kBold_Weight;
40 case SkSVGFontWeight::Type::kBolder: return SkFontStyle::kExtraBold_Weight;
41 case SkSVGFontWeight::Type::kLighter: return SkFontStyle::kLight_Weight;
42 case SkSVGFontWeight::Type::kInherit: {
43 SkASSERT(false);
44 return SkFontStyle::kNormal_Weight;
45 }
46 }
47 SkUNREACHABLE;
48 };
49
50 auto slant = [](const SkSVGFontStyle& s) {
51 switch (s.type()) {
52 case SkSVGFontStyle::Type::kNormal: return SkFontStyle::kUpright_Slant;
53 case SkSVGFontStyle::Type::kItalic: return SkFontStyle::kItalic_Slant;
54 case SkSVGFontStyle::Type::kOblique: return SkFontStyle::kOblique_Slant;
55 case SkSVGFontStyle::Type::kInherit: {
56 SkASSERT(false);
57 return SkFontStyle::kUpright_Slant;
58 }
59 }
60 SkUNREACHABLE;
61 };
62
63 const auto& family = ctx.presentationContext().fInherited.fFontFamily->family();
64 const SkFontStyle style(weight(*ctx.presentationContext().fInherited.fFontWeight),
65 SkFontStyle::kNormal_Width,
66 slant(*ctx.presentationContext().fInherited.fFontStyle));
67
68 const auto size =
69 ctx.lengthContext().resolve(ctx.presentationContext().fInherited.fFontSize->size(),
70 SkSVGLengthContext::LengthType::kVertical);
71
Florin Malita7006e152020-11-10 15:24:59 -050072 // TODO: we likely want matchFamilyStyle here, but switching away from legacyMakeTypeface
73 // changes all the results when using the default fontmgr.
74 auto tf = ctx.fontMgr()->legacyMakeTypeface(family.c_str(), style);
75
76 SkFont font(std::move(tf), size);
Florin Malita39fe8c82020-10-20 10:43:03 -040077 font.setHinting(SkFontHinting::kNone);
78 font.setSubpixel(true);
79 font.setLinearMetrics(true);
80 font.setBaselineSnap(false);
81 font.setEdging(SkFont::Edging::kAntiAlias);
82
83 return font;
84}
85
Florin Malitadec78022020-12-17 16:36:54 -050086static std::vector<float> ResolveLengths(const SkSVGLengthContext& lctx,
87 const std::vector<SkSVGLength>& lengths,
88 SkSVGLengthContext::LengthType lt) {
89 std::vector<float> resolved;
90 resolved.reserve(lengths.size());
91
92 for (const auto& l : lengths) {
93 resolved.push_back(lctx.resolve(l, lt));
94 }
95
96 return resolved;
97}
98
99static float ComputeAlignmentFactor(const SkSVGPresentationContext& pctx) {
100 switch (pctx.fInherited.fTextAnchor->type()) {
Florin Malita7dc984a2020-12-08 11:37:15 -0500101 case SkSVGTextAnchor::Type::kStart : return 0.0f;
102 case SkSVGTextAnchor::Type::kMiddle: return -0.5f;
103 case SkSVGTextAnchor::Type::kEnd : return -1.0f;
104 case SkSVGTextAnchor::Type::kInherit:
105 SkASSERT(false);
106 return 0.0f;
107 }
108 SkUNREACHABLE;
109}
110
Florin Malita512ff752020-12-06 11:50:52 -0500111} // namespace
112
Florin Malitadec78022020-12-17 16:36:54 -0500113SkSVGTextContext::ScopedPosResolver::ScopedPosResolver(const SkSVGTextContainer& txt,
114 const SkSVGLengthContext& lctx,
115 SkSVGTextContext* tctx,
116 size_t charIndexOffset)
117 : fTextContext(tctx)
118 , fParent(tctx->fPosResolver)
119 , fCharIndexOffset(charIndexOffset)
120 , fX(ResolveLengths(lctx, txt.getX(), SkSVGLengthContext::LengthType::kHorizontal))
121 , fY(ResolveLengths(lctx, txt.getY(), SkSVGLengthContext::LengthType::kVertical))
Florin Malita735ac972020-12-22 11:23:32 -0500122 , fDx(ResolveLengths(lctx, txt.getDx(), SkSVGLengthContext::LengthType::kHorizontal))
123 , fDy(ResolveLengths(lctx, txt.getDy(), SkSVGLengthContext::LengthType::kVertical))
Florin Malitadec78022020-12-17 16:36:54 -0500124{
125 fTextContext->fPosResolver = this;
126}
Florin Malita7dc984a2020-12-08 11:37:15 -0500127
Florin Malitadec78022020-12-17 16:36:54 -0500128SkSVGTextContext::ScopedPosResolver::ScopedPosResolver(const SkSVGTextContainer& txt,
129 const SkSVGLengthContext& lctx,
130 SkSVGTextContext* tctx)
131 : ScopedPosResolver(txt, lctx, tctx, tctx->fCurrentCharIndex) {}
Florin Malita9c1f1be2020-12-09 13:02:50 -0500132
Florin Malitadec78022020-12-17 16:36:54 -0500133SkSVGTextContext::ScopedPosResolver::~ScopedPosResolver() {
134 fTextContext->fPosResolver = fParent;
135}
Florin Malita9c1f1be2020-12-09 13:02:50 -0500136
Florin Malitadec78022020-12-17 16:36:54 -0500137SkSVGTextContext::PosAttrs SkSVGTextContext::ScopedPosResolver::resolve(size_t charIndex) const {
138 PosAttrs attrs;
Florin Malita9c1f1be2020-12-09 13:02:50 -0500139
Florin Malitadec78022020-12-17 16:36:54 -0500140 if (charIndex < fLastPosIndex) {
141 SkASSERT(charIndex >= fCharIndexOffset);
142 const auto localCharIndex = charIndex - fCharIndexOffset;
Florin Malita9c1f1be2020-12-09 13:02:50 -0500143
Florin Malitadec78022020-12-17 16:36:54 -0500144 const auto hasAllLocal = localCharIndex < fX.size() &&
Florin Malita735ac972020-12-22 11:23:32 -0500145 localCharIndex < fY.size() &&
146 localCharIndex < fDx.size() &&
147 localCharIndex < fDy.size();
Florin Malitadec78022020-12-17 16:36:54 -0500148 if (!hasAllLocal && fParent) {
149 attrs = fParent->resolve(charIndex);
Florin Malita9c1f1be2020-12-09 13:02:50 -0500150 }
151
Florin Malitadec78022020-12-17 16:36:54 -0500152 if (localCharIndex < fX.size()) {
153 attrs[PosAttrs::kX] = fX[localCharIndex];
154 }
155 if (localCharIndex < fY.size()) {
156 attrs[PosAttrs::kY] = fY[localCharIndex];
Florin Malita7dc984a2020-12-08 11:37:15 -0500157 }
Florin Malita735ac972020-12-22 11:23:32 -0500158 if (localCharIndex < fDx.size()) {
159 attrs[PosAttrs::kDx] = fDx[localCharIndex];
160 }
161 if (localCharIndex < fDy.size()) {
162 attrs[PosAttrs::kDy] = fDy[localCharIndex];
163 }
Florin Malita7dc984a2020-12-08 11:37:15 -0500164
Florin Malitadec78022020-12-17 16:36:54 -0500165 if (!attrs.hasAny()) {
166 // Once we stop producing explicit position data, there is no reason to
167 // continue trying for higher indices. We can suppress future lookups.
168 fLastPosIndex = charIndex;
169 }
Florin Malita7dc984a2020-12-08 11:37:15 -0500170 }
171
Florin Malitadec78022020-12-17 16:36:54 -0500172 return attrs;
173}
174
Florin Malita735ac972020-12-22 11:23:32 -0500175void SkSVGTextContext::ShapeBuffer::append(SkUnichar ch, SkVector pos) {
176 // relative pos adjustments are cumulative
177 if (!fUtf8PosAdjust.empty()) {
178 pos += fUtf8PosAdjust.back();
179 }
180
181 char utf8_buf[SkUTF::kMaxBytesInUTF8Sequence];
182 const auto utf8_len = SkToInt(SkUTF::ToUTF8(ch, utf8_buf));
183 fUtf8 .push_back_n(utf8_len, utf8_buf);
184 fUtf8PosAdjust.push_back_n(utf8_len, pos);
185}
186
187void SkSVGTextContext::shapePendingBuffer(const SkFont& font) {
188 // TODO: directionality hints?
189 const auto LTR = true;
190
191 // Initiate shaping: this will generate a series of runs via callbacks.
192 fShaper->shape(fShapeBuffer.fUtf8.data(), fShapeBuffer.fUtf8.size(),
193 font, LTR, SK_ScalarMax, this);
194 fShapeBuffer.reset();
195}
196
Florin Malitadec78022020-12-17 16:36:54 -0500197SkSVGTextContext::SkSVGTextContext(const SkSVGPresentationContext& pctx, sk_sp<SkFontMgr> fmgr)
198 : fShaper(SkShaper::Make(std::move(fmgr)))
199 , fChunkPos{ 0, 0 }
200 , fChunkAlignmentFactor(ComputeAlignmentFactor(pctx))
201{}
202
203void SkSVGTextContext::appendFragment(const SkString& txt, const SkSVGRenderContext& ctx,
204 SkSVGXmlSpace xs) {
205 // https://www.w3.org/TR/SVG11/text.html#WhiteSpace
206 // https://www.w3.org/TR/2008/REC-xml-20081126/#NT-S
207 auto filterWSDefault = [this](SkUnichar ch) -> SkUnichar {
208 // Remove all newline chars.
209 if (ch == '\n') {
210 return -1;
211 }
212
213 // Convert tab chars to space.
214 if (ch == '\t') {
215 ch = ' ';
216 }
217
218 // Consolidate contiguous space chars and strip leading spaces (fPrevCharSpace
219 // starts off as true).
220 if (fPrevCharSpace && ch == ' ') {
221 return -1;
222 }
223
224 // TODO: Strip trailing WS? Doing this across chunks would require another buffering
225 // layer. In general, trailing WS should have no rendering side effects. Skipping
226 // for now.
227 return ch;
228 };
229 auto filterWSPreserve = [](SkUnichar ch) -> SkUnichar {
230 // Convert newline and tab chars to space.
231 if (ch == '\n' || ch == '\t') {
232 ch = ' ';
233 }
234 return ch;
Florin Malita7dc984a2020-12-08 11:37:15 -0500235 };
236
Florin Malitadec78022020-12-17 16:36:54 -0500237 // Stash paints for access from SkShaper callbacks.
238 fCurrentFill = ctx.fillPaint();
239 fCurrentStroke = ctx.strokePaint();
Florin Malita7dc984a2020-12-08 11:37:15 -0500240
Florin Malitadec78022020-12-17 16:36:54 -0500241 const auto font = ResolveFont(ctx);
Florin Malita735ac972020-12-22 11:23:32 -0500242 fShapeBuffer.reserve(txt.size());
Florin Malitadec78022020-12-17 16:36:54 -0500243
244 const char* ch_ptr = txt.c_str();
245 const char* ch_end = ch_ptr + txt.size();
246
247 while (ch_ptr < ch_end) {
248 auto ch = SkUTF::NextUTF8(&ch_ptr, ch_end);
249 ch = (xs == SkSVGXmlSpace::kDefault)
250 ? filterWSDefault(ch)
251 : filterWSPreserve(ch);
252
253 if (ch < 0) {
254 // invalid utf or char filtered out
255 continue;
256 }
257
258 SkASSERT(fPosResolver);
259 const auto pos = fPosResolver->resolve(fCurrentCharIndex++);
260
261 // Absolute position adjustments define a new chunk.
262 // (https://www.w3.org/TR/SVG11/text.html#TextLayoutIntroduction)
263 if (pos.has(PosAttrs::kX) || pos.has(PosAttrs::kY)) {
Florin Malita735ac972020-12-22 11:23:32 -0500264 this->shapePendingBuffer(font);
Florin Malitadec78022020-12-17 16:36:54 -0500265 this->flushChunk(ctx);
266
267 // New chunk position.
268 if (pos.has(PosAttrs::kX)) {
269 fChunkPos.fX = pos[PosAttrs::kX];
270 }
271 if (pos.has(PosAttrs::kY)) {
272 fChunkPos.fY = pos[PosAttrs::kY];
273 }
274 }
275
Florin Malita735ac972020-12-22 11:23:32 -0500276 fShapeBuffer.append(ch, {
277 pos.has(PosAttrs::kDx) ? pos[PosAttrs::kDx] : 0,
278 pos.has(PosAttrs::kDy) ? pos[PosAttrs::kDy] : 0,
279 });
Florin Malitadec78022020-12-17 16:36:54 -0500280
281 fPrevCharSpace = (ch == ' ');
Florin Malita7dc984a2020-12-08 11:37:15 -0500282 }
Florin Malitadec78022020-12-17 16:36:54 -0500283
Florin Malita735ac972020-12-22 11:23:32 -0500284 this->shapePendingBuffer(font);
285
286 // Note: at this point we have shaped and buffered RunRecs for the current fragment.
287 // The active text chunk continues until an explicit or implicit flush.
Florin Malitadec78022020-12-17 16:36:54 -0500288}
289
290void SkSVGTextContext::flushChunk(const SkSVGRenderContext& ctx) {
291 // The final rendering offset is determined by cumulative chunk advances and alignment.
292 const auto pos = fChunkPos + fChunkAdvance * fChunkAlignmentFactor;
293
294 SkTextBlobBuilder blobBuilder;
295
296 for (const auto& run : fRuns) {
Florin Malitae5a21712020-12-30 11:08:53 -0500297 const auto& buf = blobBuilder.allocRunRSXform(run.font, SkToInt(run.glyphCount));
298 std::copy(run.glyphs.get(), run.glyphs.get() + run.glyphCount, buf.glyphs);
299 for (size_t i = 0; i < run.glyphCount; ++i) {
300 const auto& pos = run.glyphPos[i];
301 const auto rotation = 0.0f; // TODO
302 buf.xforms()[i] = SkRSXform::MakeFromRadians(/*scale=*/1,
303 rotation,
304 pos.fX, pos.fY, 0, 0);
305 }
Florin Malitadec78022020-12-17 16:36:54 -0500306
307 // Technically, blobs with compatible paints could be merged --
308 // but likely not worth the effort.
309 const auto blob = blobBuilder.make();
310 if (run.fillPaint) {
311 ctx.canvas()->drawTextBlob(blob, pos.fX, pos.fY, *run.fillPaint);
312 }
313 if (run.strokePaint) {
314 ctx.canvas()->drawTextBlob(blob, pos.fX, pos.fY, *run.strokePaint);
315 }
Florin Malita7dc984a2020-12-08 11:37:15 -0500316 }
Florin Malita7dc984a2020-12-08 11:37:15 -0500317
Florin Malitadec78022020-12-17 16:36:54 -0500318 fChunkPos += fChunkAdvance;
319 fChunkAdvance = {0,0};
320 fChunkAlignmentFactor = ComputeAlignmentFactor(ctx.presentationContext());
Florin Malita7dc984a2020-12-08 11:37:15 -0500321
Florin Malitadec78022020-12-17 16:36:54 -0500322 fRuns.clear();
323}
Florin Malita7dc984a2020-12-08 11:37:15 -0500324
Florin Malitadec78022020-12-17 16:36:54 -0500325SkShaper::RunHandler::Buffer SkSVGTextContext::runBuffer(const RunInfo& ri) {
326 SkASSERT(ri.glyphCount);
Florin Malita9c1f1be2020-12-09 13:02:50 -0500327
Florin Malitadec78022020-12-17 16:36:54 -0500328 fRuns.push_back({
329 ri.fFont,
330 fCurrentFill ? std::make_unique<SkPaint>(*fCurrentFill) : nullptr,
331 fCurrentStroke ? std::make_unique<SkPaint>(*fCurrentStroke) : nullptr,
332 std::make_unique<SkGlyphID[]>(ri.glyphCount),
333 std::make_unique<SkPoint[] >(ri.glyphCount),
334 ri.glyphCount,
335 ri.fAdvance,
336 });
337
Florin Malita735ac972020-12-22 11:23:32 -0500338 // Ensure sufficient space to temporarily fetch cluster information.
339 fShapeClusterBuffer.resize(std::max(fShapeClusterBuffer.size(), ri.glyphCount));
340
Florin Malitadec78022020-12-17 16:36:54 -0500341 return {
342 fRuns.back().glyphs.get(),
343 fRuns.back().glyphPos.get(),
344 nullptr,
Florin Malita735ac972020-12-22 11:23:32 -0500345 fShapeClusterBuffer.data(),
Florin Malitadec78022020-12-17 16:36:54 -0500346 fChunkAdvance,
347 };
348}
349
350void SkSVGTextContext::commitRunBuffer(const RunInfo& ri) {
Florin Malita735ac972020-12-22 11:23:32 -0500351 // apply position adjustments
352 for (size_t i = 0; i < ri.glyphCount; ++i) {
353 const auto utf8_index = fShapeClusterBuffer[i];
354 fRuns.back().glyphPos[i] += fShapeBuffer.fUtf8PosAdjust[SkToInt(utf8_index)];
355 }
356
357 // Position adjustments are cumulative - we only need to advance the current chunk
358 // with the last value.
359 fChunkAdvance += ri.fAdvance + fShapeBuffer.fUtf8PosAdjust.back();
Florin Malitadec78022020-12-17 16:36:54 -0500360}
Florin Malita512ff752020-12-06 11:50:52 -0500361
Florin Malitaadc68892020-12-15 10:52:26 -0500362void SkSVGTextFragment::renderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
363 SkSVGXmlSpace xs) const {
364 SkSVGRenderContext localContext(ctx, this);
365
366 if (this->onPrepareToRender(&localContext)) {
367 this->onRenderText(localContext, tctx, xs);
368 }
369}
370
371SkPath SkSVGTextFragment::onAsPath(const SkSVGRenderContext&) const {
372 // TODO
373 return SkPath();
374}
375
Florin Malita512ff752020-12-06 11:50:52 -0500376void SkSVGTextContainer::appendChild(sk_sp<SkSVGNode> child) {
377 // Only allow text nodes.
378 switch (child->tag()) {
379 case SkSVGTag::kText:
380 case SkSVGTag::kTextLiteral:
381 case SkSVGTag::kTSpan:
Florin Malitaadc68892020-12-15 10:52:26 -0500382 fChildren.push_back(
383 sk_sp<SkSVGTextFragment>(static_cast<SkSVGTextFragment*>(child.release())));
Florin Malita512ff752020-12-06 11:50:52 -0500384 break;
385 default:
386 break;
387 }
388}
389
Florin Malitaadc68892020-12-15 10:52:26 -0500390void SkSVGTextContainer::onRenderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
391 SkSVGXmlSpace) const {
Florin Malitadec78022020-12-17 16:36:54 -0500392 const SkSVGTextContext::ScopedPosResolver resolver(*this, ctx.lengthContext(), tctx);
393
Florin Malitaadc68892020-12-15 10:52:26 -0500394 for (const auto& frag : fChildren) {
395 // Containers always override xml:space with the local value.
396 frag->renderText(ctx, tctx, this->getXmlSpace());
397 }
Florin Malita9c1f1be2020-12-09 13:02:50 -0500398}
399
400// https://www.w3.org/TR/SVG11/text.html#WhiteSpace
401template <>
402bool SkSVGAttributeParser::parse(SkSVGXmlSpace* xs) {
403 static constexpr std::tuple<const char*, SkSVGXmlSpace> gXmlSpaceMap[] = {
404 {"default" , SkSVGXmlSpace::kDefault },
405 {"preserve", SkSVGXmlSpace::kPreserve},
406 };
407
408 return this->parseEnumMap(gXmlSpaceMap, xs) && this->parseEOSToken();
409}
410
Florin Malita512ff752020-12-06 11:50:52 -0500411bool SkSVGTextContainer::parseAndSetAttribute(const char* name, const char* value) {
412 return INHERITED::parseAndSetAttribute(name, value) ||
Florin Malitadec78022020-12-17 16:36:54 -0500413 this->setX(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("x", name, value)) ||
414 this->setY(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("y", name, value)) ||
Florin Malita735ac972020-12-22 11:23:32 -0500415 this->setDx(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("dx", name, value)) ||
416 this->setDy(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("dy", name, value)) ||
Florin Malita9c1f1be2020-12-09 13:02:50 -0500417 this->setXmlSpace(SkSVGAttributeParser::parse<SkSVGXmlSpace>("xml:space", name, value));
Florin Malita512ff752020-12-06 11:50:52 -0500418}
419
Florin Malitaadc68892020-12-15 10:52:26 -0500420void SkSVGTextContainer::onRender(const SkSVGRenderContext& ctx) const {
421 // Root text nodes establish a new text layout context.
Florin Malitadec78022020-12-17 16:36:54 -0500422 SkSVGTextContext tctx(ctx.presentationContext(), ctx.fontMgr());
Florin Malita512ff752020-12-06 11:50:52 -0500423
Florin Malitaadc68892020-12-15 10:52:26 -0500424 this->onRenderText(ctx, &tctx, this->getXmlSpace());
Florin Malita512ff752020-12-06 11:50:52 -0500425
Florin Malita7dc984a2020-12-08 11:37:15 -0500426 tctx.flushChunk(ctx);
Florin Malita512ff752020-12-06 11:50:52 -0500427}
428
Florin Malitaadc68892020-12-15 10:52:26 -0500429void SkSVGTextLiteral::onRenderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
430 SkSVGXmlSpace xs) const {
431 SkASSERT(tctx);
Florin Malita512ff752020-12-06 11:50:52 -0500432
Florin Malitaadc68892020-12-15 10:52:26 -0500433 tctx->appendFragment(this->getText(), ctx, xs);
Xavier Phane29cdaf2020-03-26 16:15:14 +0000434}