fmalita | 6ceef3d | 2016-07-26 18:46:34 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2016 Google Inc. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license that can be |
| 5 | * found in the LICENSE file. |
| 6 | */ |
| 7 | |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 8 | #include "include/core/SkCanvas.h" |
| 9 | #include "include/core/SkMatrix.h" |
| 10 | #include "include/pathops/SkPathOps.h" |
Mike Klein | 8aa0edf | 2020-10-16 11:04:18 -0500 | [diff] [blame] | 11 | #include "include/private/SkTPin.h" |
Florin Malita | b341810 | 2020-10-15 18:10:29 -0400 | [diff] [blame] | 12 | #include "modules/svg/include/SkSVGNode.h" |
| 13 | #include "modules/svg/include/SkSVGRenderContext.h" |
| 14 | #include "modules/svg/include/SkSVGValue.h" |
Mike Klein | c0bd9f9 | 2019-04-23 12:05:21 -0500 | [diff] [blame] | 15 | #include "src/core/SkTLazy.h" |
fmalita | 6ceef3d | 2016-07-26 18:46:34 -0700 | [diff] [blame] | 16 | |
Tyler Denniston | 04e03bc | 2020-12-09 14:16:25 -0500 | [diff] [blame] | 17 | SkSVGNode::SkSVGNode(SkSVGTag t) : fTag(t) { |
| 18 | // Uninherited presentation attributes need a non-null default value. |
| 19 | fPresentationAttributes.fStopColor.set(SkSVGColor(SK_ColorBLACK)); |
| 20 | fPresentationAttributes.fStopOpacity.set(SkSVGNumberType(1.0f)); |
Tyler Denniston | 8ed0443 | 2020-12-10 15:51:04 -0500 | [diff] [blame] | 21 | fPresentationAttributes.fFloodColor.set(SkSVGColor(SK_ColorBLACK)); |
| 22 | fPresentationAttributes.fFloodOpacity.set(SkSVGNumberType(1.0f)); |
Tyler Denniston | 32b3089 | 2021-01-26 14:36:32 -0500 | [diff] [blame] | 23 | fPresentationAttributes.fLightingColor.set(SkSVGColor(SK_ColorWHITE)); |
Tyler Denniston | 04e03bc | 2020-12-09 14:16:25 -0500 | [diff] [blame] | 24 | } |
fmalita | 6ceef3d | 2016-07-26 18:46:34 -0700 | [diff] [blame] | 25 | |
| 26 | SkSVGNode::~SkSVGNode() { } |
| 27 | |
fmalita | 397a517 | 2016-08-08 11:38:55 -0700 | [diff] [blame] | 28 | void SkSVGNode::render(const SkSVGRenderContext& ctx) const { |
Tyler Denniston | 53281c7 | 2020-10-22 15:54:24 -0400 | [diff] [blame] | 29 | SkSVGRenderContext localContext(ctx, this); |
fmalita | 6ceef3d | 2016-07-26 18:46:34 -0700 | [diff] [blame] | 30 | |
fmalita | 397a517 | 2016-08-08 11:38:55 -0700 | [diff] [blame] | 31 | if (this->onPrepareToRender(&localContext)) { |
| 32 | this->onRender(localContext); |
fmalita | 6ceef3d | 2016-07-26 18:46:34 -0700 | [diff] [blame] | 33 | } |
fmalita | 397a517 | 2016-08-08 11:38:55 -0700 | [diff] [blame] | 34 | } |
fmalita | 6ceef3d | 2016-07-26 18:46:34 -0700 | [diff] [blame] | 35 | |
fmalita | 28d5b72 | 2016-09-12 17:06:47 -0700 | [diff] [blame] | 36 | bool SkSVGNode::asPaint(const SkSVGRenderContext& ctx, SkPaint* paint) const { |
| 37 | SkSVGRenderContext localContext(ctx); |
| 38 | |
| 39 | return this->onPrepareToRender(&localContext) && this->onAsPaint(localContext, paint); |
| 40 | } |
| 41 | |
Florin Malita | ce8840e | 2016-12-08 09:26:47 -0500 | [diff] [blame] | 42 | SkPath SkSVGNode::asPath(const SkSVGRenderContext& ctx) const { |
| 43 | SkSVGRenderContext localContext(ctx); |
Florin Malita | 7d52988 | 2016-12-08 16:04:24 -0500 | [diff] [blame] | 44 | if (!this->onPrepareToRender(&localContext)) { |
| 45 | return SkPath(); |
| 46 | } |
| 47 | |
| 48 | SkPath path = this->onAsPath(localContext); |
| 49 | |
| 50 | if (const auto* clipPath = localContext.clipPath()) { |
| 51 | // There is a clip-path present on the current node. |
| 52 | Op(path, *clipPath, kIntersect_SkPathOp, &path); |
| 53 | } |
| 54 | |
| 55 | return path; |
Florin Malita | ce8840e | 2016-12-08 09:26:47 -0500 | [diff] [blame] | 56 | } |
| 57 | |
Tyler Denniston | f548a02 | 2020-10-27 15:02:02 -0400 | [diff] [blame] | 58 | SkRect SkSVGNode::objectBoundingBox(const SkSVGRenderContext& ctx) const { |
| 59 | return this->onObjectBoundingBox(ctx); |
Tyler Denniston | 53281c7 | 2020-10-22 15:54:24 -0400 | [diff] [blame] | 60 | } |
| 61 | |
fmalita | 397a517 | 2016-08-08 11:38:55 -0700 | [diff] [blame] | 62 | bool SkSVGNode::onPrepareToRender(SkSVGRenderContext* ctx) const { |
fmalita | bef51c2 | 2016-09-20 15:45:57 -0700 | [diff] [blame] | 63 | ctx->applyPresentationAttributes(fPresentationAttributes, |
| 64 | this->hasChildren() ? 0 : SkSVGRenderContext::kLeaf); |
Florin Malita | ffe6ae4 | 2017-10-12 11:33:28 -0400 | [diff] [blame] | 65 | |
| 66 | // visibility:hidden disables rendering |
John Stiles | a008b0f | 2020-08-16 08:48:02 -0400 | [diff] [blame] | 67 | const auto visibility = ctx->presentationContext().fInherited.fVisibility->type(); |
Florin Malita | ffe6ae4 | 2017-10-12 11:33:28 -0400 | [diff] [blame] | 68 | return visibility != SkSVGVisibility::Type::kHidden; |
fmalita | 6ceef3d | 2016-07-26 18:46:34 -0700 | [diff] [blame] | 69 | } |
| 70 | |
Florin Malita | f4403e7 | 2020-04-10 14:14:04 +0000 | [diff] [blame] | 71 | void SkSVGNode::setAttribute(SkSVGAttribute attr, const SkSVGValue& v) { |
fmalita | 6ceef3d | 2016-07-26 18:46:34 -0700 | [diff] [blame] | 72 | this->onSetAttribute(attr, v); |
| 73 | } |
| 74 | |
Florin Malita | 0929425 | 2020-04-09 08:54:29 -0400 | [diff] [blame] | 75 | template <typename T> |
| 76 | void SetInheritedByDefault(SkTLazy<T>& presentation_attribute, const T& value) { |
| 77 | if (value.type() != T::Type::kInherit) { |
| 78 | presentation_attribute.set(value); |
| 79 | } else { |
| 80 | // kInherited values are semantically equivalent to |
| 81 | // the absence of a local presentation attribute. |
| 82 | presentation_attribute.reset(); |
| 83 | } |
| 84 | } |
| 85 | |
Florin Malita | 8c42567 | 2020-11-06 13:49:37 -0500 | [diff] [blame] | 86 | bool SkSVGNode::parseAndSetAttribute(const char* n, const char* v) { |
Tyler Denniston | 79832e3 | 2020-11-18 09:34:07 -0500 | [diff] [blame] | 87 | #define PARSE_AND_SET(svgName, attrName) \ |
| 88 | this->set##attrName( \ |
| 89 | SkSVGAttributeParser::parseProperty<decltype(fPresentationAttributes.f##attrName)>( \ |
| 90 | svgName, n, v)) |
| 91 | |
Tyler Denniston | 7bb85db | 2021-01-13 12:08:04 -0500 | [diff] [blame] | 92 | return PARSE_AND_SET( "clip-path" , ClipPath) |
| 93 | || PARSE_AND_SET("clip-rule" , ClipRule) |
| 94 | || PARSE_AND_SET("color" , Color) |
Florin Malita | 73d57bf | 2021-01-15 08:58:09 -0500 | [diff] [blame] | 95 | || PARSE_AND_SET("color-interpolation" , ColorInterpolation) |
Tyler Denniston | 7bb85db | 2021-01-13 12:08:04 -0500 | [diff] [blame] | 96 | || PARSE_AND_SET("color-interpolation-filters", ColorInterpolationFilters) |
| 97 | || PARSE_AND_SET("fill" , Fill) |
| 98 | || PARSE_AND_SET("fill-opacity" , FillOpacity) |
| 99 | || PARSE_AND_SET("fill-rule" , FillRule) |
| 100 | || PARSE_AND_SET("filter" , Filter) |
| 101 | || PARSE_AND_SET("flood-color" , FloodColor) |
| 102 | || PARSE_AND_SET("flood-opacity" , FloodOpacity) |
| 103 | || PARSE_AND_SET("font-family" , FontFamily) |
| 104 | || PARSE_AND_SET("font-size" , FontSize) |
| 105 | || PARSE_AND_SET("font-style" , FontStyle) |
| 106 | || PARSE_AND_SET("font-weight" , FontWeight) |
Tyler Denniston | 32b3089 | 2021-01-26 14:36:32 -0500 | [diff] [blame] | 107 | || PARSE_AND_SET("lighting-color" , LightingColor) |
Tyler Denniston | 7bb85db | 2021-01-13 12:08:04 -0500 | [diff] [blame] | 108 | || PARSE_AND_SET("mask" , Mask) |
| 109 | || PARSE_AND_SET("opacity" , Opacity) |
| 110 | || PARSE_AND_SET("stop-color" , StopColor) |
| 111 | || PARSE_AND_SET("stop-opacity" , StopOpacity) |
| 112 | || PARSE_AND_SET("stroke" , Stroke) |
| 113 | || PARSE_AND_SET("stroke-dasharray" , StrokeDashArray) |
| 114 | || PARSE_AND_SET("stroke-dashoffset" , StrokeDashOffset) |
| 115 | || PARSE_AND_SET("stroke-linecap" , StrokeLineCap) |
| 116 | || PARSE_AND_SET("stroke-linejoin" , StrokeLineJoin) |
| 117 | || PARSE_AND_SET("stroke-miterlimit" , StrokeMiterLimit) |
| 118 | || PARSE_AND_SET("stroke-opacity" , StrokeOpacity) |
| 119 | || PARSE_AND_SET("stroke-width" , StrokeWidth) |
| 120 | || PARSE_AND_SET("text-anchor" , TextAnchor) |
| 121 | || PARSE_AND_SET("visibility" , Visibility); |
Tyler Denniston | 79832e3 | 2020-11-18 09:34:07 -0500 | [diff] [blame] | 122 | |
| 123 | #undef PARSE_AND_SET |
Tyler Denniston | 5715499 | 2020-11-04 16:08:30 -0500 | [diff] [blame] | 124 | } |
Tyler Denniston | 1f4cd07 | 2021-02-05 09:08:33 -0500 | [diff] [blame^] | 125 | |
| 126 | // https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute |
| 127 | SkMatrix SkSVGNode::ComputeViewboxMatrix(const SkRect& viewBox, |
| 128 | const SkRect& viewPort, |
| 129 | SkSVGPreserveAspectRatio par) { |
| 130 | SkASSERT(!viewBox.isEmpty()); |
| 131 | SkASSERT(!viewPort.isEmpty()); |
| 132 | |
| 133 | auto compute_scale = [&]() -> SkV2 { |
| 134 | const auto sx = viewPort.width() / viewBox.width(), |
| 135 | sy = viewPort.height() / viewBox.height(); |
| 136 | |
| 137 | if (par.fAlign == SkSVGPreserveAspectRatio::kNone) { |
| 138 | // none -> anisotropic scaling, regardless of fScale |
| 139 | return {sx, sy}; |
| 140 | } |
| 141 | |
| 142 | // isotropic scaling |
| 143 | const auto s = par.fScale == SkSVGPreserveAspectRatio::kMeet |
| 144 | ? std::min(sx, sy) |
| 145 | : std::max(sx, sy); |
| 146 | return {s, s}; |
| 147 | }; |
| 148 | |
| 149 | auto compute_trans = [&](const SkV2& scale) -> SkV2 { |
| 150 | static constexpr float gAlignCoeffs[] = { |
| 151 | 0.0f, // Min |
| 152 | 0.5f, // Mid |
| 153 | 1.0f // Max |
| 154 | }; |
| 155 | |
| 156 | const size_t x_coeff = par.fAlign >> 0 & 0x03, |
| 157 | y_coeff = par.fAlign >> 2 & 0x03; |
| 158 | |
| 159 | SkASSERT(x_coeff < SK_ARRAY_COUNT(gAlignCoeffs) && |
| 160 | y_coeff < SK_ARRAY_COUNT(gAlignCoeffs)); |
| 161 | |
| 162 | const auto tx = -viewBox.x() * scale.x, |
| 163 | ty = -viewBox.y() * scale.y, |
| 164 | dx = viewPort.width() - viewBox.width() * scale.x, |
| 165 | dy = viewPort.height() - viewBox.height() * scale.y; |
| 166 | |
| 167 | return { |
| 168 | tx + dx * gAlignCoeffs[x_coeff], |
| 169 | ty + dy * gAlignCoeffs[y_coeff] |
| 170 | }; |
| 171 | }; |
| 172 | |
| 173 | const auto s = compute_scale(), |
| 174 | t = compute_trans(s); |
| 175 | |
| 176 | return SkMatrix::Translate(t.x, t.y) * |
| 177 | SkMatrix::Scale(s.x, s.y); |
| 178 | } |