1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2000 Dirk Mueller (mueller (at) kde.org) 5 * (C) 2006 Allan Sandfeld Jensen (kde (at) carewolf.com) 6 * (C) 2006 Samuel Weinig (sam.weinig (at) gmail.com) 7 * Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. 8 * Copyright (C) 2010 Google Inc. All rights reserved. 9 * 10 * This library is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU Library General Public 12 * License as published by the Free Software Foundation; either 13 * version 2 of the License, or (at your option) any later version. 14 * 15 * This library is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 * Library General Public License for more details. 19 * 20 * You should have received a copy of the GNU Library General Public License 21 * along with this library; see the file COPYING.LIB. If not, write to 22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 23 * Boston, MA 02110-1301, USA. 24 * 25 */ 26 27 #include "config.h" 28 #include "RenderImage.h" 29 30 #include "Frame.h" 31 #include "GraphicsContext.h" 32 #include "HTMLAreaElement.h" 33 #include "HTMLImageElement.h" 34 #include "HTMLInputElement.h" 35 #include "HTMLMapElement.h" 36 #include "HTMLNames.h" 37 #include "HitTestResult.h" 38 #include "Page.h" 39 #include "RenderLayer.h" 40 #include "RenderView.h" 41 #include "SelectionController.h" 42 #include "TextRun.h" 43 #include <wtf/UnusedParam.h> 44 45 #ifdef ANDROID_LAYOUT 46 #include "Settings.h" 47 #endif 48 49 #if ENABLE(WML) 50 #include "WMLImageElement.h" 51 #include "WMLNames.h" 52 #endif 53 54 using namespace std; 55 56 namespace WebCore { 57 58 using namespace HTMLNames; 59 60 RenderImage::RenderImage(Node* node) 61 : RenderReplaced(node, IntSize(0, 0)) 62 , m_needsToSetSizeForAltText(false) 63 { 64 updateAltText(); 65 66 view()->frameView()->setIsVisuallyNonEmpty(); 67 } 68 69 RenderImage::~RenderImage() 70 { 71 ASSERT(m_imageResource); 72 m_imageResource->shutdown(); 73 } 74 75 void RenderImage::setImageResource(PassOwnPtr<RenderImageResource> imageResource) 76 { 77 ASSERT(!m_imageResource); 78 m_imageResource = imageResource; 79 m_imageResource->initialize(this); 80 } 81 82 // If we'll be displaying either alt text or an image, add some padding. 83 static const unsigned short paddingWidth = 4; 84 static const unsigned short paddingHeight = 4; 85 86 // Alt text is restricted to this maximum size, in pixels. These are 87 // signed integers because they are compared with other signed values. 88 static const float maxAltTextWidth = 1024; 89 static const int maxAltTextHeight = 256; 90 91 IntSize RenderImage::imageSizeForError(CachedImage* newImage) const 92 { 93 ASSERT_ARG(newImage, newImage); 94 ASSERT_ARG(newImage, newImage->image()); 95 96 // imageSize() returns 0 for the error image. We need the true size of the 97 // error image, so we have to get it by grabbing image() directly. 98 return IntSize(paddingWidth + newImage->image()->width() * style()->effectiveZoom(), paddingHeight + newImage->image()->height() * style()->effectiveZoom()); 99 } 100 101 // Sets the image height and width to fit the alt text. Returns true if the 102 // image size changed. 103 bool RenderImage::setImageSizeForAltText(CachedImage* newImage /* = 0 */) 104 { 105 IntSize imageSize; 106 if (newImage && newImage->image()) 107 imageSize = imageSizeForError(newImage); 108 else if (!m_altText.isEmpty() || newImage) { 109 // If we'll be displaying either text or an image, add a little padding. 110 imageSize = IntSize(paddingWidth, paddingHeight); 111 } 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 IntSize textSize(min(font.width(TextRun(m_altText.characters(), m_altText.length())), maxAltTextWidth), min(font.fontMetrics().height(), maxAltTextHeight)); 117 imageSize = imageSize.expandedTo(textSize); 118 } 119 120 if (imageSize == intrinsicSize()) 121 return false; 122 123 setIntrinsicSize(imageSize); 124 return true; 125 } 126 127 void RenderImage::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 128 { 129 RenderReplaced::styleDidChange(diff, oldStyle); 130 if (m_needsToSetSizeForAltText) { 131 if (!m_altText.isEmpty() && setImageSizeForAltText(m_imageResource->cachedImage())) 132 imageDimensionsChanged(true /* imageSizeChanged */); 133 m_needsToSetSizeForAltText = false; 134 } 135 } 136 137 void RenderImage::imageChanged(WrappedImagePtr newImage, const IntRect* rect) 138 { 139 if (documentBeingDestroyed()) 140 return; 141 142 if (hasBoxDecorations() || hasMask()) 143 RenderReplaced::imageChanged(newImage, rect); 144 145 if (!m_imageResource) 146 return; 147 148 if (newImage != m_imageResource->imagePtr() || !newImage) 149 return; 150 151 bool imageSizeChanged = false; 152 153 // Set image dimensions, taking into account the size of the alt text. 154 if (m_imageResource->errorOccurred()) { 155 if (!m_altText.isEmpty() && document()->isPendingStyleRecalc()) { 156 ASSERT(node()); 157 if (node()) { 158 m_needsToSetSizeForAltText = true; 159 node()->setNeedsStyleRecalc(SyntheticStyleChange); 160 } 161 return; 162 } 163 imageSizeChanged = setImageSizeForAltText(m_imageResource->cachedImage()); 164 } 165 166 imageDimensionsChanged(imageSizeChanged, rect); 167 } 168 169 void RenderImage::imageDimensionsChanged(bool imageSizeChanged, const IntRect* rect) 170 { 171 bool shouldRepaint = true; 172 173 if (m_imageResource->imageSize(style()->effectiveZoom()) != intrinsicSize() || imageSizeChanged) { 174 if (!m_imageResource->errorOccurred()) 175 setIntrinsicSize(m_imageResource->imageSize(style()->effectiveZoom())); 176 177 // In the case of generated image content using :before/:after, we might not be in the 178 // render tree yet. In that case, we don't need to worry about check for layout, since we'll get a 179 // layout when we get added in to the render tree hierarchy later. 180 if (containingBlock()) { 181 // lets see if we need to relayout at all.. 182 int oldwidth = width(); 183 int oldheight = height(); 184 if (!preferredLogicalWidthsDirty()) 185 setPreferredLogicalWidthsDirty(true); 186 computeLogicalWidth(); 187 computeLogicalHeight(); 188 189 if (imageSizeChanged || width() != oldwidth || height() != oldheight) { 190 shouldRepaint = false; 191 if (!selfNeedsLayout()) 192 setNeedsLayout(true); 193 } 194 195 setWidth(oldwidth); 196 setHeight(oldheight); 197 } 198 } 199 200 if (shouldRepaint) { 201 IntRect repaintRect; 202 if (rect) { 203 // The image changed rect is in source image coordinates (pre-zooming), 204 // so map from the bounds of the image to the contentsBox. 205 repaintRect = enclosingIntRect(mapRect(*rect, FloatRect(FloatPoint(), m_imageResource->imageSize(1.0f)), contentBoxRect())); 206 // Guard against too-large changed rects. 207 repaintRect.intersect(contentBoxRect()); 208 } else 209 repaintRect = contentBoxRect(); 210 211 repaintRectangle(repaintRect); 212 213 #if USE(ACCELERATED_COMPOSITING) 214 if (hasLayer()) { 215 // Tell any potential compositing layers that the image needs updating. 216 layer()->contentChanged(RenderLayer::ImageChanged); 217 } 218 #endif 219 } 220 } 221 222 void RenderImage::notifyFinished(CachedResource* newImage) 223 { 224 if (!m_imageResource) 225 return; 226 227 if (documentBeingDestroyed()) 228 return; 229 230 #if USE(ACCELERATED_COMPOSITING) 231 if (newImage == m_imageResource->cachedImage() && hasLayer()) { 232 // tell any potential compositing layers 233 // that the image is done and they can reference it directly. 234 layer()->contentChanged(RenderLayer::ImageChanged); 235 } 236 #else 237 UNUSED_PARAM(newImage); 238 #endif 239 } 240 241 void RenderImage::paintReplaced(PaintInfo& paintInfo, int tx, int ty) 242 { 243 int cWidth = contentWidth(); 244 int cHeight = contentHeight(); 245 int leftBorder = borderLeft(); 246 int topBorder = borderTop(); 247 int leftPad = paddingLeft(); 248 int topPad = paddingTop(); 249 250 GraphicsContext* context = paintInfo.context; 251 252 if (!m_imageResource->hasImage() || m_imageResource->errorOccurred()) { 253 if (paintInfo.phase == PaintPhaseSelection) 254 return; 255 256 if (cWidth > 2 && cHeight > 2) { 257 // Draw an outline rect where the image should be. 258 #ifdef ANDROID_FIX // see http://b/issue?id=2052757 259 context->save(); 260 #endif 261 context->setStrokeStyle(SolidStroke); 262 context->setStrokeColor(Color::lightGray, style()->colorSpace()); 263 context->setFillColor(Color::transparent, style()->colorSpace()); 264 context->drawRect(IntRect(tx + leftBorder + leftPad, ty + topBorder + topPad, cWidth, cHeight)); 265 #ifdef ANDROID_FIX // see http://b/issue?id=2052757 266 context->restore(); 267 #endif 268 269 bool errorPictureDrawn = false; 270 int imageX = 0; 271 int imageY = 0; 272 // When calculating the usable dimensions, exclude the pixels of 273 // the ouline rect so the error image/alt text doesn't draw on it. 274 int usableWidth = cWidth - 2; 275 int usableHeight = cHeight - 2; 276 277 RefPtr<Image> image = m_imageResource->image(); 278 279 if (m_imageResource->errorOccurred() && !image->isNull() && usableWidth >= image->width() && usableHeight >= image->height()) { 280 // Center the error image, accounting for border and padding. 281 int centerX = (usableWidth - image->width()) / 2; 282 if (centerX < 0) 283 centerX = 0; 284 int centerY = (usableHeight - image->height()) / 2; 285 if (centerY < 0) 286 centerY = 0; 287 imageX = leftBorder + leftPad + centerX + 1; 288 imageY = topBorder + topPad + centerY + 1; 289 context->drawImage(image.get(), style()->colorSpace(), IntPoint(tx + imageX, ty + imageY)); 290 errorPictureDrawn = true; 291 } 292 293 if (!m_altText.isEmpty()) { 294 String text = document()->displayStringModifiedByEncoding(m_altText); 295 context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace()); 296 int ax = tx + leftBorder + leftPad; 297 int ay = ty + topBorder + topPad; 298 const Font& font = style()->font(); 299 const FontMetrics& fontMetrics = font.fontMetrics(); 300 int ascent = fontMetrics.ascent(); 301 302 // Only draw the alt text if it'll fit within the content box, 303 // and only if it fits above the error image. 304 TextRun textRun(text.characters(), text.length()); 305 int textWidth = font.width(textRun); 306 if (errorPictureDrawn) { 307 if (usableWidth >= textWidth && fontMetrics.height() <= imageY) 308 context->drawText(font, textRun, IntPoint(ax, ay + ascent)); 309 } else if (usableWidth >= textWidth && cHeight >= fontMetrics.height()) 310 context->drawText(font, textRun, IntPoint(ax, ay + ascent)); 311 } 312 } 313 } else if (m_imageResource->hasImage() && cWidth > 0 && cHeight > 0) { 314 RefPtr<Image> img = m_imageResource->image(cWidth, cHeight); 315 if (!img || img->isNull()) 316 return; 317 318 #if PLATFORM(MAC) 319 if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled()) 320 paintCustomHighlight(tx - x(), ty - y(), style()->highlight(), true); 321 #endif 322 323 IntSize contentSize(cWidth, cHeight); 324 IntRect rect(IntPoint(tx + leftBorder + leftPad, ty + topBorder + topPad), contentSize); 325 paintIntoRect(context, rect); 326 } 327 } 328 329 void RenderImage::paint(PaintInfo& paintInfo, int tx, int ty) 330 { 331 RenderReplaced::paint(paintInfo, tx, ty); 332 333 if (paintInfo.phase == PaintPhaseOutline) 334 paintAreaElementFocusRing(paintInfo); 335 } 336 337 void RenderImage::paintAreaElementFocusRing(PaintInfo& paintInfo) 338 { 339 Document* document = this->document(); 340 341 if (document->printing() || !document->frame()->selection()->isFocusedAndActive()) 342 return; 343 344 if (paintInfo.context->paintingDisabled() && !paintInfo.context->updatingControlTints()) 345 return; 346 347 Node* focusedNode = document->focusedNode(); 348 if (!focusedNode || !focusedNode->hasTagName(areaTag)) 349 return; 350 351 HTMLAreaElement* areaElement = static_cast<HTMLAreaElement*>(focusedNode); 352 if (areaElement->imageElement() != node()) 353 return; 354 355 // Even if the theme handles focus ring drawing for entire elements, it won't do it for 356 // an area within an image, so we don't call RenderTheme::supportsFocusRing here. 357 358 Path path = areaElement->computePath(this); 359 if (path.isEmpty()) 360 return; 361 362 // FIXME: Do we need additional code to clip the path to the image's bounding box? 363 364 RenderStyle* areaElementStyle = areaElement->computedStyle(); 365 unsigned short outlineWidth = areaElementStyle->outlineWidth(); 366 if (!outlineWidth) 367 return; 368 369 paintInfo.context->drawFocusRing(path, outlineWidth, 370 areaElementStyle->outlineOffset(), 371 areaElementStyle->visitedDependentColor(CSSPropertyOutlineColor)); 372 } 373 374 void RenderImage::areaElementFocusChanged(HTMLAreaElement* element) 375 { 376 ASSERT_UNUSED(element, element->imageElement() == node()); 377 378 // It would be more efficient to only repaint the focus ring rectangle 379 // for the passed-in area element. That would require adding functions 380 // to the area element class. 381 repaint(); 382 } 383 384 void RenderImage::paintIntoRect(GraphicsContext* context, const IntRect& rect) 385 { 386 if (!m_imageResource->hasImage() || m_imageResource->errorOccurred() || rect.width() <= 0 || rect.height() <= 0) 387 return; 388 389 RefPtr<Image> img = m_imageResource->image(rect.width(), rect.height()); 390 if (!img || img->isNull()) 391 return; 392 393 HTMLImageElement* imageElt = (node() && node()->hasTagName(imgTag)) ? static_cast<HTMLImageElement*>(node()) : 0; 394 CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : CompositeSourceOver; 395 Image* image = m_imageResource->image().get(); 396 bool useLowQualityScaling = shouldPaintAtLowQuality(context, image, image, rect.size()); 397 context->drawImage(m_imageResource->image(rect.width(), rect.height()).get(), style()->colorSpace(), rect, compositeOperator, useLowQualityScaling); 398 } 399 400 int RenderImage::minimumReplacedHeight() const 401 { 402 return m_imageResource->errorOccurred() ? intrinsicSize().height() : 0; 403 } 404 405 HTMLMapElement* RenderImage::imageMap() const 406 { 407 HTMLImageElement* i = node() && node()->hasTagName(imgTag) ? static_cast<HTMLImageElement*>(node()) : 0; 408 return i ? i->document()->getImageMap(i->fastGetAttribute(usemapAttr)) : 0; 409 } 410 411 bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction) 412 { 413 HitTestResult tempResult(result.point(), result.topPadding(), result.rightPadding(), result.bottomPadding(), result.leftPadding()); 414 bool inside = RenderReplaced::nodeAtPoint(request, tempResult, x, y, tx, ty, hitTestAction); 415 416 if (tempResult.innerNode() && node()) { 417 if (HTMLMapElement* map = imageMap()) { 418 IntRect contentBox = contentBoxRect(); 419 float zoom = style()->effectiveZoom(); 420 int mapX = lroundf((x - tx - this->x() - contentBox.x()) / zoom); 421 int mapY = lroundf((y - ty - this->y() - contentBox.y()) / zoom); 422 if (map->mapMouseEvent(mapX, mapY, contentBox.size(), tempResult)) 423 tempResult.setInnerNonSharedNode(node()); 424 } 425 } 426 427 if (!inside && result.isRectBasedTest()) 428 result.append(tempResult); 429 if (inside) 430 result = tempResult; 431 return inside; 432 } 433 434 void RenderImage::updateAltText() 435 { 436 if (!node()) 437 return; 438 439 if (node()->hasTagName(inputTag)) 440 m_altText = static_cast<HTMLInputElement*>(node())->altText(); 441 else if (node()->hasTagName(imgTag)) 442 m_altText = static_cast<HTMLImageElement*>(node())->altText(); 443 #if ENABLE(WML) 444 else if (node()->hasTagName(WMLNames::imgTag)) 445 m_altText = static_cast<WMLImageElement*>(node())->altText(); 446 #endif 447 } 448 449 bool RenderImage::isLogicalWidthSpecified() const 450 { 451 switch (style()->logicalWidth().type()) { 452 case Fixed: 453 case Percent: 454 return true; 455 case Auto: 456 case Relative: // FIXME: Shouldn't this case return true? 457 case Intrinsic: 458 case MinIntrinsic: 459 return false; 460 } 461 ASSERT(false); 462 return false; 463 } 464 465 bool RenderImage::isLogicalHeightSpecified() const 466 { 467 switch (style()->logicalHeight().type()) { 468 case Fixed: 469 case Percent: 470 return true; 471 case Auto: 472 case Relative: // FIXME: Shouldn't this case return true? 473 case Intrinsic: 474 case MinIntrinsic: 475 return false; 476 } 477 ASSERT(false); 478 return false; 479 } 480 481 int RenderImage::computeReplacedLogicalWidth(bool includeMaxWidth) const 482 { 483 if (m_imageResource->imageHasRelativeWidth()) 484 if (RenderObject* cb = isPositioned() ? container() : containingBlock()) { 485 if (cb->isBox()) 486 m_imageResource->setImageContainerSize(IntSize(toRenderBox(cb)->availableWidth(), toRenderBox(cb)->availableHeight())); 487 } 488 489 int logicalWidth; 490 if (isLogicalWidthSpecified()) 491 logicalWidth = computeReplacedLogicalWidthUsing(style()->logicalWidth()); 492 else if (m_imageResource->usesImageContainerSize()) { 493 IntSize size = m_imageResource->imageSize(style()->effectiveZoom()); 494 logicalWidth = style()->isHorizontalWritingMode() ? size.width() : size.height(); 495 } else if (m_imageResource->imageHasRelativeWidth()) 496 logicalWidth = 0; // If the image is relatively-sized, set the width to 0 until there is a set container size. 497 else 498 logicalWidth = calcAspectRatioLogicalWidth(); 499 500 int minLogicalWidth = computeReplacedLogicalWidthUsing(style()->logicalMinWidth()); 501 int maxLogicalWidth = !includeMaxWidth || style()->logicalMaxWidth().isUndefined() ? logicalWidth : computeReplacedLogicalWidthUsing(style()->logicalMaxWidth()); 502 503 #ifdef ANDROID_LAYOUT 504 logicalWidth = max(minLogicalWidth, min(logicalWidth, maxLogicalWidth)); 505 // in SSR mode, we will fit the image to its container width 506 if (document()->settings()->layoutAlgorithm() == Settings::kLayoutSSR) { 507 int cw = containingBlockLogicalWidthForContent(); 508 if (cw && logicalWidth > cw) 509 logicalWidth = cw; 510 } 511 return logicalWidth; 512 #else 513 return max(minLogicalWidth, min(logicalWidth, maxLogicalWidth)); 514 #endif 515 } 516 517 int RenderImage::computeReplacedLogicalHeight() const 518 { 519 int logicalHeight; 520 if (isLogicalHeightSpecified()) 521 logicalHeight = computeReplacedLogicalHeightUsing(style()->logicalHeight()); 522 else if (m_imageResource->usesImageContainerSize()) { 523 IntSize size = m_imageResource->imageSize(style()->effectiveZoom()); 524 logicalHeight = style()->isHorizontalWritingMode() ? size.height() : size.width(); 525 } else if (m_imageResource->imageHasRelativeHeight()) 526 logicalHeight = 0; // If the image is relatively-sized, set the height to 0 until there is a set container size. 527 else 528 logicalHeight = calcAspectRatioLogicalHeight(); 529 530 int minLogicalHeight = computeReplacedLogicalHeightUsing(style()->logicalMinHeight()); 531 int maxLogicalHeight = style()->logicalMaxHeight().isUndefined() ? logicalHeight : computeReplacedLogicalHeightUsing(style()->logicalMaxHeight()); 532 533 #ifdef ANDROID_LAYOUT 534 logicalHeight = max(minLogicalHeight, min(logicalHeight, maxLogicalHeight)); 535 // in SSR mode, we will fit the image to its container width 536 if (logicalHeight && document()->settings()->layoutAlgorithm() == Settings::kLayoutSSR) { 537 int logicalWidth; 538 if (isLogicalWidthSpecified()) 539 logicalWidth = computeReplacedLogicalWidthUsing(style()->width()); 540 else 541 logicalWidth = calcAspectRatioLogicalWidth(); 542 int minLogicalWidth = computeReplacedLogicalWidthUsing(style()->minWidth()); 543 int maxLogicalWidth = style()->maxWidth().value() == undefinedLength ? logicalWidth : 544 computeReplacedLogicalWidthUsing(style()->maxWidth()); 545 logicalWidth = max(minLogicalWidth, min(logicalWidth, maxLogicalWidth)); 546 547 int cw = containingBlockLogicalWidthForContent(); 548 if (cw && logicalWidth && logicalWidth > cw) 549 logicalHeight = cw * logicalHeight / logicalWidth; // preserve aspect ratio 550 } 551 return logicalHeight; 552 #else 553 return max(minLogicalHeight, min(logicalHeight, maxLogicalHeight)); 554 #endif 555 } 556 557 int RenderImage::calcAspectRatioLogicalWidth() const 558 { 559 int intrinsicWidth = intrinsicLogicalWidth(); 560 int intrinsicHeight = intrinsicLogicalHeight(); 561 if (!intrinsicHeight) 562 return 0; 563 if (!m_imageResource->hasImage() || m_imageResource->errorOccurred()) 564 return intrinsicWidth; // Don't bother scaling. 565 return RenderBox::computeReplacedLogicalHeight() * intrinsicWidth / intrinsicHeight; 566 } 567 568 int RenderImage::calcAspectRatioLogicalHeight() const 569 { 570 int intrinsicWidth = intrinsicLogicalWidth(); 571 int intrinsicHeight = intrinsicLogicalHeight(); 572 if (!intrinsicWidth) 573 return 0; 574 if (!m_imageResource->hasImage() || m_imageResource->errorOccurred()) 575 return intrinsicHeight; // Don't bother scaling. 576 return RenderBox::computeReplacedLogicalWidth() * intrinsicHeight / intrinsicWidth; 577 } 578 579 } // namespace WebCore 580