blob: 8227cc19088ea68e6008cb5b425e27980f3dccca [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"
16#include "include/core/SkString.h"
Florin Malita7dc984a2020-12-08 11:37:15 -050017#include "modules/skshaper/include/SkShaper.h"
Florin Malitab3418102020-10-15 18:10:29 -040018#include "modules/svg/include/SkSVGRenderContext.h"
19#include "modules/svg/include/SkSVGValue.h"
Florin Malitadec78022020-12-17 16:36:54 -050020#include "modules/svg/src/SkSVGTextPriv.h"
Florin Malita9c1f1be2020-12-09 13:02:50 -050021#include "src/utils/SkUTF.h"
Xavier Phane29cdaf2020-03-26 16:15:14 +000022
Florin Malita512ff752020-12-06 11:50:52 -050023namespace {
Xavier Phane29cdaf2020-03-26 16:15:14 +000024
Florin Malita512ff752020-12-06 11:50:52 -050025static SkFont ResolveFont(const SkSVGRenderContext& ctx) {
Florin Malita39fe8c82020-10-20 10:43:03 -040026 auto weight = [](const SkSVGFontWeight& w) {
27 switch (w.type()) {
28 case SkSVGFontWeight::Type::k100: return SkFontStyle::kThin_Weight;
29 case SkSVGFontWeight::Type::k200: return SkFontStyle::kExtraLight_Weight;
30 case SkSVGFontWeight::Type::k300: return SkFontStyle::kLight_Weight;
31 case SkSVGFontWeight::Type::k400: return SkFontStyle::kNormal_Weight;
32 case SkSVGFontWeight::Type::k500: return SkFontStyle::kMedium_Weight;
33 case SkSVGFontWeight::Type::k600: return SkFontStyle::kSemiBold_Weight;
34 case SkSVGFontWeight::Type::k700: return SkFontStyle::kBold_Weight;
35 case SkSVGFontWeight::Type::k800: return SkFontStyle::kExtraBold_Weight;
36 case SkSVGFontWeight::Type::k900: return SkFontStyle::kBlack_Weight;
37 case SkSVGFontWeight::Type::kNormal: return SkFontStyle::kNormal_Weight;
38 case SkSVGFontWeight::Type::kBold: return SkFontStyle::kBold_Weight;
39 case SkSVGFontWeight::Type::kBolder: return SkFontStyle::kExtraBold_Weight;
40 case SkSVGFontWeight::Type::kLighter: return SkFontStyle::kLight_Weight;
41 case SkSVGFontWeight::Type::kInherit: {
42 SkASSERT(false);
43 return SkFontStyle::kNormal_Weight;
44 }
45 }
46 SkUNREACHABLE;
47 };
48
49 auto slant = [](const SkSVGFontStyle& s) {
50 switch (s.type()) {
51 case SkSVGFontStyle::Type::kNormal: return SkFontStyle::kUpright_Slant;
52 case SkSVGFontStyle::Type::kItalic: return SkFontStyle::kItalic_Slant;
53 case SkSVGFontStyle::Type::kOblique: return SkFontStyle::kOblique_Slant;
54 case SkSVGFontStyle::Type::kInherit: {
55 SkASSERT(false);
56 return SkFontStyle::kUpright_Slant;
57 }
58 }
59 SkUNREACHABLE;
60 };
61
62 const auto& family = ctx.presentationContext().fInherited.fFontFamily->family();
63 const SkFontStyle style(weight(*ctx.presentationContext().fInherited.fFontWeight),
64 SkFontStyle::kNormal_Width,
65 slant(*ctx.presentationContext().fInherited.fFontStyle));
66
67 const auto size =
68 ctx.lengthContext().resolve(ctx.presentationContext().fInherited.fFontSize->size(),
69 SkSVGLengthContext::LengthType::kVertical);
70
Florin Malita7006e152020-11-10 15:24:59 -050071 // TODO: we likely want matchFamilyStyle here, but switching away from legacyMakeTypeface
72 // changes all the results when using the default fontmgr.
73 auto tf = ctx.fontMgr()->legacyMakeTypeface(family.c_str(), style);
74
75 SkFont font(std::move(tf), size);
Florin Malita39fe8c82020-10-20 10:43:03 -040076 font.setHinting(SkFontHinting::kNone);
77 font.setSubpixel(true);
78 font.setLinearMetrics(true);
79 font.setBaselineSnap(false);
80 font.setEdging(SkFont::Edging::kAntiAlias);
81
82 return font;
83}
84
Florin Malitadec78022020-12-17 16:36:54 -050085static std::vector<float> ResolveLengths(const SkSVGLengthContext& lctx,
86 const std::vector<SkSVGLength>& lengths,
87 SkSVGLengthContext::LengthType lt) {
88 std::vector<float> resolved;
89 resolved.reserve(lengths.size());
90
91 for (const auto& l : lengths) {
92 resolved.push_back(lctx.resolve(l, lt));
93 }
94
95 return resolved;
96}
97
98static float ComputeAlignmentFactor(const SkSVGPresentationContext& pctx) {
99 switch (pctx.fInherited.fTextAnchor->type()) {
Florin Malita7dc984a2020-12-08 11:37:15 -0500100 case SkSVGTextAnchor::Type::kStart : return 0.0f;
101 case SkSVGTextAnchor::Type::kMiddle: return -0.5f;
102 case SkSVGTextAnchor::Type::kEnd : return -1.0f;
103 case SkSVGTextAnchor::Type::kInherit:
104 SkASSERT(false);
105 return 0.0f;
106 }
107 SkUNREACHABLE;
108}
109
Florin Malita512ff752020-12-06 11:50:52 -0500110} // namespace
111
Florin Malitadec78022020-12-17 16:36:54 -0500112SkSVGTextContext::ScopedPosResolver::ScopedPosResolver(const SkSVGTextContainer& txt,
113 const SkSVGLengthContext& lctx,
114 SkSVGTextContext* tctx,
115 size_t charIndexOffset)
116 : fTextContext(tctx)
117 , fParent(tctx->fPosResolver)
118 , fCharIndexOffset(charIndexOffset)
119 , fX(ResolveLengths(lctx, txt.getX(), SkSVGLengthContext::LengthType::kHorizontal))
120 , fY(ResolveLengths(lctx, txt.getY(), SkSVGLengthContext::LengthType::kVertical))
121{
122 fTextContext->fPosResolver = this;
123}
Florin Malita7dc984a2020-12-08 11:37:15 -0500124
Florin Malitadec78022020-12-17 16:36:54 -0500125SkSVGTextContext::ScopedPosResolver::ScopedPosResolver(const SkSVGTextContainer& txt,
126 const SkSVGLengthContext& lctx,
127 SkSVGTextContext* tctx)
128 : ScopedPosResolver(txt, lctx, tctx, tctx->fCurrentCharIndex) {}
Florin Malita9c1f1be2020-12-09 13:02:50 -0500129
Florin Malitadec78022020-12-17 16:36:54 -0500130SkSVGTextContext::ScopedPosResolver::~ScopedPosResolver() {
131 fTextContext->fPosResolver = fParent;
132}
Florin Malita9c1f1be2020-12-09 13:02:50 -0500133
Florin Malitadec78022020-12-17 16:36:54 -0500134SkSVGTextContext::PosAttrs SkSVGTextContext::ScopedPosResolver::resolve(size_t charIndex) const {
135 PosAttrs attrs;
Florin Malita9c1f1be2020-12-09 13:02:50 -0500136
Florin Malitadec78022020-12-17 16:36:54 -0500137 if (charIndex < fLastPosIndex) {
138 SkASSERT(charIndex >= fCharIndexOffset);
139 const auto localCharIndex = charIndex - fCharIndexOffset;
Florin Malita9c1f1be2020-12-09 13:02:50 -0500140
Florin Malitadec78022020-12-17 16:36:54 -0500141 const auto hasAllLocal = localCharIndex < fX.size() &&
142 localCharIndex < fY.size();
143 if (!hasAllLocal && fParent) {
144 attrs = fParent->resolve(charIndex);
Florin Malita9c1f1be2020-12-09 13:02:50 -0500145 }
146
Florin Malitadec78022020-12-17 16:36:54 -0500147 if (localCharIndex < fX.size()) {
148 attrs[PosAttrs::kX] = fX[localCharIndex];
149 }
150 if (localCharIndex < fY.size()) {
151 attrs[PosAttrs::kY] = fY[localCharIndex];
Florin Malita7dc984a2020-12-08 11:37:15 -0500152 }
153
Florin Malitadec78022020-12-17 16:36:54 -0500154 if (!attrs.hasAny()) {
155 // Once we stop producing explicit position data, there is no reason to
156 // continue trying for higher indices. We can suppress future lookups.
157 fLastPosIndex = charIndex;
158 }
Florin Malita7dc984a2020-12-08 11:37:15 -0500159 }
160
Florin Malitadec78022020-12-17 16:36:54 -0500161 return attrs;
162}
163
164SkSVGTextContext::SkSVGTextContext(const SkSVGPresentationContext& pctx, sk_sp<SkFontMgr> fmgr)
165 : fShaper(SkShaper::Make(std::move(fmgr)))
166 , fChunkPos{ 0, 0 }
167 , fChunkAlignmentFactor(ComputeAlignmentFactor(pctx))
168{}
169
170void SkSVGTextContext::appendFragment(const SkString& txt, const SkSVGRenderContext& ctx,
171 SkSVGXmlSpace xs) {
172 // https://www.w3.org/TR/SVG11/text.html#WhiteSpace
173 // https://www.w3.org/TR/2008/REC-xml-20081126/#NT-S
174 auto filterWSDefault = [this](SkUnichar ch) -> SkUnichar {
175 // Remove all newline chars.
176 if (ch == '\n') {
177 return -1;
178 }
179
180 // Convert tab chars to space.
181 if (ch == '\t') {
182 ch = ' ';
183 }
184
185 // Consolidate contiguous space chars and strip leading spaces (fPrevCharSpace
186 // starts off as true).
187 if (fPrevCharSpace && ch == ' ') {
188 return -1;
189 }
190
191 // TODO: Strip trailing WS? Doing this across chunks would require another buffering
192 // layer. In general, trailing WS should have no rendering side effects. Skipping
193 // for now.
194 return ch;
195 };
196 auto filterWSPreserve = [](SkUnichar ch) -> SkUnichar {
197 // Convert newline and tab chars to space.
198 if (ch == '\n' || ch == '\t') {
199 ch = ' ';
200 }
201 return ch;
Florin Malita7dc984a2020-12-08 11:37:15 -0500202 };
203
Florin Malitadec78022020-12-17 16:36:54 -0500204 // Stash paints for access from SkShaper callbacks.
205 fCurrentFill = ctx.fillPaint();
206 fCurrentStroke = ctx.strokePaint();
Florin Malita7dc984a2020-12-08 11:37:15 -0500207
Florin Malitadec78022020-12-17 16:36:54 -0500208 const auto font = ResolveFont(ctx);
Florin Malita7dc984a2020-12-08 11:37:15 -0500209
Florin Malitadec78022020-12-17 16:36:54 -0500210 SkSTArray<128, char, true> filtered;
211 filtered.reserve_back(SkToInt(txt.size()));
212
213 auto shapePending = [&filtered, &font, this]() {
214 // TODO: directionality hints?
215 const auto LTR = true;
216 // Initiate shaping: this will generate a series of runs via callbacks.
217 fShaper->shape(filtered.data(), filtered.size(), font, LTR, SK_ScalarMax, this);
218 filtered.reset();
219 };
220
221 const char* ch_ptr = txt.c_str();
222 const char* ch_end = ch_ptr + txt.size();
223
224 while (ch_ptr < ch_end) {
225 auto ch = SkUTF::NextUTF8(&ch_ptr, ch_end);
226 ch = (xs == SkSVGXmlSpace::kDefault)
227 ? filterWSDefault(ch)
228 : filterWSPreserve(ch);
229
230 if (ch < 0) {
231 // invalid utf or char filtered out
232 continue;
233 }
234
235 SkASSERT(fPosResolver);
236 const auto pos = fPosResolver->resolve(fCurrentCharIndex++);
237
238 // Absolute position adjustments define a new chunk.
239 // (https://www.w3.org/TR/SVG11/text.html#TextLayoutIntroduction)
240 if (pos.has(PosAttrs::kX) || pos.has(PosAttrs::kY)) {
241 shapePending();
242 this->flushChunk(ctx);
243
244 // New chunk position.
245 if (pos.has(PosAttrs::kX)) {
246 fChunkPos.fX = pos[PosAttrs::kX];
247 }
248 if (pos.has(PosAttrs::kY)) {
249 fChunkPos.fY = pos[PosAttrs::kY];
250 }
251 }
252
253 char utf8_buf[SkUTF::kMaxBytesInUTF8Sequence];
254 filtered.push_back_n(SkToInt(SkUTF::ToUTF8(ch, utf8_buf)), utf8_buf);
255
256 fPrevCharSpace = (ch == ' ');
Florin Malita7dc984a2020-12-08 11:37:15 -0500257 }
Florin Malitadec78022020-12-17 16:36:54 -0500258
259 // Note: at this point we have shaped and buffered the current fragment The active
260 // text chunk continues until an explicit or implicit flush.
261 shapePending();
262}
263
264void SkSVGTextContext::flushChunk(const SkSVGRenderContext& ctx) {
265 // The final rendering offset is determined by cumulative chunk advances and alignment.
266 const auto pos = fChunkPos + fChunkAdvance * fChunkAlignmentFactor;
267
268 SkTextBlobBuilder blobBuilder;
269
270 for (const auto& run : fRuns) {
271 const auto& buf = blobBuilder.allocRunPos(run.font, SkToInt(run.glyphCount));
272 std::copy(run.glyphs .get(), run.glyphs .get() + run.glyphCount, buf.glyphs);
273 std::copy(run.glyphPos.get(), run.glyphPos.get() + run.glyphCount, buf.points());
274
275 // Technically, blobs with compatible paints could be merged --
276 // but likely not worth the effort.
277 const auto blob = blobBuilder.make();
278 if (run.fillPaint) {
279 ctx.canvas()->drawTextBlob(blob, pos.fX, pos.fY, *run.fillPaint);
280 }
281 if (run.strokePaint) {
282 ctx.canvas()->drawTextBlob(blob, pos.fX, pos.fY, *run.strokePaint);
283 }
Florin Malita7dc984a2020-12-08 11:37:15 -0500284 }
Florin Malita7dc984a2020-12-08 11:37:15 -0500285
Florin Malitadec78022020-12-17 16:36:54 -0500286 fChunkPos += fChunkAdvance;
287 fChunkAdvance = {0,0};
288 fChunkAlignmentFactor = ComputeAlignmentFactor(ctx.presentationContext());
Florin Malita7dc984a2020-12-08 11:37:15 -0500289
Florin Malitadec78022020-12-17 16:36:54 -0500290 fRuns.clear();
291}
Florin Malita7dc984a2020-12-08 11:37:15 -0500292
Florin Malitadec78022020-12-17 16:36:54 -0500293SkShaper::RunHandler::Buffer SkSVGTextContext::runBuffer(const RunInfo& ri) {
294 SkASSERT(ri.glyphCount);
Florin Malita9c1f1be2020-12-09 13:02:50 -0500295
Florin Malitadec78022020-12-17 16:36:54 -0500296 fRuns.push_back({
297 ri.fFont,
298 fCurrentFill ? std::make_unique<SkPaint>(*fCurrentFill) : nullptr,
299 fCurrentStroke ? std::make_unique<SkPaint>(*fCurrentStroke) : nullptr,
300 std::make_unique<SkGlyphID[]>(ri.glyphCount),
301 std::make_unique<SkPoint[] >(ri.glyphCount),
302 ri.glyphCount,
303 ri.fAdvance,
304 });
305
306 return {
307 fRuns.back().glyphs.get(),
308 fRuns.back().glyphPos.get(),
309 nullptr,
310 nullptr,
311 fChunkAdvance,
312 };
313}
314
315void SkSVGTextContext::commitRunBuffer(const RunInfo& ri) {
316 fChunkAdvance += ri.fAdvance;
317}
Florin Malita512ff752020-12-06 11:50:52 -0500318
Florin Malitaadc68892020-12-15 10:52:26 -0500319void SkSVGTextFragment::renderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
320 SkSVGXmlSpace xs) const {
321 SkSVGRenderContext localContext(ctx, this);
322
323 if (this->onPrepareToRender(&localContext)) {
324 this->onRenderText(localContext, tctx, xs);
325 }
326}
327
328SkPath SkSVGTextFragment::onAsPath(const SkSVGRenderContext&) const {
329 // TODO
330 return SkPath();
331}
332
Florin Malita512ff752020-12-06 11:50:52 -0500333void SkSVGTextContainer::appendChild(sk_sp<SkSVGNode> child) {
334 // Only allow text nodes.
335 switch (child->tag()) {
336 case SkSVGTag::kText:
337 case SkSVGTag::kTextLiteral:
338 case SkSVGTag::kTSpan:
Florin Malitaadc68892020-12-15 10:52:26 -0500339 fChildren.push_back(
340 sk_sp<SkSVGTextFragment>(static_cast<SkSVGTextFragment*>(child.release())));
Florin Malita512ff752020-12-06 11:50:52 -0500341 break;
342 default:
343 break;
344 }
345}
346
Florin Malitaadc68892020-12-15 10:52:26 -0500347void SkSVGTextContainer::onRenderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
348 SkSVGXmlSpace) const {
Florin Malitadec78022020-12-17 16:36:54 -0500349 const SkSVGTextContext::ScopedPosResolver resolver(*this, ctx.lengthContext(), tctx);
350
Florin Malitaadc68892020-12-15 10:52:26 -0500351 for (const auto& frag : fChildren) {
352 // Containers always override xml:space with the local value.
353 frag->renderText(ctx, tctx, this->getXmlSpace());
354 }
Florin Malita9c1f1be2020-12-09 13:02:50 -0500355}
356
357// https://www.w3.org/TR/SVG11/text.html#WhiteSpace
358template <>
359bool SkSVGAttributeParser::parse(SkSVGXmlSpace* xs) {
360 static constexpr std::tuple<const char*, SkSVGXmlSpace> gXmlSpaceMap[] = {
361 {"default" , SkSVGXmlSpace::kDefault },
362 {"preserve", SkSVGXmlSpace::kPreserve},
363 };
364
365 return this->parseEnumMap(gXmlSpaceMap, xs) && this->parseEOSToken();
366}
367
Florin Malita512ff752020-12-06 11:50:52 -0500368bool SkSVGTextContainer::parseAndSetAttribute(const char* name, const char* value) {
369 return INHERITED::parseAndSetAttribute(name, value) ||
Florin Malitadec78022020-12-17 16:36:54 -0500370 this->setX(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("x", name, value)) ||
371 this->setY(SkSVGAttributeParser::parse<std::vector<SkSVGLength>>("y", name, value)) ||
Florin Malita9c1f1be2020-12-09 13:02:50 -0500372 this->setXmlSpace(SkSVGAttributeParser::parse<SkSVGXmlSpace>("xml:space", name, value));
Florin Malita512ff752020-12-06 11:50:52 -0500373}
374
Florin Malitaadc68892020-12-15 10:52:26 -0500375void SkSVGTextContainer::onRender(const SkSVGRenderContext& ctx) const {
376 // Root text nodes establish a new text layout context.
Florin Malitadec78022020-12-17 16:36:54 -0500377 SkSVGTextContext tctx(ctx.presentationContext(), ctx.fontMgr());
Florin Malita512ff752020-12-06 11:50:52 -0500378
Florin Malitaadc68892020-12-15 10:52:26 -0500379 this->onRenderText(ctx, &tctx, this->getXmlSpace());
Florin Malita512ff752020-12-06 11:50:52 -0500380
Florin Malita7dc984a2020-12-08 11:37:15 -0500381 tctx.flushChunk(ctx);
Florin Malita512ff752020-12-06 11:50:52 -0500382}
383
Florin Malitaadc68892020-12-15 10:52:26 -0500384void SkSVGTextLiteral::onRenderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
385 SkSVGXmlSpace xs) const {
386 SkASSERT(tctx);
Florin Malita512ff752020-12-06 11:50:52 -0500387
Florin Malitaadc68892020-12-15 10:52:26 -0500388 tctx->appendFragment(this->getText(), ctx, xs);
Xavier Phane29cdaf2020-03-26 16:15:14 +0000389}