| /* |
| * 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 "experimental/svg/model/SkSVGPattern.h" |
| |
| #include "experimental/svg/model/SkSVGRenderContext.h" |
| #include "experimental/svg/model/SkSVGValue.h" |
| #include "include/core/SkPictureRecorder.h" |
| #include "include/core/SkShader.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(recorder.finishRecordingAsPicture()->makeShader( |
| SkTileMode::kRepeat, |
| SkTileMode::kRepeat, |
| patternTransform, |
| &tile)); |
| return true; |
| } |