1/*
2 * Copyright (C) 2007, 2008 Rob Buis <buis@kde.org>
3 * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
4 * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
5 * Copyright (C) 2009 Google, Inc. All rights reserved.
6 * Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
7 * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
8 * Copyright (C) 2018 Adobe Systems Incorporated. All rights reserved.
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
19 *
20 * You should have received a copy of the GNU Library General Public License
21 * along with this library; see the file COPYING.LIB. If not, write to
22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 */
25
26#include "config.h"
27#include "SVGRenderSupport.h"
28
29#include "NodeRenderStyle.h"
30#include "RenderChildIterator.h"
31#include "RenderElement.h"
32#include "RenderGeometryMap.h"
33#include "RenderIterator.h"
34#include "RenderLayer.h"
35#include "RenderSVGImage.h"
36#include "RenderSVGResourceClipper.h"
37#include "RenderSVGResourceFilter.h"
38#include "RenderSVGResourceMarker.h"
39#include "RenderSVGResourceMasker.h"
40#include "RenderSVGRoot.h"
41#include "RenderSVGText.h"
42#include "RenderSVGTransformableContainer.h"
43#include "RenderSVGViewportContainer.h"
44#include "SVGResources.h"
45#include "SVGResourcesCache.h"
46#include "TransformState.h"
47
48namespace WebCore {
49
50LayoutRect SVGRenderSupport::clippedOverflowRectForRepaint(const RenderElement& renderer, const RenderLayerModelObject* repaintContainer)
51{
52 // Return early for any cases where we don't actually paint
53 if (renderer.style().visibility() != Visibility::Visible && !renderer.enclosingLayer()->hasVisibleContent())
54 return LayoutRect();
55
56 // Pass our local paint rect to computeFloatVisibleRectInContainer() which will
57 // map to parent coords and recurse up the parent chain.
58 FloatRect repaintRect = renderer.repaintRectInLocalCoordinates();
59 const SVGRenderStyle& svgStyle = renderer.style().svgStyle();
60 if (const ShadowData* shadow = svgStyle.shadow())
61 shadow->adjustRectForShadow(repaintRect);
62 return enclosingLayoutRect(renderer.computeFloatRectForRepaint(repaintRect, repaintContainer));
63}
64
65Optional<FloatRect> SVGRenderSupport::computeFloatVisibleRectInContainer(const RenderElement& renderer, const FloatRect& rect, const RenderLayerModelObject* container, RenderObject::VisibleRectContext context)
66{
67 FloatRect adjustedRect = rect;
68 const SVGRenderStyle& svgStyle = renderer.style().svgStyle();
69 if (const ShadowData* shadow = svgStyle.shadow())
70 shadow->adjustRectForShadow(adjustedRect);
71 adjustedRect.inflate(renderer.style().outlineWidth());
72
73 // Translate to coords in our parent renderer, and then call computeFloatVisibleRectInContainer() on our parent.
74 adjustedRect = renderer.localToParentTransform().mapRect(adjustedRect);
75 return renderer.parent()->computeFloatVisibleRectInContainer(adjustedRect, container, context);
76}
77
78const RenderElement& SVGRenderSupport::localToParentTransform(const RenderElement& renderer, AffineTransform &transform)
79{
80 ASSERT(renderer.parent());
81 auto& parent = *renderer.parent();
82
83 // At the SVG/HTML boundary (aka RenderSVGRoot), we apply the localToBorderBoxTransform
84 // to map an element from SVG viewport coordinates to CSS box coordinates.
85 if (is<RenderSVGRoot>(parent))
86 transform = downcast<RenderSVGRoot>(parent).localToBorderBoxTransform() * renderer.localToParentTransform();
87 else
88 transform = renderer.localToParentTransform();
89
90 return parent;
91}
92
93void SVGRenderSupport::mapLocalToContainer(const RenderElement& renderer, const RenderLayerModelObject* repaintContainer, TransformState& transformState, bool* wasFixed)
94{
95 AffineTransform transform;
96 auto& parent = localToParentTransform(renderer, transform);
97
98 transformState.applyTransform(transform);
99
100 MapCoordinatesFlags mode = UseTransforms;
101 parent.mapLocalToContainer(repaintContainer, transformState, mode, wasFixed);
102}
103
104const RenderElement* SVGRenderSupport::pushMappingToContainer(const RenderElement& renderer, const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap& geometryMap)
105{
106 ASSERT_UNUSED(ancestorToStopAt, ancestorToStopAt != &renderer);
107
108 AffineTransform transform;
109 auto& parent = localToParentTransform(renderer, transform);
110
111 geometryMap.push(&renderer, transform);
112 return &parent;
113}
114
115bool SVGRenderSupport::checkForSVGRepaintDuringLayout(const RenderElement& renderer)
116{
117 if (!renderer.checkForRepaintDuringLayout())
118 return false;
119 // When a parent container is transformed in SVG, all children will be painted automatically
120 // so we are able to skip redundant repaint checks.
121 auto parent = renderer.parent();
122 return !(is<RenderSVGContainer>(parent) && downcast<RenderSVGContainer>(*parent).didTransformToRootUpdate());
123}
124
125// Update a bounding box taking into account the validity of the other bounding box.
126static inline void updateObjectBoundingBox(FloatRect& objectBoundingBox, bool& objectBoundingBoxValid, const RenderObject* other, FloatRect otherBoundingBox)
127{
128 bool otherValid = is<RenderSVGContainer>(*other) ? downcast<RenderSVGContainer>(*other).isObjectBoundingBoxValid() : true;
129 if (!otherValid)
130 return;
131
132 if (!objectBoundingBoxValid) {
133 objectBoundingBox = otherBoundingBox;
134 objectBoundingBoxValid = true;
135 return;
136 }
137
138 objectBoundingBox.uniteEvenIfEmpty(otherBoundingBox);
139}
140
141void SVGRenderSupport::computeContainerBoundingBoxes(const RenderElement& container, FloatRect& objectBoundingBox, bool& objectBoundingBoxValid, FloatRect& strokeBoundingBox, FloatRect& repaintBoundingBox)
142{
143 objectBoundingBox = FloatRect();
144 objectBoundingBoxValid = false;
145 strokeBoundingBox = FloatRect();
146
147 // When computing the strokeBoundingBox, we use the repaintRects of the container's children so that the container's stroke includes
148 // the resources applied to the children (such as clips and filters). This allows filters applied to containers to correctly bound
149 // the children, and also improves inlining of SVG content, as the stroke bound is used in that situation also.
150 for (auto& current : childrenOfType<RenderObject>(container)) {
151 if (current.isSVGHiddenContainer())
152 continue;
153
154 // Don't include elements in the union that do not render.
155 if (is<RenderSVGShape>(current) && downcast<RenderSVGShape>(current).isRenderingDisabled())
156 continue;
157
158 const AffineTransform& transform = current.localToParentTransform();
159 if (transform.isIdentity()) {
160 updateObjectBoundingBox(objectBoundingBox, objectBoundingBoxValid, &current, current.objectBoundingBox());
161 strokeBoundingBox.unite(current.repaintRectInLocalCoordinates());
162 } else {
163 updateObjectBoundingBox(objectBoundingBox, objectBoundingBoxValid, &current, transform.mapRect(current.objectBoundingBox()));
164 strokeBoundingBox.unite(transform.mapRect(current.repaintRectInLocalCoordinates()));
165 }
166 }
167
168 repaintBoundingBox = strokeBoundingBox;
169}
170
171bool SVGRenderSupport::paintInfoIntersectsRepaintRect(const FloatRect& localRepaintRect, const AffineTransform& localTransform, const PaintInfo& paintInfo)
172{
173 if (localTransform.isIdentity())
174 return localRepaintRect.intersects(paintInfo.rect);
175
176 return localTransform.mapRect(localRepaintRect).intersects(paintInfo.rect);
177}
178
179RenderSVGRoot* SVGRenderSupport::findTreeRootObject(RenderElement& start)
180{
181 return lineageOfType<RenderSVGRoot>(start).first();
182}
183
184const RenderSVGRoot* SVGRenderSupport::findTreeRootObject(const RenderElement& start)
185{
186 return lineageOfType<RenderSVGRoot>(start).first();
187}
188
189static inline void invalidateResourcesOfChildren(RenderElement& renderer)
190{
191 ASSERT(!renderer.needsLayout());
192 if (auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer))
193 resources->removeClientFromCache(renderer, false);
194
195 for (auto& child : childrenOfType<RenderElement>(renderer))
196 invalidateResourcesOfChildren(child);
197}
198
199static inline bool layoutSizeOfNearestViewportChanged(const RenderElement& renderer)
200{
201 const RenderElement* start = &renderer;
202 while (start && !is<RenderSVGRoot>(*start) && !is<RenderSVGViewportContainer>(*start))
203 start = start->parent();
204
205 ASSERT(start);
206 if (is<RenderSVGViewportContainer>(*start))
207 return downcast<RenderSVGViewportContainer>(*start).isLayoutSizeChanged();
208
209 return downcast<RenderSVGRoot>(*start).isLayoutSizeChanged();
210}
211
212bool SVGRenderSupport::transformToRootChanged(RenderElement* ancestor)
213{
214 while (ancestor && !is<RenderSVGRoot>(*ancestor)) {
215 if (is<RenderSVGTransformableContainer>(*ancestor))
216 return downcast<RenderSVGTransformableContainer>(*ancestor).didTransformToRootUpdate();
217 if (is<RenderSVGViewportContainer>(*ancestor))
218 return downcast<RenderSVGViewportContainer>(*ancestor).didTransformToRootUpdate();
219 ancestor = ancestor->parent();
220 }
221
222 return false;
223}
224
225void SVGRenderSupport::layoutDifferentRootIfNeeded(const RenderElement& renderer)
226{
227 if (auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer)) {
228 auto* svgRoot = SVGRenderSupport::findTreeRootObject(renderer);
229 ASSERT(svgRoot);
230 resources->layoutDifferentRootIfNeeded(svgRoot);
231 }
232}
233
234void SVGRenderSupport::layoutChildren(RenderElement& start, bool selfNeedsLayout)
235{
236 bool layoutSizeChanged = layoutSizeOfNearestViewportChanged(start);
237 bool transformChanged = transformToRootChanged(&start);
238 HashSet<RenderElement*> elementsThatDidNotReceiveLayout;
239
240 for (auto& child : childrenOfType<RenderObject>(start)) {
241 bool needsLayout = selfNeedsLayout;
242 bool childEverHadLayout = child.everHadLayout();
243
244 if (transformChanged) {
245 // If the transform changed we need to update the text metrics (note: this also happens for layoutSizeChanged=true).
246 if (is<RenderSVGText>(child))
247 downcast<RenderSVGText>(child).setNeedsTextMetricsUpdate();
248 needsLayout = true;
249 }
250
251 if (layoutSizeChanged && is<SVGElement>(*child.node())) {
252 // When selfNeedsLayout is false and the layout size changed, we have to check whether this child uses relative lengths
253 auto& element = downcast<SVGElement>(*child.node());
254 if (element.hasRelativeLengths()) {
255 // When the layout size changed and when using relative values tell the RenderSVGShape to update its shape object
256 if (is<RenderSVGShape>(child))
257 downcast<RenderSVGShape>(child).setNeedsShapeUpdate();
258 else if (is<RenderSVGText>(child)) {
259 auto& svgText = downcast<RenderSVGText>(child);
260 svgText.setNeedsTextMetricsUpdate();
261 svgText.setNeedsPositioningValuesUpdate();
262 }
263
264 needsLayout = true;
265 }
266 }
267
268 if (needsLayout)
269 child.setNeedsLayout(MarkOnlyThis);
270
271 if (child.needsLayout()) {
272 layoutDifferentRootIfNeeded(downcast<RenderElement>(child));
273 downcast<RenderElement>(child).layout();
274 // Renderers are responsible for repainting themselves when changing, except
275 // for the initial paint to avoid potential double-painting caused by non-sensical "old" bounds.
276 // We could handle this in the individual objects, but for now it's easier to have
277 // parent containers call repaint(). (RenderBlock::layout* has similar logic.)
278 if (!childEverHadLayout)
279 child.repaint();
280 } else if (layoutSizeChanged && is<RenderElement>(child))
281 elementsThatDidNotReceiveLayout.add(&downcast<RenderElement>(child));
282
283 ASSERT(!child.needsLayout());
284 }
285
286 if (!layoutSizeChanged) {
287 ASSERT(elementsThatDidNotReceiveLayout.isEmpty());
288 return;
289 }
290
291 // If the layout size changed, invalidate all resources of all children that didn't go through the layout() code path.
292 for (auto* element : elementsThatDidNotReceiveLayout)
293 invalidateResourcesOfChildren(*element);
294}
295
296bool SVGRenderSupport::isOverflowHidden(const RenderElement& renderer)
297{
298 // RenderSVGRoot should never query for overflow state - it should always clip itself to the initial viewport size.
299 ASSERT(!renderer.isDocumentElementRenderer());
300
301 return renderer.style().overflowX() == Overflow::Hidden || renderer.style().overflowX() == Overflow::Scroll;
302}
303
304void SVGRenderSupport::intersectRepaintRectWithResources(const RenderElement& renderer, FloatRect& repaintRect)
305{
306 auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer);
307 if (!resources)
308 return;
309
310 if (RenderSVGResourceFilter* filter = resources->filter())
311 repaintRect = filter->resourceBoundingBox(renderer);
312
313 if (RenderSVGResourceClipper* clipper = resources->clipper())
314 repaintRect.intersect(clipper->resourceBoundingBox(renderer));
315
316 if (RenderSVGResourceMasker* masker = resources->masker())
317 repaintRect.intersect(masker->resourceBoundingBox(renderer));
318}
319
320bool SVGRenderSupport::filtersForceContainerLayout(const RenderElement& renderer)
321{
322 // If any of this container's children need to be laid out, and a filter is applied
323 // to the container, we need to repaint the entire container.
324 if (!renderer.normalChildNeedsLayout())
325 return false;
326
327 auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer);
328 if (!resources || !resources->filter())
329 return false;
330
331 return true;
332}
333
334inline FloatRect clipPathReferenceBox(const RenderElement& renderer, CSSBoxType boxType)
335{
336 FloatRect referenceBox;
337 switch (boxType) {
338 case CSSBoxType::BorderBox:
339 case CSSBoxType::MarginBox:
340 case CSSBoxType::StrokeBox:
341 // FIXME: strokeBoundingBox() takes dasharray into account but shouldn't.
342 referenceBox = renderer.strokeBoundingBox();
343 break;
344 case CSSBoxType::ViewBox:
345 if (renderer.element()) {
346 FloatSize viewportSize;
347 SVGLengthContext(downcast<SVGElement>(renderer.element())).determineViewport(viewportSize);
348 referenceBox.setSize(viewportSize);
349 break;
350 }
351 FALLTHROUGH;
352 case CSSBoxType::ContentBox:
353 case CSSBoxType::FillBox:
354 case CSSBoxType::PaddingBox:
355 case CSSBoxType::BoxMissing:
356 referenceBox = renderer.objectBoundingBox();
357 break;
358 }
359 return referenceBox;
360}
361
362inline bool isPointInCSSClippingArea(const RenderElement& renderer, const FloatPoint& point)
363{
364 ClipPathOperation* clipPathOperation = renderer.style().clipPath();
365 if (is<ShapeClipPathOperation>(clipPathOperation)) {
366 auto& clipPath = downcast<ShapeClipPathOperation>(*clipPathOperation);
367 FloatRect referenceBox = clipPathReferenceBox(renderer, clipPath.referenceBox());
368 if (!referenceBox.contains(point))
369 return false;
370 return clipPath.pathForReferenceRect(referenceBox).contains(point, clipPath.windRule());
371 }
372 if (is<BoxClipPathOperation>(clipPathOperation)) {
373 auto& clipPath = downcast<BoxClipPathOperation>(*clipPathOperation);
374 FloatRect referenceBox = clipPathReferenceBox(renderer, clipPath.referenceBox());
375 if (!referenceBox.contains(point))
376 return false;
377 return clipPath.pathForReferenceRect(FloatRoundedRect {referenceBox}).contains(point);
378 }
379
380 return true;
381}
382
383void SVGRenderSupport::clipContextToCSSClippingArea(GraphicsContext& context, const RenderElement& renderer)
384{
385 ClipPathOperation* clipPathOperation = renderer.style().clipPath();
386 if (is<ShapeClipPathOperation>(clipPathOperation)) {
387 auto& clipPath = downcast<ShapeClipPathOperation>(*clipPathOperation);
388 FloatRect referenceBox = clipPathReferenceBox(renderer, clipPath.referenceBox());
389 context.clipPath(clipPath.pathForReferenceRect(referenceBox), clipPath.windRule());
390 }
391 if (is<BoxClipPathOperation>(clipPathOperation)) {
392 auto& clipPath = downcast<BoxClipPathOperation>(*clipPathOperation);
393 FloatRect referenceBox = clipPathReferenceBox(renderer, clipPath.referenceBox());
394 context.clipPath(clipPath.pathForReferenceRect(FloatRoundedRect {referenceBox}));
395 }
396}
397
398bool SVGRenderSupport::pointInClippingArea(const RenderElement& renderer, const FloatPoint& point)
399{
400 ClipPathOperation* clipPathOperation = renderer.style().clipPath();
401 if (is<ShapeClipPathOperation>(clipPathOperation) || is<BoxClipPathOperation>(clipPathOperation))
402 return isPointInCSSClippingArea(renderer, point);
403
404 // We just take clippers into account to determine if a point is on the node. The Specification may
405 // change later and we also need to check maskers.
406 auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer);
407 if (!resources)
408 return true;
409
410 if (RenderSVGResourceClipper* clipper = resources->clipper())
411 return clipper->hitTestClipContent(renderer.objectBoundingBox(), point);
412
413 return true;
414}
415
416void SVGRenderSupport::applyStrokeStyleToContext(GraphicsContext* context, const RenderStyle& style, const RenderElement& renderer)
417{
418 ASSERT(context);
419 ASSERT(renderer.element());
420 ASSERT(renderer.element()->isSVGElement());
421
422 const SVGRenderStyle& svgStyle = style.svgStyle();
423
424 SVGLengthContext lengthContext(downcast<SVGElement>(renderer.element()));
425 context->setStrokeThickness(lengthContext.valueForLength(style.strokeWidth()));
426 context->setLineCap(style.capStyle());
427 context->setLineJoin(style.joinStyle());
428 if (style.joinStyle() == MiterJoin)
429 context->setMiterLimit(style.strokeMiterLimit());
430
431 const Vector<SVGLengthValue>& dashes = svgStyle.strokeDashArray();
432 if (dashes.isEmpty())
433 context->setStrokeStyle(SolidStroke);
434 else {
435 DashArray dashArray;
436 dashArray.reserveInitialCapacity(dashes.size());
437 bool canSetLineDash = false;
438
439 for (auto& dash : dashes) {
440 dashArray.uncheckedAppend(dash.value(lengthContext));
441 if (dashArray.last() > 0)
442 canSetLineDash = true;
443 }
444
445 if (canSetLineDash)
446 context->setLineDash(dashArray, lengthContext.valueForLength(svgStyle.strokeDashOffset()));
447 else
448 context->setStrokeStyle(SolidStroke);
449 }
450}
451
452void SVGRenderSupport::styleChanged(RenderElement& renderer, const RenderStyle* oldStyle)
453{
454#if ENABLE(CSS_COMPOSITING)
455 if (renderer.element() && renderer.element()->isSVGElement() && (!oldStyle || renderer.style().hasBlendMode() != oldStyle->hasBlendMode()))
456 SVGRenderSupport::updateMaskedAncestorShouldIsolateBlending(renderer);
457#else
458 UNUSED_PARAM(renderer);
459 UNUSED_PARAM(oldStyle);
460#endif
461}
462
463#if ENABLE(CSS_COMPOSITING)
464bool SVGRenderSupport::isolatesBlending(const RenderStyle& style)
465{
466 return style.svgStyle().isolatesBlending() || style.hasFilter() || style.hasBlendMode() || style.opacity() < 1.0f;
467}
468
469void SVGRenderSupport::updateMaskedAncestorShouldIsolateBlending(const RenderElement& renderer)
470{
471 ASSERT(renderer.element());
472 ASSERT(renderer.element()->isSVGElement());
473
474 bool maskedAncestorShouldIsolateBlending = renderer.style().hasBlendMode();
475 for (auto* ancestor = renderer.element()->parentElement(); ancestor && ancestor->isSVGElement(); ancestor = ancestor->parentElement()) {
476 if (!downcast<SVGElement>(*ancestor).isSVGGraphicsElement())
477 continue;
478
479 const auto* style = ancestor->computedStyle();
480 if (!style || !isolatesBlending(*style))
481 continue;
482
483 if (ancestor->computedStyle()->svgStyle().hasMasker())
484 downcast<SVGGraphicsElement>(*ancestor).setShouldIsolateBlending(maskedAncestorShouldIsolateBlending);
485
486 return;
487 }
488}
489#endif
490}
491