Reviewed by Darin.
[WebKit-https.git] / WebCore / kcanvas / RenderSVGImage.cpp
1 /*
2     Copyright (C) 2006 Alexander Kellett <lypanov@kde.org>
3     Copyright (C) 2006 Apple Computer, Inc.
4
5     This file is part of the WebKit 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     along 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 #if SVG_SUPPORT
25 #include "RenderSVGImage.h"
26
27 #include "Attr.h"
28 #include "GraphicsContext.h"
29 #include "KCanvasMaskerQuartz.h"
30 #include "KCanvasRenderingStyle.h"
31 #include "KCanvasResourcesQuartz.h"
32 #include "KRenderingDevice.h"
33 #include "SVGAnimatedLength.h"
34 #include "SVGAnimatedPreserveAspectRatio.h"
35 #include "SVGImageElement.h"
36 #include "SVGImageElement.h"
37 #include "ksvg.h"
38 #include <wtf/OwnPtr.h>
39
40 namespace WebCore {
41
42 RenderSVGImage::RenderSVGImage(SVGImageElement *impl)
43 : RenderImage(impl)
44 {
45 }
46
47 RenderSVGImage::~RenderSVGImage()
48 {
49 }
50
51 void RenderSVGImage::adjustRectsForAspectRatio(FloatRect& destRect, FloatRect& srcRect, SVGPreserveAspectRatio *aspectRatio)
52 {
53     float origDestWidth = destRect.width();
54     float origDestHeight = destRect.height();
55     if (aspectRatio->meetOrSlice() == SVG_MEETORSLICE_MEET) {
56         float widthToHeightMultiplier = srcRect.height() / srcRect.width();
57         if (origDestHeight > (origDestWidth * widthToHeightMultiplier)) {
58             destRect.setHeight(origDestWidth * widthToHeightMultiplier);
59             switch(aspectRatio->align()) {
60                 case SVG_PRESERVEASPECTRATIO_XMINYMID:
61                 case SVG_PRESERVEASPECTRATIO_XMIDYMID:
62                 case SVG_PRESERVEASPECTRATIO_XMAXYMID:
63                     destRect.setY(origDestHeight / 2 - destRect.height() / 2);
64                     break;
65                 case SVG_PRESERVEASPECTRATIO_XMINYMAX:
66                 case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
67                 case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
68                     destRect.setY(origDestHeight - destRect.height());
69                     break;
70             }
71         }
72         if (origDestWidth > (origDestHeight / widthToHeightMultiplier)) {
73             destRect.setWidth(origDestHeight / widthToHeightMultiplier);
74             switch(aspectRatio->align()) {
75                 case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
76                 case SVG_PRESERVEASPECTRATIO_XMIDYMID:
77                 case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
78                     destRect.setX(origDestWidth / 2 - destRect.width() / 2);
79                     break;
80                 case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
81                 case SVG_PRESERVEASPECTRATIO_XMAXYMID:
82                 case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
83                     destRect.setX(origDestWidth - destRect.width());
84                     break;
85             }
86         }
87     } else if (aspectRatio->meetOrSlice() == SVG_MEETORSLICE_SLICE) {
88         float widthToHeightMultiplier = srcRect.height() / srcRect.width();
89         // if the destination height is less than the height of the image we'll be drawing
90         if (origDestHeight < (origDestWidth * widthToHeightMultiplier)) {
91             float destToSrcMultiplier = srcRect.width() / destRect.width();
92             srcRect.setHeight(destRect.height() * destToSrcMultiplier);
93             switch(aspectRatio->align()) {
94                 case SVG_PRESERVEASPECTRATIO_XMINYMID:
95                 case SVG_PRESERVEASPECTRATIO_XMIDYMID:
96                 case SVG_PRESERVEASPECTRATIO_XMAXYMID:
97                     srcRect.setY(image()->height() / 2 - srcRect.height() / 2);
98                     break;
99                 case SVG_PRESERVEASPECTRATIO_XMINYMAX:
100                 case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
101                 case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
102                     srcRect.setY(image()->height() - srcRect.height());
103                     break;
104             }
105         }
106         // if the destination width is less than the width of the image we'll be drawing
107         if (origDestWidth < (origDestHeight / widthToHeightMultiplier)) {
108             float destToSrcMultiplier = srcRect.height() / destRect.height();
109             srcRect.setWidth(destRect.width() * destToSrcMultiplier);
110             switch(aspectRatio->align()) {
111                 case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
112                 case SVG_PRESERVEASPECTRATIO_XMIDYMID:
113                 case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
114                     srcRect.setX(image()->width() / 2 - srcRect.width() / 2);
115                     break;
116                 case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
117                 case SVG_PRESERVEASPECTRATIO_XMAXYMID:
118                 case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
119                     srcRect.setX(image()->width() - srcRect.width());
120                     break;
121             }
122         }
123     }
124 }
125
126 void RenderSVGImage::paint(PaintInfo& paintInfo, int parentX, int parentY)
127 {
128     if (paintInfo.p->paintingDisabled() || (paintInfo.phase != PaintPhaseForeground) || style()->visibility() == HIDDEN)
129         return;
130     
131     KRenderingDevice* device = renderingDevice();
132     KRenderingDeviceContext* context = device->currentContext();
133     bool shouldPopContext = false;
134     if (context)
135         paintInfo.p->save();
136     else {
137         // Need to push a device context on the stack if empty.
138         context = paintInfo.p->createRenderingDeviceContext();
139         device->pushContext(context);
140         shouldPopContext = true;
141     }
142
143     context->concatCTM(QMatrix().translate(parentX, parentY));
144     context->concatCTM(localTransform());
145     translateForAttributes();
146     
147     FloatRect boundingBox = relativeBBox(true);
148     const SVGRenderStyle *svgStyle = style()->svgStyle();
149             
150     if (KCanvasClipper *clipper = getClipperById(document(), svgStyle->clipPath().mid(1)))
151         clipper->applyClip(boundingBox);
152
153     if (KCanvasMasker *masker = getMaskerById(document(), svgStyle->maskElement().mid(1)))
154         masker->applyMask(boundingBox);
155
156     KCanvasFilter *filter = getFilterById(document(), svgStyle->filter().mid(1));
157     if (filter)
158         filter->prepareFilter(boundingBox);
159     
160     OwnPtr<GraphicsContext> c(device->currentContext()->createGraphicsContext());
161
162     float opacity = style()->opacity();
163     if (opacity < 1.0f)
164         c->beginTransparencyLayer(opacity);
165
166     PaintInfo pi(paintInfo);
167     pi.p = c.get();
168     pi.r = absoluteTransform().invert().mapRect(paintInfo.r);
169
170     int x = 0, y = 0;
171     if (!shouldPaint(pi, x, y))
172         return;
173         
174     SVGImageElement *imageElt = static_cast<SVGImageElement *>(node());
175         
176     if (imageElt->preserveAspectRatio()->baseVal()->align() == SVG_PRESERVEASPECTRATIO_NONE)
177         RenderImage::paint(pi, 0, 0);
178     else {
179         FloatRect destRect(m_x, m_y, contentWidth(), contentHeight());
180         FloatRect srcRect(0, 0, image()->width(), image()->height());
181         adjustRectsForAspectRatio(destRect, srcRect, imageElt->preserveAspectRatio()->baseVal());
182         c->drawImage(image(), destRect, srcRect);
183     }
184
185     if (filter)
186         filter->applyFilter(boundingBox);
187     
188     if (opacity < 1.0f)
189         c->endTransparencyLayer();
190
191     // restore drawing state
192     if (!shouldPopContext)
193         paintInfo.p->restore();
194     else {
195         device->popContext();
196         delete context;
197     }
198 }
199
200 void RenderSVGImage::computeAbsoluteRepaintRect(IntRect& r, bool f)
201 {
202     QMatrix transform = translationForAttributes() * localTransform();
203     r = transform.mapRect(r);
204     
205     RenderImage::computeAbsoluteRepaintRect(r, f);
206 }
207
208 bool RenderSVGImage::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction)
209 {
210     QMatrix totalTransform = absoluteTransform();
211     totalTransform *= translationForAttributes();
212     double localX, localY;
213     totalTransform.invert().map(_x + _tx, _y + _ty, &localX, &localY);
214     return RenderImage::nodeAtPoint(info, (int)localX, (int)localY, 0, 0, hitTestAction);
215 }
216
217 bool RenderSVGImage::requiresLayer()
218 {
219     return false;
220 }
221
222 void RenderSVGImage::layout()
223 {
224     KHTMLAssert(needsLayout());
225     KHTMLAssert(minMaxKnown());
226
227     IntRect oldBounds;
228     bool checkForRepaint = checkForRepaintDuringLayout();
229     if (checkForRepaint)
230         oldBounds = m_absoluteBounds;
231
232     // minimum height
233     m_height = cachedImage() && cachedImage() ? intrinsicHeight() : 0;
234
235     calcWidth();
236     calcHeight();
237
238     m_absoluteBounds = getAbsoluteRepaintRect();
239
240     if (checkForRepaint)
241         repaintAfterLayoutIfNeeded(oldBounds, oldBounds);
242     
243     setNeedsLayout(false);
244 }
245
246 FloatRect RenderSVGImage::relativeBBox(bool includeStroke) const
247 {
248     SVGImageElement *image = static_cast<SVGImageElement *>(node());
249     float xOffset = image->x()->baseVal() ? image->x()->baseVal()->value() : 0;
250     float yOffset = image->y()->baseVal() ? image->y()->baseVal()->value() : 0;
251     return FloatRect(xOffset, yOffset, width(), height());
252 }
253
254 void RenderSVGImage::imageChanged(CachedImage* image)
255 {
256     RenderImage::imageChanged(image);
257     // We override to invalidate a larger rect, since SVG images can draw outside their "bounds"
258     repaintRectangle(getAbsoluteRepaintRect());
259 }
260
261 IntRect RenderSVGImage::getAbsoluteRepaintRect()
262 {
263     SVGImageElement *image = static_cast<SVGImageElement *>(node());
264     float xOffset = image->x()->baseVal() ? image->x()->baseVal()->value() : 0;
265     float yOffset = image->y()->baseVal() ? image->y()->baseVal()->value() : 0;
266     FloatRect repaintRect = absoluteTransform().mapRect(FloatRect(xOffset, yOffset, width(), height()));
267
268     // Filters can expand the bounding box
269     KCanvasFilter *filter = getFilterById(document(), style()->svgStyle()->filter().mid(1));
270     if (filter)
271         repaintRect.unite(filter->filterBBoxForItemBBox(repaintRect));
272
273     return enclosingIntRect(repaintRect);
274 }
275
276 void RenderSVGImage::absoluteRects(DeprecatedValueList<IntRect>& rects, int _tx, int _ty)
277 {
278     rects.append(getAbsoluteRepaintRect());
279 }
280
281
282 QMatrix RenderSVGImage::translationForAttributes()
283 {
284     SVGImageElement *image = static_cast<SVGImageElement *>(node());
285     float xOffset = image->x()->baseVal() ? image->x()->baseVal()->value() : 0;
286     float yOffset = image->y()->baseVal() ? image->y()->baseVal()->value() : 0;
287     return QMatrix().translate(xOffset, yOffset);
288 }
289
290 void RenderSVGImage::translateForAttributes()
291 {
292     KRenderingDeviceContext *context = renderingDevice()->currentContext();
293     context->concatCTM(translationForAttributes());
294 }
295
296 }
297
298 #endif // SVG_SUPPORT