SVGRenderSupport.cpp [plain text]
#include "config.h"
#include "SVGRenderSupport.h"
#include "NodeRenderStyle.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 {
FloatRect SVGRenderSupport::repaintRectForRendererInLocalCoordinatesExcludingSVGShadow(const RenderElement& renderer)
{
if (is<RenderSVGModelObject>(renderer))
return downcast<RenderSVGModelObject>(renderer).repaintRectInLocalCoordinatesExcludingSVGShadow();
return renderer.repaintRectInLocalCoordinates();
}
LayoutRect SVGRenderSupport::clippedOverflowRectForRepaint(const RenderElement& renderer, const RenderLayerModelObject* repaintContainer)
{
if (renderer.style().visibility() != VISIBLE && !renderer.enclosingLayer()->hasVisibleContent())
return LayoutRect();
FloatRect repaintRect = repaintRectForRendererInLocalCoordinatesExcludingSVGShadow(renderer);
const SVGRenderStyle& svgStyle = renderer.style().svgStyle();
if (const ShadowData* shadow = svgStyle.shadow())
shadow->adjustRectForShadow(repaintRect);
renderer.computeFloatRectForRepaint(repaintContainer, repaintRect);
return enclosingLayoutRect(repaintRect);
}
void SVGRenderSupport::computeFloatRectForRepaint(const RenderElement& renderer, const RenderLayerModelObject* repaintContainer, FloatRect& repaintRect, bool fixed)
{
const SVGRenderStyle& svgStyle = renderer.style().svgStyle();
if (const ShadowData* shadow = svgStyle.shadow())
shadow->adjustRectForShadow(repaintRect);
repaintRect.inflate(renderer.style().outlineWidth());
repaintRect = renderer.localToParentTransform().mapRect(repaintRect);
renderer.parent()->computeFloatRectForRepaint(repaintContainer, repaintRect, fixed);
}
const RenderElement& SVGRenderSupport::localToParentTransform(const RenderElement& renderer, AffineTransform &transform)
{
ASSERT(renderer.parent());
auto& parent = *renderer.parent();
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;
auto parent = renderer.parent();
return !(is<RenderSVGContainer>(parent) && downcast<RenderSVGContainer>(*parent).didTransformToRootUpdate());
}
static inline void updateObjectBoundingBox(FloatRect& objectBoundingBox, bool& objectBoundingBoxValid, 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();
for (RenderObject* current = container.firstChild(); current; current = current->nextSibling()) {
if (current->isSVGHiddenContainer())
continue;
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);
}
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::layoutChildren(RenderElement& start, bool selfNeedsLayout)
{
bool layoutSizeChanged = layoutSizeOfNearestViewportChanged(start);
bool transformChanged = transformToRootChanged(&start);
bool hasSVGShadow = rendererHasSVGShadow(start);
bool needsBoundariesUpdate = start.needsBoundariesUpdate();
HashSet<RenderElement*> elementsThatDidNotReceiveLayout;
for (RenderObject* child = start.firstChild(); child; child = child->nextSibling()) {
bool needsLayout = selfNeedsLayout;
bool childEverHadLayout = child->everHadLayout();
if (needsBoundariesUpdate && hasSVGShadow) {
child->setNeedsBoundariesUpdate();
needsLayout = true;
}
if (transformChanged) {
if (is<RenderSVGText>(*child))
downcast<RenderSVGText>(*child).setNeedsTextMetricsUpdate();
needsLayout = true;
}
if (layoutSizeChanged) {
if (SVGElement* element = is<SVGElement>(*child->node()) ? downcast<SVGElement>(child->node()) : nullptr) {
if (element->hasRelativeLengths()) {
if (is<RenderSVGShape>(*child))
downcast<RenderSVGShape>(*child).setNeedsShapeUpdate();
else if (is<RenderSVGText>(*child)) {
RenderSVGText& svgText = downcast<RenderSVGText>(*child);
svgText.setNeedsTextMetricsUpdate();
svgText.setNeedsPositioningValuesUpdate();
}
needsLayout = true;
}
}
}
if (needsLayout)
child->setNeedsLayout(MarkOnlyThis);
if (child->needsLayout()) {
downcast<RenderElement>(*child).layout();
if (!childEverHadLayout)
child->repaint();
} else if (layoutSizeChanged && is<RenderElement>(*child))
elementsThatDidNotReceiveLayout.add(downcast<RenderElement>(child));
ASSERT(!child->needsLayout());
}
if (!layoutSizeChanged) {
ASSERT(elementsThatDidNotReceiveLayout.isEmpty());
return;
}
for (auto* element : elementsThatDidNotReceiveLayout)
invalidateResourcesOfChildren(*element);
}
bool SVGRenderSupport::isOverflowHidden(const RenderElement& renderer)
{
ASSERT(!renderer.isRoot());
return renderer.style().overflowX() == OHIDDEN || renderer.style().overflowX() == OSCROLL;
}
bool SVGRenderSupport::rendererHasSVGShadow(const RenderObject& renderer)
{
if (is<RenderSVGModelObject>(renderer))
return downcast<RenderSVGModelObject>(renderer).hasSVGShadow();
if (is<RenderSVGRoot>(renderer))
return downcast<RenderSVGRoot>(renderer).hasSVGShadow();
return false;
}
void SVGRenderSupport::setRendererHasSVGShadow(RenderObject& renderer, bool hasShadow)
{
if (is<RenderSVGModelObject>(renderer)) {
downcast<RenderSVGModelObject>(renderer).setHasSVGShadow(hasShadow);
return;
}
if (is<RenderSVGRoot>(renderer))
downcast<RenderSVGRoot>(renderer).setHasSVGShadow(hasShadow);
}
void SVGRenderSupport::intersectRepaintRectWithShadows(const RenderElement& renderer, FloatRect& repaintRect)
{
auto currentObject = &renderer;
AffineTransform localToRootTransform;
while (currentObject && rendererHasSVGShadow(*currentObject)) {
const RenderStyle& style = currentObject->style();
const SVGRenderStyle& svgStyle = style.svgStyle();
if (const ShadowData* shadow = svgStyle.shadow())
shadow->adjustRectForShadow(repaintRect);
repaintRect = currentObject->localToParentTransform().mapRect(repaintRect);
localToRootTransform *= currentObject->localToParentTransform();
currentObject = currentObject->parent();
};
if (localToRootTransform.isIdentity())
return;
AffineTransform rootToLocalTransform = localToRootTransform.inverse();
repaintRect = rootToLocalTransform.mapRect(repaintRect);
}
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 (!renderer.normalChildNeedsLayout())
return false;
auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer);
if (!resources || !resources->filter())
return false;
return true;
}
bool SVGRenderSupport::pointInClippingArea(const RenderElement& renderer, const FloatPoint& point)
{
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(svgStyle.strokeWidth()));
context->setLineCap(svgStyle.capStyle());
context->setLineJoin(svgStyle.joinStyle());
if (svgStyle.joinStyle() == MiterJoin)
context->setMiterLimit(svgStyle.strokeMiterLimit());
const Vector<SVGLength>& dashes = svgStyle.strokeDashArray();
if (dashes.isEmpty())
context->setStrokeStyle(SolidStroke);
else {
DashArray dashArray;
dashArray.reserveInitialCapacity(dashes.size());
for (auto& dash : dashes)
dashArray.uncheckedAppend(dash.value(lengthContext));
context->setLineDash(dashArray, lengthContext.valueForLength(svgStyle.strokeDashOffset()));
}
}
void SVGRenderSupport::childAdded(RenderElement& parent, RenderObject& child)
{
SVGRenderSupport::setRendererHasSVGShadow(child, SVGRenderSupport::rendererHasSVGShadow(parent) || SVGRenderSupport::rendererHasSVGShadow(child));
}
void SVGRenderSupport::styleChanged(RenderElement& renderer, const RenderStyle* oldStyle)
{
auto parent = renderer.parent();
SVGRenderSupport::setRendererHasSVGShadow(renderer, (parent && SVGRenderSupport::rendererHasSVGShadow(*parent)) || renderer.style().svgStyle().shadow());
#if ENABLE(CSS_COMPOSITING)
if (renderer.element() && renderer.element()->isSVGElement() && (!oldStyle || renderer.style().hasBlendMode() != oldStyle->hasBlendMode()))
SVGRenderSupport::updateMaskedAncestorShouldIsolateBlending(renderer);
#else
UNUSED_PARAM(oldStyle);
#endif
}
#if ENABLE(CSS_COMPOSITING)
bool SVGRenderSupport::isolatesBlending(const RenderStyle& style)
{
return style.svgStyle().isolatesBlending() || 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() || !isolatesBlending(*ancestor->computedStyle()))
continue;
if (ancestor->computedStyle()->svgStyle().hasMasker())
downcast<SVGGraphicsElement>(*ancestor).setShouldIsolateBlending(maskedAncestorShouldIsolateBlending);
return;
}
}
#endif
}