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