blob: 79ac1611dc9b40346b796529f0bf07d74d524478 [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 Malita2d059fc2021-01-05 11:53:15 -0500124 , fRotate(txt.getRotate())
Florin Malitadec78022020-12-17 16:36:54 -0500125{
126 fTextContext->fPosResolver = this;
127}
Florin Malita7dc984a2020-12-08 11:37:15 -0500128
Florin Malitadec78022020-12-17 16:36:54 -0500129SkSVGTextContext::ScopedPosResolver::ScopedPosResolver(const SkSVGTextContainer& txt,
130 const SkSVGLengthContext& lctx,
131 SkSVGTextContext* tctx)
132 : ScopedPosResolver(txt, lctx, tctx, tctx->fCurrentCharIndex) {}
Florin Malita9c1f1be2020-12-09 13:02:50 -0500133
Florin Malitadec78022020-12-17 16:36:54 -0500134SkSVGTextContext::ScopedPosResolver::~ScopedPosResolver() {
135 fTextContext->fPosResolver = fParent;
136}
Florin Malita9c1f1be2020-12-09 13:02:50 -0500137
Florin Malitadec78022020-12-17 16:36:54 -0500138SkSVGTextContext::PosAttrs SkSVGTextContext::ScopedPosResolver::resolve(size_t charIndex) const {
139 PosAttrs attrs;
Florin Malita9c1f1be2020-12-09 13:02:50 -0500140
Florin Malitadec78022020-12-17 16:36:54 -0500141 if (charIndex < fLastPosIndex) {
142 SkASSERT(charIndex >= fCharIndexOffset);
143 const auto localCharIndex = charIndex - fCharIndexOffset;
Florin Malita9c1f1be2020-12-09 13:02:50 -0500144
Florin Malitadec78022020-12-17 16:36:54 -0500145 const auto hasAllLocal = localCharIndex < fX.size() &&
Florin Malita735ac972020-12-22 11:23:32 -0500146 localCharIndex < fY.size() &&
147 localCharIndex < fDx.size() &&
Florin Malita2d059fc2021-01-05 11:53:15 -0500148 localCharIndex < fDy.size() &&
149 localCharIndex < fRotate.size();
Florin Malitadec78022020-12-17 16:36:54 -0500150 if (!hasAllLocal && fParent) {
151 attrs = fParent->resolve(charIndex);
Florin Malita9c1f1be2020-12-09 13:02:50 -0500152 }
153
Florin Malitadec78022020-12-17 16:36:54 -0500154 if (localCharIndex < fX.size()) {
155 attrs[PosAttrs::kX] = fX[localCharIndex];
156 }
157 if (localCharIndex < fY.size()) {
158 attrs[PosAttrs::kY] = fY[localCharIndex];
Florin Malita7dc984a2020-12-08 11:37:15 -0500159 }
Florin Malita735ac972020-12-22 11:23:32 -0500160 if (localCharIndex < fDx.size()) {
161 attrs[PosAttrs::kDx] = fDx[localCharIndex];
162 }
163 if (localCharIndex < fDy.size()) {
164 attrs[PosAttrs::kDy] = fDy[localCharIndex];
165 }
Florin Malita7dc984a2020-12-08 11:37:15 -0500166
Florin Malita2d059fc2021-01-05 11:53:15 -0500167 // Rotation semantics are interestingly different [1]:
168 //
169 // - values are not cumulative
170 // - if explicit values are present at any level in the ancestor chain, those take
171 // precedence (closest ancestor)
172 // - last specified value applies to all remaining chars (closest ancestor)
173 // - these rules apply at node scope (not chunk scope)
174 //
175 // This means we need to discriminate between explicit rotation (rotate value provided for
176 // current char) and implicit rotation (ancestor has some values - but not for the requested
177 // char - we use the last specified value).
178 //
179 // [1] https://www.w3.org/TR/SVG11/text.html#TSpanElementRotateAttribute
180 if (!fRotate.empty()) {
181 if (localCharIndex < fRotate.size()) {
182 // Explicit rotation value overrides anything in the ancestor chain.
183 attrs[PosAttrs::kRotate] = fRotate[localCharIndex];
184 attrs.setImplicitRotate(false);
185 } else if (!attrs.has(PosAttrs::kRotate) || attrs.isImplicitRotate()){
186 // Local implicit rotation (last specified value) overrides ancestor implicit
187 // rotation.
188 attrs[PosAttrs::kRotate] = fRotate.back();
189 attrs.setImplicitRotate(true);
190 }
191 }
192
Florin Malitadec78022020-12-17 16:36:54 -0500193 if (!attrs.hasAny()) {
194 // Once we stop producing explicit position data, there is no reason to
195 // continue trying for higher indices. We can suppress future lookups.
196 fLastPosIndex = charIndex;
197 }
Florin Malita7dc984a2020-12-08 11:37:15 -0500198 }
199
Florin Malitadec78022020-12-17 16:36:54 -0500200 return attrs;
201}
202
Florin Malita2d059fc2021-01-05 11:53:15 -0500203void SkSVGTextContext::ShapeBuffer::append(SkUnichar ch, PositionAdjustment pos) {
Florin Malita735ac972020-12-22 11:23:32 -0500204 // relative pos adjustments are cumulative
205 if (!fUtf8PosAdjust.empty()) {
Florin Malita2d059fc2021-01-05 11:53:15 -0500206 pos.offset += fUtf8PosAdjust.back().offset;
Florin Malita735ac972020-12-22 11:23:32 -0500207 }
208
209 char utf8_buf[SkUTF::kMaxBytesInUTF8Sequence];
210 const auto utf8_len = SkToInt(SkUTF::ToUTF8(ch, utf8_buf));
211 fUtf8 .push_back_n(utf8_len, utf8_buf);
212 fUtf8PosAdjust.push_back_n(utf8_len, pos);
213}
214
215void SkSVGTextContext::shapePendingBuffer(const SkFont& font) {
216 // TODO: directionality hints?
217 const auto LTR = true;
218
219 // Initiate shaping: this will generate a series of runs via callbacks.
220 fShaper->shape(fShapeBuffer.fUtf8.data(), fShapeBuffer.fUtf8.size(),
221 font, LTR, SK_ScalarMax, this);
222 fShapeBuffer.reset();
223}
224
Florin Malitadec78022020-12-17 16:36:54 -0500225SkSVGTextContext::SkSVGTextContext(const SkSVGPresentationContext& pctx, sk_sp<SkFontMgr> fmgr)
226 : fShaper(SkShaper::Make(std::move(fmgr)))
227 , fChunkPos{ 0, 0 }
228 , fChunkAlignmentFactor(ComputeAlignmentFactor(pctx))
229{}
230
231void SkSVGTextContext::appendFragment(const SkString& txt, const SkSVGRenderContext& ctx,
232 SkSVGXmlSpace xs) {
233 // https://www.w3.org/TR/SVG11/text.html#WhiteSpace
234 // https://www.w3.org/TR/2008/REC-xml-20081126/#NT-S
235 auto filterWSDefault = [this](SkUnichar ch) -> SkUnichar {
236 // Remove all newline chars.
237 if (ch == '\n') {
238 return -1;
239 }
240
241 // Convert tab chars to space.
242 if (ch == '\t') {
243 ch = ' ';
244 }
245
246 // Consolidate contiguous space chars and strip leading spaces (fPrevCharSpace
247 // starts off as true).
248 if (fPrevCharSpace && ch == ' ') {
249 return -1;
250 }
251
252 // TODO: Strip trailing WS? Doing this across chunks would require another buffering
253 // layer. In general, trailing WS should have no rendering side effects. Skipping
254 // for now.
255 return ch;
256 };
257 auto filterWSPreserve = [](SkUnichar ch) -> SkUnichar {
258 // Convert newline and tab chars to space.
259 if (ch == '\n' || ch == '\t') {
260 ch = ' ';
261 }
262 return ch;
Florin Malita7dc984a2020-12-08 11:37:15 -0500263 };
264
Florin Malitadec78022020-12-17 16:36:54 -0500265 // Stash paints for access from SkShaper callbacks.
266 fCurrentFill = ctx.fillPaint();
267 fCurrentStroke = ctx.strokePaint();
Florin Malita7dc984a2020-12-08 11:37:15 -0500268
Florin Malitadec78022020-12-17 16:36:54 -0500269 const auto font = ResolveFont(ctx);
Florin Malita735ac972020-12-22 11:23:32 -0500270 fShapeBuffer.reserve(txt.size());
Florin Malitadec78022020-12-17 16:36:54 -0500271
272 const char* ch_ptr = txt.c_str();
273 const char* ch_end = ch_ptr + txt.size();
274
275 while (ch_ptr < ch_end) {
276 auto ch = SkUTF::NextUTF8(&ch_ptr, ch_end);
277 ch = (xs == SkSVGXmlSpace::kDefault)
278 ? filterWSDefault(ch)
279 : filterWSPreserve(ch);
280
281 if (ch < 0) {
282 // invalid utf or char filtered out
283 continue;
284 }
285
286 SkASSERT(fPosResolver);
287 const auto pos = fPosResolver->resolve(fCurrentCharIndex++);
288
289 // Absolute position adjustments define a new chunk.
290 // (https://www.w3.org/TR/SVG11/text.html#TextLayoutIntroduction)
291 if (pos.has(PosAttrs::kX) || pos.has(PosAttrs::kY)) {
Florin Malita735ac972020-12-22 11:23:32 -0500292 this->shapePendingBuffer(font);
Florin Malitadec78022020-12-17 16:36:54 -0500293 this->flushChunk(ctx);
294
295 // New chunk position.
296 if (pos.has(PosAttrs::kX)) {
297 fChunkPos.fX = pos[PosAttrs::kX];
298 }
299 if (pos.has(PosAttrs::kY)) {
300 fChunkPos.fY = pos[PosAttrs::kY];
301 }
302 }
303
Florin Malita735ac972020-12-22 11:23:32 -0500304 fShapeBuffer.append(ch, {
Florin Malita2d059fc2021-01-05 11:53:15 -0500305 {
306 pos.has(PosAttrs::kDx) ? pos[PosAttrs::kDx] : 0,
307 pos.has(PosAttrs::kDy) ? pos[PosAttrs::kDy] : 0,
308 },
309 pos.has(PosAttrs::kRotate) ? pos[PosAttrs::kRotate] : 0,
Florin Malita735ac972020-12-22 11:23:32 -0500310 });
Florin Malitadec78022020-12-17 16:36:54 -0500311
312 fPrevCharSpace = (ch == ' ');
Florin Malita7dc984a2020-12-08 11:37:15 -0500313 }
Florin Malitadec78022020-12-17 16:36:54 -0500314
Florin Malita735ac972020-12-22 11:23:32 -0500315 this->shapePendingBuffer(font);
316
317 // Note: at this point we have shaped and buffered RunRecs for the current fragment.
318 // The active text chunk continues until an explicit or implicit flush.
Florin Malitadec78022020-12-17 16:36:54 -0500319}
320
321void SkSVGTextContext::flushChunk(const SkSVGRenderContext& ctx) {
322 // The final rendering offset is determined by cumulative chunk advances and alignment.
323 const auto pos = fChunkPos + fChunkAdvance * fChunkAlignmentFactor;
324
325 SkTextBlobBuilder blobBuilder;
326
327 for (const auto& run : fRuns) {
Florin Malitae5a21712020-12-30 11:08:53 -0500328 const auto& buf = blobBuilder.allocRunRSXform(run.font, SkToInt(run.glyphCount));
329 std::copy(run.glyphs.get(), run.glyphs.get() + run.glyphCount, buf.glyphs);
330 for (size_t i = 0; i < run.glyphCount; ++i) {
331 const auto& pos = run.glyphPos[i];
Florin Malita2d059fc2021-01-05 11:53:15 -0500332 buf.xforms()[i] = SkRSXform::MakeFromRadians(/*scale=*/ 1,
333 SkDegreesToRadians(run.glyphRot[i]),
Florin Malitae5a21712020-12-30 11:08:53 -0500334 pos.fX, pos.fY, 0, 0);
335 }
Florin Malitadec78022020-12-17 16:36:54 -0500336
337 // Technically, blobs with compatible paints could be merged --
338 // but likely not worth the effort.
339 const auto blob = blobBuilder.make();
340 if (run.fillPaint) {
341 ctx.canvas()->drawTextBlob(blob, pos.fX, pos.fY, *run.fillPaint);
342 }
343 if (run.strokePaint) {
344 ctx.canvas()->drawTextBlob(blob, pos.fX, pos.fY, *run.strokePaint);
345 }
Florin Malita7dc984a2020-12-08 11:37:15 -0500346 }
Florin Malita7dc984a2020-12-08 11:37:15 -0500347
Florin Malitadec78022020-12-17 16:36:54 -0500348 fChunkPos += fChunkAdvance;
349 fChunkAdvance = {0,0};
350 fChunkAlignmentFactor = ComputeAlignmentFactor(ctx.presentationContext());
Florin Malita7dc984a2020-12-08 11:37:15 -0500351
Florin Malitadec78022020-12-17 16:36:54 -0500352 fRuns.clear();
353}
Florin Malita7dc984a2020-12-08 11:37:15 -0500354
Florin Malitadec78022020-12-17 16:36:54 -0500355SkShaper::RunHandler::Buffer SkSVGTextContext::runBuffer(const RunInfo& ri) {
356 SkASSERT(ri.glyphCount);
Florin Malita9c1f1be2020-12-09 13:02:50 -0500357
Florin Malitadec78022020-12-17 16:36:54 -0500358 fRuns.push_back({
359 ri.fFont,
360 fCurrentFill ? std::make_unique<SkPaint>(*fCurrentFill) : nullptr,
361 fCurrentStroke ? std::make_unique<SkPaint>(*fCurrentStroke) : nullptr,
362 std::make_unique<SkGlyphID[]>(ri.glyphCount),
363 std::make_unique<SkPoint[] >(ri.glyphCount),
Florin Malita2d059fc2021-01-05 11:53:15 -0500364 std::make_unique<float[] >(ri.glyphCount),
Florin Malitadec78022020-12-17 16:36:54 -0500365 ri.glyphCount,
366 ri.fAdvance,
367 });
368
Florin Malita735ac972020-12-22 11:23:32 -0500369 // Ensure sufficient space to temporarily fetch cluster information.
370 fShapeClusterBuffer.resize(std::max(fShapeClusterBuffer.size(), ri.glyphCount));
371
Florin Malitadec78022020-12-17 16:36:54 -0500372 return {
373 fRuns.back().glyphs.get(),
374 fRuns.back().glyphPos.get(),
375 nullptr,
Florin Malita735ac972020-12-22 11:23:32 -0500376 fShapeClusterBuffer.data(),
Florin Malitadec78022020-12-17 16:36:54 -0500377 fChunkAdvance,
378 };
379}
380
381void SkSVGTextContext::commitRunBuffer(const RunInfo& ri) {
Florin Malita2d059fc2021-01-05 11:53:15 -0500382 const auto& current_run = fRuns.back();
383
Florin Malita735ac972020-12-22 11:23:32 -0500384 // apply position adjustments
385 for (size_t i = 0; i < ri.glyphCount; ++i) {
386 const auto utf8_index = fShapeClusterBuffer[i];
Florin Malita2d059fc2021-01-05 11:53:15 -0500387 const auto& pos = fShapeBuffer.fUtf8PosAdjust[SkToInt(utf8_index)];
388
389 current_run.glyphPos[i] += pos.offset;
390 current_run.glyphRot[i] = pos.rotation;
Florin Malita735ac972020-12-22 11:23:32 -0500391 }
392
393 // Position adjustments are cumulative - we only need to advance the current chunk
394 // with the last value.
Florin Malita2d059fc2021-01-05 11:53:15 -0500395 fChunkAdvance += ri.fAdvance + fShapeBuffer.fUtf8PosAdjust.back().offset;
Florin Malitadec78022020-12-17 16:36:54 -0500396}
Florin Malita512ff752020-12-06 11:50:52 -0500397
Florin Malitaadc68892020-12-15 10:52:26 -0500398void SkSVGTextFragment::renderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
399 SkSVGXmlSpace xs) const {
400 SkSVGRenderContext localContext(ctx, this);
401
402 if (this->onPrepareToRender(&localContext)) {
403 this->onRenderText(localContext, tctx, xs);
404 }
405}
406
407SkPath SkSVGTextFragment::onAsPath(const SkSVGRenderContext&) const {
408 // TODO
409 return SkPath();
410}
411
Florin Malita512ff752020-12-06 11:50:52 -0500412void SkSVGTextContainer::appendChild(sk_sp<SkSVGNode> child) {
413 // Only allow text nodes.
414 switch (child->tag()) {
415 case SkSVGTag::kText:
416 case SkSVGTag::kTextLiteral:
417 case SkSVGTag::kTSpan:
Florin Malitaadc68892020-12-15 10:52:26 -0500418 fChildren.push_back(
419 sk_sp<SkSVGTextFragment>(static_cast<SkSVGTextFragment*>(child.release())));
Florin Malita512ff752020-12-06 11:50:52 -0500420 break;
421 default:
422 break;
423 }
424}
425
Florin Malitaadc68892020-12-15 10:52:26 -0500426void SkSVGTextContainer::onRenderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
427 SkSVGXmlSpace) const {
Florin Malitadec78022020-12-17 16:36:54 -0500428 const SkSVGTextContext::ScopedPosResolver resolver(*this, ctx.lengthContext(), tctx);
429
Florin Malitaadc68892020-12-15 10:52:26 -0500430 for (const auto& frag : fChildren) {
431 // Containers always override xml:space with the local value.
432 frag->renderText(ctx, tctx, this->getXmlSpace());
433 }
Florin Malita9c1f1be2020-12-09 13:02:50 -0500434}
435
436// https://www.w3.org/TR/SVG11/text.html#WhiteSpace
437template <>
438bool SkSVGAttributeParser::parse(SkSVGXmlSpace* xs) {
439 static constexpr std::tuple<const char*, SkSVGXmlSpace> gXmlSpaceMap[] = {
440 {"default" , SkSVGXmlSpace::kDefault },
441 {"preserve", SkSVGXmlSpace::kPreserve},
442 };
443
444 return this->parseEnumMap(gXmlSpaceMap, xs) && this->parseEOSToken();
445}
446
Florin Malita512ff752020-12-06 11:50:52 -0500447bool SkSVGTextContainer::parseAndSetAttribute(const char* name, const char* value) {
448 return INHERITED::parseAndSetAttribute(name, value) ||
Florin Malitadec78022020-12-17 16:36:54 -0500449 this->setX(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("x", name, value)) ||
450 this->setY(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("y", name, value)) ||
Florin Malita735ac972020-12-22 11:23:32 -0500451 this->setDx(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("dx", name, value)) ||
452 this->setDy(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("dy", name, value)) ||
Florin Malita2d059fc2021-01-05 11:53:15 -0500453 this->setRotate(SkSVGAttributeParser::parse<std::vector<SkSVGNumberType>>("rotate",
454 name,
455 value)) ||
Florin Malita9c1f1be2020-12-09 13:02:50 -0500456 this->setXmlSpace(SkSVGAttributeParser::parse<SkSVGXmlSpace>("xml:space", name, value));
Florin Malita512ff752020-12-06 11:50:52 -0500457}
458
Florin Malitaadc68892020-12-15 10:52:26 -0500459void SkSVGTextContainer::onRender(const SkSVGRenderContext& ctx) const {
460 // Root text nodes establish a new text layout context.
Florin Malitadec78022020-12-17 16:36:54 -0500461 SkSVGTextContext tctx(ctx.presentationContext(), ctx.fontMgr());
Florin Malita512ff752020-12-06 11:50:52 -0500462
Florin Malitaadc68892020-12-15 10:52:26 -0500463 this->onRenderText(ctx, &tctx, this->getXmlSpace());
Florin Malita512ff752020-12-06 11:50:52 -0500464
Florin Malita7dc984a2020-12-08 11:37:15 -0500465 tctx.flushChunk(ctx);
Florin Malita512ff752020-12-06 11:50:52 -0500466}
467
Florin Malitaadc68892020-12-15 10:52:26 -0500468void SkSVGTextLiteral::onRenderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
469 SkSVGXmlSpace xs) const {
470 SkASSERT(tctx);
Florin Malita512ff752020-12-06 11:50:52 -0500471
Florin Malitaadc68892020-12-15 10:52:26 -0500472 tctx->appendFragment(this->getText(), ctx, xs);
Xavier Phane29cdaf2020-03-26 16:15:14 +0000473}