blob: 4e2d91b36d6812928de3881ceeaa28518d9e0e53 [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 Malita7dc984a2020-12-08 11:37:15 -050010#include <vector>
11
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 Malita9c1f1be2020-12-09 13:02:50 -050020#include "src/utils/SkUTF.h"
Xavier Phane29cdaf2020-03-26 16:15:14 +000021
Florin Malita512ff752020-12-06 11:50:52 -050022namespace {
Xavier Phane29cdaf2020-03-26 16:15:14 +000023
Florin Malita512ff752020-12-06 11:50:52 -050024static SkFont ResolveFont(const SkSVGRenderContext& ctx) {
Florin Malita39fe8c82020-10-20 10:43:03 -040025 auto weight = [](const SkSVGFontWeight& w) {
26 switch (w.type()) {
27 case SkSVGFontWeight::Type::k100: return SkFontStyle::kThin_Weight;
28 case SkSVGFontWeight::Type::k200: return SkFontStyle::kExtraLight_Weight;
29 case SkSVGFontWeight::Type::k300: return SkFontStyle::kLight_Weight;
30 case SkSVGFontWeight::Type::k400: return SkFontStyle::kNormal_Weight;
31 case SkSVGFontWeight::Type::k500: return SkFontStyle::kMedium_Weight;
32 case SkSVGFontWeight::Type::k600: return SkFontStyle::kSemiBold_Weight;
33 case SkSVGFontWeight::Type::k700: return SkFontStyle::kBold_Weight;
34 case SkSVGFontWeight::Type::k800: return SkFontStyle::kExtraBold_Weight;
35 case SkSVGFontWeight::Type::k900: return SkFontStyle::kBlack_Weight;
36 case SkSVGFontWeight::Type::kNormal: return SkFontStyle::kNormal_Weight;
37 case SkSVGFontWeight::Type::kBold: return SkFontStyle::kBold_Weight;
38 case SkSVGFontWeight::Type::kBolder: return SkFontStyle::kExtraBold_Weight;
39 case SkSVGFontWeight::Type::kLighter: return SkFontStyle::kLight_Weight;
40 case SkSVGFontWeight::Type::kInherit: {
41 SkASSERT(false);
42 return SkFontStyle::kNormal_Weight;
43 }
44 }
45 SkUNREACHABLE;
46 };
47
48 auto slant = [](const SkSVGFontStyle& s) {
49 switch (s.type()) {
50 case SkSVGFontStyle::Type::kNormal: return SkFontStyle::kUpright_Slant;
51 case SkSVGFontStyle::Type::kItalic: return SkFontStyle::kItalic_Slant;
52 case SkSVGFontStyle::Type::kOblique: return SkFontStyle::kOblique_Slant;
53 case SkSVGFontStyle::Type::kInherit: {
54 SkASSERT(false);
55 return SkFontStyle::kUpright_Slant;
56 }
57 }
58 SkUNREACHABLE;
59 };
60
61 const auto& family = ctx.presentationContext().fInherited.fFontFamily->family();
62 const SkFontStyle style(weight(*ctx.presentationContext().fInherited.fFontWeight),
63 SkFontStyle::kNormal_Width,
64 slant(*ctx.presentationContext().fInherited.fFontStyle));
65
66 const auto size =
67 ctx.lengthContext().resolve(ctx.presentationContext().fInherited.fFontSize->size(),
68 SkSVGLengthContext::LengthType::kVertical);
69
Florin Malita7006e152020-11-10 15:24:59 -050070 // TODO: we likely want matchFamilyStyle here, but switching away from legacyMakeTypeface
71 // changes all the results when using the default fontmgr.
72 auto tf = ctx.fontMgr()->legacyMakeTypeface(family.c_str(), style);
73
74 SkFont font(std::move(tf), size);
Florin Malita39fe8c82020-10-20 10:43:03 -040075 font.setHinting(SkFontHinting::kNone);
76 font.setSubpixel(true);
77 font.setLinearMetrics(true);
78 font.setBaselineSnap(false);
79 font.setEdging(SkFont::Edging::kAntiAlias);
80
81 return font;
82}
83
Florin Malita7dc984a2020-12-08 11:37:15 -050084static float ComputeAlignmentFactor(const SkSVGRenderContext& ctx) {
85 switch (ctx.presentationContext().fInherited.fTextAnchor->type()) {
86 case SkSVGTextAnchor::Type::kStart : return 0.0f;
87 case SkSVGTextAnchor::Type::kMiddle: return -0.5f;
88 case SkSVGTextAnchor::Type::kEnd : return -1.0f;
89 case SkSVGTextAnchor::Type::kInherit:
90 SkASSERT(false);
91 return 0.0f;
92 }
93 SkUNREACHABLE;
94}
95
Florin Malita512ff752020-12-06 11:50:52 -050096} // namespace
97
Florin Malita7dc984a2020-12-08 11:37:15 -050098// SkSVGTextContext is responsible for sequencing input text chars into "chunks".
99// A single text chunk can span multiple structural elements (<text>, <tspan>, etc),
100// and per [1] new chunks are emitted
101//
102// a) for each top level text element (<text>, <textPath>)
103// b) whenever a character with an explicit absolute position is encountered
104//
105// The implementation queues shaped run data until a full text chunk is resolved, at which
106// point we have enough information to perform final alignment and rendering.
107//
108// [1] https://www.w3.org/TR/SVG11/text.html#TextLayoutIntroduction
109class SkSVGTextContext final : SkShaper::RunHandler {
110public:
111 SkSVGTextContext(const SkSVGTextContainer& tcontainer, const SkSVGRenderContext& ctx)
112 : fShaper(SkShaper::Make(ctx.fontMgr()))
113 , fChunkPos{ ctx.lengthContext().resolve(tcontainer.getX(),
114 SkSVGLengthContext::LengthType::kHorizontal),
115 ctx.lengthContext().resolve(tcontainer.getY(),
116 SkSVGLengthContext::LengthType::kVertical)}
117 , fChunkAlignmentFactor(ComputeAlignmentFactor(ctx))
118 {}
119
120 // Queues codepoints for rendering.
Florin Malitaadc68892020-12-15 10:52:26 -0500121 void appendFragment(const SkString& txt, const SkSVGRenderContext& ctx, SkSVGXmlSpace xs) {
Florin Malita9c1f1be2020-12-09 13:02:50 -0500122 // https://www.w3.org/TR/SVG11/text.html#WhiteSpace
123 // https://www.w3.org/TR/2008/REC-xml-20081126/#NT-S
124 auto filterWSDefault = [this](SkUnichar ch) -> SkUnichar {
125 // Remove all newline chars.
126 if (ch == '\n') {
127 return -1;
128 }
129
130 // Convert tab chars to space.
131 if (ch == '\t') {
132 ch = ' ';
133 }
134
135 // Consolidate contiguous space chars and strip leading spaces (fPrevCharSpace
136 // starts off as true).
137 if (fPrevCharSpace && ch == ' ') {
138 return -1;
139 }
140
141 // TODO: Strip trailing WS? Doing this across chunks would require another buffering
142 // layer. In general, trailing WS should have no rendering side effects. Skipping
143 // for now.
144 return ch;
145 };
146 auto filterWSPreserve = [](SkUnichar ch) -> SkUnichar {
147 // Convert newline and tab chars to space.
148 if (ch == '\n' || ch == '\t') {
149 ch = ' ';
150 }
151 return ch;
152 };
153
Florin Malita9c1f1be2020-12-09 13:02:50 -0500154 SkSTArray<128, char, true> filtered;
155 filtered.reserve_back(SkToInt(txt.size()));
156
157 const char* ch_ptr = txt.c_str();
158 const char* ch_end = ch_ptr + txt.size();
159
160 while (ch_ptr < ch_end) {
161 auto ch = SkUTF::NextUTF8(&ch_ptr, ch_end);
Florin Malitaadc68892020-12-15 10:52:26 -0500162 ch = (xs == SkSVGXmlSpace::kDefault)
Florin Malita9c1f1be2020-12-09 13:02:50 -0500163 ? filterWSDefault(ch)
164 : filterWSPreserve(ch);
165
166 if (ch < 0) {
167 // invalid utf or char filtered out
168 continue;
169 }
170
171 char utf8_buf[SkUTF::kMaxBytesInUTF8Sequence];
172 filtered.push_back_n(SkToInt(SkUTF::ToUTF8(ch, utf8_buf)), utf8_buf);
173
174 fPrevCharSpace = (ch == ' ');
175 }
176
Florin Malita7dc984a2020-12-08 11:37:15 -0500177 // TODO: absolute positioned chars => chunk breaks
178
179 // Stash paints for access from SkShaper callbacks.
180 fCurrentFill = ctx.fillPaint();
181 fCurrentStroke = ctx.strokePaint();
182
183 // TODO: directionality hints?
184 const auto LTR = true;
185
186 // Initiate shaping: this will generate a series of runs via callbacks.
Florin Malita9c1f1be2020-12-09 13:02:50 -0500187 fShaper->shape(filtered.data(), filtered.size(), ResolveFont(ctx), LTR, SK_ScalarMax, this);
Florin Malita7dc984a2020-12-08 11:37:15 -0500188 }
189
190 // Perform actual rendering for queued codepoints.
191 void flushChunk(const SkSVGRenderContext& ctx) {
192 // The final rendering offset is determined by cumulative chunk advances and alignment.
193 const auto pos = fChunkPos + fChunkAdvance * fChunkAlignmentFactor;
194
195 SkTextBlobBuilder blobBuilder;
196
197 for (const auto& run : fRuns) {
198 const auto& buf = blobBuilder.allocRunPos(run.font, SkToInt(run.glyphCount));
199 std::copy(run.glyphs .get(), run.glyphs .get() + run.glyphCount, buf.glyphs);
200 std::copy(run.glyphPos.get(), run.glyphPos.get() + run.glyphCount, buf.points());
201
202 // Technically, blobs with compatible paints could be merged --
203 // but likely not worth the effort.
204 const auto blob = blobBuilder.make();
205 if (run.fillPaint) {
206 ctx.canvas()->drawTextBlob(blob, pos.fX, pos.fY, *run.fillPaint);
207 }
208 if (run.strokePaint) {
209 ctx.canvas()->drawTextBlob(blob, pos.fX, pos.fY, *run.strokePaint);
210 }
211 }
212
213 fChunkPos += fChunkAdvance;
214 fChunkAdvance = {0,0};
215 fChunkAlignmentFactor = ComputeAlignmentFactor(ctx);
216
217 fRuns.clear();
218 }
219
220private:
221 struct RunRec {
222 SkFont font;
223 std::unique_ptr<SkPaint> fillPaint,
224 strokePaint;
225 std::unique_ptr<SkGlyphID[]> glyphs;
226 std::unique_ptr<SkPoint[]> glyphPos;
227 size_t glyphCount;
228 SkVector advance;
229 };
230
231 // SkShaper callbacks
232 void beginLine() override {}
233 void runInfo(const RunInfo&) override {}
234 void commitRunInfo() override {}
235 Buffer runBuffer(const RunInfo& ri) override {
236 SkASSERT(ri.glyphCount);
237
238 fRuns.push_back({
239 ri.fFont,
240 fCurrentFill ? std::make_unique<SkPaint>(*fCurrentFill) : nullptr,
241 fCurrentStroke ? std::make_unique<SkPaint>(*fCurrentStroke) : nullptr,
242 std::make_unique<SkGlyphID[]>(ri.glyphCount),
243 std::make_unique<SkPoint[] >(ri.glyphCount),
244 ri.glyphCount,
245 ri.fAdvance,
246 });
247
248 return {
249 fRuns.back().glyphs.get(),
250 fRuns.back().glyphPos.get(),
251 nullptr,
252 nullptr,
253 fChunkAdvance,
254 };
255 }
256 void commitRunBuffer(const RunInfo& ri) override {
257 fChunkAdvance += ri.fAdvance;
258 }
259 void commitLine() override {}
260
261 // http://www.w3.org/TR/SVG11/text.html#TextLayout
262 const std::unique_ptr<SkShaper> fShaper;
263 std::vector<RunRec> fRuns;
264
265 SkPoint fChunkPos; // current text chunk position
266 SkVector fChunkAdvance = {0,0}; // cumulative advance
267 float fChunkAlignmentFactor; // current chunk alignment
268
269 // cached for access from SkShaper callbacks.
270 const SkPaint* fCurrentFill;
271 const SkPaint* fCurrentStroke;
Florin Malita9c1f1be2020-12-09 13:02:50 -0500272
273 bool fPrevCharSpace = true; // WS filter state
Florin Malita512ff752020-12-06 11:50:52 -0500274};
275
Florin Malitaadc68892020-12-15 10:52:26 -0500276void SkSVGTextFragment::renderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
277 SkSVGXmlSpace xs) const {
278 SkSVGRenderContext localContext(ctx, this);
279
280 if (this->onPrepareToRender(&localContext)) {
281 this->onRenderText(localContext, tctx, xs);
282 }
283}
284
285SkPath SkSVGTextFragment::onAsPath(const SkSVGRenderContext&) const {
286 // TODO
287 return SkPath();
288}
289
Florin Malita512ff752020-12-06 11:50:52 -0500290void SkSVGTextContainer::appendChild(sk_sp<SkSVGNode> child) {
291 // Only allow text nodes.
292 switch (child->tag()) {
293 case SkSVGTag::kText:
294 case SkSVGTag::kTextLiteral:
295 case SkSVGTag::kTSpan:
Florin Malitaadc68892020-12-15 10:52:26 -0500296 fChildren.push_back(
297 sk_sp<SkSVGTextFragment>(static_cast<SkSVGTextFragment*>(child.release())));
Florin Malita512ff752020-12-06 11:50:52 -0500298 break;
299 default:
300 break;
301 }
302}
303
Florin Malitaadc68892020-12-15 10:52:26 -0500304void SkSVGTextContainer::onRenderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
305 SkSVGXmlSpace) const {
306 for (const auto& frag : fChildren) {
307 // Containers always override xml:space with the local value.
308 frag->renderText(ctx, tctx, this->getXmlSpace());
309 }
Florin Malita9c1f1be2020-12-09 13:02:50 -0500310}
311
312// https://www.w3.org/TR/SVG11/text.html#WhiteSpace
313template <>
314bool SkSVGAttributeParser::parse(SkSVGXmlSpace* xs) {
315 static constexpr std::tuple<const char*, SkSVGXmlSpace> gXmlSpaceMap[] = {
316 {"default" , SkSVGXmlSpace::kDefault },
317 {"preserve", SkSVGXmlSpace::kPreserve},
318 };
319
320 return this->parseEnumMap(gXmlSpaceMap, xs) && this->parseEOSToken();
321}
322
Florin Malita512ff752020-12-06 11:50:52 -0500323bool SkSVGTextContainer::parseAndSetAttribute(const char* name, const char* value) {
324 return INHERITED::parseAndSetAttribute(name, value) ||
325 this->setX(SkSVGAttributeParser::parse<SkSVGLength>("x", name, value)) ||
Florin Malita9c1f1be2020-12-09 13:02:50 -0500326 this->setY(SkSVGAttributeParser::parse<SkSVGLength>("y", name, value)) ||
327 this->setXmlSpace(SkSVGAttributeParser::parse<SkSVGXmlSpace>("xml:space", name, value));
Florin Malita512ff752020-12-06 11:50:52 -0500328}
329
Florin Malitaadc68892020-12-15 10:52:26 -0500330void SkSVGTextContainer::onRender(const SkSVGRenderContext& ctx) const {
331 // Root text nodes establish a new text layout context.
Florin Malita7dc984a2020-12-08 11:37:15 -0500332 SkSVGTextContext tctx(*this, ctx);
Florin Malita512ff752020-12-06 11:50:52 -0500333
Florin Malitaadc68892020-12-15 10:52:26 -0500334 this->onRenderText(ctx, &tctx, this->getXmlSpace());
Florin Malita512ff752020-12-06 11:50:52 -0500335
Florin Malita7dc984a2020-12-08 11:37:15 -0500336 tctx.flushChunk(ctx);
Florin Malita512ff752020-12-06 11:50:52 -0500337}
338
Florin Malitaadc68892020-12-15 10:52:26 -0500339void SkSVGTextLiteral::onRenderText(const SkSVGRenderContext& ctx, SkSVGTextContext* tctx,
340 SkSVGXmlSpace xs) const {
341 SkASSERT(tctx);
Florin Malita512ff752020-12-06 11:50:52 -0500342
Florin Malitaadc68892020-12-15 10:52:26 -0500343 tctx->appendFragment(this->getText(), ctx, xs);
Xavier Phane29cdaf2020-03-26 16:15:14 +0000344}