blob: bf7cf0a09c132f4af81a80cc9e0b7566f91ee816 [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"
Xavier Phane29cdaf2020-03-26 16:15:14 +000020
Florin Malita512ff752020-12-06 11:50:52 -050021namespace {
Xavier Phane29cdaf2020-03-26 16:15:14 +000022
Florin Malita512ff752020-12-06 11:50:52 -050023static SkFont ResolveFont(const SkSVGRenderContext& ctx) {
Florin Malita39fe8c82020-10-20 10:43:03 -040024 auto weight = [](const SkSVGFontWeight& w) {
25 switch (w.type()) {
26 case SkSVGFontWeight::Type::k100: return SkFontStyle::kThin_Weight;
27 case SkSVGFontWeight::Type::k200: return SkFontStyle::kExtraLight_Weight;
28 case SkSVGFontWeight::Type::k300: return SkFontStyle::kLight_Weight;
29 case SkSVGFontWeight::Type::k400: return SkFontStyle::kNormal_Weight;
30 case SkSVGFontWeight::Type::k500: return SkFontStyle::kMedium_Weight;
31 case SkSVGFontWeight::Type::k600: return SkFontStyle::kSemiBold_Weight;
32 case SkSVGFontWeight::Type::k700: return SkFontStyle::kBold_Weight;
33 case SkSVGFontWeight::Type::k800: return SkFontStyle::kExtraBold_Weight;
34 case SkSVGFontWeight::Type::k900: return SkFontStyle::kBlack_Weight;
35 case SkSVGFontWeight::Type::kNormal: return SkFontStyle::kNormal_Weight;
36 case SkSVGFontWeight::Type::kBold: return SkFontStyle::kBold_Weight;
37 case SkSVGFontWeight::Type::kBolder: return SkFontStyle::kExtraBold_Weight;
38 case SkSVGFontWeight::Type::kLighter: return SkFontStyle::kLight_Weight;
39 case SkSVGFontWeight::Type::kInherit: {
40 SkASSERT(false);
41 return SkFontStyle::kNormal_Weight;
42 }
43 }
44 SkUNREACHABLE;
45 };
46
47 auto slant = [](const SkSVGFontStyle& s) {
48 switch (s.type()) {
49 case SkSVGFontStyle::Type::kNormal: return SkFontStyle::kUpright_Slant;
50 case SkSVGFontStyle::Type::kItalic: return SkFontStyle::kItalic_Slant;
51 case SkSVGFontStyle::Type::kOblique: return SkFontStyle::kOblique_Slant;
52 case SkSVGFontStyle::Type::kInherit: {
53 SkASSERT(false);
54 return SkFontStyle::kUpright_Slant;
55 }
56 }
57 SkUNREACHABLE;
58 };
59
60 const auto& family = ctx.presentationContext().fInherited.fFontFamily->family();
61 const SkFontStyle style(weight(*ctx.presentationContext().fInherited.fFontWeight),
62 SkFontStyle::kNormal_Width,
63 slant(*ctx.presentationContext().fInherited.fFontStyle));
64
65 const auto size =
66 ctx.lengthContext().resolve(ctx.presentationContext().fInherited.fFontSize->size(),
67 SkSVGLengthContext::LengthType::kVertical);
68
Florin Malita7006e152020-11-10 15:24:59 -050069 // TODO: we likely want matchFamilyStyle here, but switching away from legacyMakeTypeface
70 // changes all the results when using the default fontmgr.
71 auto tf = ctx.fontMgr()->legacyMakeTypeface(family.c_str(), style);
72
73 SkFont font(std::move(tf), size);
Florin Malita39fe8c82020-10-20 10:43:03 -040074 font.setHinting(SkFontHinting::kNone);
75 font.setSubpixel(true);
76 font.setLinearMetrics(true);
77 font.setBaselineSnap(false);
78 font.setEdging(SkFont::Edging::kAntiAlias);
79
80 return font;
81}
82
Florin Malita7dc984a2020-12-08 11:37:15 -050083static float ComputeAlignmentFactor(const SkSVGRenderContext& ctx) {
84 switch (ctx.presentationContext().fInherited.fTextAnchor->type()) {
85 case SkSVGTextAnchor::Type::kStart : return 0.0f;
86 case SkSVGTextAnchor::Type::kMiddle: return -0.5f;
87 case SkSVGTextAnchor::Type::kEnd : return -1.0f;
88 case SkSVGTextAnchor::Type::kInherit:
89 SkASSERT(false);
90 return 0.0f;
91 }
92 SkUNREACHABLE;
93}
94
Florin Malita512ff752020-12-06 11:50:52 -050095} // namespace
96
Florin Malita7dc984a2020-12-08 11:37:15 -050097// SkSVGTextContext is responsible for sequencing input text chars into "chunks".
98// A single text chunk can span multiple structural elements (<text>, <tspan>, etc),
99// and per [1] new chunks are emitted
100//
101// a) for each top level text element (<text>, <textPath>)
102// b) whenever a character with an explicit absolute position is encountered
103//
104// The implementation queues shaped run data until a full text chunk is resolved, at which
105// point we have enough information to perform final alignment and rendering.
106//
107// [1] https://www.w3.org/TR/SVG11/text.html#TextLayoutIntroduction
108class SkSVGTextContext final : SkShaper::RunHandler {
109public:
110 SkSVGTextContext(const SkSVGTextContainer& tcontainer, const SkSVGRenderContext& ctx)
111 : fShaper(SkShaper::Make(ctx.fontMgr()))
112 , fChunkPos{ ctx.lengthContext().resolve(tcontainer.getX(),
113 SkSVGLengthContext::LengthType::kHorizontal),
114 ctx.lengthContext().resolve(tcontainer.getY(),
115 SkSVGLengthContext::LengthType::kVertical)}
116 , fChunkAlignmentFactor(ComputeAlignmentFactor(ctx))
117 {}
118
119 // Queues codepoints for rendering.
120 void appendFragment(const SkString& txt, const SkSVGRenderContext& ctx) {
121 // TODO: xml::space filtering
122 // TODO: absolute positioned chars => chunk breaks
123
124 // Stash paints for access from SkShaper callbacks.
125 fCurrentFill = ctx.fillPaint();
126 fCurrentStroke = ctx.strokePaint();
127
128 // TODO: directionality hints?
129 const auto LTR = true;
130
131 // Initiate shaping: this will generate a series of runs via callbacks.
132 fShaper->shape(txt.c_str(), txt.size(), ResolveFont(ctx), LTR, SK_ScalarMax, this);
133 }
134
135 // Perform actual rendering for queued codepoints.
136 void flushChunk(const SkSVGRenderContext& ctx) {
137 // The final rendering offset is determined by cumulative chunk advances and alignment.
138 const auto pos = fChunkPos + fChunkAdvance * fChunkAlignmentFactor;
139
140 SkTextBlobBuilder blobBuilder;
141
142 for (const auto& run : fRuns) {
143 const auto& buf = blobBuilder.allocRunPos(run.font, SkToInt(run.glyphCount));
144 std::copy(run.glyphs .get(), run.glyphs .get() + run.glyphCount, buf.glyphs);
145 std::copy(run.glyphPos.get(), run.glyphPos.get() + run.glyphCount, buf.points());
146
147 // Technically, blobs with compatible paints could be merged --
148 // but likely not worth the effort.
149 const auto blob = blobBuilder.make();
150 if (run.fillPaint) {
151 ctx.canvas()->drawTextBlob(blob, pos.fX, pos.fY, *run.fillPaint);
152 }
153 if (run.strokePaint) {
154 ctx.canvas()->drawTextBlob(blob, pos.fX, pos.fY, *run.strokePaint);
155 }
156 }
157
158 fChunkPos += fChunkAdvance;
159 fChunkAdvance = {0,0};
160 fChunkAlignmentFactor = ComputeAlignmentFactor(ctx);
161
162 fRuns.clear();
163 }
164
165private:
166 struct RunRec {
167 SkFont font;
168 std::unique_ptr<SkPaint> fillPaint,
169 strokePaint;
170 std::unique_ptr<SkGlyphID[]> glyphs;
171 std::unique_ptr<SkPoint[]> glyphPos;
172 size_t glyphCount;
173 SkVector advance;
174 };
175
176 // SkShaper callbacks
177 void beginLine() override {}
178 void runInfo(const RunInfo&) override {}
179 void commitRunInfo() override {}
180 Buffer runBuffer(const RunInfo& ri) override {
181 SkASSERT(ri.glyphCount);
182
183 fRuns.push_back({
184 ri.fFont,
185 fCurrentFill ? std::make_unique<SkPaint>(*fCurrentFill) : nullptr,
186 fCurrentStroke ? std::make_unique<SkPaint>(*fCurrentStroke) : nullptr,
187 std::make_unique<SkGlyphID[]>(ri.glyphCount),
188 std::make_unique<SkPoint[] >(ri.glyphCount),
189 ri.glyphCount,
190 ri.fAdvance,
191 });
192
193 return {
194 fRuns.back().glyphs.get(),
195 fRuns.back().glyphPos.get(),
196 nullptr,
197 nullptr,
198 fChunkAdvance,
199 };
200 }
201 void commitRunBuffer(const RunInfo& ri) override {
202 fChunkAdvance += ri.fAdvance;
203 }
204 void commitLine() override {}
205
206 // http://www.w3.org/TR/SVG11/text.html#TextLayout
207 const std::unique_ptr<SkShaper> fShaper;
208 std::vector<RunRec> fRuns;
209
210 SkPoint fChunkPos; // current text chunk position
211 SkVector fChunkAdvance = {0,0}; // cumulative advance
212 float fChunkAlignmentFactor; // current chunk alignment
213
214 // cached for access from SkShaper callbacks.
215 const SkPaint* fCurrentFill;
216 const SkPaint* fCurrentStroke;
Florin Malita512ff752020-12-06 11:50:52 -0500217};
218
219void SkSVGTextContainer::appendChild(sk_sp<SkSVGNode> child) {
220 // Only allow text nodes.
221 switch (child->tag()) {
222 case SkSVGTag::kText:
223 case SkSVGTag::kTextLiteral:
224 case SkSVGTag::kTSpan:
225 this->INHERITED::appendChild(child);
226 break;
227 default:
228 break;
229 }
230}
231
232bool SkSVGTextContainer::parseAndSetAttribute(const char* name, const char* value) {
233 return INHERITED::parseAndSetAttribute(name, value) ||
234 this->setX(SkSVGAttributeParser::parse<SkSVGLength>("x", name, value)) ||
235 this->setY(SkSVGAttributeParser::parse<SkSVGLength>("y", name, value));
236}
237
Florin Malita39fe8c82020-10-20 10:43:03 -0400238void SkSVGText::onRender(const SkSVGRenderContext& ctx) const {
Florin Malita512ff752020-12-06 11:50:52 -0500239 // <text> establishes a new text layout context.
Florin Malita7dc984a2020-12-08 11:37:15 -0500240 SkSVGTextContext tctx(*this, ctx);
Florin Malita512ff752020-12-06 11:50:52 -0500241
Florin Malita7dc984a2020-12-08 11:37:15 -0500242 SkSVGRenderContext local_ctx(ctx, tctx);
243 this->INHERITED::onRender(local_ctx);
Florin Malita512ff752020-12-06 11:50:52 -0500244
Florin Malita7dc984a2020-12-08 11:37:15 -0500245 tctx.flushChunk(ctx);
Florin Malita512ff752020-12-06 11:50:52 -0500246}
247
248void SkSVGTextLiteral::onRender(const SkSVGRenderContext& ctx) const {
249 auto* tctx = ctx.textContext();
250 if (!tctx) {
251 return;
252 }
253
Florin Malita7dc984a2020-12-08 11:37:15 -0500254 tctx->appendFragment(this->getText(), ctx);
Florin Malita39fe8c82020-10-20 10:43:03 -0400255}
256
Florin Malita512ff752020-12-06 11:50:52 -0500257SkPath SkSVGTextLiteral::onAsPath(const SkSVGRenderContext&) const {
Florin Malita39fe8c82020-10-20 10:43:03 -0400258 // TODO
Florin Malita512ff752020-12-06 11:50:52 -0500259 return SkPath();
Xavier Phane29cdaf2020-03-26 16:15:14 +0000260}
261
Florin Malita512ff752020-12-06 11:50:52 -0500262SkSVGTextLiteral::~SkSVGTextLiteral() = default; // just to pin the vtable