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