You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

171 lines
5.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkSVGPattern.h"
#include "SkPictureRecorder.h"
#include "SkShader.h"
#include "SkSVGRenderContext.h"
#include "SkSVGValue.h"
SkSVGPattern::SkSVGPattern() : INHERITED(SkSVGTag::kPattern) {}
void SkSVGPattern::setX(const SkSVGLength& x) {
fAttributes.fX.set(x);
}
void SkSVGPattern::setY(const SkSVGLength& y) {
fAttributes.fY.set(y);
}
void SkSVGPattern::setWidth(const SkSVGLength& w) {
fAttributes.fWidth.set(w);
}
void SkSVGPattern::setHeight(const SkSVGLength& h) {
fAttributes.fHeight.set(h);
}
void SkSVGPattern::setHref(const SkSVGStringType& href) {
fHref = std::move(href);
}
void SkSVGPattern::setPatternTransform(const SkSVGTransformType& patternTransform) {
fAttributes.fPatternTransform.set(patternTransform);
}
void SkSVGPattern::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::kWidth:
if (const auto* w = v.as<SkSVGLengthValue>()) {
this->setWidth(*w);
}
break;
case SkSVGAttribute::kHeight:
if (const auto* h = v.as<SkSVGLengthValue>()) {
this->setHeight(*h);
}
break;
case SkSVGAttribute::kHref:
if (const auto* href = v.as<SkSVGStringValue>()) {
this->setHref(*href);
}
break;
case SkSVGAttribute::kPatternTransform:
if (const auto* t = v.as<SkSVGTransformValue>()) {
this->setPatternTransform(*t);
}
break;
default:
this->INHERITED::onSetAttribute(attr, v);
}
}
const SkSVGPattern* SkSVGPattern::hrefTarget(const SkSVGRenderContext& ctx) const {
if (fHref.value().isEmpty()) {
return nullptr;
}
const auto* href = ctx.findNodeById(fHref);
if (!href || href->tag() != SkSVGTag::kPattern) {
return nullptr;
}
return static_cast<const SkSVGPattern*>(href);
}
template <typename T>
bool inherit_if_needed(const SkTLazy<T>& src, SkTLazy<T>& dst) {
if (!dst.isValid()) {
dst = src;
return true;
}
return false;
}
/* https://www.w3.org/TR/SVG/pservers.html#PatternElementHrefAttribute
*
* Any attributes which are defined on the referenced element which are not defined on this element
* are inherited by this element. If this element has no children, and the referenced element does
* (possibly due to its own xlink:href attribute), then this element inherits the children from
* the referenced element. Inheritance can be indirect to an arbitrary level; thus, if the
* referenced element inherits attributes or children due to its own xlink:href attribute, then
* the current element can inherit those attributes or children.
*/
const SkSVGPattern* SkSVGPattern::resolveHref(const SkSVGRenderContext& ctx,
PatternAttributes* attrs) const {
const SkSVGPattern *currentNode = this,
*contentNode = this;
do {
// Bitwise OR to avoid short-circuiting.
const bool didInherit =
inherit_if_needed(currentNode->fAttributes.fX , attrs->fX) |
inherit_if_needed(currentNode->fAttributes.fY , attrs->fY) |
inherit_if_needed(currentNode->fAttributes.fWidth , attrs->fWidth) |
inherit_if_needed(currentNode->fAttributes.fHeight , attrs->fHeight) |
inherit_if_needed(currentNode->fAttributes.fPatternTransform, attrs->fPatternTransform);
if (!contentNode->hasChildren()) {
contentNode = currentNode;
}
if (contentNode->hasChildren() && !didInherit) {
// All attributes have been resolved, and a valid content node has been found.
// We can terminate the href chain early.
break;
}
// TODO: reference loop mitigation.
currentNode = currentNode->hrefTarget(ctx);
} while (currentNode);
return contentNode;
}
bool SkSVGPattern::onAsPaint(const SkSVGRenderContext& ctx, SkPaint* paint) const {
PatternAttributes attrs;
const auto* contentNode = this->resolveHref(ctx, &attrs);
const auto tile = ctx.lengthContext().resolveRect(
attrs.fX.isValid() ? *attrs.fX.get() : SkSVGLength(0),
attrs.fY.isValid() ? *attrs.fY.get() : SkSVGLength(0),
attrs.fWidth.isValid() ? *attrs.fWidth.get() : SkSVGLength(0),
attrs.fHeight.isValid() ? *attrs.fHeight.get() : SkSVGLength(0));
if (tile.isEmpty()) {
return false;
}
const SkMatrix* patternTransform = attrs.fPatternTransform.isValid()
? &attrs.fPatternTransform.get()->value()
: nullptr;
SkPictureRecorder recorder;
SkSVGRenderContext recordingContext(ctx, recorder.beginRecording(tile));
// Cannot call into INHERITED:: because SkSVGHiddenContainer skips rendering.
contentNode->SkSVGContainer::onRender(recordingContext);
paint->setShader(SkShader::MakePictureShader(recorder.finishRecordingAsPicture(),
SkShader::kRepeat_TileMode,
SkShader::kRepeat_TileMode,
patternTransform,
&tile));
return true;
}