2010-08-30 Eric Seidel <eric@webkit.org>
[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 {
52     setReplaced(true);
53 }
54
55 int RenderSVGRoot::lineHeight(bool, bool) const
56 {
57     return height() + marginTop() + marginBottom();
58 }
59
60 int RenderSVGRoot::baselinePosition(bool, bool) const
61 {
62     return height() + marginTop() + marginBottom();
63 }
64
65 void RenderSVGRoot::calcPrefWidths()
66 {
67     ASSERT(prefWidthsDirty());
68
69     int borderAndPadding = borderAndPaddingWidth();
70     int width = calcReplacedWidth(false) + borderAndPadding;
71
72     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength)
73         width = min(width, style()->maxWidth().value() + (style()->boxSizing() == CONTENT_BOX ? borderAndPadding : 0));
74
75     if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) {
76         m_minPrefWidth = 0;
77         m_maxPrefWidth = width;
78     } else
79         m_minPrefWidth = m_maxPrefWidth = width;
80
81     setPrefWidthsDirty(false);
82 }
83
84 int RenderSVGRoot::calcReplacedWidth(bool includeMaxWidth) const
85 {
86     int replacedWidth = RenderBox::calcReplacedWidth(includeMaxWidth);
87     if (!style()->width().isPercent())
88         return replacedWidth;
89
90     // FIXME: Investigate in size rounding issues
91     SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
92     return static_cast<int>(roundf(replacedWidth * svg->currentScale()));
93 }
94
95 int RenderSVGRoot::calcReplacedHeight() const
96 {
97     int replacedHeight = RenderBox::calcReplacedHeight();
98     if (!style()->height().isPercent())
99         return replacedHeight;
100
101     // FIXME: Investigate in size rounding issues
102     SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
103     return static_cast<int>(roundf(replacedHeight * svg->currentScale()));
104 }
105
106 void RenderSVGRoot::layout()
107 {
108     ASSERT(needsLayout());
109
110     // Arbitrary affine transforms are incompatible with LayoutState.
111     view()->disableLayoutState();
112
113     bool needsLayout = selfNeedsLayout();
114     LayoutRepainter repainter(*this, needsLayout && m_everHadLayout && checkForRepaintDuringLayout());
115
116     IntSize oldSize(width(), height());
117     calcWidth();
118     calcHeight();
119     calcViewport();
120
121     SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
122     m_isLayoutSizeChanged = svg->hasRelativeLengths() && oldSize != size();
123
124     SVGRenderSupport::layoutChildren(this, needsLayout);
125     m_isLayoutSizeChanged = false;
126
127     repainter.repaintAfterLayout();
128
129     view()->enableLayoutState();
130     setNeedsLayout(false);
131 }
132
133 bool RenderSVGRoot::selfWillPaint()
134 {
135 #if ENABLE(FILTERS)
136     SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this);
137     return resources && resources->filter();
138 #else
139     return false;
140 #endif
141 }
142
143 void RenderSVGRoot::paint(PaintInfo& paintInfo, int parentX, int parentY)
144 {
145     if (paintInfo.context->paintingDisabled())
146         return;
147
148     bool isVisible = style()->visibility() == VISIBLE;
149     IntPoint parentOriginInContainer(parentX, parentY);
150     IntPoint borderBoxOriginInContainer = parentOriginInContainer + parentOriginToBorderBox();
151
152     if (hasBoxDecorations() && (paintInfo.phase == PaintPhaseBlockBackground || paintInfo.phase == PaintPhaseChildBlockBackground) && isVisible)
153         paintBoxDecorations(paintInfo, borderBoxOriginInContainer.x(), borderBoxOriginInContainer.y());
154
155     if (paintInfo.phase == PaintPhaseBlockBackground)
156         return;
157
158     // An empty viewport disables rendering.  FIXME: Should we still render filters?
159     if (m_viewportSize.isEmpty())
160         return;
161
162     // Don't paint if we don't have kids, except if we have filters we should paint those.
163     if (!firstChild() && !selfWillPaint())
164         return;
165
166     // Make a copy of the PaintInfo because applyTransform will modify the damage rect.
167     PaintInfo childPaintInfo(paintInfo);
168     childPaintInfo.context->save();
169
170     // Apply initial viewport clip - not affected by overflow handling
171     childPaintInfo.context->clip(overflowClipRect(borderBoxOriginInContainer.x(), borderBoxOriginInContainer.y()));
172
173     // Convert from container offsets (html renderers) to a relative transform (svg renderers).
174     // Transform from our paint container's coordinate system to our local coords.
175     childPaintInfo.applyTransform(localToRepaintContainerTransform(parentOriginInContainer));
176
177     bool continueRendering = true;
178     if (childPaintInfo.phase == PaintPhaseForeground)
179         continueRendering = SVGRenderSupport::prepareToRenderSVGContent(this, childPaintInfo);
180
181     if (continueRendering)
182         RenderBox::paint(childPaintInfo, 0, 0);
183
184     if (childPaintInfo.phase == PaintPhaseForeground)
185         SVGRenderSupport::finishRenderSVGContent(this, childPaintInfo, paintInfo.context);
186
187     childPaintInfo.context->restore();
188
189     if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth() && isVisible)
190         paintOutline(paintInfo.context, borderBoxOriginInContainer.x(), borderBoxOriginInContainer.y(), width(), height());
191 }
192
193 void RenderSVGRoot::destroy()
194 {
195     SVGResourcesCache::clientDestroyed(this);
196     RenderBox::destroy();
197 }
198
199 void RenderSVGRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
200 {
201     RenderBox::styleDidChange(diff, oldStyle);
202     SVGResourcesCache::clientStyleChanged(this, diff, style());
203 }
204
205 void RenderSVGRoot::updateFromElement()
206 {
207     RenderBox::updateFromElement();
208     SVGResourcesCache::clientUpdatedFromElement(this, style());
209 }
210
211 void RenderSVGRoot::calcViewport()
212 {
213     SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
214
215     if (!svg->hasSetContainerSize()) {
216         // In the normal case of <svg> being stand-alone or in a CSSBoxModel object we use
217         // RenderBox::width()/height() (which pulls data from RenderStyle)
218         m_viewportSize = FloatSize(width(), height());
219         return;
220     }
221
222     // In the SVGImage case grab the SVGLength values off of SVGSVGElement and use
223     // the special relativeWidthValue accessors which respect the specified containerSize
224     // FIXME: Check how SVGImage + zooming is supposed to be handled?
225     SVGLength width = svg->width();
226     SVGLength height = svg->height();
227     m_viewportSize = FloatSize(width.unitType() == LengthTypePercentage ? svg->relativeWidthValue() : width.value(svg),
228                                height.unitType() == LengthTypePercentage ? svg->relativeHeightValue() : height.value(svg));
229 }
230
231 // RenderBox methods will expect coordinates w/o any transforms in coordinates
232 // relative to our borderBox origin.  This method gives us exactly that.
233 AffineTransform RenderSVGRoot::localToBorderBoxTransform() const
234 {
235     IntSize borderAndPadding = borderOriginToContentBox();
236     SVGSVGElement* svg = static_cast<SVGSVGElement*>(node());
237     float scale = svg->currentScale();
238     FloatPoint translate = svg->currentTranslate();
239     AffineTransform ctm(scale, 0, 0, scale, borderAndPadding.width() + translate.x(), borderAndPadding.height() + translate.y());
240     return svg->viewBoxToViewTransform(width() / scale, height() / scale) * ctm;
241 }
242
243 IntSize RenderSVGRoot::parentOriginToBorderBox() const
244 {
245     return IntSize(x(), y());
246 }
247
248 IntSize RenderSVGRoot::borderOriginToContentBox() const
249 {
250     return IntSize(borderLeft() + paddingLeft(), borderTop() + paddingTop());
251 }
252
253 AffineTransform RenderSVGRoot::localToRepaintContainerTransform(const IntPoint& parentOriginInContainer) const
254 {
255     AffineTransform parentToContainer(localToParentTransform());
256     return parentToContainer.translateRight(parentOriginInContainer.x(), parentOriginInContainer.y());
257 }
258
259 const AffineTransform& RenderSVGRoot::localToParentTransform() const
260 {
261     IntSize parentToBorderBoxOffset = parentOriginToBorderBox();
262
263     AffineTransform borderBoxOriginToParentOrigin(localToBorderBoxTransform());
264     borderBoxOriginToParentOrigin.translateRight(parentToBorderBoxOffset.width(), parentToBorderBoxOffset.height());
265
266     m_localToParentTransform = borderBoxOriginToParentOrigin;
267     return m_localToParentTransform;
268 }
269
270 FloatRect RenderSVGRoot::objectBoundingBox() const
271 {
272     return SVGRenderSupport::computeContainerBoundingBox(this, SVGRenderSupport::ObjectBoundingBox);
273 }
274
275 FloatRect RenderSVGRoot::strokeBoundingBox() const
276 {
277     return SVGRenderSupport::computeContainerBoundingBox(this, SVGRenderSupport::StrokeBoundingBox);
278 }
279
280 FloatRect RenderSVGRoot::repaintRectInLocalCoordinates() const
281 {
282     FloatRect repaintRect = SVGRenderSupport::computeContainerBoundingBox(this, SVGRenderSupport::RepaintBoundingBox);
283
284     const SVGRenderStyle* svgStyle = style()->svgStyle();
285     if (const ShadowData* shadow = svgStyle->shadow())
286         shadow->adjustRectForShadow(repaintRect);
287
288     repaintRect.inflate(borderAndPaddingWidth());
289     return repaintRect;
290 }
291
292 IntRect RenderSVGRoot::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer)
293 {
294     return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer);
295 }
296
297 void RenderSVGRoot::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed)
298 {
299     // Apply our local transforms (except for x/y translation), then our shadow, 
300     // and then call RenderBox's method to handle all the normal CSS Box model bits
301     repaintRect = localToBorderBoxTransform().mapRect(repaintRect);
302
303     // Apply initial viewport clip - not affected by overflow settings    
304     repaintRect.intersect(enclosingIntRect(FloatRect(FloatPoint(), m_viewportSize)));
305
306     const SVGRenderStyle* svgStyle = style()->svgStyle();
307     if (const ShadowData* shadow = svgStyle->shadow())
308         shadow->adjustRectForShadow(repaintRect);
309
310     RenderBox::computeRectForRepaint(repaintContainer, repaintRect, fixed);
311 }
312
313 void RenderSVGRoot::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed , bool useTransforms, TransformState& transformState) const
314 {
315     ASSERT(!fixed); // We should have no fixed content in the SVG rendering tree.
316     ASSERT(useTransforms); // mapping a point through SVG w/o respecting trasnforms is useless.
317
318     // Transform to our border box and let RenderBox transform the rest of the way.
319     transformState.applyTransform(localToBorderBoxTransform());
320     RenderBox::mapLocalToContainer(repaintContainer, fixed, useTransforms, transformState);
321 }
322
323 bool RenderSVGRoot::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction)
324 {
325     IntPoint pointInContainer(_x, _y);
326     IntSize containerToParentOffset(_tx, _ty);
327
328     IntPoint pointInParent = pointInContainer - containerToParentOffset;
329     IntPoint pointInBorderBox = pointInParent - parentOriginToBorderBox();
330
331     // Note: For now, we're ignoring hits to border and padding for <svg>
332     IntPoint pointInContentBox = pointInBorderBox - borderOriginToContentBox();
333     if (!contentBoxRect().contains(pointInContentBox))
334         return false;
335
336     IntPoint localPoint = localToParentTransform().inverse().mapPoint(pointInParent);
337
338     for (RenderObject* child = lastChild(); child; child = child->previousSibling()) {
339         if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) {
340             // FIXME: CSS/HTML assumes the local point is relative to the border box, right?
341             updateHitTestResult(result, pointInBorderBox);
342             // FIXME: nodeAtFloatPoint() doesn't handle rect-based hit tests yet.
343             result.addNodeToRectBasedTestResult(child->node(), _x, _y);
344             return true;
345         }
346     }
347
348     // Spec: Only graphical elements can be targeted by the mouse, so we don't check self here.
349     // 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."
350     return false;
351 }
352
353 }
354
355 #endif // ENABLE(SVG)