OLD | NEW |
| (Empty) |
1 /* | |
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> | |
4 * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. | |
5 * Copyright (C) 2011 Dirk Schulze <krit@webkit.org> | |
6 * | |
7 * This library is free software; you can redistribute it and/or | |
8 * modify it under the terms of the GNU Library General Public | |
9 * License as published by the Free Software Foundation; either | |
10 * version 2 of the License, or (at your option) any later version. | |
11 * | |
12 * This library is distributed in the hope that it will be useful, | |
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Library General Public License for more details. | |
16 * | |
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 | |
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
20 * Boston, MA 02110-1301, USA. | |
21 */ | |
22 | |
23 #include "config.h" | |
24 #include "core/rendering/svg/RenderSVGResourceClipper.h" | |
25 | |
26 #include "core/SVGNames.h" | |
27 #include "core/dom/ElementTraversal.h" | |
28 #include "core/layout/HitTestResult.h" | |
29 #include "core/layout/PaintInfo.h" | |
30 #include "core/layout/svg/SVGLayoutSupport.h" | |
31 #include "core/layout/svg/SVGResources.h" | |
32 #include "core/layout/svg/SVGResourcesCache.h" | |
33 #include "core/svg/SVGUseElement.h" | |
34 #include "platform/RuntimeEnabledFeatures.h" | |
35 #include "platform/graphics/GraphicsContextStateSaver.h" | |
36 #include "platform/graphics/paint/ClipPathDisplayItem.h" | |
37 #include "platform/graphics/paint/DisplayItemList.h" | |
38 #include "third_party/skia/include/core/SkPicture.h" | |
39 #include "wtf/TemporaryChange.h" | |
40 | |
41 namespace blink { | |
42 | |
43 RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node) | |
44 : RenderSVGResourceContainer(node) | |
45 , m_inClipExpansion(false) | |
46 { | |
47 } | |
48 | |
49 RenderSVGResourceClipper::~RenderSVGResourceClipper() | |
50 { | |
51 } | |
52 | |
53 void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidatio
n) | |
54 { | |
55 m_clipContentPicture.clear(); | |
56 m_clipBoundaries = FloatRect(); | |
57 markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInval
idation : ParentOnlyInvalidation); | |
58 } | |
59 | |
60 void RenderSVGResourceClipper::removeClientFromCache(LayoutObject* client, bool
markForInvalidation) | |
61 { | |
62 ASSERT(client); | |
63 markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidati
on : ParentOnlyInvalidation); | |
64 } | |
65 | |
66 bool RenderSVGResourceClipper::applyStatefulResource(LayoutObject* object, Graph
icsContext*& context, ClipperState& clipperState) | |
67 { | |
68 ASSERT(object); | |
69 ASSERT(context); | |
70 | |
71 clearInvalidationMask(); | |
72 | |
73 return applyClippingToContext(object, object->objectBoundingBox(), object->p
aintInvalidationRectInLocalCoordinates(), context, clipperState); | |
74 } | |
75 | |
76 bool RenderSVGResourceClipper::tryPathOnlyClipping(DisplayItemClient client, Gra
phicsContext* context, | |
77 const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundi
ngBox) { | |
78 // If the current clip-path gets clipped itself, we have to fallback to mask
ing. | |
79 if (!style()->svgStyle().clipperResource().isEmpty()) | |
80 return false; | |
81 WindRule clipRule = RULE_NONZERO; | |
82 Path clipPath = Path(); | |
83 | |
84 for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()
); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement
)) { | |
85 LayoutObject* renderer = childElement->renderer(); | |
86 if (!renderer) | |
87 continue; | |
88 // Only shapes or paths are supported for direct clipping. We need to fa
llback to masking for texts. | |
89 if (renderer->isSVGText()) | |
90 return false; | |
91 if (!childElement->isSVGGraphicsElement()) | |
92 continue; | |
93 SVGGraphicsElement* styled = toSVGGraphicsElement(childElement); | |
94 const LayoutStyle* style = renderer->style(); | |
95 if (!style || style->display() == NONE || style->visibility() != VISIBLE
) | |
96 continue; | |
97 const SVGLayoutStyle& svgStyle = style->svgStyle(); | |
98 // Current shape in clip-path gets clipped too. Fallback to masking. | |
99 if (!svgStyle.clipperResource().isEmpty()) | |
100 return false; | |
101 | |
102 if (clipPath.isEmpty()) { | |
103 // First clip shape. | |
104 styled->toClipPath(clipPath); | |
105 clipRule = svgStyle.clipRule(); | |
106 clipPath.setWindRule(clipRule); | |
107 continue; | |
108 } | |
109 | |
110 if (RuntimeEnabledFeatures::pathOpsSVGClippingEnabled()) { | |
111 // Attempt to generate a combined clip path, fall back to masking if
not possible. | |
112 Path subPath; | |
113 styled->toClipPath(subPath); | |
114 subPath.setWindRule(svgStyle.clipRule()); | |
115 if (!clipPath.unionPath(subPath)) | |
116 return false; | |
117 } else { | |
118 return false; | |
119 } | |
120 } | |
121 // Only one visible shape/path was found. Directly continue clipping and tra
nsform the content to userspace if necessary. | |
122 if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { | |
123 AffineTransform transform; | |
124 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); | |
125 transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.h
eight()); | |
126 clipPath.transform(transform); | |
127 } | |
128 | |
129 // Transform path by animatedLocalTransform. | |
130 clipPath.transform(animatedLocalTransform); | |
131 | |
132 // The SVG specification wants us to clip everything, if clip-path doesn't h
ave a child. | |
133 if (clipPath.isEmpty()) | |
134 clipPath.addRect(FloatRect()); | |
135 | |
136 if (RuntimeEnabledFeatures::slimmingPaintEnabled()) { | |
137 context->displayItemList()->add(BeginClipPathDisplayItem::create(client,
clipPath, clipRule)); | |
138 } else { | |
139 BeginClipPathDisplayItem clipPathDisplayItem(client, clipPath, clipRule)
; | |
140 clipPathDisplayItem.replay(context); | |
141 } | |
142 | |
143 return true; | |
144 } | |
145 | |
146 bool RenderSVGResourceClipper::applyClippingToContext(LayoutObject* target, cons
t FloatRect& targetBoundingBox, | |
147 const FloatRect& paintInvalidationRect, GraphicsContext* context, ClipperSta
te& clipperState) | |
148 { | |
149 ASSERT(target); | |
150 ASSERT(context); | |
151 ASSERT(clipperState == ClipperNotApplied); | |
152 ASSERT_WITH_SECURITY_IMPLICATION(!needsLayout()); | |
153 | |
154 if (paintInvalidationRect.isEmpty() || m_inClipExpansion) | |
155 return false; | |
156 TemporaryChange<bool> inClipExpansionChange(m_inClipExpansion, true); | |
157 | |
158 AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->ca
lculateAnimatedLocalTransform(); | |
159 // When drawing a clip for non-SVG elements, the CTM does not include the zo
om factor. | |
160 // In this case, we need to apply the zoom scale explicitly - but only for c
lips with | |
161 // userSpaceOnUse units (the zoom is accounted for objectBoundingBox-resolve
d lengths). | |
162 if (!target->isSVG() && clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_USERS
PACEONUSE) { | |
163 ASSERT(style()); | |
164 animatedLocalTransform.scale(style()->effectiveZoom()); | |
165 } | |
166 | |
167 // First, try to apply the clip as a clipPath. | |
168 if (tryPathOnlyClipping(target->displayItemClient(), context, animatedLocalT
ransform, targetBoundingBox)) { | |
169 clipperState = ClipperAppliedPath; | |
170 return true; | |
171 } | |
172 | |
173 // Fall back to masking. | |
174 clipperState = ClipperAppliedMask; | |
175 | |
176 // Mask layer start | |
177 context->beginTransparencyLayer(1, &paintInvalidationRect); | |
178 { | |
179 GraphicsContextStateSaver maskContentSaver(*context); | |
180 context->concatCTM(animatedLocalTransform); | |
181 | |
182 // clipPath can also be clipped by another clipPath. | |
183 SVGResources* resources = SVGResourcesCache::cachedResourcesForLayoutObj
ect(this); | |
184 RenderSVGResourceClipper* clipPathClipper = resources ? resources->clipp
er() : 0; | |
185 ClipperState clipPathClipperState = ClipperNotApplied; | |
186 if (clipPathClipper && !clipPathClipper->applyClippingToContext(this, ta
rgetBoundingBox, paintInvalidationRect, context, clipPathClipperState)) { | |
187 // FIXME: Awkward state micro-management. Ideally, GraphicsContextSt
ateSaver should | |
188 // a) pop saveLayers also | |
189 // b) pop multiple states if needed (similarly to SkCanvas::restor
eToCount()) | |
190 // Then we should be able to replace this mess with a single, top-le
vel GCSS. | |
191 maskContentSaver.restore(); | |
192 context->endLayer(); | |
193 return false; | |
194 } | |
195 | |
196 drawClipMaskContent(context, targetBoundingBox); | |
197 | |
198 if (clipPathClipper) | |
199 clipPathClipper->postApplyStatefulResource(this, context, clipPathCl
ipperState); | |
200 } | |
201 | |
202 // Masked content layer start. | |
203 context->beginLayer(1, SkXfermode::kSrcIn_Mode, &paintInvalidationRect); | |
204 | |
205 return true; | |
206 } | |
207 | |
208 void RenderSVGResourceClipper::postApplyStatefulResource(LayoutObject* target, G
raphicsContext*& context, ClipperState& clipperState) | |
209 { | |
210 switch (clipperState) { | |
211 case ClipperAppliedPath: | |
212 // Path-only clipping, no layers to restore but we need to emit an end t
o the clip path display item. | |
213 { | |
214 if (RuntimeEnabledFeatures::slimmingPaintEnabled()) { | |
215 context->displayItemList()->add(EndClipPathDisplayItem::create(t
arget->displayItemClient())); | |
216 } else { | |
217 EndClipPathDisplayItem endClipPathDisplayItem(target->displayIte
mClient()); | |
218 endClipPathDisplayItem.replay(context); | |
219 } | |
220 } | |
221 break; | |
222 case ClipperAppliedMask: | |
223 // Transfer content layer -> mask layer (SrcIn) | |
224 context->endLayer(); | |
225 // Transfer mask layer -> bg layer (SrcOver) | |
226 context->endLayer(); | |
227 break; | |
228 default: | |
229 ASSERT_NOT_REACHED(); | |
230 } | |
231 } | |
232 | |
233 void RenderSVGResourceClipper::drawClipMaskContent(GraphicsContext* context, con
st FloatRect& targetBoundingBox) | |
234 { | |
235 ASSERT(context); | |
236 | |
237 AffineTransform contentTransformation; | |
238 if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { | |
239 contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox
.y()); | |
240 contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetB
oundingBox.height()); | |
241 context->concatCTM(contentTransformation); | |
242 } | |
243 | |
244 if (!m_clipContentPicture) { | |
245 SubtreeContentTransformScope contentTransformScope(contentTransformation
); | |
246 createPicture(context); | |
247 } | |
248 | |
249 context->drawPicture(m_clipContentPicture.get()); | |
250 } | |
251 | |
252 void RenderSVGResourceClipper::createPicture(GraphicsContext* context) | |
253 { | |
254 ASSERT(context); | |
255 ASSERT(frame()); | |
256 | |
257 // Using strokeBoundingBox (instead of paintInvalidationRectInLocalCoordinat
es) to avoid the intersection | |
258 // with local clips/mask, which may yield incorrect results when mixing obje
ctBoundingBox and | |
259 // userSpaceOnUse units (http://crbug.com/294900). | |
260 FloatRect bounds = strokeBoundingBox(); | |
261 context->beginRecording(bounds); | |
262 | |
263 for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()
); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement
)) { | |
264 LayoutObject* renderer = childElement->renderer(); | |
265 if (!renderer) | |
266 continue; | |
267 | |
268 const LayoutStyle* style = renderer->style(); | |
269 if (!style || style->display() == NONE || style->visibility() != VISIBLE
) | |
270 continue; | |
271 | |
272 WindRule newClipRule = style->svgStyle().clipRule(); | |
273 bool isUseElement = isSVGUseElement(*childElement); | |
274 if (isUseElement) { | |
275 SVGUseElement& useElement = toSVGUseElement(*childElement); | |
276 renderer = useElement.rendererClipChild(); | |
277 if (!renderer) | |
278 continue; | |
279 if (!useElement.hasAttribute(SVGNames::clip_ruleAttr)) | |
280 newClipRule = renderer->style()->svgStyle().clipRule(); | |
281 } | |
282 | |
283 // Only shapes, paths and texts are allowed for clipping. | |
284 if (!renderer->isSVGShape() && !renderer->isSVGText()) | |
285 continue; | |
286 | |
287 context->setFillRule(newClipRule); | |
288 | |
289 if (isUseElement) | |
290 renderer = childElement->renderer(); | |
291 | |
292 // Switch to a paint behavior where all children of this <clipPath> will
be rendered using special constraints: | |
293 // - fill-opacity/stroke-opacity/opacity set to 1 | |
294 // - masker/filter not applied when rendering the children | |
295 // - fill is set to the initial fill paint server (solid, black) | |
296 // - stroke is set to the initial stroke paint server (none) | |
297 PaintInfo info(context, LayoutRect::infiniteIntRect(), PaintPhaseForegro
und, PaintBehaviorRenderingClipPathAsMask); | |
298 renderer->paint(info, IntPoint()); | |
299 } | |
300 | |
301 m_clipContentPicture = context->endRecording(); | |
302 } | |
303 | |
304 void RenderSVGResourceClipper::calculateClipContentPaintInvalidationRect() | |
305 { | |
306 // This is a rough heuristic to appraise the clip size and doesn't consider
clip on clip. | |
307 for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()
); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement
)) { | |
308 LayoutObject* renderer = childElement->renderer(); | |
309 if (!renderer) | |
310 continue; | |
311 if (!renderer->isSVGShape() && !renderer->isSVGText() && !isSVGUseElemen
t(*childElement)) | |
312 continue; | |
313 const LayoutStyle* style = renderer->style(); | |
314 if (!style || style->display() == NONE || style->visibility() != VISIBLE
) | |
315 continue; | |
316 m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(render
er->paintInvalidationRectInLocalCoordinates())); | |
317 } | |
318 m_clipBoundaries = toSVGClipPathElement(element())->calculateAnimatedLocalTr
ansform().mapRect(m_clipBoundaries); | |
319 } | |
320 | |
321 bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundin
gBox, const FloatPoint& nodeAtPoint) | |
322 { | |
323 FloatPoint point = nodeAtPoint; | |
324 if (!SVGLayoutSupport::pointInClippingArea(this, point)) | |
325 return false; | |
326 | |
327 if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { | |
328 AffineTransform transform; | |
329 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); | |
330 transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.h
eight()); | |
331 point = transform.inverse().mapPoint(point); | |
332 } | |
333 | |
334 AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->ca
lculateAnimatedLocalTransform(); | |
335 if (!animatedLocalTransform.isInvertible()) | |
336 return false; | |
337 | |
338 point = animatedLocalTransform.inverse().mapPoint(point); | |
339 | |
340 for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()
); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement
)) { | |
341 LayoutObject* renderer = childElement->renderer(); | |
342 if (!renderer) | |
343 continue; | |
344 if (!renderer->isSVGShape() && !renderer->isSVGText() && !isSVGUseElemen
t(*childElement)) | |
345 continue; | |
346 IntPoint hitPoint; | |
347 HitTestResult result(hitPoint); | |
348 if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipCon
tent), result, point, HitTestForeground)) | |
349 return true; | |
350 } | |
351 | |
352 return false; | |
353 } | |
354 | |
355 FloatRect RenderSVGResourceClipper::resourceBoundingBox(const LayoutObject* obje
ct) | |
356 { | |
357 // Resource was not layouted yet. Give back the boundingBox of the object. | |
358 if (selfNeedsLayout()) | |
359 return object->objectBoundingBox(); | |
360 | |
361 if (m_clipBoundaries.isEmpty()) | |
362 calculateClipContentPaintInvalidationRect(); | |
363 | |
364 if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { | |
365 FloatRect objectBoundingBox = object->objectBoundingBox(); | |
366 AffineTransform transform; | |
367 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); | |
368 transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.h
eight()); | |
369 return transform.mapRect(m_clipBoundaries); | |
370 } | |
371 | |
372 return m_clipBoundaries; | |
373 } | |
374 | |
375 } | |
OLD | NEW |