24284691184351881b7d31ac5b8881ce47e27914
[WebKit-https.git] / WebCore / rendering / RenderSVGRoot.cpp
1 /*
2     Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org>
3                   2004, 2005, 2007, 2008, 2009 Rob Buis <buis@kde.org>
4                   2007 Eric Seidel <eric@webkit.org>
5                   2009 Google, Inc.
6
7     This library is free software; you can redistribute it and/or
8     modify it under the terms of the GNU Library General Public
9     License as published by the Free Software Foundation; either
10     version 2 of the License, or (at your option) any later version.
11
12     This library is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15     Library General Public License for more details.
16
17     You should have received a copy of the GNU Library General Public License
18     aint with this library; see the file COPYING.LIB.  If not, write to
19     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20     Boston, MA 02110-1301, USA.
21 */
22
23 #include "config.h"
24
25 #if ENABLE(SVG)
26 #include "RenderSVGRoot.h"
27
28 #include "GraphicsContext.h"
29 #include "HitTestResult.h"
30 #include "RenderSVGContainer.h"
31 #include "RenderSVGResource.h"
32 #include "RenderView.h"
33 #include "SVGLength.h"
34 #include "SVGRenderSupport.h"
35 #include "SVGResources.h"
36 #include "SVGSVGElement.h"
37 #include "SVGStyledElement.h"
38 #include "TransformState.h"
39
40 #if ENABLE(FILTERS)
41 #include "RenderSVGResourceFilter.h"
42 #endif
43
44 using namespace std;
45
46 namespace WebCore {
47
48 RenderSVGRoot::RenderSVGRoot(SVGStyledElement* node)
49     : RenderBox(node)
50     , m_isLayoutSizeChanged(false)
51     , m_needsBoundariesOrTransformUpdate(true)
52 {
53     setReplaced(true);
54 }
55
56 int RenderSVGRoot::lineHeight(bool, bool) const
57 {
58     return height() + marginTop() + marginBottom();
59 }
60
61 int RenderSVGRoot::baselinePosition(bool, bool) const
62 {
63     return height() + marginTop() + marginBottom();
64 }
65
66 void RenderSVGRoot::calcPrefWidths()
67 {
68     ASSERT(prefWidthsDirty());
69
70     int borderAndPadding = borderAndPaddingWidth();
71     int width = calcReplacedWidth(false) + borderAndPadding;
72
73     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength)
74         width = min(width, style()->maxWidth().value() + (style()->boxSizing() == CONTENT_BOX ? borderAndPadding : 0));
75
76     if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) {
77         m_minPrefWidth = 0;
78         m_maxPrefWidth = width;
79     } else
80         m_minPrefWidth = m_maxPrefWidth = width;
81
82     setPrefWidthsDirty(false);
83 }
84
85 int RenderSVGRoot::calcReplacedWidth(bool includeMaxWidth) const
86 {
87     int replacedWidth = RenderBox::calcReplacedWidth(includeMaxWidth);
88     if (!style()->width().isPercent())
89         return replacedWidth;
90
91     // FIXME: Investigate in size rounding issues
92     SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
93     return static_cast<int>(roundf(replacedWidth * svg->currentScale()));
94 }
95
96 int RenderSVGRoot::calcReplacedHeight() const
97 {
98     int replacedHeight = RenderBox::calcReplacedHeight();
99     if (!style()->height().isPercent())
100         return replacedHeight;
101
102     // FIXME: Investigate in size rounding issues
103     SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
104     return static_cast<int>(roundf(replacedHeight * svg->currentScale()));
105 }
106
107 void RenderSVGRoot::layout()
108 {
109     ASSERT(needsLayout());
110
111     // Arbitrary affine transforms are incompatible with LayoutState.
112     view()->disableLayoutState();
113
114     bool needsLayout = selfNeedsLayout();
115     LayoutRepainter repainter(*this, needsLayout && m_everHadLayout && checkForRepaintDuringLayout());
116
117     IntSize oldSize(width(), height());
118     calcWidth();
119     calcHeight();
120     calcViewport();
121
122     SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
123     m_isLayoutSizeChanged = svg->hasRelativeLengths() && oldSize != size();
124
125     SVGRenderSupport::layoutChildren(this, needsLayout);
126     m_isLayoutSizeChanged = false;
127
128     // At this point LayoutRepainter already grabbed the old bounds,
129     // recalculate them now so repaintAfterLayout() uses the new bounds.
130     if (m_needsBoundariesOrTransformUpdate) {
131         updateCachedBoundaries();
132         m_needsBoundariesOrTransformUpdate = false;
133     }
134
135     repainter.repaintAfterLayout();
136
137     view()->enableLayoutState();
138     setNeedsLayout(false);
139 }
140
141 bool RenderSVGRoot::selfWillPaint()
142 {
143 #if ENABLE(FILTERS)
144     SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this);
145     return resources && resources->filter();
146 #else
147     return false;
148 #endif
149 }
150
151 void RenderSVGRoot::paint(PaintInfo& paintInfo, int parentX, int parentY)
152 {
153     if (paintInfo.context->paintingDisabled())
154         return;
155
156     bool isVisible = style()->visibility() == VISIBLE;
157     IntPoint parentOriginInContainer(parentX, parentY);
158     IntPoint borderBoxOriginInContainer = parentOriginInContainer + parentOriginToBorderBox();
159
160     if (hasBoxDecorations() && (paintInfo.phase == PaintPhaseBlockBackground || paintInfo.phase == PaintPhaseChildBlockBackground) && isVisible)
161         paintBoxDecorations(paintInfo, borderBoxOriginInContainer.x(), borderBoxOriginInContainer.y());
162
163     if (paintInfo.phase == PaintPhaseBlockBackground)
164         return;
165
166     // An empty viewport disables rendering.  FIXME: Should we still render filters?
167     if (m_viewportSize.isEmpty())
168         return;
169
170     // Don't paint if we don't have kids, except if we have filters we should paint those.
171     if (!firstChild() && !selfWillPaint())
172         return;
173
174     // Make a copy of the PaintInfo because applyTransform will modify the damage rect.
175     PaintInfo childPaintInfo(paintInfo);
176     childPaintInfo.context->save();
177
178     // Apply initial viewport clip - not affected by overflow handling
179     childPaintInfo.context->clip(overflowClipRect(borderBoxOriginInContainer.x(), borderBoxOriginInContainer.y()));
180
181     // Convert from container offsets (html renderers) to a relative transform (svg renderers).
182     // Transform from our paint container's coordinate system to our local coords.
183     childPaintInfo.applyTransform(localToRepaintContainerTransform(parentOriginInContainer));
184
185     bool continueRendering = true;
186     if (childPaintInfo.phase == PaintPhaseForeground)
187         continueRendering = SVGRenderSupport::prepareToRenderSVGContent(this, childPaintInfo);
188
189     if (continueRendering)
190         RenderBox::paint(childPaintInfo, 0, 0);
191
192     if (childPaintInfo.phase == PaintPhaseForeground)
193         SVGRenderSupport::finishRenderSVGContent(this, childPaintInfo, paintInfo.context);
194
195     childPaintInfo.context->restore();
196
197     if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth() && isVisible)
198         paintOutline(paintInfo.context, borderBoxOriginInContainer.x(), borderBoxOriginInContainer.y(), width(), height());
199 }
200
201 void RenderSVGRoot::destroy()
202 {
203     SVGResourcesCache::clientDestroyed(this);
204     RenderBox::destroy();
205 }
206
207 void RenderSVGRoot::styleWillChange(StyleDifference diff, const RenderStyle* newStyle)
208 {
209     if (diff == StyleDifferenceLayout)
210         setNeedsBoundariesUpdate();
211     RenderBox::styleWillChange(diff, newStyle);
212 }
213
214 void RenderSVGRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
215 {
216     RenderBox::styleDidChange(diff, oldStyle);
217     SVGResourcesCache::clientStyleChanged(this, diff, style());
218 }
219
220 void RenderSVGRoot::updateFromElement()
221 {
222     RenderBox::updateFromElement();
223     SVGResourcesCache::clientUpdatedFromElement(this, style());
224 }
225
226 void RenderSVGRoot::calcViewport()
227 {
228     SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
229
230     if (!svg->hasSetContainerSize()) {
231         // In the normal case of <svg> being stand-alone or in a CSSBoxModel object we use
232         // RenderBox::width()/height() (which pulls data from RenderStyle)
233         m_viewportSize = FloatSize(width(), height());
234         return;
235     }
236
237     // In the SVGImage case grab the SVGLength values off of SVGSVGElement and use
238     // the special relativeWidthValue accessors which respect the specified containerSize
239     // FIXME: Check how SVGImage + zooming is supposed to be handled?
240     SVGLength width = svg->width();
241     SVGLength height = svg->height();
242     m_viewportSize = FloatSize(width.unitType() == LengthTypePercentage ? svg->relativeWidthValue() : width.value(svg),
243                                height.unitType() == LengthTypePercentage ? svg->relativeHeightValue() : height.value(svg));
244 }
245
246 // RenderBox methods will expect coordinates w/o any transforms in coordinates
247 // relative to our borderBox origin.  This method gives us exactly that.
248 AffineTransform RenderSVGRoot::localToBorderBoxTransform() const
249 {
250     IntSize borderAndPadding = borderOriginToContentBox();
251     SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
252     float scale = svg->currentScale();
253     FloatPoint translate = svg->currentTranslate();
254     AffineTransform ctm(scale, 0, 0, scale, borderAndPadding.width() + translate.x(), borderAndPadding.height() + translate.y());
255     return svg->viewBoxToViewTransform(width() / scale, height() / scale) * ctm;
256 }
257
258 IntSize RenderSVGRoot::parentOriginToBorderBox() const
259 {
260     return IntSize(x(), y());
261 }
262
263 IntSize RenderSVGRoot::borderOriginToContentBox() const
264 {
265     return IntSize(borderLeft() + paddingLeft(), borderTop() + paddingTop());
266 }
267
268 AffineTransform RenderSVGRoot::localToRepaintContainerTransform(const IntPoint& parentOriginInContainer) const
269 {
270     AffineTransform parentToContainer(localToParentTransform());
271     return parentToContainer.translateRight(parentOriginInContainer.x(), parentOriginInContainer.y());
272 }
273
274 const AffineTransform& RenderSVGRoot::localToParentTransform() const
275 {
276     IntSize parentToBorderBoxOffset = parentOriginToBorderBox();
277
278     AffineTransform borderBoxOriginToParentOrigin(localToBorderBoxTransform());
279     borderBoxOriginToParentOrigin.translateRight(parentToBorderBoxOffset.width(), parentToBorderBoxOffset.height());
280
281     m_localToParentTransform = borderBoxOriginToParentOrigin;
282     return m_localToParentTransform;
283 }
284
285 IntRect RenderSVGRoot::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer)
286 {
287     return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer);
288 }
289
290 void RenderSVGRoot::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed)
291 {
292     // Apply our local transforms (except for x/y translation), then our shadow, 
293     // and then call RenderBox's method to handle all the normal CSS Box model bits
294     repaintRect = localToBorderBoxTransform().mapRect(repaintRect);
295
296     // Apply initial viewport clip - not affected by overflow settings    
297     repaintRect.intersect(enclosingIntRect(FloatRect(FloatPoint(), m_viewportSize)));
298
299     const SVGRenderStyle* svgStyle = style()->svgStyle();
300     if (const ShadowData* shadow = svgStyle->shadow())
301         shadow->adjustRectForShadow(repaintRect);
302
303     RenderBox::computeRectForRepaint(repaintContainer, repaintRect, fixed);
304 }
305
306 void RenderSVGRoot::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed , bool useTransforms, TransformState& transformState) const
307 {
308     ASSERT(!fixed); // We should have no fixed content in the SVG rendering tree.
309     ASSERT(useTransforms); // mapping a point through SVG w/o respecting trasnforms is useless.
310
311     // Transform to our border box and let RenderBox transform the rest of the way.
312     transformState.applyTransform(localToBorderBoxTransform());
313     RenderBox::mapLocalToContainer(repaintContainer, fixed, useTransforms, transformState);
314 }
315
316 void RenderSVGRoot::updateCachedBoundaries()
317 {
318     m_objectBoundingBox = FloatRect();
319     m_strokeBoundingBox = FloatRect();
320     m_repaintBoundingBox = FloatRect();
321
322     SVGRenderSupport::computeContainerBoundingBoxes(this, m_objectBoundingBox, m_strokeBoundingBox, m_repaintBoundingBox);
323     SVGRenderSupport::intersectRepaintRectWithResources(this, m_repaintBoundingBox);
324
325     const SVGRenderStyle* svgStyle = style()->svgStyle();
326     if (const ShadowData* shadow = svgStyle->shadow())
327         shadow->adjustRectForShadow(m_repaintBoundingBox);
328
329     m_repaintBoundingBox.inflate(borderAndPaddingWidth());
330 }
331
332 bool RenderSVGRoot::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction)
333 {
334     IntPoint pointInContainer(_x, _y);
335     IntSize containerToParentOffset(_tx, _ty);
336
337     IntPoint pointInParent = pointInContainer - containerToParentOffset;
338     IntPoint pointInBorderBox = pointInParent - parentOriginToBorderBox();
339
340     // Note: For now, we're ignoring hits to border and padding for <svg>
341     IntPoint pointInContentBox = pointInBorderBox - borderOriginToContentBox();
342     if (!contentBoxRect().contains(pointInContentBox))
343         return false;
344
345     IntPoint localPoint = localToParentTransform().inverse().mapPoint(pointInParent);
346
347     for (RenderObject* child = lastChild(); child; child = child->previousSibling()) {
348         if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) {
349             // FIXME: CSS/HTML assumes the local point is relative to the border box, right?
350             updateHitTestResult(result, pointInBorderBox);
351             // FIXME: nodeAtFloatPoint() doesn't handle rect-based hit tests yet.
352             result.addNodeToRectBasedTestResult(child->node(), _x, _y);
353             return true;
354         }
355     }
356
357     // Spec: Only graphical elements can be targeted by the mouse, so we don't check self here.
358     // 16.4: "If there are no graphics elements whose relevant graphics content is under the pointer (i.e., there is no target element), the event is not dispatched."
359     return false;
360 }
361
362 }
363
364 #endif // ENABLE(SVG)