58fe14ad4418c5711cea6676ee0fc94005602fed
[WebKit-https.git] / WebCore / rendering / render_image.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
5  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
6  *           (C) 2000 Dirk Mueller (mueller@kde.org)
7  * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, Inc.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB.  If not, write to
21  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  *
24  */
25
26 #include "config.h"
27 #include "render_image.h"
28
29 #include "CachedImage.h"
30 #include "DocumentImpl.h"
31 #include "HTMLInputElementImpl.h"
32 #include "helper.h"
33 #include "html_imageimpl.h"
34 #include "render_canvas.h"
35 #include <qpainter.h>
36 #include "Pen.h"
37 #include "htmlnames.h"
38
39 namespace WebCore {
40
41 using namespace HTMLNames;
42
43 RenderImage::RenderImage(NodeImpl *_node)
44     : RenderReplaced(_node)
45 {
46     m_cachedImage = 0;
47     m_selectionState = SelectionNone;
48     setIntrinsicWidth(0);
49     setIntrinsicHeight(0);
50     updateAltText();
51 }
52
53 RenderImage::~RenderImage()
54 {
55     if (m_cachedImage)
56         m_cachedImage->deref(this);
57 }
58
59 void RenderImage::setStyle(RenderStyle* _style)
60 {
61     RenderReplaced::setStyle(_style);
62     
63     setShouldPaintBackgroundOrBorder(true);
64 }
65
66 void RenderImage::setContentObject(CachedObject* co)
67 {
68     if (co && m_cachedImage != co) {
69         if (m_cachedImage)
70             m_cachedImage->deref(this);
71         m_cachedImage = static_cast<CachedImage*>(co);
72         if (m_cachedImage)
73             m_cachedImage->ref(this);
74     }
75 }
76
77 void RenderImage::setCachedImage(CachedImage* newImage)
78 {
79     if (m_cachedImage != newImage) {
80         if (m_cachedImage)
81             m_cachedImage->deref(this);
82         m_cachedImage = newImage;
83         if (m_cachedImage)
84             m_cachedImage->ref(this);
85         if (m_cachedImage->isErrorImage())
86             imageChanged(m_cachedImage, IntRect(0,0,16,16));
87     }
88 }
89
90 void RenderImage::imageChanged(CachedImage* o, const IntRect& r)
91 {
92     if (o != m_cachedImage) {
93         RenderReplaced::imageChanged(o, r);
94         return;
95     }
96
97     bool iwchanged = false;
98
99     if (o->isErrorImage()) {
100         int iw = o->image().width() + 4;
101         int ih = o->image().height() + 4;
102
103         // we have an alt and the user meant it (its not a text we invented)
104         if (!m_altText.isEmpty()) {
105             const QFontMetrics &fm = style()->fontMetrics();
106             IntRect br = fm.boundingRect(0, 0, 1024, 256, Qt::AlignAuto|Qt::WordBreak, m_altText.qstring(), 0, 0);  // FIX: correct tabwidth?
107             if (br.width() > iw)
108                 iw = br.width();
109             if (br.height() > ih)
110                 ih = br.height();
111         }
112
113         if (iw != intrinsicWidth()) {
114             setIntrinsicWidth(iw);
115             iwchanged = true;
116         }
117         if (ih != intrinsicHeight()) {
118             setIntrinsicHeight(ih);
119             iwchanged = true;
120         }
121     }
122
123     bool needlayout = false;
124
125     // Image dimensions have been changed, see what needs to be done
126      if ((o->imageSize().width() != intrinsicWidth() || o->imageSize().height() != intrinsicHeight() || iwchanged)) {
127         if(!o->isErrorImage()) {
128             setIntrinsicWidth(o->imageSize().width());
129             setIntrinsicHeight(o->imageSize().height());
130         }
131
132         // In the case of generated image content using :before/:after, we might not be in the
133         // render tree yet.  In that case, we don't need to worry about check for layout, since we'll get a
134         // layout when we get added in to the render tree hierarchy later.
135         if (containingBlock()) {
136             // lets see if we need to relayout at all..
137             int oldwidth = m_width;
138             int oldheight = m_height;
139             calcWidth();
140             calcHeight();
141     
142             if(iwchanged || m_width != oldwidth || m_height != oldheight)
143                 needlayout = true;
144     
145             m_width = oldwidth;
146             m_height = oldheight;
147         }
148     }
149
150     // Stop the previous image, if it may be animating.
151     image().stopAnimations();
152
153     if (needlayout) {
154         if (!selfNeedsLayout())
155             setNeedsLayout(true);
156         if (minMaxKnown())
157             setMinMaxKnown(false);
158     }
159     else
160         // FIXME: We always just do a complete repaint, since we always pass in the full image
161         // rect at the moment anyway.
162         repaintRectangle(IntRect(borderLeft()+paddingLeft(), borderTop()+paddingTop(), contentWidth(), contentHeight()));
163 }
164
165 void RenderImage::resetAnimation()
166 {
167     if (m_cachedImage) {
168         image().resetAnimation();
169         if (!needsLayout())
170             repaint();
171     }
172 }
173
174 void RenderImage::paint(PaintInfo& i, int _tx, int _ty)
175 {
176     if (!shouldPaint(i, _tx, _ty)) return;
177
178     _tx += m_x;
179     _ty += m_y;
180         
181     if (shouldPaintBackgroundOrBorder() && i.phase != PaintActionOutline) 
182         paintBoxDecorations(i, _tx, _ty);
183
184     QPainter* p = i.p;
185     
186     if (i.phase == PaintActionOutline && style()->outlineWidth() && style()->visibility() == VISIBLE)
187         paintOutline(p, _tx, _ty, width(), height(), style());
188     
189     if (i.phase != PaintActionForeground && i.phase != PaintActionSelection)
190         return;
191
192     if (!shouldPaintWithinRoot(i))
193         return;
194         
195     bool isPrinting = i.p->printing();
196     bool drawSelectionTint = isSelected() && !isPrinting;
197     if (i.phase == PaintActionSelection) {
198         if (selectionState() == SelectionNone) {
199             return;
200         }
201         drawSelectionTint = false;
202     }
203         
204     int cWidth = contentWidth();
205     int cHeight = contentHeight();
206     int leftBorder = borderLeft();
207     int topBorder = borderTop();
208     int leftPad = paddingLeft();
209     int topPad = paddingTop();
210
211     if (isPrinting && !canvas()->printImages())
212         return;
213
214     if (!m_cachedImage || image().isNull() || errorOccurred()) {
215         if (i.phase == PaintActionSelection)
216             return;
217
218         if (cWidth > 2 && cHeight > 2) {
219             if (!errorOccurred()) {
220                 p->setPen (Color::lightGray);
221                 p->setBrush (WebCore::Brush::NoBrush);
222                 p->drawRect (_tx + leftBorder + leftPad, _ty + topBorder + topPad, cWidth, cHeight);
223             }
224             
225             bool errorPictureDrawn = false;
226             int imageX = 0, imageY = 0;
227             int usableWidth = cWidth;
228             int usableHeight = cHeight;
229             
230             if (errorOccurred() && !image().isNull() && (usableWidth >= image().width()) && (usableHeight >= image().height())) {
231                 // Center the error image, accounting for border and padding.
232                 int centerX = (usableWidth - image().width())/2;
233                 if (centerX < 0)
234                     centerX = 0;
235                 int centerY = (usableHeight - image().height())/2;
236                 if (centerY < 0)
237                     centerY = 0;
238                 imageX = leftBorder + leftPad + centerX;
239                 imageY = topBorder + topPad + centerY;
240                 p->drawImageAtPoint(image(), IntPoint(_tx + imageX, _ty + imageY));
241                 errorPictureDrawn = true;
242             }
243             
244             if (!m_altText.isEmpty()) {
245                 QString text = m_altText.qstring();
246                 text.replace(QChar('\\'), backslashAsCurrencySymbol());
247                 p->setFont (style()->font());
248                 p->setPen (style()->color());
249                 int ax = _tx + leftBorder + leftPad;
250                 int ay = _ty + topBorder + topPad;
251                 const QFontMetrics &fm = style()->fontMetrics();
252                 int ascent = fm.ascent();
253                 
254                 // Only draw the alt text if it'll fit within the content box,
255                 // and only if it fits above the error image.
256                 int textWidth = fm.width (text, 0, 0, text.length());
257                 if (errorPictureDrawn) {
258                     if (usableWidth >= textWidth && fm.height() <= imageY)
259                         p->drawText(ax, ay+ascent, tabWidth(), 0, 0 /* ignored */, 0 /* ignored */, Qt::WordBreak  /* not supported */, text );
260                 } else if (usableWidth >= textWidth && cHeight >= fm.height())
261                     p->drawText(ax, ay+ascent, tabWidth(), 0, 0 /* ignored */, 0 /* ignored */, Qt::WordBreak  /* not supported */, text );
262             }
263         }
264     }
265     else if (m_cachedImage) {
266         IntRect rect(IntPoint(_tx + leftBorder + leftPad, _ty + topBorder + topPad), IntSize(cWidth, cHeight));
267         
268         HTMLImageElementImpl* imageElt = (element() && element()->hasTagName(imgTag)) ? static_cast<HTMLImageElementImpl*>(element()) : 0;
269         Image::CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : Image::CompositeSourceOver;
270         p->drawImageInRect(image(), rect, compositeOperator);
271
272         if (drawSelectionTint)
273             p->fillRect(selectionRect(), selectionColor(p));
274     }
275 }
276
277 void RenderImage::layout()
278 {
279     KHTMLAssert(needsLayout());
280     KHTMLAssert( minMaxKnown() );
281
282     IntRect oldBounds;
283     bool checkForRepaint = checkForRepaintDuringLayout();
284     if (checkForRepaint)
285         oldBounds = getAbsoluteRepaintRect();
286
287     // minimum height
288     m_height = m_cachedImage && m_cachedImage->isErrorImage() ? intrinsicHeight() : 0;
289
290     calcWidth();
291     calcHeight();
292
293     if (checkForRepaint)
294         repaintAfterLayoutIfNeeded(oldBounds, oldBounds);
295     
296     setNeedsLayout(false);
297 }
298
299 HTMLMapElementImpl* RenderImage::imageMap()
300 {
301     HTMLImageElementImpl* i = element() && element()->hasTagName(imgTag) ? static_cast<HTMLImageElementImpl*>(element()) : 0;
302     return i ? i->getDocument()->getImageMap(i->imageMap()) : 0;
303 }
304
305 bool RenderImage::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty,
306                               HitTestAction hitTestAction)
307 {
308     bool inside = RenderReplaced::nodeAtPoint(info, _x, _y, _tx, _ty, hitTestAction);
309
310     if (inside && element()) {
311         int tx = _tx + m_x;
312         int ty = _ty + m_y;
313         
314         HTMLMapElementImpl* map = imageMap();
315         if (map) {
316             // we're a client side image map
317             inside = map->mapMouseEvent(_x - tx, _y - ty, contentWidth(), contentHeight(), info);
318             info.setInnerNonSharedNode(element());
319         }
320     }
321
322     return inside;
323 }
324
325 void RenderImage::updateAltText()
326 {
327     if (!element())
328         return;
329         
330     if (element()->hasTagName(inputTag))
331         m_altText = static_cast<HTMLInputElementImpl*>(element())->altText();
332     else if (element()->hasTagName(imgTag))
333         m_altText = static_cast<HTMLImageElementImpl*>(element())->altText();
334 }
335
336 bool RenderImage::isWidthSpecified() const
337 {
338     switch (style()->width().type) {
339         case Fixed:
340         case Percent:
341             return true;
342         default:
343             return false;
344     }
345     assert(false);
346     return false;
347 }
348
349 bool RenderImage::isHeightSpecified() const
350 {
351     switch (style()->height().type) {
352         case Fixed:
353         case Percent:
354             return true;
355         default:
356             return false;
357     }
358     assert(false);
359     return false;
360 }
361
362 int RenderImage::calcReplacedWidth() const
363 {
364     // If height is specified and not width, preserve aspect ratio.
365     if (isHeightSpecified() && !isWidthSpecified()) {
366         if (intrinsicHeight() == 0)
367             return 0;
368         if (!m_cachedImage || m_cachedImage->isErrorImage())
369             return intrinsicWidth(); // Don't bother scaling.
370         return calcReplacedHeight() * intrinsicWidth() / intrinsicHeight();
371     }
372     return RenderReplaced::calcReplacedWidth();
373 }
374
375 int RenderImage::calcReplacedHeight() const
376 {
377     // If width is specified and not height, preserve aspect ratio.
378     if (isWidthSpecified() && !isHeightSpecified()) {
379         if (intrinsicWidth() == 0)
380             return 0;
381         if (!m_cachedImage || m_cachedImage->isErrorImage())
382             return intrinsicHeight(); // Don't bother scaling.
383         return calcReplacedWidth() * intrinsicHeight() / intrinsicWidth();
384     }
385     return RenderReplaced::calcReplacedHeight();
386 }
387
388 }