blob: ef46a5a85ce8181e3fe0aa5286e9aa10e5a8ee41 [file] [log] [blame]
/*
* Copyright (C) 2007, 2008 Rob Buis <buis@kde.org>
* Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
* Copyright (C) 2007 Eric Seidel <eric@webkit.org>
* Copyright (C) 2009 Google, Inc. All rights reserved.
* Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
* Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
* Copyright (C) 2018 Adobe Systems Incorporated. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "SVGRenderSupport.h"
#include "NodeRenderStyle.h"
#include "RenderChildIterator.h"
#include "RenderElement.h"
#include "RenderGeometryMap.h"
#include "RenderIterator.h"
#include "RenderLayer.h"
#include "RenderSVGImage.h"
#include "RenderSVGResourceClipper.h"
#include "RenderSVGResourceFilter.h"
#include "RenderSVGResourceMarker.h"
#include "RenderSVGResourceMasker.h"
#include "RenderSVGRoot.h"
#include "RenderSVGText.h"
#include "RenderSVGTransformableContainer.h"
#include "RenderSVGViewportContainer.h"
#include "SVGResources.h"
#include "SVGResourcesCache.h"
#include "TransformState.h"
namespace WebCore {
LayoutRect SVGRenderSupport::clippedOverflowRectForRepaint(const RenderElement& renderer, const RenderLayerModelObject* repaintContainer)
{
// Return early for any cases where we don't actually paint
if (renderer.style().visibility() != Visibility::Visible && !renderer.enclosingLayer()->hasVisibleContent())
return LayoutRect();
// Pass our local paint rect to computeFloatVisibleRectInContainer() which will
// map to parent coords and recurse up the parent chain.
FloatRect repaintRect = renderer.repaintRectInLocalCoordinates();
const SVGRenderStyle& svgStyle = renderer.style().svgStyle();
if (const ShadowData* shadow = svgStyle.shadow())
shadow->adjustRectForShadow(repaintRect);
return enclosingLayoutRect(renderer.computeFloatRectForRepaint(repaintRect, repaintContainer));
}
Optional<FloatRect> SVGRenderSupport::computeFloatVisibleRectInContainer(const RenderElement& renderer, const FloatRect& rect, const RenderLayerModelObject* container, RenderObject::VisibleRectContext context)
{
FloatRect adjustedRect = rect;
const SVGRenderStyle& svgStyle = renderer.style().svgStyle();
if (const ShadowData* shadow = svgStyle.shadow())
shadow->adjustRectForShadow(adjustedRect);
adjustedRect.inflate(renderer.style().outlineWidth());
// Translate to coords in our parent renderer, and then call computeFloatVisibleRectInContainer() on our parent.
adjustedRect = renderer.localToParentTransform().mapRect(adjustedRect);
return renderer.parent()->computeFloatVisibleRectInContainer(adjustedRect, container, context);
}
const RenderElement& SVGRenderSupport::localToParentTransform(const RenderElement& renderer, AffineTransform &transform)
{
ASSERT(renderer.parent());
auto& parent = *renderer.parent();
// At the SVG/HTML boundary (aka RenderSVGRoot), we apply the localToBorderBoxTransform
// to map an element from SVG viewport coordinates to CSS box coordinates.
if (is<RenderSVGRoot>(parent))
transform = downcast<RenderSVGRoot>(parent).localToBorderBoxTransform() * renderer.localToParentTransform();
else
transform = renderer.localToParentTransform();
return parent;
}
void SVGRenderSupport::mapLocalToContainer(const RenderElement& renderer, const RenderLayerModelObject* repaintContainer, TransformState& transformState, bool* wasFixed)
{
AffineTransform transform;
auto& parent = localToParentTransform(renderer, transform);
transformState.applyTransform(transform);
MapCoordinatesFlags mode = UseTransforms;
parent.mapLocalToContainer(repaintContainer, transformState, mode, wasFixed);
}
const RenderElement* SVGRenderSupport::pushMappingToContainer(const RenderElement& renderer, const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap& geometryMap)
{
ASSERT_UNUSED(ancestorToStopAt, ancestorToStopAt != &renderer);
AffineTransform transform;
auto& parent = localToParentTransform(renderer, transform);
geometryMap.push(&renderer, transform);
return &parent;
}
bool SVGRenderSupport::checkForSVGRepaintDuringLayout(const RenderElement& renderer)
{
if (!renderer.checkForRepaintDuringLayout())
return false;
// When a parent container is transformed in SVG, all children will be painted automatically
// so we are able to skip redundant repaint checks.
auto parent = renderer.parent();
return !(is<RenderSVGContainer>(parent) && downcast<RenderSVGContainer>(*parent).didTransformToRootUpdate());
}
// Update a bounding box taking into account the validity of the other bounding box.
static inline void updateObjectBoundingBox(FloatRect& objectBoundingBox, bool& objectBoundingBoxValid, const RenderObject* other, FloatRect otherBoundingBox)
{
bool otherValid = is<RenderSVGContainer>(*other) ? downcast<RenderSVGContainer>(*other).isObjectBoundingBoxValid() : true;
if (!otherValid)
return;
if (!objectBoundingBoxValid) {
objectBoundingBox = otherBoundingBox;
objectBoundingBoxValid = true;
return;
}
objectBoundingBox.uniteEvenIfEmpty(otherBoundingBox);
}
void SVGRenderSupport::computeContainerBoundingBoxes(const RenderElement& container, FloatRect& objectBoundingBox, bool& objectBoundingBoxValid, FloatRect& strokeBoundingBox, FloatRect& repaintBoundingBox)
{
objectBoundingBox = FloatRect();
objectBoundingBoxValid = false;
strokeBoundingBox = FloatRect();
// When computing the strokeBoundingBox, we use the repaintRects of the container's children so that the container's stroke includes
// the resources applied to the children (such as clips and filters). This allows filters applied to containers to correctly bound
// the children, and also improves inlining of SVG content, as the stroke bound is used in that situation also.
for (auto& current : childrenOfType<RenderObject>(container)) {
if (current.isSVGHiddenContainer())
continue;
// Don't include elements in the union that do not render.
if (is<RenderSVGShape>(current) && downcast<RenderSVGShape>(current).isRenderingDisabled())
continue;
const AffineTransform& transform = current.localToParentTransform();
if (transform.isIdentity()) {
updateObjectBoundingBox(objectBoundingBox, objectBoundingBoxValid, &current, current.objectBoundingBox());
strokeBoundingBox.unite(current.repaintRectInLocalCoordinates());
} else {
updateObjectBoundingBox(objectBoundingBox, objectBoundingBoxValid, &current, transform.mapRect(current.objectBoundingBox()));
strokeBoundingBox.unite(transform.mapRect(current.repaintRectInLocalCoordinates()));
}
}
repaintBoundingBox = strokeBoundingBox;
}
bool SVGRenderSupport::paintInfoIntersectsRepaintRect(const FloatRect& localRepaintRect, const AffineTransform& localTransform, const PaintInfo& paintInfo)
{
if (localTransform.isIdentity())
return localRepaintRect.intersects(paintInfo.rect);
return localTransform.mapRect(localRepaintRect).intersects(paintInfo.rect);
}
RenderSVGRoot* SVGRenderSupport::findTreeRootObject(RenderElement& start)
{
return lineageOfType<RenderSVGRoot>(start).first();
}
const RenderSVGRoot* SVGRenderSupport::findTreeRootObject(const RenderElement& start)
{
return lineageOfType<RenderSVGRoot>(start).first();
}
static inline void invalidateResourcesOfChildren(RenderElement& renderer)
{
ASSERT(!renderer.needsLayout());
if (auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer))
resources->removeClientFromCache(renderer, false);
for (auto& child : childrenOfType<RenderElement>(renderer))
invalidateResourcesOfChildren(child);
}
static inline bool layoutSizeOfNearestViewportChanged(const RenderElement& renderer)
{
const RenderElement* start = &renderer;
while (start && !is<RenderSVGRoot>(*start) && !is<RenderSVGViewportContainer>(*start))
start = start->parent();
ASSERT(start);
if (is<RenderSVGViewportContainer>(*start))
return downcast<RenderSVGViewportContainer>(*start).isLayoutSizeChanged();
return downcast<RenderSVGRoot>(*start).isLayoutSizeChanged();
}
bool SVGRenderSupport::transformToRootChanged(RenderElement* ancestor)
{
while (ancestor && !is<RenderSVGRoot>(*ancestor)) {
if (is<RenderSVGTransformableContainer>(*ancestor))
return downcast<RenderSVGTransformableContainer>(*ancestor).didTransformToRootUpdate();
if (is<RenderSVGViewportContainer>(*ancestor))
return downcast<RenderSVGViewportContainer>(*ancestor).didTransformToRootUpdate();
ancestor = ancestor->parent();
}
return false;
}
void SVGRenderSupport::layoutDifferentRootIfNeeded(const RenderElement& renderer)
{
if (auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer)) {
auto* svgRoot = SVGRenderSupport::findTreeRootObject(renderer);
ASSERT(svgRoot);
resources->layoutDifferentRootIfNeeded(svgRoot);
}
}
void SVGRenderSupport::layoutChildren(RenderElement& start, bool selfNeedsLayout)
{
bool layoutSizeChanged = layoutSizeOfNearestViewportChanged(start);
bool transformChanged = transformToRootChanged(&start);
HashSet<RenderElement*> elementsThatDidNotReceiveLayout;
for (auto& child : childrenOfType<RenderObject>(start)) {
bool needsLayout = selfNeedsLayout;
bool childEverHadLayout = child.everHadLayout();
if (transformChanged) {
// If the transform changed we need to update the text metrics (note: this also happens for layoutSizeChanged=true).
if (is<RenderSVGText>(child))
downcast<RenderSVGText>(child).setNeedsTextMetricsUpdate();
needsLayout = true;
}
if (layoutSizeChanged && is<SVGElement>(*child.node())) {
// When selfNeedsLayout is false and the layout size changed, we have to check whether this child uses relative lengths
auto& element = downcast<SVGElement>(*child.node());
if (element.hasRelativeLengths()) {
// When the layout size changed and when using relative values tell the RenderSVGShape to update its shape object
if (is<RenderSVGShape>(child))
downcast<RenderSVGShape>(child).setNeedsShapeUpdate();
else if (is<RenderSVGText>(child)) {
auto& svgText = downcast<RenderSVGText>(child);
svgText.setNeedsTextMetricsUpdate();
svgText.setNeedsPositioningValuesUpdate();
}
needsLayout = true;
}
}
if (needsLayout)
child.setNeedsLayout(MarkOnlyThis);
if (child.needsLayout()) {
layoutDifferentRootIfNeeded(downcast<RenderElement>(child));
downcast<RenderElement>(child).layout();
// Renderers are responsible for repainting themselves when changing, except
// for the initial paint to avoid potential double-painting caused by non-sensical "old" bounds.
// We could handle this in the individual objects, but for now it's easier to have
// parent containers call repaint(). (RenderBlock::layout* has similar logic.)
if (!childEverHadLayout)
child.repaint();
} else if (layoutSizeChanged && is<RenderElement>(child))
elementsThatDidNotReceiveLayout.add(&downcast<RenderElement>(child));
ASSERT(!child.needsLayout());
}
if (!layoutSizeChanged) {
ASSERT(elementsThatDidNotReceiveLayout.isEmpty());
return;
}
// If the layout size changed, invalidate all resources of all children that didn't go through the layout() code path.
for (auto* element : elementsThatDidNotReceiveLayout)
invalidateResourcesOfChildren(*element);
}
bool SVGRenderSupport::isOverflowHidden(const RenderElement& renderer)
{
// RenderSVGRoot should never query for overflow state - it should always clip itself to the initial viewport size.
ASSERT(!renderer.isDocumentElementRenderer());
return renderer.style().overflowX() == Overflow::Hidden || renderer.style().overflowX() == Overflow::Scroll;
}
void SVGRenderSupport::intersectRepaintRectWithResources(const RenderElement& renderer, FloatRect& repaintRect)
{
auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer);
if (!resources)
return;
if (RenderSVGResourceFilter* filter = resources->filter())
repaintRect = filter->resourceBoundingBox(renderer);
if (RenderSVGResourceClipper* clipper = resources->clipper())
repaintRect.intersect(clipper->resourceBoundingBox(renderer));
if (RenderSVGResourceMasker* masker = resources->masker())
repaintRect.intersect(masker->resourceBoundingBox(renderer));
}
bool SVGRenderSupport::filtersForceContainerLayout(const RenderElement& renderer)
{
// If any of this container's children need to be laid out, and a filter is applied
// to the container, we need to repaint the entire container.
if (!renderer.normalChildNeedsLayout())
return false;
auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer);
if (!resources || !resources->filter())
return false;
return true;
}
inline FloatRect clipPathReferenceBox(const RenderElement& renderer, CSSBoxType boxType)
{
FloatRect referenceBox;
switch (boxType) {
case CSSBoxType::BorderBox:
case CSSBoxType::MarginBox:
case CSSBoxType::StrokeBox:
// FIXME: strokeBoundingBox() takes dasharray into account but shouldn't.
referenceBox = renderer.strokeBoundingBox();
break;
case CSSBoxType::ViewBox:
if (renderer.element()) {
FloatSize viewportSize;
SVGLengthContext(downcast<SVGElement>(renderer.element())).determineViewport(viewportSize);
referenceBox.setSize(viewportSize);
break;
}
FALLTHROUGH;
case CSSBoxType::ContentBox:
case CSSBoxType::FillBox:
case CSSBoxType::PaddingBox:
case CSSBoxType::BoxMissing:
referenceBox = renderer.objectBoundingBox();
break;
}
return referenceBox;
}
inline bool isPointInCSSClippingArea(const RenderElement& renderer, const FloatPoint& point)
{
ClipPathOperation* clipPathOperation = renderer.style().clipPath();
if (is<ShapeClipPathOperation>(clipPathOperation)) {
auto& clipPath = downcast<ShapeClipPathOperation>(*clipPathOperation);
FloatRect referenceBox = clipPathReferenceBox(renderer, clipPath.referenceBox());
if (!referenceBox.contains(point))
return false;
return clipPath.pathForReferenceRect(referenceBox).contains(point, clipPath.windRule());
}
if (is<BoxClipPathOperation>(clipPathOperation)) {
auto& clipPath = downcast<BoxClipPathOperation>(*clipPathOperation);
FloatRect referenceBox = clipPathReferenceBox(renderer, clipPath.referenceBox());
if (!referenceBox.contains(point))
return false;
return clipPath.pathForReferenceRect(FloatRoundedRect {referenceBox}).contains(point);
}
return true;
}
void SVGRenderSupport::clipContextToCSSClippingArea(GraphicsContext& context, const RenderElement& renderer)
{
ClipPathOperation* clipPathOperation = renderer.style().clipPath();
if (is<ShapeClipPathOperation>(clipPathOperation)) {
auto& clipPath = downcast<ShapeClipPathOperation>(*clipPathOperation);
FloatRect referenceBox = clipPathReferenceBox(renderer, clipPath.referenceBox());
context.clipPath(clipPath.pathForReferenceRect(referenceBox), clipPath.windRule());
}
if (is<BoxClipPathOperation>(clipPathOperation)) {
auto& clipPath = downcast<BoxClipPathOperation>(*clipPathOperation);
FloatRect referenceBox = clipPathReferenceBox(renderer, clipPath.referenceBox());
context.clipPath(clipPath.pathForReferenceRect(FloatRoundedRect {referenceBox}));
}
}
bool SVGRenderSupport::pointInClippingArea(const RenderElement& renderer, const FloatPoint& point)
{
ClipPathOperation* clipPathOperation = renderer.style().clipPath();
if (is<ShapeClipPathOperation>(clipPathOperation) || is<BoxClipPathOperation>(clipPathOperation))
return isPointInCSSClippingArea(renderer, point);
// We just take clippers into account to determine if a point is on the node. The Specification may
// change later and we also need to check maskers.
auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer);
if (!resources)
return true;
if (RenderSVGResourceClipper* clipper = resources->clipper())
return clipper->hitTestClipContent(renderer.objectBoundingBox(), point);
return true;
}
void SVGRenderSupport::applyStrokeStyleToContext(GraphicsContext* context, const RenderStyle& style, const RenderElement& renderer)
{
ASSERT(context);
ASSERT(renderer.element());
ASSERT(renderer.element()->isSVGElement());
const SVGRenderStyle& svgStyle = style.svgStyle();
SVGLengthContext lengthContext(downcast<SVGElement>(renderer.element()));
context->setStrokeThickness(lengthContext.valueForLength(style.strokeWidth()));
context->setLineCap(style.capStyle());
context->setLineJoin(style.joinStyle());
if (style.joinStyle() == MiterJoin)
context->setMiterLimit(style.strokeMiterLimit());
const Vector<SVGLengthValue>& dashes = svgStyle.strokeDashArray();
if (dashes.isEmpty())
context->setStrokeStyle(SolidStroke);
else {
DashArray dashArray;
dashArray.reserveInitialCapacity(dashes.size());
bool canSetLineDash = false;
for (auto& dash : dashes) {
dashArray.uncheckedAppend(dash.value(lengthContext));
if (dashArray.last() > 0)
canSetLineDash = true;
}
if (canSetLineDash)
context->setLineDash(dashArray, lengthContext.valueForLength(svgStyle.strokeDashOffset()));
else
context->setStrokeStyle(SolidStroke);
}
}
void SVGRenderSupport::styleChanged(RenderElement& renderer, const RenderStyle* oldStyle)
{
#if ENABLE(CSS_COMPOSITING)
if (renderer.element() && renderer.element()->isSVGElement() && (!oldStyle || renderer.style().hasBlendMode() != oldStyle->hasBlendMode()))
SVGRenderSupport::updateMaskedAncestorShouldIsolateBlending(renderer);
#else
UNUSED_PARAM(renderer);
UNUSED_PARAM(oldStyle);
#endif
}
#if ENABLE(CSS_COMPOSITING)
bool SVGRenderSupport::isolatesBlending(const RenderStyle& style)
{
return style.svgStyle().isolatesBlending() || style.hasFilter() || style.hasBlendMode() || style.opacity() < 1.0f;
}
void SVGRenderSupport::updateMaskedAncestorShouldIsolateBlending(const RenderElement& renderer)
{
ASSERT(renderer.element());
ASSERT(renderer.element()->isSVGElement());
bool maskedAncestorShouldIsolateBlending = renderer.style().hasBlendMode();
for (auto* ancestor = renderer.element()->parentElement(); ancestor && ancestor->isSVGElement(); ancestor = ancestor->parentElement()) {
if (!downcast<SVGElement>(*ancestor).isSVGGraphicsElement())
continue;
const auto* style = ancestor->computedStyle();
if (!style || !isolatesBlending(*style))
continue;
if (ancestor->computedStyle()->svgStyle().hasMasker())
downcast<SVGGraphicsElement>(*ancestor).setShouldIsolateBlending(maskedAncestorShouldIsolateBlending);
return;
}
}
#endif
}