OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> | 2 * Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> |
3 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org> | 3 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org> |
4 * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. | 4 * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. |
5 * Copyright (C) 2011 Dirk Schulze <krit@webkit.org> | 5 * Copyright (C) 2011 Dirk Schulze <krit@webkit.org> |
6 * | 6 * |
7 * This library is free software; you can redistribute it and/or | 7 * This library is free software; you can redistribute it and/or |
8 * modify it under the terms of the GNU Library General Public | 8 * modify it under the terms of the GNU Library General Public |
9 * License as published by the Free Software Foundation; either | 9 * License as published by the Free Software Foundation; either |
10 * version 2 of the License, or (at your option) any later version. | 10 * version 2 of the License, or (at your option) any later version. |
11 * | 11 * |
12 * This library is distributed in the hope that it will be useful, | 12 * This library is distributed in the hope that it will be useful, |
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 * Library General Public License for more details. | 15 * Library General Public License for more details. |
16 * | 16 * |
17 * You should have received a copy of the GNU Library General Public License | 17 * You should have received a copy of the GNU Library General Public License |
18 * along with this library; see the file COPYING.LIB. If not, write to | 18 * along with this library; see the file COPYING.LIB. If not, write to |
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
20 * Boston, MA 02110-1301, USA. | 20 * Boston, MA 02110-1301, USA. |
21 */ | 21 */ |
22 | 22 |
23 #include "core/layout/svg/LayoutSVGResourceClipper.h" | 23 #include "core/layout/svg/LayoutSVGResourceClipper.h" |
24 | 24 |
25 #include "core/SVGNames.h" | |
26 #include "core/dom/ElementTraversal.h" | 25 #include "core/dom/ElementTraversal.h" |
27 #include "core/layout/HitTestResult.h" | 26 #include "core/layout/HitTestResult.h" |
28 #include "core/layout/svg/SVGLayoutSupport.h" | 27 #include "core/layout/svg/SVGLayoutSupport.h" |
29 #include "core/paint/PaintInfo.h" | 28 #include "core/paint/PaintInfo.h" |
30 #include "core/svg/SVGGeometryElement.h" | 29 #include "core/svg/SVGGeometryElement.h" |
31 #include "core/svg/SVGUseElement.h" | 30 #include "core/svg/SVGUseElement.h" |
32 #include "platform/RuntimeEnabledFeatures.h" | |
33 #include "platform/graphics/paint/SkPictureBuilder.h" | 31 #include "platform/graphics/paint/SkPictureBuilder.h" |
34 #include "third_party/skia/include/core/SkPicture.h" | 32 #include "third_party/skia/include/core/SkPicture.h" |
35 #include "third_party/skia/include/pathops/SkPathOps.h" | 33 #include "third_party/skia/include/pathops/SkPathOps.h" |
36 | 34 |
37 namespace blink { | 35 namespace blink { |
38 | 36 |
| 37 namespace { |
| 38 |
| 39 bool requiresMask(const SVGElement& childElement) { |
| 40 // TODO(fs): This needs to special-case <use> in a way similar to |
| 41 // contributesToClip. (crbug.com/604677) |
| 42 LayoutObject* layoutObject = childElement.layoutObject(); |
| 43 DCHECK(layoutObject); |
| 44 // Only basic shapes or paths are supported for direct clipping. We need to |
| 45 // fallback to masking for texts. |
| 46 if (layoutObject->isSVGText()) |
| 47 return true; |
| 48 // Current shape in clip-path gets clipped too. Fallback to masking. |
| 49 return layoutObject->styleRef().clipPath(); |
| 50 } |
| 51 |
| 52 bool contributesToClip(const SVGGraphicsElement& element) { |
| 53 const LayoutObject* layoutObject = element.layoutObject(); |
| 54 if (!layoutObject) |
| 55 return false; |
| 56 const ComputedStyle& style = layoutObject->styleRef(); |
| 57 if (style.display() == EDisplay::None || |
| 58 style.visibility() != EVisibility::Visible) |
| 59 return false; |
| 60 // Only shapes, paths and texts are allowed for clipping. |
| 61 return layoutObject->isSVGShape() || layoutObject->isSVGText(); |
| 62 } |
| 63 |
| 64 bool contributesToClip(const SVGElement& element) { |
| 65 // <use> within <clipPath> have a restricted content model. |
| 66 // (https://drafts.fxtf.org/css-masking-1/#ClipPathElement) |
| 67 if (isSVGUseElement(element)) { |
| 68 const LayoutObject* useLayoutObject = element.layoutObject(); |
| 69 if (!useLayoutObject || |
| 70 useLayoutObject->styleRef().display() == EDisplay::None) |
| 71 return false; |
| 72 const SVGGraphicsElement* clippingElement = |
| 73 toSVGUseElement(element).visibleTargetGraphicsElementForClipping(); |
| 74 if (!clippingElement) |
| 75 return false; |
| 76 return contributesToClip(*clippingElement); |
| 77 } |
| 78 if (!element.isSVGGraphicsElement()) |
| 79 return false; |
| 80 return contributesToClip(toSVGGraphicsElement(element)); |
| 81 } |
| 82 |
| 83 void pathFromElement(const SVGElement& element, Path& clipPath) { |
| 84 if (isSVGGeometryElement(element)) |
| 85 toSVGGeometryElement(element).toClipPath(clipPath); |
| 86 else if (isSVGUseElement(element)) |
| 87 toSVGUseElement(element).toClipPath(clipPath); |
| 88 } |
| 89 |
| 90 } // namespace |
| 91 |
39 LayoutSVGResourceClipper::LayoutSVGResourceClipper(SVGClipPathElement* node) | 92 LayoutSVGResourceClipper::LayoutSVGResourceClipper(SVGClipPathElement* node) |
40 : LayoutSVGResourceContainer(node), m_inClipExpansion(false) {} | 93 : LayoutSVGResourceContainer(node), m_inClipExpansion(false) {} |
41 | 94 |
42 LayoutSVGResourceClipper::~LayoutSVGResourceClipper() {} | 95 LayoutSVGResourceClipper::~LayoutSVGResourceClipper() {} |
43 | 96 |
44 void LayoutSVGResourceClipper::removeAllClientsFromCache( | 97 void LayoutSVGResourceClipper::removeAllClientsFromCache( |
45 bool markForInvalidation) { | 98 bool markForInvalidation) { |
46 m_clipContentPath.clear(); | 99 m_clipContentPath.clear(); |
47 m_clipContentPicture.reset(); | 100 m_clipContentPicture.reset(); |
48 m_localClipBounds = FloatRect(); | 101 m_localClipBounds = FloatRect(); |
(...skipping 16 matching lines...) Expand all Loading... |
65 | 118 |
66 // If the current clip-path gets clipped itself, we have to fallback to | 119 // If the current clip-path gets clipped itself, we have to fallback to |
67 // masking. | 120 // masking. |
68 if (styleRef().clipPath()) | 121 if (styleRef().clipPath()) |
69 return false; | 122 return false; |
70 | 123 |
71 unsigned opCount = 0; | 124 unsigned opCount = 0; |
72 bool usingBuilder = false; | 125 bool usingBuilder = false; |
73 SkOpBuilder clipPathBuilder; | 126 SkOpBuilder clipPathBuilder; |
74 | 127 |
75 for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); | 128 for (const SVGElement& childElement : |
76 childElement; | 129 Traversal<SVGElement>::childrenOf(*element())) { |
77 childElement = Traversal<SVGElement>::nextSibling(*childElement)) { | 130 if (!contributesToClip(childElement)) |
78 LayoutObject* childLayoutObject = childElement->layoutObject(); | |
79 if (!childLayoutObject) | |
80 continue; | |
81 // Only shapes or paths are supported for direct clipping. We need to | |
82 // fallback to masking for texts. | |
83 if (childLayoutObject->isSVGText()) { | |
84 m_clipContentPath.clear(); | |
85 return false; | |
86 } | |
87 if (!childElement->isSVGGraphicsElement()) | |
88 continue; | 131 continue; |
89 | 132 |
90 const ComputedStyle* style = childLayoutObject->style(); | 133 if (requiresMask(childElement)) { |
91 if (!style || style->display() == EDisplay::None || | |
92 (style->visibility() != EVisibility::Visible && | |
93 !isSVGUseElement(*childElement))) | |
94 continue; | |
95 | |
96 // Current shape in clip-path gets clipped too. Fallback to masking. | |
97 if (style->clipPath()) { | |
98 m_clipContentPath.clear(); | 134 m_clipContentPath.clear(); |
99 return false; | 135 return false; |
100 } | 136 } |
101 | 137 |
102 // First clip shape. | 138 // First clip shape. |
103 if (m_clipContentPath.isEmpty()) { | 139 if (m_clipContentPath.isEmpty()) { |
104 if (isSVGGeometryElement(childElement)) | 140 pathFromElement(childElement, m_clipContentPath); |
105 toSVGGeometryElement(childElement)->toClipPath(m_clipContentPath); | |
106 else if (isSVGUseElement(childElement)) | |
107 toSVGUseElement(childElement)->toClipPath(m_clipContentPath); | |
108 | |
109 continue; | 141 continue; |
110 } | 142 } |
111 | 143 |
112 // Multiple shapes require PathOps. In some degenerate cases PathOps can | 144 // Multiple shapes require PathOps. In some degenerate cases PathOps can |
113 // exhibit quadratic behavior, so we cap the number of ops to a reasonable | 145 // exhibit quadratic behavior, so we cap the number of ops to a reasonable |
114 // count. | 146 // count. |
115 const unsigned kMaxOps = 42; | 147 const unsigned kMaxOps = 42; |
116 if (++opCount > kMaxOps) { | 148 if (++opCount > kMaxOps) { |
117 m_clipContentPath.clear(); | 149 m_clipContentPath.clear(); |
118 return false; | 150 return false; |
119 } | 151 } |
120 | 152 |
121 // Second clip shape => start using the builder. | 153 // Second clip shape => start using the builder. |
122 if (!usingBuilder) { | 154 if (!usingBuilder) { |
123 clipPathBuilder.add(m_clipContentPath.getSkPath(), kUnion_SkPathOp); | 155 clipPathBuilder.add(m_clipContentPath.getSkPath(), kUnion_SkPathOp); |
124 usingBuilder = true; | 156 usingBuilder = true; |
125 } | 157 } |
126 | 158 |
127 Path subPath; | 159 Path subPath; |
128 if (isSVGGeometryElement(childElement)) | 160 pathFromElement(childElement, subPath); |
129 toSVGGeometryElement(childElement)->toClipPath(subPath); | |
130 else if (isSVGUseElement(childElement)) | |
131 toSVGUseElement(childElement)->toClipPath(subPath); | |
132 | 161 |
133 clipPathBuilder.add(subPath.getSkPath(), kUnion_SkPathOp); | 162 clipPathBuilder.add(subPath.getSkPath(), kUnion_SkPathOp); |
134 } | 163 } |
135 | 164 |
136 if (usingBuilder) { | 165 if (usingBuilder) { |
137 SkPath resolvedPath; | 166 SkPath resolvedPath; |
138 clipPathBuilder.resolve(&resolvedPath); | 167 clipPathBuilder.resolve(&resolvedPath); |
139 m_clipContentPath = resolvedPath; | 168 m_clipContentPath = resolvedPath; |
140 } | 169 } |
141 | 170 |
(...skipping 28 matching lines...) Expand all Loading... |
170 if (m_clipContentPicture) | 199 if (m_clipContentPicture) |
171 return m_clipContentPicture; | 200 return m_clipContentPicture; |
172 | 201 |
173 // Using strokeBoundingBox (instead of visualRectInLocalSVGCoordinates) to | 202 // Using strokeBoundingBox (instead of visualRectInLocalSVGCoordinates) to |
174 // avoid the intersection with local clips/mask, which may yield incorrect | 203 // avoid the intersection with local clips/mask, which may yield incorrect |
175 // results when mixing objectBoundingBox and userSpaceOnUse units | 204 // results when mixing objectBoundingBox and userSpaceOnUse units |
176 // (http://crbug.com/294900). | 205 // (http://crbug.com/294900). |
177 FloatRect bounds = strokeBoundingBox(); | 206 FloatRect bounds = strokeBoundingBox(); |
178 | 207 |
179 SkPictureBuilder pictureBuilder(bounds, nullptr, nullptr); | 208 SkPictureBuilder pictureBuilder(bounds, nullptr, nullptr); |
| 209 // Switch to a paint behavior where all children of this <clipPath> will be |
| 210 // laid out using special constraints: |
| 211 // - fill-opacity/stroke-opacity/opacity set to 1 |
| 212 // - masker/filter not applied when laying out the children |
| 213 // - fill is set to the initial fill paint server (solid, black) |
| 214 // - stroke is set to the initial stroke paint server (none) |
| 215 PaintInfo info(pictureBuilder.context(), LayoutRect::infiniteIntRect(), |
| 216 PaintPhaseForeground, GlobalPaintNormalPhase, |
| 217 PaintLayerPaintingRenderingClipPathAsMask); |
180 | 218 |
181 for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); | 219 for (const SVGElement& childElement : |
182 childElement; | 220 Traversal<SVGElement>::childrenOf(*element())) { |
183 childElement = Traversal<SVGElement>::nextSibling(*childElement)) { | 221 if (!contributesToClip(childElement)) |
184 LayoutObject* layoutObject = childElement->layoutObject(); | |
185 if (!layoutObject) | |
186 continue; | 222 continue; |
187 | 223 // Use the LayoutObject of the direct child even if it is a <use>. In that |
188 const ComputedStyle* style = layoutObject->style(); | 224 // case, we will paint the targeted element indirectly. |
189 if (!style || style->display() == EDisplay::None || | 225 const LayoutObject* layoutObject = childElement.layoutObject(); |
190 (style->visibility() != EVisibility::Visible && | |
191 !isSVGUseElement(*childElement))) | |
192 continue; | |
193 | |
194 bool isUseElement = isSVGUseElement(*childElement); | |
195 if (isUseElement) { | |
196 const SVGGraphicsElement* clippingElement = | |
197 toSVGUseElement(*childElement) | |
198 .visibleTargetGraphicsElementForClipping(); | |
199 if (!clippingElement) | |
200 continue; | |
201 | |
202 layoutObject = clippingElement->layoutObject(); | |
203 if (!layoutObject) | |
204 continue; | |
205 } | |
206 | |
207 // Only shapes, paths and texts are allowed for clipping. | |
208 if (!layoutObject->isSVGShape() && !layoutObject->isSVGText()) | |
209 continue; | |
210 | |
211 if (isUseElement) | |
212 layoutObject = childElement->layoutObject(); | |
213 | |
214 // Switch to a paint behavior where all children of this <clipPath> will be | |
215 // laid out using special constraints: | |
216 // - fill-opacity/stroke-opacity/opacity set to 1 | |
217 // - masker/filter not applied when laying out the children | |
218 // - fill is set to the initial fill paint server (solid, black) | |
219 // - stroke is set to the initial stroke paint server (none) | |
220 PaintInfo info(pictureBuilder.context(), LayoutRect::infiniteIntRect(), | |
221 PaintPhaseForeground, GlobalPaintNormalPhase, | |
222 PaintLayerPaintingRenderingClipPathAsMask); | |
223 layoutObject->paint(info, IntPoint()); | 226 layoutObject->paint(info, IntPoint()); |
224 } | 227 } |
225 | 228 |
226 m_clipContentPicture = pictureBuilder.endRecording(); | 229 m_clipContentPicture = pictureBuilder.endRecording(); |
227 return m_clipContentPicture; | 230 return m_clipContentPicture; |
228 } | 231 } |
229 | 232 |
230 void LayoutSVGResourceClipper::calculateLocalClipBounds() { | 233 void LayoutSVGResourceClipper::calculateLocalClipBounds() { |
231 // This is a rough heuristic to appraise the clip size and doesn't consider | 234 // This is a rough heuristic to appraise the clip size and doesn't consider |
232 // clip on clip. | 235 // clip on clip. |
233 for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); | 236 for (const SVGElement& childElement : |
234 childElement; | 237 Traversal<SVGElement>::childrenOf(*element())) { |
235 childElement = Traversal<SVGElement>::nextSibling(*childElement)) { | 238 if (!contributesToClip(childElement)) |
236 LayoutObject* layoutObject = childElement->layoutObject(); | |
237 if (!layoutObject) | |
238 continue; | 239 continue; |
239 if (!layoutObject->isSVGShape() && !layoutObject->isSVGText() && | 240 const LayoutObject* layoutObject = childElement.layoutObject(); |
240 !isSVGUseElement(*childElement)) | |
241 continue; | |
242 const ComputedStyle* style = layoutObject->style(); | |
243 if (!style || style->display() == EDisplay::None || | |
244 (style->visibility() != EVisibility::Visible && | |
245 !isSVGUseElement(*childElement))) | |
246 continue; | |
247 if (isSVGUseElement(*childElement) && | |
248 !toSVGUseElement(*childElement) | |
249 .visibleTargetGraphicsElementForClipping()) | |
250 continue; | |
251 | |
252 m_localClipBounds.unite(layoutObject->localToSVGParentTransform().mapRect( | 241 m_localClipBounds.unite(layoutObject->localToSVGParentTransform().mapRect( |
253 layoutObject->visualRectInLocalSVGCoordinates())); | 242 layoutObject->visualRectInLocalSVGCoordinates())); |
254 } | 243 } |
255 } | 244 } |
256 | 245 |
257 bool LayoutSVGResourceClipper::hitTestClipContent( | 246 bool LayoutSVGResourceClipper::hitTestClipContent( |
258 const FloatRect& objectBoundingBox, | 247 const FloatRect& objectBoundingBox, |
259 const FloatPoint& nodeAtPoint) { | 248 const FloatPoint& nodeAtPoint) { |
260 FloatPoint point = nodeAtPoint; | 249 FloatPoint point = nodeAtPoint; |
261 if (!SVGLayoutSupport::pointInClippingArea(*this, point)) | 250 if (!SVGLayoutSupport::pointInClippingArea(*this, point)) |
262 return false; | 251 return false; |
263 | 252 |
264 if (clipPathUnits() == SVGUnitTypes::kSvgUnitTypeObjectboundingbox) { | 253 if (clipPathUnits() == SVGUnitTypes::kSvgUnitTypeObjectboundingbox) { |
265 AffineTransform transform; | 254 AffineTransform transform; |
266 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); | 255 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); |
267 transform.scaleNonUniform(objectBoundingBox.width(), | 256 transform.scaleNonUniform(objectBoundingBox.width(), |
268 objectBoundingBox.height()); | 257 objectBoundingBox.height()); |
269 point = transform.inverse().mapPoint(point); | 258 point = transform.inverse().mapPoint(point); |
270 } | 259 } |
271 | 260 |
272 AffineTransform animatedLocalTransform = | 261 AffineTransform animatedLocalTransform = |
273 toSVGClipPathElement(element())->calculateTransform( | 262 toSVGClipPathElement(element())->calculateTransform( |
274 SVGElement::IncludeMotionTransform); | 263 SVGElement::IncludeMotionTransform); |
275 if (!animatedLocalTransform.isInvertible()) | 264 if (!animatedLocalTransform.isInvertible()) |
276 return false; | 265 return false; |
277 | 266 |
278 point = animatedLocalTransform.inverse().mapPoint(point); | 267 point = animatedLocalTransform.inverse().mapPoint(point); |
279 | 268 |
280 for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); | 269 for (const SVGElement& childElement : |
281 childElement; | 270 Traversal<SVGElement>::childrenOf(*element())) { |
282 childElement = Traversal<SVGElement>::nextSibling(*childElement)) { | 271 if (!contributesToClip(childElement)) |
283 LayoutObject* layoutObject = childElement->layoutObject(); | |
284 if (!layoutObject) | |
285 continue; | |
286 if (!layoutObject->isSVGShape() && !layoutObject->isSVGText() && | |
287 !isSVGUseElement(*childElement)) | |
288 continue; | 272 continue; |
289 IntPoint hitPoint; | 273 IntPoint hitPoint; |
290 HitTestResult result(HitTestRequest::SVGClipContent, hitPoint); | 274 HitTestResult result(HitTestRequest::SVGClipContent, hitPoint); |
| 275 LayoutObject* layoutObject = childElement.layoutObject(); |
291 if (layoutObject->nodeAtFloatPoint(result, point, HitTestForeground)) | 276 if (layoutObject->nodeAtFloatPoint(result, point, HitTestForeground)) |
292 return true; | 277 return true; |
293 } | 278 } |
294 | |
295 return false; | 279 return false; |
296 } | 280 } |
297 | 281 |
298 FloatRect LayoutSVGResourceClipper::resourceBoundingBox( | 282 FloatRect LayoutSVGResourceClipper::resourceBoundingBox( |
299 const FloatRect& referenceBox) { | 283 const FloatRect& referenceBox) { |
300 // The resource has not been layouted yet. Return the reference box. | 284 // The resource has not been layouted yet. Return the reference box. |
301 if (selfNeedsLayout()) | 285 if (selfNeedsLayout()) |
302 return referenceBox; | 286 return referenceBox; |
303 | 287 |
304 if (m_localClipBounds.isEmpty()) | 288 if (m_localClipBounds.isEmpty()) |
305 calculateLocalClipBounds(); | 289 calculateLocalClipBounds(); |
306 | 290 |
307 AffineTransform transform = | 291 AffineTransform transform = |
308 toSVGClipPathElement(element())->calculateTransform( | 292 toSVGClipPathElement(element())->calculateTransform( |
309 SVGElement::IncludeMotionTransform); | 293 SVGElement::IncludeMotionTransform); |
310 if (clipPathUnits() == SVGUnitTypes::kSvgUnitTypeObjectboundingbox) { | 294 if (clipPathUnits() == SVGUnitTypes::kSvgUnitTypeObjectboundingbox) { |
311 transform.translate(referenceBox.x(), referenceBox.y()); | 295 transform.translate(referenceBox.x(), referenceBox.y()); |
312 transform.scaleNonUniform(referenceBox.width(), referenceBox.height()); | 296 transform.scaleNonUniform(referenceBox.width(), referenceBox.height()); |
313 } | 297 } |
314 return transform.mapRect(m_localClipBounds); | 298 return transform.mapRect(m_localClipBounds); |
315 } | 299 } |
316 | 300 |
317 } // namespace blink | 301 } // namespace blink |
OLD | NEW |