[svg] xml:space support

Add xml:space attribute and implement related white space filters.

(https://www.w3.org/TR/SVG11/text.html#WhiteSpace)

Bug: skia:10840
Change-Id: I52fda50fae1cd7cf8b0dd7c1a2ee2e667ffa947b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/342299
Reviewed-by: Tyler Denniston <tdenniston@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Florin Malita <fmalita@google.com>
This commit is contained in:
Florin Malita 2020-12-09 13:02:50 -05:00 committed by Skia Commit-Bot
parent a4bd478bc2
commit 9c1f1be078
4 changed files with 92 additions and 3 deletions

View File

@ -132,6 +132,9 @@ public:
return fFontMgr ? fFontMgr : SkFontMgr::RefDefault();
}
SkSVGXmlSpace getXmlSpace() const { return fXmlSpace; }
void setXmlSpace(SkSVGXmlSpace xs) { fXmlSpace = xs; }
private:
// Stack-only
void* operator new(size_t) = delete;
@ -156,6 +159,8 @@ private:
// clipPath, if present for the current context (not inherited).
SkTLazy<SkPath> fClipPath;
SkSVGXmlSpace fXmlSpace = SkSVGXmlSpace::kDefault;
const SkSVGNode* fNode;
};

View File

@ -18,11 +18,14 @@ public:
SVG_ATTR(X, SkSVGLength, SkSVGLength(0))
SVG_ATTR(Y, SkSVGLength, SkSVGLength(0))
SVG_ATTR(XmlSpace, SkSVGXmlSpace, SkSVGXmlSpace::kDefault)
protected:
explicit SkSVGTextContainer(SkSVGTag t) : INHERITED(t) {}
private:
void appendChild(sk_sp<SkSVGNode>) final;
bool onPrepareToRender(SkSVGRenderContext*) const final;
bool parseAndSetAttribute(const char*, const char*) override;

View File

@ -652,4 +652,9 @@ struct SkSVGFeTurbulenceType {
explicit SkSVGFeTurbulenceType(Type type) : fType(type) {}
};
enum class SkSVGXmlSpace {
kDefault,
kPreserve,
};
#endif // SkSVGTypes_DEFINED

View File

@ -17,6 +17,7 @@
#include "modules/skshaper/include/SkShaper.h"
#include "modules/svg/include/SkSVGRenderContext.h"
#include "modules/svg/include/SkSVGValue.h"
#include "src/utils/SkUTF.h"
namespace {
@ -118,7 +119,63 @@ public:
// Queues codepoints for rendering.
void appendFragment(const SkString& txt, const SkSVGRenderContext& ctx) {
// TODO: xml::space filtering
// https://www.w3.org/TR/SVG11/text.html#WhiteSpace
// https://www.w3.org/TR/2008/REC-xml-20081126/#NT-S
auto filterWSDefault = [this](SkUnichar ch) -> SkUnichar {
// Remove all newline chars.
if (ch == '\n') {
return -1;
}
// Convert tab chars to space.
if (ch == '\t') {
ch = ' ';
}
// Consolidate contiguous space chars and strip leading spaces (fPrevCharSpace
// starts off as true).
if (fPrevCharSpace && ch == ' ') {
return -1;
}
// TODO: Strip trailing WS? Doing this across chunks would require another buffering
// layer. In general, trailing WS should have no rendering side effects. Skipping
// for now.
return ch;
};
auto filterWSPreserve = [](SkUnichar ch) -> SkUnichar {
// Convert newline and tab chars to space.
if (ch == '\n' || ch == '\t') {
ch = ' ';
}
return ch;
};
const auto xmlSpace = ctx.getXmlSpace();
SkSTArray<128, char, true> filtered;
filtered.reserve_back(SkToInt(txt.size()));
const char* ch_ptr = txt.c_str();
const char* ch_end = ch_ptr + txt.size();
while (ch_ptr < ch_end) {
auto ch = SkUTF::NextUTF8(&ch_ptr, ch_end);
ch = (xmlSpace == SkSVGXmlSpace::kDefault)
? filterWSDefault(ch)
: filterWSPreserve(ch);
if (ch < 0) {
// invalid utf or char filtered out
continue;
}
char utf8_buf[SkUTF::kMaxBytesInUTF8Sequence];
filtered.push_back_n(SkToInt(SkUTF::ToUTF8(ch, utf8_buf)), utf8_buf);
fPrevCharSpace = (ch == ' ');
}
// TODO: absolute positioned chars => chunk breaks
// Stash paints for access from SkShaper callbacks.
@ -129,7 +186,7 @@ public:
const auto LTR = true;
// Initiate shaping: this will generate a series of runs via callbacks.
fShaper->shape(txt.c_str(), txt.size(), ResolveFont(ctx), LTR, SK_ScalarMax, this);
fShaper->shape(filtered.data(), filtered.size(), ResolveFont(ctx), LTR, SK_ScalarMax, this);
}
// Perform actual rendering for queued codepoints.
@ -214,6 +271,8 @@ private:
// cached for access from SkShaper callbacks.
const SkPaint* fCurrentFill;
const SkPaint* fCurrentStroke;
bool fPrevCharSpace = true; // WS filter state
};
void SkSVGTextContainer::appendChild(sk_sp<SkSVGNode> child) {
@ -229,10 +288,27 @@ void SkSVGTextContainer::appendChild(sk_sp<SkSVGNode> child) {
}
}
bool SkSVGTextContainer::onPrepareToRender(SkSVGRenderContext* ctx) const {
ctx->setXmlSpace(this->getXmlSpace());
return this->INHERITED::onPrepareToRender(ctx);
}
// https://www.w3.org/TR/SVG11/text.html#WhiteSpace
template <>
bool SkSVGAttributeParser::parse(SkSVGXmlSpace* xs) {
static constexpr std::tuple<const char*, SkSVGXmlSpace> gXmlSpaceMap[] = {
{"default" , SkSVGXmlSpace::kDefault },
{"preserve", SkSVGXmlSpace::kPreserve},
};
return this->parseEnumMap(gXmlSpaceMap, xs) && this->parseEOSToken();
}
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));
this->setY(SkSVGAttributeParser::parse<SkSVGLength>("y", name, value)) ||
this->setXmlSpace(SkSVGAttributeParser::parse<SkSVGXmlSpace>("xml:space", name, value));
}
void SkSVGText::onRender(const SkSVGRenderContext& ctx) const {