*/
#include "config.h"
-
#include "core/rendering/svg/SVGRenderSupport.h"
+#include "core/frame/FrameView.h"
+#include "core/frame/LocalFrame.h"
+#include "core/rendering/PaintInfo.h"
#include "core/rendering/RenderGeometryMap.h"
#include "core/rendering/RenderLayer.h"
#include "core/rendering/SubtreeLayoutScope.h"
#include "core/rendering/svg/RenderSVGResourceFilter.h"
#include "core/rendering/svg/RenderSVGResourceMasker.h"
#include "core/rendering/svg/RenderSVGRoot.h"
+#include "core/rendering/svg/RenderSVGShape.h"
#include "core/rendering/svg/RenderSVGText.h"
#include "core/rendering/svg/RenderSVGViewportContainer.h"
#include "core/rendering/svg/SVGResources.h"
#include "core/svg/SVGElement.h"
#include "platform/geometry/TransformState.h"
-namespace WebCore {
+namespace blink {
-LayoutRect SVGRenderSupport::clippedOverflowRectForRepaint(const RenderObject* object, const RenderLayerModelObject* repaintContainer)
+LayoutRect SVGRenderSupport::clippedOverflowRectForPaintInvalidation(const RenderObject* object, const RenderLayerModelObject* paintInvalidationContainer, const PaintInvalidationState* paintInvalidationState)
{
// Return early for any cases where we don't actually paint
if (object->style()->visibility() != VISIBLE && !object->enclosingLayer()->hasVisibleContent())
return LayoutRect();
- // Pass our local paint rect to computeRectForRepaint() which will
+ // Pass our local paint rect to computeRectForPaintInvalidation() which will
// map to parent coords and recurse up the parent chain.
- FloatRect repaintRect = object->repaintRectInLocalCoordinates();
- object->computeFloatRectForRepaint(repaintContainer, repaintRect);
- return enclosingLayoutRect(repaintRect);
+ FloatRect paintInvalidationRect = object->paintInvalidationRectInLocalCoordinates();
+ paintInvalidationRect.inflate(object->style()->outlineWidth());
+
+ if (paintInvalidationState && paintInvalidationState->canMapToContainer(paintInvalidationContainer)) {
+ // Compute accumulated SVG transform and apply to local paint rect.
+ AffineTransform transform = paintInvalidationState->svgTransform() * object->localToParentTransform();
+ paintInvalidationRect = transform.mapRect(paintInvalidationRect);
+ // FIXME: These are quirks carried forward from RenderSVGRoot::computeFloatRectForPaintInvalidation.
+ LayoutRect rect;
+ if (!paintInvalidationRect.isEmpty())
+ rect = enclosingIntRect(paintInvalidationRect);
+ // Offset by SVG root paint offset and apply clipping as needed.
+ rect.move(paintInvalidationState->paintOffset());
+ if (paintInvalidationState->isClipped())
+ rect.intersect(paintInvalidationState->clipRect());
+ return rect;
+ }
+
+ object->computeFloatRectForPaintInvalidation(paintInvalidationContainer, paintInvalidationRect, paintInvalidationState);
+ return enclosingLayoutRect(paintInvalidationRect);
}
-void SVGRenderSupport::computeFloatRectForRepaint(const RenderObject* object, const RenderLayerModelObject* repaintContainer, FloatRect& repaintRect, bool fixed)
+void SVGRenderSupport::computeFloatRectForPaintInvalidation(const RenderObject* object, const RenderLayerModelObject* paintInvalidationContainer, FloatRect& paintInvalidationRect, const PaintInvalidationState* paintInvalidationState)
{
- repaintRect.inflate(object->style()->outlineWidth());
-
- // Translate to coords in our parent renderer, and then call computeFloatRectForRepaint() on our parent.
- repaintRect = object->localToParentTransform().mapRect(repaintRect);
- object->parent()->computeFloatRectForRepaint(repaintContainer, repaintRect, fixed);
+ // Translate to coords in our parent renderer, and then call computeFloatRectForPaintInvalidation() on our parent.
+ paintInvalidationRect = object->localToParentTransform().mapRect(paintInvalidationRect);
+ object->parent()->computeFloatRectForPaintInvalidation(paintInvalidationContainer, paintInvalidationRect, paintInvalidationState);
}
-void SVGRenderSupport::mapLocalToContainer(const RenderObject* object, const RenderLayerModelObject* repaintContainer, TransformState& transformState, bool* wasFixed)
+void SVGRenderSupport::mapLocalToContainer(const RenderObject* object, const RenderLayerModelObject* paintInvalidationContainer, TransformState& transformState, bool* wasFixed, const PaintInvalidationState* paintInvalidationState)
{
transformState.applyTransform(object->localToParentTransform());
+ if (paintInvalidationState && paintInvalidationState->canMapToContainer(paintInvalidationContainer)) {
+ // |svgTransform| contains localToBorderBoxTransform mentioned below.
+ transformState.applyTransform(paintInvalidationState->svgTransform());
+ transformState.move(paintInvalidationState->paintOffset());
+ return;
+ }
+
RenderObject* parent = object->parent();
// At the SVG/HTML boundary (aka RenderSVGRoot), we apply the localToBorderBoxTransform
transformState.applyTransform(toRenderSVGRoot(parent)->localToBorderBoxTransform());
MapCoordinatesFlags mode = UseTransforms;
- parent->mapLocalToContainer(repaintContainer, transformState, mode, wasFixed);
+ parent->mapLocalToContainer(paintInvalidationContainer, transformState, mode, wasFixed, paintInvalidationState);
}
const RenderObject* SVGRenderSupport::pushMappingToContainer(const RenderObject* object, const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap& geometryMap)
return parent;
}
-bool SVGRenderSupport::checkForSVGRepaintDuringLayout(RenderObject* object)
-{
- if (!object->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.
- RenderObject* parent = object->parent();
- return !(parent && parent->isSVGContainer() && toRenderSVGContainer(parent)->didTransformToRootUpdate());
-}
-
// Update a bounding box taking into account the validity of the other bounding box.
-static inline void updateObjectBoundingBox(FloatRect& objectBoundingBox, bool& objectBoundingBoxValid, RenderObject* other, FloatRect otherBoundingBox)
+inline void SVGRenderSupport::updateObjectBoundingBox(FloatRect& objectBoundingBox, bool& objectBoundingBoxValid, RenderObject* other, FloatRect otherBoundingBox)
{
bool otherValid = other->isSVGContainer() ? toRenderSVGContainer(other)->isObjectBoundingBoxValid() : true;
if (!otherValid)
objectBoundingBox.uniteEvenIfEmpty(otherBoundingBox);
}
-void SVGRenderSupport::computeContainerBoundingBoxes(const RenderObject* container, FloatRect& objectBoundingBox, bool& objectBoundingBoxValid, FloatRect& strokeBoundingBox, FloatRect& repaintBoundingBox)
+void SVGRenderSupport::computeContainerBoundingBoxes(const RenderObject* container, FloatRect& objectBoundingBox, bool& objectBoundingBoxValid, FloatRect& strokeBoundingBox, FloatRect& paintInvalidationBoundingBox)
{
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
+ // When computing the strokeBoundingBox, we use the paintInvalidationRects 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 (RenderObject* current = container->firstChild(); current; current = current->nextSibling()) {
+ for (RenderObject* current = container->slowFirstChild(); current; current = current->nextSibling()) {
if (current->isSVGHiddenContainer())
continue;
+ // Don't include elements in the union that do not render.
+ if (current->isSVGShape() && toRenderSVGShape(current)->isShapeEmpty())
+ continue;
+
const AffineTransform& transform = current->localToParentTransform();
updateObjectBoundingBox(objectBoundingBox, objectBoundingBoxValid, current,
transform.mapRect(current->objectBoundingBox()));
- strokeBoundingBox.unite(transform.mapRect(current->repaintRectInLocalCoordinates()));
+ strokeBoundingBox.unite(transform.mapRect(current->paintInvalidationRectInLocalCoordinates()));
}
- repaintBoundingBox = strokeBoundingBox;
+ paintInvalidationBoundingBox = strokeBoundingBox;
}
-bool SVGRenderSupport::paintInfoIntersectsRepaintRect(const FloatRect& localRepaintRect, const AffineTransform& localTransform, const PaintInfo& paintInfo)
+bool SVGRenderSupport::paintInfoIntersectsPaintInvalidationRect(const FloatRect& localPaintInvalidationRect, const AffineTransform& localTransform, const PaintInfo& paintInfo)
{
- return localTransform.mapRect(localRepaintRect).intersects(paintInfo.rect);
+ return localTransform.mapRect(localPaintInvalidationRect).intersects(paintInfo.rect);
}
const RenderSVGRoot* SVGRenderSupport::findTreeRootObject(const RenderObject* start)
return toRenderSVGRoot(start);
}
-static inline void invalidateResourcesOfChildren(RenderObject* start)
-{
- ASSERT(!start->needsLayout());
- if (SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(start))
- resources->removeClientFromCache(start, false);
-
- for (RenderObject* child = start->firstChild(); child; child = child->nextSibling())
- invalidateResourcesOfChildren(child);
-}
-
-static inline bool layoutSizeOfNearestViewportChanged(const RenderObject* start)
+inline bool SVGRenderSupport::layoutSizeOfNearestViewportChanged(const RenderObject* start)
{
while (start && !start->isSVGRoot() && !start->isSVGViewportContainer())
start = start->parent();
void SVGRenderSupport::layoutChildren(RenderObject* start, bool selfNeedsLayout)
{
- bool layoutSizeChanged = layoutSizeOfNearestViewportChanged(start);
+ // When hasRelativeLengths() is false, no descendants have relative lengths
+ // (hence no one is interested in viewport size changes).
+ bool layoutSizeChanged = toSVGElement(start->node())->hasRelativeLengths()
+ && layoutSizeOfNearestViewportChanged(start);
bool transformChanged = transformToRootChanged(start);
- HashSet<RenderObject*> notlayoutedObjects;
- for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) {
- bool needsLayout = selfNeedsLayout;
- bool childEverHadLayout = child->everHadLayout();
+ for (RenderObject* child = start->slowFirstChild(); child; child = child->nextSibling()) {
+ bool forceLayout = selfNeedsLayout;
if (transformChanged) {
// If the transform changed we need to update the text metrics (note: this also happens for layoutSizeChanged=true).
if (child->isSVGText())
toRenderSVGText(child)->setNeedsTextMetricsUpdate();
- needsLayout = true;
+ forceLayout = true;
}
if (layoutSizeChanged) {
// When selfNeedsLayout is false and the layout size changed, we have to check whether this child uses relative lengths
if (SVGElement* element = child->node()->isSVGElement() ? toSVGElement(child->node()) : 0) {
if (element->hasRelativeLengths()) {
+ // FIXME: this should be done on invalidation, not during layout.
// When the layout size changed and when using relative values tell the RenderSVGShape to update its shape object
- if (child->isSVGShape())
+ if (child->isSVGShape()) {
toRenderSVGShape(child)->setNeedsShapeUpdate();
- else if (child->isSVGText()) {
+ } else if (child->isSVGText()) {
toRenderSVGText(child)->setNeedsTextMetricsUpdate();
toRenderSVGText(child)->setNeedsPositioningValuesUpdate();
}
- needsLayout = true;
+ forceLayout = true;
}
}
}
- SubtreeLayoutScope layoutScope(child);
+ SubtreeLayoutScope layoutScope(*child);
// Resource containers are nasty: they can invalidate clients outside the current SubtreeLayoutScope.
// Since they only care about viewport size changes (to resolve their relative lengths), we trigger
// their invalidation directly from SVGSVGElement::svgAttributeChange() or at a higher
// SubtreeLayoutScope (in RenderView::layout()).
- if (needsLayout && !child->isSVGResourceContainer())
+ if (forceLayout && !child->isSVGResourceContainer())
layoutScope.setNeedsLayout(child);
+ // Lay out any referenced resources before the child.
layoutResourcesIfNeeded(child);
-
- if (child->needsLayout()) {
- 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 && !RuntimeEnabledFeatures::repaintAfterLayoutEnabled())
- child->repaint();
- } else if (layoutSizeChanged)
- notlayoutedObjects.add(child);
- }
-
- if (!layoutSizeChanged) {
- ASSERT(notlayoutedObjects.isEmpty());
- return;
+ child->layoutIfNeeded();
}
-
- // If the layout size changed, invalidate all resources of all children that didn't go through the layout() code path.
- HashSet<RenderObject*>::iterator end = notlayoutedObjects.end();
- for (HashSet<RenderObject*>::iterator it = notlayoutedObjects.begin(); it != end; ++it)
- invalidateResourcesOfChildren(*it);
}
void SVGRenderSupport::layoutResourcesIfNeeded(const RenderObject* object)
bool SVGRenderSupport::isOverflowHidden(const RenderObject* object)
{
- // SVG doesn't support independent x/y overflow
- ASSERT(object->style()->overflowX() == object->style()->overflowY());
-
- // OSCROLL is never set for SVG - see StyleResolver::adjustRenderStyle
- ASSERT(object->style()->overflowX() != OSCROLL);
-
// RenderSVGRoot should never query for overflow state - it should always clip itself to the initial viewport size.
- ASSERT(!object->isRoot());
+ ASSERT(!object->isDocumentElement());
+
+ return object->style()->overflowX() == OHIDDEN || object->style()->overflowX() == OSCROLL;
+}
- return object->style()->overflowX() == OHIDDEN;
+bool SVGRenderSupport::isRenderingClipPathAsMaskImage(const RenderObject& object)
+{
+ return object.frame() && object.frame()->view() && object.frame()->view()->paintBehavior() & PaintBehaviorRenderingClipPathAsMask;
}
-void SVGRenderSupport::intersectRepaintRectWithResources(const RenderObject* renderer, FloatRect& repaintRect)
+void SVGRenderSupport::intersectPaintInvalidationRectWithResources(const RenderObject* renderer, FloatRect& paintInvalidationRect)
{
ASSERT(renderer);
return;
if (RenderSVGResourceFilter* filter = resources->filter())
- repaintRect = filter->resourceBoundingBox(renderer);
+ paintInvalidationRect = filter->resourceBoundingBox(renderer);
if (RenderSVGResourceClipper* clipper = resources->clipper())
- repaintRect.intersect(clipper->resourceBoundingBox(renderer));
+ paintInvalidationRect.intersect(clipper->resourceBoundingBox(renderer));
if (RenderSVGResourceMasker* masker = resources->masker())
- repaintRect.intersect(masker->resourceBoundingBox(renderer));
+ paintInvalidationRect.intersect(masker->resourceBoundingBox(renderer));
}
bool SVGRenderSupport::filtersForceContainerLayout(RenderObject* object)
{
// 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.
+ // to the container, we need to issue paint invalidations the entire container.
if (!object->normalChildNeedsLayout())
return false;
return true;
}
+bool SVGRenderSupport::transformToUserSpaceAndCheckClipping(RenderObject* object, const AffineTransform& localTransform, const FloatPoint& pointInParent, FloatPoint& localPoint)
+{
+ if (!localTransform.isInvertible())
+ return false;
+ localPoint = localTransform.inverse().mapPoint(pointInParent);
+ return pointInClippingArea(object, localPoint);
+}
+
void SVGRenderSupport::applyStrokeStyleToContext(GraphicsContext* context, const RenderStyle* style, const RenderObject* object)
{
ASSERT(context);
ASSERT(object->node());
ASSERT(object->node()->isSVGElement());
- const SVGRenderStyle* svgStyle = style->svgStyle();
- ASSERT(svgStyle);
+ const SVGRenderStyle& svgStyle = style->svgStyle();
SVGLengthContext lengthContext(toSVGElement(object->node()));
- context->setStrokeThickness(svgStyle->strokeWidth()->value(lengthContext));
- context->setLineCap(svgStyle->capStyle());
- context->setLineJoin(svgStyle->joinStyle());
- context->setMiterLimit(svgStyle->strokeMiterLimit());
+ context->setStrokeThickness(svgStyle.strokeWidth()->value(lengthContext));
+ context->setLineCap(svgStyle.capStyle());
+ context->setLineJoin(svgStyle.joinStyle());
+ context->setMiterLimit(svgStyle.strokeMiterLimit());
- RefPtr<SVGLengthList> dashes = svgStyle->strokeDashArray();
+ RefPtr<SVGLengthList> dashes = svgStyle.strokeDashArray();
if (dashes->isEmpty())
return;
for (; it != itEnd; ++it)
dashArray.append(it->value(lengthContext));
- context->setLineDash(dashArray, svgStyle->strokeDashOffset()->value(lengthContext));
+ context->setLineDash(dashArray, svgStyle.strokeDashOffset()->value(lengthContext));
}
void SVGRenderSupport::applyStrokeStyleToStrokeData(StrokeData* strokeData, const RenderStyle* style, const RenderObject* object)
ASSERT(object->node());
ASSERT(object->node()->isSVGElement());
- const SVGRenderStyle* svgStyle = style->svgStyle();
- ASSERT(svgStyle);
+ const SVGRenderStyle& svgStyle = style->svgStyle();
SVGLengthContext lengthContext(toSVGElement(object->node()));
- strokeData->setThickness(svgStyle->strokeWidth()->value(lengthContext));
- strokeData->setLineCap(svgStyle->capStyle());
- strokeData->setLineJoin(svgStyle->joinStyle());
- strokeData->setMiterLimit(svgStyle->strokeMiterLimit());
+ strokeData->setThickness(svgStyle.strokeWidth()->value(lengthContext));
+ strokeData->setLineCap(svgStyle.capStyle());
+ strokeData->setLineJoin(svgStyle.joinStyle());
+ strokeData->setMiterLimit(svgStyle.strokeMiterLimit());
- RefPtr<SVGLengthList> dashes = svgStyle->strokeDashArray();
+ RefPtr<SVGLengthList> dashes = svgStyle.strokeDashArray();
if (dashes->isEmpty())
return;
for (size_t i = 0; i < length; ++i)
dashArray.append(dashes->at(i)->value(lengthContext));
- strokeData->setLineDash(dashArray, svgStyle->strokeDashOffset()->value(lengthContext));
+ strokeData->setLineDash(dashArray, svgStyle.strokeDashOffset()->value(lengthContext));
+}
+
+bool SVGRenderSupport::updateGraphicsContext(GraphicsContextStateSaver& stateSaver, RenderStyle* style, RenderObject& renderer, RenderSVGResourceMode resourceMode, const AffineTransform* additionalPaintServerTransform)
+{
+ ASSERT(style);
+
+ GraphicsContext* context = stateSaver.context();
+ if (isRenderingClipPathAsMaskImage(renderer)) {
+ if (resourceMode == ApplyToStrokeMode)
+ return false;
+ context->setAlphaAsFloat(1);
+ context->setFillColor(SVGRenderStyle::initialFillPaintColor());
+ return true;
+ }
+
+ SVGPaintServer paintServer = SVGPaintServer::requestForRenderer(renderer, style, resourceMode);
+ if (!paintServer.isValid())
+ return false;
+
+ if (additionalPaintServerTransform && paintServer.isTransformDependent())
+ paintServer.prependTransform(*additionalPaintServerTransform);
+
+ paintServer.apply(*context, resourceMode, &stateSaver);
+
+ const SVGRenderStyle& svgStyle = style->svgStyle();
+
+ if (resourceMode == ApplyToFillMode) {
+ context->setAlphaAsFloat(svgStyle.fillOpacity());
+ context->setFillRule(svgStyle.fillRule());
+ } else {
+ context->setAlphaAsFloat(svgStyle.strokeOpacity());
+ applyStrokeStyleToContext(context, style, &renderer);
+ }
+ return true;
}
bool SVGRenderSupport::isRenderableTextNode(const RenderObject* object)