Reviewed by eseidel.
[WebKit-https.git] / WebCore / kcanvas / RenderSVGContainer.cpp
1 /*
2     Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
3                   2004, 2005 Rob Buis <buis@kde.org>
4
5     This file is part of the KDE project
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., 59 Temple Place - Suite 330,
20     Boston, MA 02111-1307, USA.
21 */
22
23 #include "config.h"
24 #ifdef SVG_SUPPORT
25 #include "RenderSVGContainer.h"
26
27 #include "KCanvasClipper.h"
28 #include "KCanvasMasker.h"
29 #include "KCanvasRenderingStyle.h"
30 #include "KRenderingDevice.h"
31 #include "SVGStyledElement.h"
32 #include "GraphicsContext.h"
33 #include "SVGLength.h"
34 #include "SVGMarkerElement.h"
35 #include "SVGSVGElement.h"
36 #include "SVGStyledTransformableElement.h"
37
38 namespace WebCore {
39
40 RenderSVGContainer::RenderSVGContainer(SVGStyledElement *node)
41     : RenderContainer(node)
42     , m_drawsContents(true)
43     , m_slice(false)
44 {
45     setReplaced(true);
46 }
47
48 RenderSVGContainer::~RenderSVGContainer()
49 {
50 }
51
52 bool RenderSVGContainer::drawsContents() const
53 {
54     return m_drawsContents;
55 }
56
57 void RenderSVGContainer::setDrawsContents(bool drawsContents)
58 {
59     m_drawsContents = drawsContents;
60 }
61
62 AffineTransform RenderSVGContainer::localTransform() const
63 {
64     return m_matrix;
65 }
66
67 void RenderSVGContainer::setLocalTransform(const AffineTransform& matrix)
68 {
69     m_matrix = matrix;
70 }
71
72 bool RenderSVGContainer::canHaveChildren() const
73 {
74     return true;
75 }
76     
77 bool RenderSVGContainer::requiresLayer()
78 {
79     // Only allow an <svg> element to generate a layer when it's positioned in a non-SVG context
80     return (isPositioned() || isRelPositioned()) && (parent() && parent()->element() && !parent()->element()->isSVGElement());
81 }
82
83 short RenderSVGContainer::lineHeight(bool b, bool isRootLineBox) const
84 {
85     return height() + marginTop() + marginBottom();
86 }
87
88 short RenderSVGContainer::baselinePosition(bool b, bool isRootLineBox) const
89 {
90     return height() + marginTop() + marginBottom();
91 }
92
93 void RenderSVGContainer::calcMinMaxWidth()
94 {
95     ASSERT(!minMaxKnown());
96     m_minWidth = m_maxWidth = 0;
97     setMinMaxKnown();
98 }
99
100 void RenderSVGContainer::layout()
101 {
102     ASSERT(needsLayout());
103     ASSERT(minMaxKnown());
104
105     if (selfNeedsLayout())
106         calcViewport();
107
108     IntRect oldBounds;
109     bool checkForRepaint = checkForRepaintDuringLayout();
110     if (selfNeedsLayout() && checkForRepaint)
111         oldBounds = m_absoluteBounds;
112
113     RenderObject* child = firstChild();
114     while (child) {
115         if (!child->isRenderPath() || static_cast<RenderPath*>(child)->hasPercentageValues())
116             child->setNeedsLayout(true);
117         child = child->nextSibling();
118     }
119     RenderContainer::layout();
120     calcWidth();
121     calcHeight();
122
123     m_absoluteBounds = getAbsoluteRepaintRect();
124
125     if (selfNeedsLayout() && checkForRepaint)
126         repaintAfterLayoutIfNeeded(oldBounds, oldBounds);
127 }
128
129 void RenderSVGContainer::paint(PaintInfo& paintInfo, int parentX, int parentY)
130 {
131     if (paintInfo.p->paintingDisabled())
132         return;
133     
134     // No one should be transforming us via these.
135     //ASSERT(m_x == 0);
136     //ASSERT(m_y == 0);
137         
138     if (shouldPaintBackgroundOrBorder() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection)) 
139         paintBoxDecorations(paintInfo, parentX, parentY);
140     
141     if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth() && style()->visibility() == VISIBLE)
142         paintOutline(paintInfo.p, parentX, parentY, width(), height(), style());
143     
144     if (paintInfo.phase != PaintPhaseForeground || !drawsContents() || style()->visibility() == HIDDEN)
145         return;
146     
147     KCanvasFilter* filter = getFilterById(document(), style()->svgStyle()->filter().substring(1));
148     if (!firstChild() && !filter)
149         return; // Spec: groups w/o children still may render filter content.
150     
151     KRenderingDevice* device = renderingDevice();
152     KRenderingDeviceContext* deviceContext = device->currentContext();
153     bool shouldPopContext = false;
154     if (!deviceContext) {
155         // I only need to setup for KCanvas rendering if it hasn't already been done.
156         deviceContext = paintInfo.p->createRenderingDeviceContext();
157         device->pushContext(deviceContext);
158         shouldPopContext = true;
159     } else
160         paintInfo.p->save();
161     
162     if (parentX != 0 || parentY != 0) {
163         // Translate from parent offsets (khtml) to a relative transform (ksvg2/kcanvas)
164         deviceContext->concatCTM(AffineTransform().translate(parentX, parentY));
165         parentX = parentY = 0;
166     }
167     
168     if (!viewport().isEmpty()) {
169         if (style()->overflowX() != OVISIBLE)
170             paintInfo.p->addClip(enclosingIntRect(viewport())); // FIXME: Eventually we'll want float-precision clipping
171         deviceContext->concatCTM(AffineTransform().translate(viewport().x(), viewport().y()));
172     }
173     
174     if (!localTransform().isIdentity())
175         deviceContext->concatCTM(localTransform());
176     
177     if (KCanvasClipper *clipper = getClipperById(document(), style()->svgStyle()->clipPath().substring(1)))
178         clipper->applyClip(relativeBBox(true));
179
180     if (KCanvasMasker *masker = getMaskerById(document(), style()->svgStyle()->maskElement().substring(1)))
181         masker->applyMask(relativeBBox(true));
182
183     float opacity = style()->opacity();
184     if (opacity < 1.0f)
185         paintInfo.p->beginTransparencyLayer(opacity);
186
187     if (filter)
188         filter->prepareFilter(relativeBBox(true));
189     
190     if (!viewBox().isEmpty())
191         deviceContext->concatCTM(viewportTransform());
192     
193     RenderContainer::paint(paintInfo, 0, 0);
194     
195     if (filter)
196         filter->applyFilter(relativeBBox(true));
197     
198     if (opacity < 1.0f)
199         paintInfo.p->endTransparencyLayer();
200     
201     // restore drawing state
202     if (shouldPopContext) {
203         device->popContext();
204         delete deviceContext;
205     } else
206         paintInfo.p->restore();
207 }
208
209 FloatRect RenderSVGContainer::viewport() const
210 {
211     return m_viewport;
212 }
213
214 void RenderSVGContainer::calcViewport()
215 {
216     SVGElement* svgelem = static_cast<SVGElement*>(element());
217     if (svgelem->hasTagName(SVGNames::svgTag)) {
218         SVGSVGElement* svg = static_cast<SVGSVGElement*>(element());
219         double x = svg->x()->value();
220         double y = svg->y()->value();
221         double w = svg->width()->value();
222         double h = svg->height()->value();
223         m_viewport = FloatRect(x, y, w, h);
224     } else if (svgelem->hasTagName(SVGNames::markerTag)) {
225         SVGMarkerElement* svg = static_cast<SVGMarkerElement*>(element());
226             double w = svg->markerWidth()->value();
227         double h = svg->markerHeight()->value();
228         m_viewport = FloatRect(0, 0, w, h);
229     }
230 }
231
232 void RenderSVGContainer::setViewBox(const FloatRect& viewBox)
233 {
234     m_viewBox = viewBox;
235 }
236
237 FloatRect RenderSVGContainer::viewBox() const
238 {
239     return m_viewBox;
240 }
241
242 void RenderSVGContainer::setAlign(KCAlign align)
243 {
244     m_align = align;
245 }
246
247 KCAlign RenderSVGContainer::align() const
248 {
249     return m_align;
250 }
251
252 AffineTransform RenderSVGContainer::viewportTransform() const
253 {
254     if (!viewBox().isEmpty()) {
255         FloatRect viewportRect = viewport();
256         if (!parent()->isKCanvasContainer())
257             viewportRect = FloatRect(viewport().x(), viewport().y(), width(), height());
258         return getAspectRatio(viewBox(), viewportRect);
259     }
260     return AffineTransform();
261 }
262
263 IntRect RenderSVGContainer::getAbsoluteRepaintRect()
264 {
265     IntRect repaintRect;
266     
267     for (RenderObject *current = firstChild(); current != 0; current = current->nextSibling())
268         repaintRect.unite(current->getAbsoluteRepaintRect());
269     
270     // Filters can expand the bounding box
271     KCanvasFilter *filter = getFilterById(document(), style()->svgStyle()->filter().substring(1));
272     if (filter)
273         repaintRect.unite(enclosingIntRect(filter->filterBBoxForItemBBox(repaintRect)));
274
275     // FIXME: what about transform?
276
277     return repaintRect;
278 }
279
280 void RenderSVGContainer::computeAbsoluteRepaintRect(IntRect& r, bool f)
281 {
282     AffineTransform transform = localTransform();
283     r = transform.mapRect(r);
284     
285     // FIXME: consider filter
286
287     RenderContainer::computeAbsoluteRepaintRect(r, f);
288 }
289
290 AffineTransform RenderSVGContainer::absoluteTransform() const
291 {
292     return viewportTransform() * RenderContainer::absoluteTransform();
293 }
294
295 bool RenderSVGContainer::fillContains(const FloatPoint& p) const
296 {
297     RenderObject *current = firstChild();
298     while (current != 0) {
299         if (current->isRenderPath() && static_cast<RenderPath*>(current)->fillContains(p))
300             return true;
301         current = current->nextSibling();
302     }
303
304     return false;
305 }
306
307 bool RenderSVGContainer::strokeContains(const FloatPoint& p) const
308 {
309     RenderObject *current = firstChild();
310     while (current != 0) {
311         if (current->isRenderPath() && static_cast<RenderPath*>(current)->strokeContains(p))
312             return true;
313         current = current->nextSibling();
314     }
315
316     return false;
317 }
318
319 FloatRect RenderSVGContainer::relativeBBox(bool includeStroke) const
320 {
321     FloatRect rect;
322     
323     RenderObject *current = firstChild();
324     for (; current != 0; current = current->nextSibling()) {
325         FloatRect childBBox = current->relativeBBox(includeStroke);
326         FloatRect mappedBBox = current->localTransform().mapRect(childBBox);
327         rect.unite(mappedBBox);
328     }
329
330     return rect;
331 }
332
333 void RenderSVGContainer::setSlice(bool slice)
334 {
335     m_slice = slice;
336 }
337
338 bool RenderSVGContainer::slice() const
339 {
340     return m_slice;
341 }
342
343 AffineTransform RenderSVGContainer::getAspectRatio(const FloatRect& logical, const FloatRect& physical) const
344 {
345     AffineTransform temp;
346
347     float logicX = logical.x();
348     float logicY = logical.y();
349     float logicWidth = logical.width();
350     float logicHeight = logical.height();
351     float physWidth = physical.width();
352     float physHeight = physical.height();
353
354     float vpar = logicWidth / logicHeight;
355     float svgar = physWidth / physHeight;
356
357     if (align() == ALIGN_NONE) {
358         temp.scale(physWidth / logicWidth, physHeight / logicHeight);
359         temp.translate(-logicX, -logicY);
360     } else if ((vpar < svgar && !slice()) || (vpar >= svgar && slice())) {
361         temp.scale(physHeight / logicHeight, physHeight / logicHeight);
362
363         if (align() == ALIGN_XMINYMIN || align() == ALIGN_XMINYMID || align() == ALIGN_XMINYMAX)
364             temp.translate(-logicX, -logicY);
365         else if (align() == ALIGN_XMIDYMIN || align() == ALIGN_XMIDYMID || align() == ALIGN_XMIDYMAX)
366             temp.translate(-logicX - (logicWidth - physWidth * logicHeight / physHeight) / 2, -logicY);
367         else
368             temp.translate(-logicX - (logicWidth - physWidth * logicHeight / physHeight), -logicY);
369     } else {
370         temp.scale(physWidth / logicWidth, physWidth / logicWidth);
371
372         if (align() == ALIGN_XMINYMIN || align() == ALIGN_XMIDYMIN || align() == ALIGN_XMAXYMIN)
373             temp.translate(-logicX, -logicY);
374         else if (align() == ALIGN_XMINYMID || align() == ALIGN_XMIDYMID || align() == ALIGN_XMAXYMID)
375             temp.translate(-logicX, -logicY - (logicHeight - physHeight * logicWidth / physWidth) / 2);
376         else
377             temp.translate(-logicX, -logicY - (logicHeight - physHeight * logicWidth / physWidth));
378     }
379
380     return temp;
381 }
382
383 }
384
385 // vim:ts=4:noet
386 #endif // SVG_SUPPORT
387