2
* Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org>
3
* Copyright (C) 2004, 2005, 2007, 2008, 2009 Rob Buis <buis@kde.org>
4
* Copyright (C) 2007 Eric Seidel <eric@webkit.org>
5
* Copyright (C) 2009 Google, Inc.
6
* Copyright (C) Research In Motion Limited 2011. All rights reserved.
8
* This library is free software; you can redistribute it and/or
9
* modify it under the terms of the GNU Library General Public
10
* License as published by the Free Software Foundation; either
11
* version 2 of the License, or (at your option) any later version.
13
* This library is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
* Library General Public License for more details.
18
* You should have received a copy of the GNU Library General Public License
19
* along with this library; see the file COPYING.LIB. If not, write to
20
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21
* Boston, MA 02110-1301, USA.
27
#include "RenderSVGRoot.h"
30
#include "ChromeClient.h"
32
#include "GraphicsContext.h"
33
#include "HitTestResult.h"
34
#include "LayoutRepainter.h"
36
#include "RenderPart.h"
37
#include "RenderSVGContainer.h"
38
#include "RenderSVGResource.h"
39
#include "RenderSVGResourceContainer.h"
40
#include "RenderView.h"
41
#include "SVGLength.h"
42
#include "SVGRenderingContext.h"
43
#include "SVGResources.h"
44
#include "SVGResourcesCache.h"
45
#include "SVGSVGElement.h"
46
#include "SVGStyledElement.h"
47
#include "SVGViewSpec.h"
48
#include "TransformState.h"
51
#include "RenderSVGResourceFilter.h"
58
RenderSVGRoot::RenderSVGRoot(SVGStyledElement* node)
59
: RenderReplaced(node)
60
, m_objectBoundingBoxValid(false)
61
, m_isLayoutSizeChanged(false)
62
, m_needsBoundariesOrTransformUpdate(true)
63
, m_hasSVGShadow(false)
67
RenderSVGRoot::~RenderSVGRoot()
71
void RenderSVGRoot::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio, bool& isPercentageIntrinsicSize) const
73
// Spec: http://www.w3.org/TR/SVG/coords.html#IntrinsicSizing
74
// SVG needs to specify how to calculate some intrinsic sizing properties to enable inclusion within other languages.
75
// The intrinsic width and height of the viewport of SVG content must be determined from the āwidthā and āheightā attributes.
76
// If either of these are not specified, a value of '100%' must be assumed. Note: the āwidthā and āheightā attributes are not
77
// the same as the CSS width and height properties. Specifically, percentage values do not provide an intrinsic width or height,
78
// and do not indicate a percentage of the containing block. Rather, once the viewport is established, they indicate the portion
79
// of the viewport that is actually covered by image data.
80
SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
81
Length intrinsicWidthAttribute = svg->intrinsicWidth(SVGSVGElement::IgnoreCSSProperties);
82
Length intrinsicHeightAttribute = svg->intrinsicHeight(SVGSVGElement::IgnoreCSSProperties);
84
// The intrinsic aspect ratio of the viewport of SVG content is necessary for example, when including SVG from an āobjectā
85
// element in HTML styled with CSS. It is possible (indeed, common) for an SVG graphic to have an intrinsic aspect ratio but
86
// not to have an intrinsic width or height. The intrinsic aspect ratio must be calculated based upon the following rules:
87
// - The aspect ratio is calculated by dividing a width by a height.
88
// - If the āwidthā and āheightā of the rootmost āsvgā element are both specified with unit identifiers (in, mm, cm, pt, pc,
89
// px, em, ex) or in user units, then the aspect ratio is calculated from the āwidthā and āheightā attributes after
90
// resolving both values to user units.
91
if (intrinsicWidthAttribute.isFixed() || intrinsicHeightAttribute.isFixed()) {
92
if (intrinsicWidthAttribute.isFixed())
93
intrinsicSize.setWidth(floatValueForLength(intrinsicWidthAttribute, 0));
94
if (intrinsicHeightAttribute.isFixed())
95
intrinsicSize.setHeight(floatValueForLength(intrinsicHeightAttribute, 0));
96
if (!intrinsicSize.isEmpty())
97
intrinsicRatio = intrinsicSize.width() / static_cast<double>(intrinsicSize.height());
101
// - If either/both of the āwidthā and āheightā of the rootmost āsvgā element are in percentage units (or omitted), the
102
// aspect ratio is calculated from the width and height values of the āviewBoxā specified for the current SVG document
103
// fragment. If the āviewBoxā is not correctly specified, or set to 'none', the intrinsic aspect ratio cannot be
104
// calculated and is considered unspecified.
105
intrinsicSize = svg->viewBox().size();
106
if (!intrinsicSize.isEmpty()) {
107
// The viewBox can only yield an intrinsic ratio, not an intrinsic size.
108
intrinsicRatio = intrinsicSize.width() / static_cast<double>(intrinsicSize.height());
109
intrinsicSize = FloatSize();
113
// If our intrinsic size is in percentage units, return those to the caller through the intrinsicSize. Notify the caller
114
// about the special situation, by setting isPercentageIntrinsicSize=true, so it knows how to interpret the return values.
115
if (intrinsicWidthAttribute.isPercent() && intrinsicHeightAttribute.isPercent()) {
116
isPercentageIntrinsicSize = true;
117
intrinsicSize = FloatSize(intrinsicWidthAttribute.percent(), intrinsicHeightAttribute.percent());
121
bool RenderSVGRoot::isEmbeddedThroughSVGImage() const
126
Frame* frame = node()->document()->frame();
130
// Test whether we're embedded through an img.
131
if (!frame->page() || !frame->page()->chrome())
134
ChromeClient* chromeClient = frame->page()->chrome()->client();
135
if (!chromeClient || !chromeClient->isSVGImageChromeClient())
141
bool RenderSVGRoot::isEmbeddedThroughFrameContainingSVGDocument() const
146
Frame* frame = node()->document()->frame();
150
// If our frame has an owner renderer, we're embedded through eg. object/embed/iframe,
151
// but we only negotiate if we're in an SVG document.
152
if (!frame->ownerRenderer())
154
return frame->document()->isSVGDocument();
157
static inline LayoutUnit resolveLengthAttributeForSVG(const Length& length, float scale, float maxSize, RenderView* renderView)
159
return static_cast<LayoutUnit>(valueForLength(length, maxSize, renderView) * (length.isFixed() ? scale : 1));
162
LayoutUnit RenderSVGRoot::computeReplacedLogicalWidth(bool includeMaxWidth) const
164
SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
167
// When we're embedded through SVGImage (border-image/background-image/<html:img>/...) we're forced to resize to a specific size.
168
if (!m_containerSize.isEmpty())
169
return m_containerSize.width();
171
if (style()->logicalWidth().isSpecified() || style()->logicalMaxWidth().isSpecified())
172
return RenderReplaced::computeReplacedLogicalWidth(includeMaxWidth);
174
if (svg->widthAttributeEstablishesViewport())
175
return resolveLengthAttributeForSVG(svg->intrinsicWidth(SVGSVGElement::IgnoreCSSProperties), style()->effectiveZoom(), containingBlock()->availableLogicalWidth(), view());
177
// SVG embedded through object/embed/iframe.
178
if (isEmbeddedThroughFrameContainingSVGDocument())
179
return document()->frame()->ownerRenderer()->availableLogicalWidth();
181
// SVG embedded via SVGImage (background-image/border-image/etc) / Inline SVG.
182
return RenderReplaced::computeReplacedLogicalWidth(includeMaxWidth);
185
LayoutUnit RenderSVGRoot::computeReplacedLogicalHeight() const
187
SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
190
// When we're embedded through SVGImage (border-image/background-image/<html:img>/...) we're forced to resize to a specific size.
191
if (!m_containerSize.isEmpty())
192
return m_containerSize.height();
194
if (style()->logicalHeight().isSpecified() || style()->logicalMaxHeight().isSpecified())
195
return RenderReplaced::computeReplacedLogicalHeight();
197
if (svg->heightAttributeEstablishesViewport()) {
198
Length height = svg->intrinsicHeight(SVGSVGElement::IgnoreCSSProperties);
199
if (height.isPercent()) {
200
RenderBlock* cb = containingBlock();
202
while (cb->isAnonymous()) {
203
cb = cb->containingBlock();
204
cb->addPercentHeightDescendant(const_cast<RenderSVGRoot*>(this));
207
RenderBlock::removePercentHeightDescendant(const_cast<RenderSVGRoot*>(this));
209
return resolveLengthAttributeForSVG(height, style()->effectiveZoom(), containingBlock()->availableLogicalHeight(), view());
212
// SVG embedded through object/embed/iframe.
213
if (isEmbeddedThroughFrameContainingSVGDocument())
214
return document()->frame()->ownerRenderer()->availableLogicalHeight();
216
// SVG embedded via SVGImage (background-image/border-image/etc) / Inline SVG.
217
return RenderReplaced::computeReplacedLogicalHeight();
220
void RenderSVGRoot::layout()
222
StackStats::LayoutCheckPoint layoutCheckPoint;
223
ASSERT(needsLayout());
225
m_resourcesNeedingToInvalidateClients.clear();
227
// Arbitrary affine transforms are incompatible with LayoutState.
228
LayoutStateDisabler layoutStateDisabler(view());
230
bool needsLayout = selfNeedsLayout();
231
LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && needsLayout);
233
LayoutSize oldSize = size();
234
updateLogicalWidth();
235
updateLogicalHeight();
236
buildLocalToBorderBoxTransform();
238
SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
239
m_isLayoutSizeChanged = needsLayout || (svg->hasRelativeLengths() && oldSize != size());
240
SVGRenderSupport::layoutChildren(this, needsLayout || SVGRenderSupport::filtersForceContainerLayout(this));
242
if (!m_resourcesNeedingToInvalidateClients.isEmpty()) {
243
// Invalidate resource clients, which may mark some nodes for layout.
244
HashSet<RenderSVGResourceContainer*>::iterator end = m_resourcesNeedingToInvalidateClients.end();
245
for (HashSet<RenderSVGResourceContainer*>::iterator it = m_resourcesNeedingToInvalidateClients.begin(); it != end; ++it)
246
(*it)->removeAllClientsFromCache();
248
m_isLayoutSizeChanged = false;
249
SVGRenderSupport::layoutChildren(this, false);
252
// At this point LayoutRepainter already grabbed the old bounds,
253
// recalculate them now so repaintAfterLayout() uses the new bounds.
254
if (m_needsBoundariesOrTransformUpdate) {
255
updateCachedBoundaries();
256
m_needsBoundariesOrTransformUpdate = false;
259
repainter.repaintAfterLayout();
261
setNeedsLayout(false);
264
void RenderSVGRoot::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
266
// An empty viewport disables rendering.
267
if (pixelSnappedBorderBoxRect().isEmpty())
270
// Don't paint, if the context explicitely disabled it.
271
if (paintInfo.context->paintingDisabled())
275
if (Frame* frame = this->frame())
276
page = frame->page();
278
// Don't paint if we don't have kids, except if we have filters we should paint those.
280
SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this);
281
if (!resources || !resources->filter()) {
282
if (page && paintInfo.phase == PaintPhaseForeground)
283
page->addRelevantUnpaintedObject(this, visualOverflowRect());
288
if (page && paintInfo.phase == PaintPhaseForeground)
289
page->addRelevantRepaintedObject(this, visualOverflowRect());
291
// Make a copy of the PaintInfo because applyTransform will modify the damage rect.
292
PaintInfo childPaintInfo(paintInfo);
293
childPaintInfo.context->save();
295
// Apply initial viewport clip - not affected by overflow handling
296
childPaintInfo.context->clip(pixelSnappedIntRect(overflowClipRect(paintOffset, paintInfo.renderRegion)));
298
// Convert from container offsets (html renderers) to a relative transform (svg renderers).
299
// Transform from our paint container's coordinate system to our local coords.
300
IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset);
301
childPaintInfo.applyTransform(AffineTransform::translation(adjustedPaintOffset.x(), adjustedPaintOffset.y()) * localToBorderBoxTransform());
303
// SVGRenderingContext must be destroyed before we restore the childPaintInfo.context, because a filter may have
304
// changed the context and it is only reverted when the SVGRenderingContext destructor finishes applying the filter.
306
SVGRenderingContext renderingContext;
307
bool continueRendering = true;
308
if (childPaintInfo.phase == PaintPhaseForeground) {
309
renderingContext.prepareToRenderSVGContent(this, childPaintInfo);
310
continueRendering = renderingContext.isRenderingPrepared();
313
if (continueRendering)
314
RenderBox::paint(childPaintInfo, LayoutPoint());
317
childPaintInfo.context->restore();
320
void RenderSVGRoot::willBeDestroyed()
322
RenderBlock::removePercentHeightDescendant(const_cast<RenderSVGRoot*>(this));
324
SVGResourcesCache::clientDestroyed(this);
325
RenderReplaced::willBeDestroyed();
328
void RenderSVGRoot::styleWillChange(StyleDifference diff, const RenderStyle* newStyle)
330
if (diff == StyleDifferenceLayout)
331
setNeedsBoundariesUpdate();
332
RenderReplaced::styleWillChange(diff, newStyle);
335
void RenderSVGRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
337
RenderReplaced::styleDidChange(diff, oldStyle);
338
SVGResourcesCache::clientStyleChanged(this, diff, style());
341
void RenderSVGRoot::addChild(RenderObject* child, RenderObject* beforeChild)
343
RenderReplaced::addChild(child, beforeChild);
344
SVGResourcesCache::clientWasAddedToTree(child, child->style());
347
void RenderSVGRoot::removeChild(RenderObject* child)
349
SVGResourcesCache::clientWillBeRemovedFromTree(child);
350
RenderReplaced::removeChild(child);
353
// RenderBox methods will expect coordinates w/o any transforms in coordinates
354
// relative to our borderBox origin. This method gives us exactly that.
355
void RenderSVGRoot::buildLocalToBorderBoxTransform()
357
SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
358
float scale = style()->effectiveZoom();
359
FloatPoint translate = svg->currentTranslate();
360
LayoutSize borderAndPadding(borderLeft() + paddingLeft(), borderTop() + paddingTop());
361
m_localToBorderBoxTransform = svg->viewBoxToViewTransform(contentWidth() / scale, contentHeight() / scale);
362
if (borderAndPadding.isEmpty() && scale == 1 && translate == FloatPoint::zero())
364
m_localToBorderBoxTransform = AffineTransform(scale, 0, 0, scale, borderAndPadding.width() + translate.x(), borderAndPadding.height() + translate.y()) * m_localToBorderBoxTransform;
367
const AffineTransform& RenderSVGRoot::localToParentTransform() const
369
// Slightly optimized version of m_localToParentTransform = AffineTransform::translation(x(), y()) * m_localToBorderBoxTransform;
370
m_localToParentTransform = m_localToBorderBoxTransform;
372
m_localToParentTransform.setE(m_localToParentTransform.e() + roundToInt(x()));
374
m_localToParentTransform.setF(m_localToParentTransform.f() + roundToInt(y()));
375
return m_localToParentTransform;
378
LayoutRect RenderSVGRoot::clippedOverflowRectForRepaint(const RenderLayerModelObject* repaintContainer) const
380
return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer);
383
void RenderSVGRoot::computeFloatRectForRepaint(const RenderLayerModelObject* repaintContainer, FloatRect& repaintRect, bool fixed) const
385
// Apply our local transforms (except for x/y translation), then our shadow,
386
// and then call RenderBox's method to handle all the normal CSS Box model bits
387
repaintRect = m_localToBorderBoxTransform.mapRect(repaintRect);
389
const SVGRenderStyle* svgStyle = style()->svgStyle();
390
if (const ShadowData* shadow = svgStyle->shadow())
391
shadow->adjustRectForShadow(repaintRect);
393
// Apply initial viewport clip - not affected by overflow settings
394
repaintRect.intersect(pixelSnappedBorderBoxRect());
396
LayoutRect rect = enclosingIntRect(repaintRect);
397
RenderReplaced::computeRectForRepaint(repaintContainer, rect, fixed);
401
// This method expects local CSS box coordinates.
402
// Callers with local SVG viewport coordinates should first apply the localToBorderBoxTransform
403
// to convert from SVG viewport coordinates to local CSS box coordinates.
404
void RenderSVGRoot::mapLocalToContainer(const RenderLayerModelObject* repaintContainer, TransformState& transformState, MapCoordinatesFlags mode, bool* wasFixed) const
406
ASSERT(mode & ~IsFixed); // We should have no fixed content in the SVG rendering tree.
407
ASSERT(mode & UseTransforms); // mapping a point through SVG w/o respecting trasnforms is useless.
409
RenderReplaced::mapLocalToContainer(repaintContainer, transformState, mode | ApplyContainerFlip, wasFixed);
412
const RenderObject* RenderSVGRoot::pushMappingToContainer(const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap& geometryMap) const
414
return RenderReplaced::pushMappingToContainer(ancestorToStopAt, geometryMap);
417
void RenderSVGRoot::updateCachedBoundaries()
419
SVGRenderSupport::computeContainerBoundingBoxes(this, m_objectBoundingBox, m_objectBoundingBoxValid, m_strokeBoundingBox, m_repaintBoundingBoxExcludingShadow);
420
SVGRenderSupport::intersectRepaintRectWithResources(this, m_repaintBoundingBoxExcludingShadow);
421
m_repaintBoundingBoxExcludingShadow.inflate(borderAndPaddingWidth());
423
m_repaintBoundingBox = m_repaintBoundingBoxExcludingShadow;
424
SVGRenderSupport::intersectRepaintRectWithShadows(this, m_repaintBoundingBox);
427
bool RenderSVGRoot::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
429
LayoutPoint pointInParent = locationInContainer.point() - toLayoutSize(accumulatedOffset);
430
LayoutPoint pointInBorderBox = pointInParent - toLayoutSize(location());
432
// Only test SVG content if the point is in our content box.
433
// FIXME: This should be an intersection when rect-based hit tests are supported by nodeAtFloatPoint.
434
if (contentBoxRect().contains(pointInBorderBox)) {
435
FloatPoint localPoint = localToParentTransform().inverse().mapPoint(FloatPoint(pointInParent));
437
for (RenderObject* child = lastChild(); child; child = child->previousSibling()) {
438
// FIXME: nodeAtFloatPoint() doesn't handle rect-based hit tests yet.
439
if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) {
440
updateHitTestResult(result, pointInBorderBox);
441
if (!result.addNodeToRectBasedTestResult(child->node(), request, locationInContainer))
447
// If we didn't early exit above, we've just hit the container <svg> element. Unlike SVG 1.1, 2nd Edition allows container elements to be hit.
448
if (hitTestAction == HitTestBlockBackground && visibleToHitTesting()) {
449
// Only return true here, if the last hit testing phase 'BlockBackground' is executed. If we'd return true in the 'Foreground' phase,
450
// hit testing would stop immediately. For SVG only trees this doesn't matter. Though when we have a <foreignObject> subtree we need
451
// to be able to detect hits on the background of a <div> element. If we'd return true here in the 'Foreground' phase, we are not able
452
// to detect these hits anymore.
453
LayoutRect boundsRect(accumulatedOffset + location(), size());
454
if (locationInContainer.intersects(boundsRect)) {
455
updateHitTestResult(result, pointInBorderBox);
456
if (!result.addNodeToRectBasedTestResult(node(), request, locationInContainer, boundsRect))
464
bool RenderSVGRoot::hasRelativeDimensions() const
466
SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
469
return svg->intrinsicHeight(SVGSVGElement::IgnoreCSSProperties).isPercent() || svg->intrinsicWidth(SVGSVGElement::IgnoreCSSProperties).isPercent();
472
bool RenderSVGRoot::hasRelativeLogicalHeight() const
474
SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
477
return svg->intrinsicHeight(SVGSVGElement::IgnoreCSSProperties).isPercent();
480
void RenderSVGRoot::addResourceForClientInvalidation(RenderSVGResourceContainer* resource)
482
RenderObject* svgRoot = resource->parent();
483
while (svgRoot && !svgRoot->isSVGRoot())
484
svgRoot = svgRoot->parent();
487
static_cast<RenderSVGRoot*>(svgRoot)->m_resourcesNeedingToInvalidateClients.add(resource);
492
#endif // ENABLE(SVG)