|
1 /* |
|
2 * Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> |
|
3 * 2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org> |
|
4 * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. |
|
5 * |
|
6 * This library is free software; you can redistribute it and/or |
|
7 * modify it under the terms of the GNU Library General Public |
|
8 * License as published by the Free Software Foundation; either |
|
9 * version 2 of the License, or (at your option) any later version. |
|
10 * |
|
11 * This library is distributed in the hope that it will be useful, |
|
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 * Library General Public License for more details. |
|
15 * |
|
16 * You should have received a copy of the GNU Library General Public License |
|
17 * along with this library; see the file COPYING.LIB. If not, write to |
|
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
|
19 * Boston, MA 02110-1301, USA. |
|
20 * |
|
21 */ |
|
22 |
|
23 #include "config.h" |
|
24 #if ENABLE(SVG) |
|
25 #include "RenderSVGResourceClipper.h" |
|
26 |
|
27 #include "AffineTransform.h" |
|
28 #include "FloatRect.h" |
|
29 #include "GraphicsContext.h" |
|
30 #include "HitTestRequest.h" |
|
31 #include "HitTestResult.h" |
|
32 #include "ImageBuffer.h" |
|
33 #include "IntRect.h" |
|
34 #include "RenderObject.h" |
|
35 #include "RenderStyle.h" |
|
36 #include "RenderSVGResource.h" |
|
37 #include "SVGClipPathElement.h" |
|
38 #include "SVGElement.h" |
|
39 #include "SVGRenderSupport.h" |
|
40 #include "SVGStyledElement.h" |
|
41 #include "SVGStyledTransformableElement.h" |
|
42 #include "SVGUnitTypes.h" |
|
43 #include "SVGUseElement.h" |
|
44 #include <wtf/UnusedParam.h> |
|
45 |
|
46 namespace WebCore { |
|
47 |
|
48 RenderSVGResourceType RenderSVGResourceClipper::s_resourceType = ClipperResourceType; |
|
49 |
|
50 RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node) |
|
51 : RenderSVGResourceContainer(node) |
|
52 , m_invalidationBlocked(false) |
|
53 { |
|
54 } |
|
55 |
|
56 RenderSVGResourceClipper::~RenderSVGResourceClipper() |
|
57 { |
|
58 deleteAllValues(m_clipper); |
|
59 m_clipper.clear(); |
|
60 } |
|
61 |
|
62 void RenderSVGResourceClipper::invalidateClients() |
|
63 { |
|
64 if (m_invalidationBlocked) |
|
65 return; |
|
66 |
|
67 HashMap<RenderObject*, ClipperData*>::const_iterator end = m_clipper.end(); |
|
68 for (HashMap<RenderObject*, ClipperData*>::const_iterator it = m_clipper.begin(); it != end; ++it) |
|
69 markForLayoutAndResourceInvalidation(it->first); |
|
70 |
|
71 deleteAllValues(m_clipper); |
|
72 m_clipper.clear(); |
|
73 m_clipBoundaries = FloatRect(); |
|
74 } |
|
75 |
|
76 void RenderSVGResourceClipper::invalidateClient(RenderObject* object) |
|
77 { |
|
78 if (m_invalidationBlocked) |
|
79 return; |
|
80 |
|
81 ASSERT(object); |
|
82 if (!m_clipper.contains(object)) |
|
83 return; |
|
84 |
|
85 delete m_clipper.take(object); |
|
86 markForLayoutAndResourceInvalidation(object); |
|
87 } |
|
88 |
|
89 bool RenderSVGResourceClipper::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode) |
|
90 { |
|
91 ASSERT(object); |
|
92 ASSERT(context); |
|
93 #ifndef NDEBUG |
|
94 ASSERT(resourceMode == ApplyToDefaultMode); |
|
95 #else |
|
96 UNUSED_PARAM(resourceMode); |
|
97 #endif |
|
98 |
|
99 // Early exit, if this resource contains a child which references ourselves. |
|
100 if (containsCyclicReference(node())) |
|
101 return false; |
|
102 |
|
103 applyClippingToContext(object, object->objectBoundingBox(), object->repaintRectInLocalCoordinates(), context); |
|
104 return true; |
|
105 } |
|
106 |
|
107 bool RenderSVGResourceClipper::pathOnlyClipping(GraphicsContext* context, const FloatRect& objectBoundingBox) |
|
108 { |
|
109 // If the current clip-path gets clipped itself, we have to fallback to masking. |
|
110 if (!style()->svgStyle()->clipperResource().isEmpty()) |
|
111 return false; |
|
112 WindRule clipRule = RULE_NONZERO; |
|
113 Path clipPath = Path(); |
|
114 |
|
115 // If clip-path only contains one visible shape or path, we can use path-based clipping. Invisible |
|
116 // shapes don't affect the clipping and can be ignored. If clip-path contains more than one |
|
117 // visible shape, the additive clipping may not work, caused by the clipRule. EvenOdd |
|
118 // as well as NonZero can cause self-clipping of the elements. |
|
119 // See also http://www.w3.org/TR/SVG/painting.html#FillRuleProperty |
|
120 for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { |
|
121 RenderObject* renderer = childNode->renderer(); |
|
122 if (!renderer) |
|
123 continue; |
|
124 // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts. |
|
125 if (renderer->isSVGText()) |
|
126 return false; |
|
127 if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyledTransformable()) |
|
128 continue; |
|
129 SVGStyledTransformableElement* styled = static_cast<SVGStyledTransformableElement*>(childNode); |
|
130 RenderStyle* style = renderer->style(); |
|
131 if (!style || style->display() == NONE || style->visibility() != VISIBLE) |
|
132 continue; |
|
133 const SVGRenderStyle* svgStyle = style->svgStyle(); |
|
134 // Current shape in clip-path gets clipped too. Fallback to masking. |
|
135 if (!svgStyle->clipperResource().isEmpty()) |
|
136 return false; |
|
137 // Fallback to masking, if there is more than one clipping path. |
|
138 if (clipPath.isEmpty()) { |
|
139 clipPath = styled->toClipPath(); |
|
140 clipRule = svgStyle->clipRule(); |
|
141 } else |
|
142 return false; |
|
143 } |
|
144 // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary. |
|
145 if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { |
|
146 AffineTransform transform; |
|
147 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); |
|
148 transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); |
|
149 clipPath.transform(transform); |
|
150 } |
|
151 // The SVG specification wants us to clip everything, if clip-path doesn't have a child. |
|
152 if (clipPath.isEmpty()) |
|
153 clipPath.addRect(FloatRect()); |
|
154 context->beginPath(); |
|
155 context->addPath(clipPath); |
|
156 context->clipPath(clipRule); |
|
157 return true; |
|
158 } |
|
159 |
|
160 bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* object, const FloatRect& objectBoundingBox, |
|
161 const FloatRect& repaintRect, GraphicsContext* context) |
|
162 { |
|
163 if (!m_clipper.contains(object)) |
|
164 m_clipper.set(object, new ClipperData); |
|
165 |
|
166 ClipperData* clipperData = m_clipper.get(object); |
|
167 if (!clipperData->clipMaskImage) { |
|
168 if (pathOnlyClipping(context, objectBoundingBox)) |
|
169 return true; |
|
170 createClipData(clipperData, objectBoundingBox, repaintRect); |
|
171 } |
|
172 |
|
173 if (!clipperData->clipMaskImage) |
|
174 return false; |
|
175 |
|
176 context->clipToImageBuffer(repaintRect, clipperData->clipMaskImage.get()); |
|
177 return true; |
|
178 } |
|
179 |
|
180 bool RenderSVGResourceClipper::createClipData(ClipperData* clipperData, const FloatRect& objectBoundingBox, const FloatRect& repaintRect) |
|
181 { |
|
182 IntRect clipMaskRect = enclosingIntRect(repaintRect); |
|
183 clipperData->clipMaskImage = ImageBuffer::create(clipMaskRect.size()); |
|
184 if (!clipperData->clipMaskImage) |
|
185 return false; |
|
186 |
|
187 GraphicsContext* maskContext = clipperData->clipMaskImage->context(); |
|
188 ASSERT(maskContext); |
|
189 |
|
190 maskContext->save(); |
|
191 maskContext->translate(-repaintRect.x(), -repaintRect.y()); |
|
192 |
|
193 // clipPath can also be clipped by another clipPath. |
|
194 if (RenderSVGResourceClipper* clipper = getRenderSVGResourceById<RenderSVGResourceClipper>(this->document(), style()->svgStyle()->clipperResource())) { |
|
195 if (!clipper->applyClippingToContext(this, objectBoundingBox, repaintRect, maskContext)) { |
|
196 maskContext->restore(); |
|
197 return false; |
|
198 } |
|
199 } |
|
200 |
|
201 SVGClipPathElement* clipPath = static_cast<SVGClipPathElement*>(node()); |
|
202 if (clipPath->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { |
|
203 maskContext->translate(objectBoundingBox.x(), objectBoundingBox.y()); |
|
204 maskContext->scale(objectBoundingBox.size()); |
|
205 } |
|
206 |
|
207 // Draw all clipPath children into a global mask. |
|
208 for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { |
|
209 RenderObject* renderer = childNode->renderer(); |
|
210 if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer) |
|
211 continue; |
|
212 RenderStyle* style = renderer->style(); |
|
213 if (!style || style->display() == NONE || style->visibility() != VISIBLE) |
|
214 continue; |
|
215 |
|
216 WindRule newClipRule = style->svgStyle()->clipRule(); |
|
217 bool isUseElement = renderer->isSVGShadowTreeRootContainer(); |
|
218 if (isUseElement) { |
|
219 SVGUseElement* useElement = static_cast<SVGUseElement*>(childNode); |
|
220 renderer = useElement->rendererClipChild(); |
|
221 if (!renderer) |
|
222 continue; |
|
223 if (!useElement->hasAttribute(SVGNames::clip_ruleAttr)) |
|
224 newClipRule = renderer->style()->svgStyle()->clipRule(); |
|
225 } |
|
226 |
|
227 // Only shapes, paths and texts are allowed for clipping. |
|
228 if (!renderer->isRenderPath() && !renderer->isSVGText()) |
|
229 continue; |
|
230 |
|
231 // Save the old RenderStyle of the current object for restoring after drawing |
|
232 // it to the MaskImage. The new intermediate RenderStyle needs to inherit from |
|
233 // the old one. |
|
234 RefPtr<RenderStyle> oldRenderStyle = renderer->style(); |
|
235 RefPtr<RenderStyle> newRenderStyle = RenderStyle::clone(oldRenderStyle.get()); |
|
236 SVGRenderStyle* svgStyle = newRenderStyle.get()->accessSVGStyle(); |
|
237 svgStyle->setFillPaint(SVGPaint::defaultFill()); |
|
238 svgStyle->setStrokePaint(SVGPaint::defaultStroke()); |
|
239 svgStyle->setFillRule(newClipRule); |
|
240 newRenderStyle.get()->setOpacity(1.0f); |
|
241 svgStyle->setFillOpacity(1.0f); |
|
242 svgStyle->setStrokeOpacity(1.0f); |
|
243 svgStyle->setFilterResource(String()); |
|
244 svgStyle->setMaskerResource(String()); |
|
245 |
|
246 // The setStyle() call results in a styleDidChange() call, which in turn invalidations the resources. |
|
247 // As we're mutating the resource on purpose, block updates until we've resetted the style again. |
|
248 m_invalidationBlocked = true; |
|
249 renderer->setStyle(newRenderStyle.release()); |
|
250 |
|
251 // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule. |
|
252 // We hsve to pass the <use> renderer itself to renderSubtreeToImage() to apply it's x/y/transform/etc. values when rendering. |
|
253 // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overriden above. |
|
254 SVGRenderSupport::renderSubtreeToImage(clipperData->clipMaskImage.get(), isUseElement ? childNode->renderer() : renderer); |
|
255 |
|
256 renderer->setStyle(oldRenderStyle.release()); |
|
257 m_invalidationBlocked = false; |
|
258 } |
|
259 |
|
260 maskContext->restore(); |
|
261 |
|
262 return true; |
|
263 } |
|
264 |
|
265 void RenderSVGResourceClipper::calculateClipContentRepaintRect() |
|
266 { |
|
267 // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip. |
|
268 for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { |
|
269 RenderObject* renderer = childNode->renderer(); |
|
270 if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer) |
|
271 continue; |
|
272 if (!renderer->isRenderPath() && !renderer->isSVGText() && !renderer->isSVGShadowTreeRootContainer()) |
|
273 continue; |
|
274 RenderStyle* style = renderer->style(); |
|
275 if (!style || style->display() == NONE || style->visibility() != VISIBLE) |
|
276 continue; |
|
277 m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->repaintRectInLocalCoordinates())); |
|
278 } |
|
279 } |
|
280 |
|
281 bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint) |
|
282 { |
|
283 // FIXME: We should be able to check whether m_clipper.contains(object) - this doesn't work at the moment |
|
284 // as resourceBoundingBox() has already created ClipperData, even if applyResource() returned false. |
|
285 // Early exit, if this resource contains a child which references ourselves. |
|
286 if (containsCyclicReference(node())) |
|
287 return false; |
|
288 |
|
289 FloatPoint point = nodeAtPoint; |
|
290 if (!SVGRenderSupport::pointInClippingArea(this, point)) |
|
291 return false; |
|
292 |
|
293 if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { |
|
294 AffineTransform transform; |
|
295 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); |
|
296 transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); |
|
297 point = transform.inverse().mapPoint(point); |
|
298 } |
|
299 |
|
300 for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { |
|
301 RenderObject* renderer = childNode->renderer(); |
|
302 if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer) |
|
303 continue; |
|
304 if (!renderer->isRenderPath() && !renderer->isSVGText() && !renderer->isSVGShadowTreeRootContainer()) |
|
305 continue; |
|
306 IntPoint hitPoint; |
|
307 HitTestResult result(hitPoint); |
|
308 if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent), result, point, HitTestForeground)) |
|
309 return true; |
|
310 } |
|
311 |
|
312 return false; |
|
313 } |
|
314 |
|
315 bool RenderSVGResourceClipper::childElementReferencesResource(const SVGRenderStyle* style, const String& referenceId) const |
|
316 { |
|
317 if (!style->hasClipper()) |
|
318 return false; |
|
319 |
|
320 return style->clipperResource() == referenceId; |
|
321 } |
|
322 |
|
323 FloatRect RenderSVGResourceClipper::resourceBoundingBox(RenderObject* object) |
|
324 { |
|
325 // Resource was not layouted yet. Give back the boundingBox of the object. |
|
326 if (selfNeedsLayout()) |
|
327 return object->objectBoundingBox(); |
|
328 |
|
329 if (m_clipBoundaries.isEmpty()) |
|
330 calculateClipContentRepaintRect(); |
|
331 |
|
332 if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { |
|
333 FloatRect objectBoundingBox = object->objectBoundingBox(); |
|
334 AffineTransform transform; |
|
335 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); |
|
336 transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); |
|
337 return transform.mapRect(m_clipBoundaries); |
|
338 } |
|
339 |
|
340 return m_clipBoundaries; |
|
341 } |
|
342 |
|
343 } |
|
344 |
|
345 #endif // ENABLE(SVG) |