64e852cb0bb899f9148e30fecc579b5ac39acb8f
[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 "HTMLImageElement.h"
34 #include "HTMLInputElement.h"
35 #include "HTMLMapElement.h"
36 #include "HTMLNames.h"
37 #include "HitTestResult.h"
38 #include "RenderView.h"
39 #include "TextStyle.h"
40
41 using namespace std;
42
43 namespace WebCore {
44
45 using namespace HTMLNames;
46
47 RenderImage::RenderImage(Node* node)
48     : RenderReplaced(node)
49     , m_cachedImage(0)
50     , m_isAnonymousImage(0)
51 {
52     m_selectionState = SelectionNone;
53     setIntrinsicWidth(0);
54     setIntrinsicHeight(0);
55     updateAltText();
56 }
57
58 RenderImage::~RenderImage()
59 {
60     if (m_cachedImage)
61         m_cachedImage->deref(this);
62 }
63
64 void RenderImage::setStyle(RenderStyle* newStyle)
65 {
66     RenderReplaced::setStyle(newStyle);
67     setShouldPaintBackgroundOrBorder(true);
68 }
69
70 void RenderImage::setContentObject(CachedResource* cachedResource)
71 {
72     if (cachedResource && m_cachedImage != cachedResource) {
73         if (m_cachedImage)
74             m_cachedImage->deref(this);
75         m_cachedImage = static_cast<CachedImage*>(cachedResource);
76         if (m_cachedImage)
77             m_cachedImage->ref(this);
78     }
79 }
80
81 void RenderImage::setCachedImage(CachedImage* newImage)
82 {
83     if (isAnonymousImage())
84         return;
85
86     if (m_cachedImage != newImage) {
87         if (m_cachedImage)
88             m_cachedImage->deref(this);
89         m_cachedImage = newImage;
90         if (m_cachedImage)
91             m_cachedImage->ref(this);
92         if (m_cachedImage->isErrorImage())
93             imageChanged(m_cachedImage);
94     }
95 }
96
97 void RenderImage::imageChanged(CachedImage* newImage)
98 {
99     if (documentBeingDestroyed())
100         return;
101
102     if (newImage != m_cachedImage) {
103         RenderReplaced::imageChanged(newImage);
104         return;
105     }
106
107     bool imageSizeChanged = false;
108
109     if (newImage->isErrorImage()) {
110         int imageWidth = newImage->image()->width() + 4;
111         int imageHeight = newImage->image()->height() + 4;
112
113         // we have an alt and the user meant it (its not a text we invented)
114         if (!m_altText.isEmpty()) {
115             const Font& font = style()->font();
116             imageWidth = max(imageWidth, min(font.width(TextRun(m_altText.characters(), m_altText.length())), 1024));
117             imageHeight = max(imageHeight, min(font.height(), 256));
118         }
119
120         if (imageWidth != intrinsicWidth()) {
121             setIntrinsicWidth(imageWidth);
122             imageSizeChanged = true;
123         }
124         if (imageHeight != intrinsicHeight()) {
125             setIntrinsicHeight(imageHeight);
126             imageSizeChanged = true;
127         }
128     }
129
130     bool ensureLayout = false;
131
132     // Image dimensions have been changed, see what needs to be done
133     if (newImage->imageSize().width() != intrinsicWidth() || newImage->imageSize().height() != intrinsicHeight() || imageSizeChanged) {
134         if (!newImage->isErrorImage()) {
135             setIntrinsicWidth(newImage->imageSize().width());
136             setIntrinsicHeight(newImage->imageSize().height());
137         }
138
139         // In the case of generated image content using :before/:after, we might not be in the
140         // render tree yet.  In that case, we don't need to worry about check for layout, since we'll get a
141         // layout when we get added in to the render tree hierarchy later.
142         if (containingBlock()) {
143             // lets see if we need to relayout at all..
144             int oldwidth = m_width;
145             int oldheight = m_height;
146             calcWidth();
147             calcHeight();
148     
149             if (imageSizeChanged || m_width != oldwidth || m_height != oldheight)
150                 ensureLayout = true;
151
152             m_width = oldwidth;
153             m_height = oldheight;
154         }
155     }
156
157     if (ensureLayout) {
158         if (!selfNeedsLayout())
159             setNeedsLayout(true);
160         if (minMaxKnown())
161             setMinMaxKnown(false);
162     } else
163         // FIXME: We always just do a complete repaint, since we always pass in the full image
164         // rect at the moment anyway.
165         repaintRectangle(IntRect(borderLeft() + paddingLeft(), borderTop() + paddingTop(), contentWidth(), contentHeight()));
166 }
167
168 void RenderImage::resetAnimation()
169 {
170     if (m_cachedImage) {
171         image()->resetAnimation();
172         if (!needsLayout())
173             repaint();
174     }
175 }
176
177 void RenderImage::paint(PaintInfo& paintInfo, int tx, int ty)
178 {
179     if (!shouldPaint(paintInfo, tx, ty))
180         return;
181
182     tx += m_x;
183     ty += m_y;
184         
185     if (shouldPaintBackgroundOrBorder() && paintInfo.phase != PaintPhaseOutline && paintInfo.phase != PaintPhaseSelfOutline) 
186         paintBoxDecorations(paintInfo, tx, ty);
187
188     GraphicsContext* context = paintInfo.context;
189
190     if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth() && style()->visibility() == VISIBLE)
191         paintOutline(context, tx, ty, width(), height(), style());
192
193     if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection)
194         return;
195
196     if (!shouldPaintWithinRoot(paintInfo))
197         return;
198         
199     bool isPrinting = document()->printing();
200     bool drawSelectionTint = isSelected() && !isPrinting;
201     if (paintInfo.phase == PaintPhaseSelection) {
202         if (selectionState() == SelectionNone)
203             return;
204         drawSelectionTint = false;
205     }
206         
207     int cWidth = contentWidth();
208     int cHeight = contentHeight();
209     int leftBorder = borderLeft();
210     int topBorder = borderTop();
211     int leftPad = paddingLeft();
212     int topPad = paddingTop();
213
214     if (isPrinting && !view()->printImages())
215         return;
216
217     if (!m_cachedImage || image()->isNull() || errorOccurred()) {
218         if (paintInfo.phase == PaintPhaseSelection)
219             return;
220
221         if (cWidth > 2 && cHeight > 2) {
222             if (!errorOccurred()) {
223                 context->setPen(Color::lightGray);
224                 context->setFillColor(Color::transparent);
225                 context->drawRect(IntRect(tx + leftBorder + leftPad, ty + topBorder + topPad, cWidth, cHeight));
226             }
227
228             bool errorPictureDrawn = false;
229             int imageX = 0;
230             int imageY = 0;
231             int usableWidth = cWidth;
232             int usableHeight = cHeight;
233
234             if (errorOccurred() && !image()->isNull() && (usableWidth >= image()->width()) && (usableHeight >= image()->height())) {
235                 // Center the error image, accounting for border and padding.
236                 int centerX = (usableWidth - image()->width()) / 2;
237                 if (centerX < 0)
238                     centerX = 0;
239                 int centerY = (usableHeight - image()->height()) / 2;
240                 if (centerY < 0)
241                     centerY = 0;
242                 imageX = leftBorder + leftPad + centerX;
243                 imageY = topBorder + topPad + centerY;
244                 context->drawImage(image(), IntPoint(tx + imageX, ty + imageY));
245                 errorPictureDrawn = true;
246             }
247
248             if (!m_altText.isEmpty()) {
249                 DeprecatedString text = m_altText.deprecatedString();
250                 text.replace('\\', backslashAsCurrencySymbol());
251                 context->setFont(style()->font());
252                 context->setPen(style()->color());
253                 int ax = tx + leftBorder + leftPad;
254                 int ay = ty + topBorder + topPad;
255                 const Font& font = style()->font();
256                 int ascent = font.ascent();
257
258                 // Only draw the alt text if it'll fit within the content box,
259                 // and only if it fits above the error image.
260                 TextRun textRun(reinterpret_cast<const UChar*>(text.unicode()), text.length());
261                 int textWidth = font.width(textRun);
262                 if (errorPictureDrawn) {
263                     if (usableWidth >= textWidth && font.height() <= imageY)
264                         context->drawText(textRun, IntPoint(ax, ay + ascent));
265                 } else if (usableWidth >= textWidth && cHeight >= font.height())
266                     context->drawText(textRun, IntPoint(ax, ay + ascent));
267             }
268         }
269     } else if (m_cachedImage) {
270 #if PLATFORM(MAC)
271         if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled())
272             paintCustomHighlight(tx - m_x, ty - m_y, style()->highlight(), true);
273 #endif
274
275         IntRect rect(IntPoint(tx + leftBorder + leftPad, ty + topBorder + topPad), IntSize(cWidth, cHeight));
276
277         HTMLImageElement* imageElt = (element() && element()->hasTagName(imgTag)) ? static_cast<HTMLImageElement*>(element()) : 0;
278         CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : CompositeSourceOver;
279         context->drawImage(image(), rect, compositeOperator);
280
281         if (drawSelectionTint)
282             context->fillRect(selectionRect(), selectionBackgroundColor());
283     }
284 }
285
286 void RenderImage::layout()
287 {
288     ASSERT(needsLayout());
289     ASSERT(minMaxKnown());
290
291     IntRect oldBounds;
292     bool checkForRepaint = checkForRepaintDuringLayout();
293     if (checkForRepaint)
294         oldBounds = getAbsoluteRepaintRect();
295
296     // minimum height
297     m_height = m_cachedImage && m_cachedImage->isErrorImage() ? intrinsicHeight() : 0;
298
299     calcWidth();
300     calcHeight();
301
302     if (checkForRepaint)
303         repaintAfterLayoutIfNeeded(oldBounds, oldBounds);
304     
305     setNeedsLayout(false);
306 }
307
308 HTMLMapElement* RenderImage::imageMap()
309 {
310     HTMLImageElement* i = element() && element()->hasTagName(imgTag) ? static_cast<HTMLImageElement*>(element()) : 0;
311     return i ? i->document()->getImageMap(i->imageMap()) : 0;
312 }
313
314 bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction)
315 {
316     bool inside = RenderReplaced::nodeAtPoint(request, result, _x, _y, _tx, _ty, hitTestAction);
317
318     if (inside && element()) {
319         int tx = _tx + m_x;
320         int ty = _ty + m_y;
321         
322         HTMLMapElement* map = imageMap();
323         if (map) {
324             // we're a client side image map
325             inside = map->mapMouseEvent(_x - tx, _y - ty, IntSize(contentWidth(), contentHeight()), result);
326             result.setInnerNonSharedNode(element());
327         }
328     }
329
330     return inside;
331 }
332
333 void RenderImage::updateAltText()
334 {
335     if (!element())
336         return;
337
338     if (element()->hasTagName(inputTag))
339         m_altText = static_cast<HTMLInputElement*>(element())->altText();
340     else if (element()->hasTagName(imgTag))
341         m_altText = static_cast<HTMLImageElement*>(element())->altText();
342 }
343
344 bool RenderImage::isWidthSpecified() const
345 {
346     switch (style()->width().type()) {
347         case Fixed:
348         case Percent:
349             return true;
350         default:
351             return false;
352     }
353     assert(false);
354     return false;
355 }
356
357 bool RenderImage::isHeightSpecified() const
358 {
359     switch (style()->height().type()) {
360         case Fixed:
361         case Percent:
362             return true;
363         default:
364             return false;
365     }
366     assert(false);
367     return false;
368 }
369
370 int RenderImage::calcReplacedWidth() const
371 {
372     int width;
373     if (isWidthSpecified())
374         width = calcReplacedWidthUsing(style()->width());
375     else
376         width = calcAspectRatioWidth();
377
378     int minW = calcReplacedWidthUsing(style()->minWidth());
379     int maxW = style()->maxWidth().value() == undefinedLength ? width : calcReplacedWidthUsing(style()->maxWidth());
380
381     return max(minW, min(width, maxW));
382 }
383
384 int RenderImage::calcReplacedHeight() const
385 {
386     int height;
387     if (isHeightSpecified())
388         height = calcReplacedHeightUsing(style()->height());
389     else
390         height = calcAspectRatioHeight();
391
392     int minH = calcReplacedHeightUsing(style()->minHeight());
393     int maxH = style()->maxHeight().value() == undefinedLength ? height : calcReplacedHeightUsing(style()->maxHeight());
394
395     return max(minH, min(height, maxH));
396 }
397
398 int RenderImage::calcAspectRatioWidth() const
399 {
400     if (!intrinsicHeight())
401         return 0;
402     if (!m_cachedImage || m_cachedImage->isErrorImage())
403         return intrinsicWidth(); // Don't bother scaling.
404     return RenderReplaced::calcReplacedHeight() * intrinsicWidth() / intrinsicHeight();
405 }
406
407 int RenderImage::calcAspectRatioHeight() const
408 {
409     if (!intrinsicWidth())
410         return 0;
411     if (!m_cachedImage || m_cachedImage->isErrorImage())
412         return intrinsicHeight(); // Don't bother scaling.
413     return RenderReplaced::calcReplacedWidth() * intrinsicHeight() / intrinsicWidth();
414 }
415
416 void RenderImage::calcMinMaxWidth()
417 {
418     ASSERT(!minMaxKnown());
419
420     m_maxWidth = calcReplacedWidth() + paddingLeft() + paddingRight() + borderLeft() + borderRight();
421
422     if (style()->width().isPercent() || style()->height().isPercent() || 
423         style()->maxWidth().isPercent() || style()->maxHeight().isPercent() ||
424         style()->minWidth().isPercent() || style()->minHeight().isPercent())
425         m_minWidth = 0;
426     else
427         m_minWidth = m_maxWidth;
428
429     setMinMaxKnown();
430 }
431
432 Image* RenderImage::nullImage()
433 {
434     static Image sharedNullImage;
435     return &sharedNullImage;
436 }
437
438 } // namespace WebCore