| /* | |
| * Copyright 2016 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "modules/svg/include/SkSVGNode.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkM44.h" | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/pathops/SkPathOps.h" | |
| #include "include/private/SkAssert.h" | |
| #include "modules/svg/include/SkSVGRenderContext.h" | |
| #include <algorithm> | |
| #include <array> | |
| #include <cstddef> | |
| #include <optional> | |
| SkSVGNode::SkSVGNode(SkSVGTag t) : fTag(t) { | |
| // Uninherited presentation attributes need a non-null default value. | |
| fPresentationAttributes.fStopColor.set(SkSVGColor(SK_ColorBLACK)); | |
| fPresentationAttributes.fStopOpacity.set(SkSVGNumberType(1.0f)); | |
| fPresentationAttributes.fFloodColor.set(SkSVGColor(SK_ColorBLACK)); | |
| fPresentationAttributes.fFloodOpacity.set(SkSVGNumberType(1.0f)); | |
| fPresentationAttributes.fLightingColor.set(SkSVGColor(SK_ColorWHITE)); | |
| } | |
| SkSVGNode::~SkSVGNode() { } | |
| void SkSVGNode::render(const SkSVGRenderContext& ctx) const { | |
| SkSVGRenderContext localContext(ctx, this); | |
| if (this->onPrepareToRender(&localContext)) { | |
| this->onRender(localContext); | |
| } | |
| } | |
| bool SkSVGNode::asPaint(const SkSVGRenderContext& ctx, SkPaint* paint) const { | |
| SkSVGRenderContext localContext(ctx); | |
| return this->onPrepareToRender(&localContext) && this->onAsPaint(localContext, paint); | |
| } | |
| SkPath SkSVGNode::asPath(const SkSVGRenderContext& ctx) const { | |
| SkSVGRenderContext localContext(ctx); | |
| if (!this->onPrepareToRender(&localContext)) { | |
| return SkPath(); | |
| } | |
| SkPath path = this->onAsPath(localContext); | |
| if (const auto* clipPath = localContext.clipPath()) { | |
| // There is a clip-path present on the current node. | |
| if (auto result = Op(path, *clipPath, kIntersect_SkPathOp)) { | |
| path = *result; | |
| } | |
| } | |
| return path; | |
| } | |
| SkRect SkSVGNode::objectBoundingBox(const SkSVGRenderContext& ctx) const { | |
| return this->onObjectBoundingBox(ctx); | |
| } | |
| bool SkSVGNode::onPrepareToRender(SkSVGRenderContext* ctx) const { | |
| ctx->applyPresentationAttributes(fPresentationAttributes, | |
| this->hasChildren() ? 0 : SkSVGRenderContext::kLeaf); | |
| // visibility:hidden and display:none disable rendering. | |
| // TODO: if display is not a value (true when display="inherit"), we currently | |
| // ignore it. Eventually we should be able to add SkASSERT(display.isValue()). | |
| const auto visibility = ctx->presentationContext().fInherited.fVisibility->type(); | |
| const auto display = fPresentationAttributes.fDisplay; // display is uninherited | |
| return visibility != SkSVGVisibility::Type::kHidden && | |
| (!display.isValue() || *display != SkSVGDisplay::kNone); | |
| } | |
| void SkSVGNode::setAttribute(SkSVGAttribute attr, const SkSVGValue& v) { | |
| this->onSetAttribute(attr, v); | |
| } | |
| template <typename T> | |
| void SetInheritedByDefault(std::optional<T>& presentation_attribute, const T& value) { | |
| if (value.type() != T::Type::kInherit) { | |
| presentation_attribute.set(value); | |
| } else { | |
| // kInherited values are semantically equivalent to | |
| // the absence of a local presentation attribute. | |
| presentation_attribute.reset(); | |
| } | |
| } | |
| bool SkSVGNode::parseAndSetAttribute(const char* n, const char* v) { | |
| #define PARSE_AND_SET(svgName, attrName) \ | |
| this->set##attrName( \ | |
| SkSVGAttributeParser::parseProperty<decltype(fPresentationAttributes.f##attrName)>( \ | |
| svgName, n, v)) | |
| return PARSE_AND_SET( "clip-path" , ClipPath) | |
| || PARSE_AND_SET("clip-rule" , ClipRule) | |
| || PARSE_AND_SET("color" , Color) | |
| || PARSE_AND_SET("color-interpolation" , ColorInterpolation) | |
| || PARSE_AND_SET("color-interpolation-filters", ColorInterpolationFilters) | |
| || PARSE_AND_SET("display" , Display) | |
| || PARSE_AND_SET("fill" , Fill) | |
| || PARSE_AND_SET("fill-opacity" , FillOpacity) | |
| || PARSE_AND_SET("fill-rule" , FillRule) | |
| || PARSE_AND_SET("filter" , Filter) | |
| || PARSE_AND_SET("flood-color" , FloodColor) | |
| || PARSE_AND_SET("flood-opacity" , FloodOpacity) | |
| || PARSE_AND_SET("font-family" , FontFamily) | |
| || PARSE_AND_SET("font-size" , FontSize) | |
| || PARSE_AND_SET("font-style" , FontStyle) | |
| || PARSE_AND_SET("font-weight" , FontWeight) | |
| || PARSE_AND_SET("lighting-color" , LightingColor) | |
| || PARSE_AND_SET("mask" , Mask) | |
| || PARSE_AND_SET("opacity" , Opacity) | |
| || PARSE_AND_SET("stop-color" , StopColor) | |
| || PARSE_AND_SET("stop-opacity" , StopOpacity) | |
| || PARSE_AND_SET("stroke" , Stroke) | |
| || PARSE_AND_SET("stroke-dasharray" , StrokeDashArray) | |
| || PARSE_AND_SET("stroke-dashoffset" , StrokeDashOffset) | |
| || PARSE_AND_SET("stroke-linecap" , StrokeLineCap) | |
| || PARSE_AND_SET("stroke-linejoin" , StrokeLineJoin) | |
| || PARSE_AND_SET("stroke-miterlimit" , StrokeMiterLimit) | |
| || PARSE_AND_SET("stroke-opacity" , StrokeOpacity) | |
| || PARSE_AND_SET("stroke-width" , StrokeWidth) | |
| || PARSE_AND_SET("text-anchor" , TextAnchor) | |
| || PARSE_AND_SET("visibility" , Visibility); | |
| #undef PARSE_AND_SET | |
| } | |
| // https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute | |
| SkMatrix SkSVGNode::ComputeViewboxMatrix(const SkRect& viewBox, | |
| const SkRect& viewPort, | |
| SkSVGPreserveAspectRatio par) { | |
| if (viewBox.isEmpty() || viewPort.isEmpty()) { | |
| return SkMatrix::Scale(0, 0); | |
| } | |
| auto compute_scale = [&]() -> SkV2 { | |
| const auto sx = viewPort.width() / viewBox.width(), | |
| sy = viewPort.height() / viewBox.height(); | |
| if (par.fAlign == SkSVGPreserveAspectRatio::kNone) { | |
| // none -> anisotropic scaling, regardless of fScale | |
| return {sx, sy}; | |
| } | |
| // isotropic scaling | |
| const auto s = par.fScale == SkSVGPreserveAspectRatio::kMeet | |
| ? std::min(sx, sy) | |
| : std::max(sx, sy); | |
| return {s, s}; | |
| }; | |
| auto compute_trans = [&](const SkV2& scale) -> SkV2 { | |
| static constexpr auto gAlignCoeffs = std::to_array<float>({ | |
| 0.0f, // Min | |
| 0.5f, // Mid | |
| 1.0f // Max | |
| }); | |
| const size_t x_coeff = par.fAlign >> 0 & 0x03, | |
| y_coeff = par.fAlign >> 2 & 0x03; | |
| SkASSERT(x_coeff < std::size(gAlignCoeffs) && | |
| y_coeff < std::size(gAlignCoeffs)); | |
| const auto tx = -viewBox.x() * scale.x, | |
| ty = -viewBox.y() * scale.y, | |
| dx = viewPort.width() - viewBox.width() * scale.x, | |
| dy = viewPort.height() - viewBox.height() * scale.y; | |
| return { | |
| tx + dx * gAlignCoeffs[x_coeff], | |
| ty + dy * gAlignCoeffs[y_coeff] | |
| }; | |
| }; | |
| const auto s = compute_scale(), | |
| t = compute_trans(s); | |
| return SkMatrix::Translate(t.x, t.y) * | |
| SkMatrix::Scale(s.x, s.y); | |
| } |