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