1 | /* |
2 | * Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> |
3 | * Copyright (C) 2004, 2005, 2008 Rob Buis <buis@kde.org> |
4 | * Copyright (C) 2005, 2007 Eric Seidel <eric@webkit.org> |
5 | * Copyright (C) 2009 Google, Inc. |
6 | * Copyright (C) 2009 Dirk Schulze <krit@webkit.org> |
7 | * Copyright (C) Research In Motion Limited 2010. All rights reserved. |
8 | * Copyright (C) 2009 Jeff Schiller <codedread@gmail.com> |
9 | * Copyright (C) 2011 Renata Hodovan <reni@webkit.org> |
10 | * Copyright (C) 2011 University of Szeged |
11 | * |
12 | * This library is free software; you can redistribute it and/or |
13 | * modify it under the terms of the GNU Library General Public |
14 | * License as published by the Free Software Foundation; either |
15 | * version 2 of the License, or (at your option) any later version. |
16 | * |
17 | * This library is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
20 | * Library General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU Library General Public License |
23 | * along with this library; see the file COPYING.LIB. If not, write to |
24 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
25 | * Boston, MA 02110-1301, USA. |
26 | */ |
27 | |
28 | #include "config.h" |
29 | #include "RenderSVGPath.h" |
30 | |
31 | #include "SVGPathElement.h" |
32 | #include "SVGSubpathData.h" |
33 | #include <wtf/IsoMallocInlines.h> |
34 | |
35 | namespace WebCore { |
36 | |
37 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSVGPath); |
38 | |
39 | RenderSVGPath::RenderSVGPath(SVGGraphicsElement& element, RenderStyle&& style) |
40 | : RenderSVGShape(element, WTFMove(style)) |
41 | { |
42 | } |
43 | |
44 | RenderSVGPath::~RenderSVGPath() = default; |
45 | |
46 | void RenderSVGPath::updateShapeFromElement() |
47 | { |
48 | RenderSVGShape::updateShapeFromElement(); |
49 | updateZeroLengthSubpaths(); |
50 | |
51 | m_strokeBoundingBox = calculateUpdatedStrokeBoundingBox(); |
52 | } |
53 | |
54 | FloatRect RenderSVGPath::calculateUpdatedStrokeBoundingBox() const |
55 | { |
56 | FloatRect strokeBoundingBox = m_strokeBoundingBox; |
57 | |
58 | if (style().svgStyle().hasStroke()) { |
59 | // FIXME: zero-length subpaths do not respect vector-effect = non-scaling-stroke. |
60 | float strokeWidth = this->strokeWidth(); |
61 | for (size_t i = 0; i < m_zeroLengthLinecapLocations.size(); ++i) |
62 | strokeBoundingBox.unite(zeroLengthSubpathRect(m_zeroLengthLinecapLocations[i], strokeWidth)); |
63 | } |
64 | |
65 | return strokeBoundingBox; |
66 | } |
67 | |
68 | static void useStrokeStyleToFill(GraphicsContext& context) |
69 | { |
70 | if (auto gradient = context.strokeGradient()) |
71 | context.setFillGradient(*gradient); |
72 | else if (Pattern* pattern = context.strokePattern()) |
73 | context.setFillPattern(*pattern); |
74 | else |
75 | context.setFillColor(context.strokeColor()); |
76 | } |
77 | |
78 | void RenderSVGPath::strokeShape(GraphicsContext& context) const |
79 | { |
80 | if (!style().hasVisibleStroke()) |
81 | return; |
82 | |
83 | RenderSVGShape::strokeShape(context); |
84 | |
85 | if (m_zeroLengthLinecapLocations.isEmpty()) |
86 | return; |
87 | |
88 | Path* usePath; |
89 | AffineTransform nonScalingTransform; |
90 | |
91 | if (hasNonScalingStroke()) |
92 | nonScalingTransform = nonScalingStrokeTransform(); |
93 | |
94 | GraphicsContextStateSaver stateSaver(context, true); |
95 | useStrokeStyleToFill(context); |
96 | for (size_t i = 0; i < m_zeroLengthLinecapLocations.size(); ++i) { |
97 | usePath = zeroLengthLinecapPath(m_zeroLengthLinecapLocations[i]); |
98 | if (hasNonScalingStroke()) |
99 | usePath = nonScalingStrokePath(usePath, nonScalingTransform); |
100 | context.fillPath(*usePath); |
101 | } |
102 | } |
103 | |
104 | bool RenderSVGPath::shapeDependentStrokeContains(const FloatPoint& point, PointCoordinateSpace pointCoordinateSpace) |
105 | { |
106 | if (RenderSVGShape::shapeDependentStrokeContains(point, pointCoordinateSpace)) |
107 | return true; |
108 | |
109 | for (size_t i = 0; i < m_zeroLengthLinecapLocations.size(); ++i) { |
110 | ASSERT(style().svgStyle().hasStroke()); |
111 | float strokeWidth = this->strokeWidth(); |
112 | if (style().capStyle() == SquareCap) { |
113 | if (zeroLengthSubpathRect(m_zeroLengthLinecapLocations[i], strokeWidth).contains(point)) |
114 | return true; |
115 | } else { |
116 | ASSERT(style().capStyle() == RoundCap); |
117 | FloatPoint radiusVector(point.x() - m_zeroLengthLinecapLocations[i].x(), point.y() - m_zeroLengthLinecapLocations[i].y()); |
118 | if (radiusVector.lengthSquared() < strokeWidth * strokeWidth * .25f) |
119 | return true; |
120 | } |
121 | } |
122 | return false; |
123 | } |
124 | |
125 | bool RenderSVGPath::shouldStrokeZeroLengthSubpath() const |
126 | { |
127 | // Spec(11.4): Any zero length subpath shall not be stroked if the "stroke-linecap" property has a value of butt |
128 | // but shall be stroked if the "stroke-linecap" property has a value of round or square |
129 | return style().svgStyle().hasStroke() && style().capStyle() != ButtCap; |
130 | } |
131 | |
132 | Path* RenderSVGPath::zeroLengthLinecapPath(const FloatPoint& linecapPosition) const |
133 | { |
134 | static NeverDestroyed<Path> tempPath; |
135 | |
136 | tempPath.get().clear(); |
137 | if (style().capStyle() == SquareCap) |
138 | tempPath.get().addRect(zeroLengthSubpathRect(linecapPosition, this->strokeWidth())); |
139 | else |
140 | tempPath.get().addEllipse(zeroLengthSubpathRect(linecapPosition, this->strokeWidth())); |
141 | |
142 | return &tempPath.get(); |
143 | } |
144 | |
145 | FloatRect RenderSVGPath::zeroLengthSubpathRect(const FloatPoint& linecapPosition, float strokeWidth) const |
146 | { |
147 | return FloatRect(linecapPosition.x() - strokeWidth / 2, linecapPosition.y() - strokeWidth / 2, strokeWidth, strokeWidth); |
148 | } |
149 | |
150 | void RenderSVGPath::updateZeroLengthSubpaths() |
151 | { |
152 | m_zeroLengthLinecapLocations.clear(); |
153 | |
154 | if (!strokeWidth() || !shouldStrokeZeroLengthSubpath()) |
155 | return; |
156 | |
157 | SVGSubpathData subpathData(m_zeroLengthLinecapLocations); |
158 | path().apply([&subpathData](const PathElement& pathElement) { |
159 | SVGSubpathData::updateFromPathElement(subpathData, pathElement); |
160 | }); |
161 | subpathData.pathIsDone(); |
162 | } |
163 | |
164 | bool RenderSVGPath::isRenderingDisabled() const |
165 | { |
166 | // For a polygon, polyline or path, rendering is disabled if there is no path data. |
167 | // No path data is possible in the case of a missing or empty 'd' or 'points' attribute. |
168 | return path().isEmpty(); |
169 | } |
170 | |
171 | } |
172 | |