[svg] Initial <tspan> support
Introduce classes to support text node nesting:
- TextContainer -- base class for nestable text containers
(<text>, <tspan> etc)
- TextLiteral -- actual text string/payload
Example structure mapping:
<text>Foo<tspan>Bar</tspan>Baz</text>
TextContainer[text]
TextLiteral["Foo"]
TextContainer[tspan]
TextLiteral["Bar"]
TextLiteral["Baz"]
Also add text layout state (SkSVGTextContenxt) to SkSVGRenderContext.
This will be used to track layout across a text subtree.
For now we don't touch rendering, so the output is quite garbled for
non-trivial text (no advance propagation -> things draw on top of each
other).
Bug: skia:10840
Change-Id: Ic6d3990ec8635b586f5d3d226be070fbf134e391
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/341236
Commit-Queue: Florin Malita <fmalita@google.com>
Reviewed-by: Tyler Denniston <tdenniston@google.com>
diff --git a/modules/svg/src/SkSVGText.cpp b/modules/svg/src/SkSVGText.cpp
index 357e778..4e9898d 100644
--- a/modules/svg/src/SkSVGText.cpp
+++ b/modules/svg/src/SkSVGText.cpp
@@ -8,15 +8,17 @@
#include "modules/svg/include/SkSVGText.h"
#include "include/core/SkCanvas.h"
+#include "include/core/SkFont.h"
#include "include/core/SkFontMgr.h"
#include "include/core/SkFontStyle.h"
#include "include/core/SkString.h"
+#include "include/utils/SkTextUtils.h"
#include "modules/svg/include/SkSVGRenderContext.h"
#include "modules/svg/include/SkSVGValue.h"
-SkSVGText::SkSVGText() : INHERITED(SkSVGTag::kText) {}
+namespace {
-SkFont SkSVGText::resolveFont(const SkSVGRenderContext& ctx) const {
+static SkFont ResolveFont(const SkSVGRenderContext& ctx) {
auto weight = [](const SkSVGFontWeight& w) {
switch (w.type()) {
case SkSVGFontWeight::Type::k100: return SkFontStyle::kThin_Weight;
@@ -76,8 +78,54 @@
return font;
}
+} // namespace
+
+struct SkSVGTextContext {
+ SkV2 currentPos; // current text position (http://www.w3.org/TR/SVG11/text.html#TextLayout)
+ size_t currentIndex; // current character index
+};
+
+void SkSVGTextContainer::appendChild(sk_sp<SkSVGNode> child) {
+ // Only allow text nodes.
+ switch (child->tag()) {
+ case SkSVGTag::kText:
+ case SkSVGTag::kTextLiteral:
+ case SkSVGTag::kTSpan:
+ this->INHERITED::appendChild(child);
+ break;
+ default:
+ break;
+ }
+}
+
+bool SkSVGTextContainer::parseAndSetAttribute(const char* name, const char* value) {
+ return INHERITED::parseAndSetAttribute(name, value) ||
+ this->setX(SkSVGAttributeParser::parse<SkSVGLength>("x", name, value)) ||
+ this->setY(SkSVGAttributeParser::parse<SkSVGLength>("y", name, value));
+}
+
void SkSVGText::onRender(const SkSVGRenderContext& ctx) const {
- const auto font = this->resolveFont(ctx);
+ // <text> establishes a new text layout context.
+ SkSVGTextContext tctx {
+ {
+ ctx.lengthContext().resolve(this->getX(), SkSVGLengthContext::LengthType::kHorizontal),
+ ctx.lengthContext().resolve(this->getY(), SkSVGLengthContext::LengthType::kVertical),
+ },
+ 0
+ };
+
+ SkSVGRenderContext lctx(ctx, tctx);
+
+ return this->INHERITED::onRender(lctx);
+}
+
+void SkSVGTextLiteral::onRender(const SkSVGRenderContext& ctx) const {
+ auto* tctx = ctx.textContext();
+ if (!tctx) {
+ return;
+ }
+
+ const auto font = ResolveFont(ctx);
const auto text_align = [](const SkSVGTextAnchor& anchor) {
switch (anchor.type()) {
@@ -93,43 +141,21 @@
const auto align = text_align(*ctx.presentationContext().fInherited.fTextAnchor);
if (const SkPaint* fillPaint = ctx.fillPaint()) {
- SkTextUtils::DrawString(ctx.canvas(), fText.c_str(), fX.value(), fY.value(), font,
- *fillPaint, align);
+ SkTextUtils::DrawString(ctx.canvas(), fText.c_str(),
+ tctx->currentPos.x, tctx->currentPos.y,
+ font, *fillPaint, align);
}
if (const SkPaint* strokePaint = ctx.strokePaint()) {
- SkTextUtils::DrawString(ctx.canvas(), fText.c_str(), fX.value(), fY.value(), font,
- *strokePaint, align);
+ SkTextUtils::DrawString(ctx.canvas(), fText.c_str(),
+ tctx->currentPos.x, tctx->currentPos.y,
+ font, *strokePaint, align);
}
}
-void SkSVGText::appendChild(sk_sp<SkSVGNode>) {
+SkPath SkSVGTextLiteral::onAsPath(const SkSVGRenderContext&) const {
// TODO
+ return SkPath();
}
-SkPath SkSVGText::onAsPath(const SkSVGRenderContext& ctx) const {
- SkPath path;
- return path;
-}
-
-void SkSVGText::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
- switch (attr) {
- case SkSVGAttribute::kX:
- if (const auto* x = v.as<SkSVGLengthValue>()) {
- this->setX(*x);
- }
- break;
- case SkSVGAttribute::kY:
- if (const auto* y = v.as<SkSVGLengthValue>()) {
- this->setY(*y);
- }
- break;
- case SkSVGAttribute::kText:
- if (const auto* text = v.as<SkSVGStringValue>()) {
- this->setText(*text);
- }
- break;
- default:
- this->INHERITED::onSetAttribute(attr, v);
- }
-}
+SkSVGTextLiteral::~SkSVGTextLiteral() = default; // just to pin the vtable