File indexing completed on 2024-04-28 15:24:03
0001 /** 0002 * This file is part of the DOM implementation for KDE. 0003 * 0004 * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) 0005 * (C) 1999 Antti Koivisto (koivisto@kde.org) 0006 * (C) 2000-2003 Dirk Mueller (mueller@kde.org) 0007 * (C) 2003 Apple Computer, Inc. 0008 * 0009 * This library is free software; you can redistribute it and/or 0010 * modify it under the terms of the GNU Library General Public 0011 * License as published by the Free Software Foundation; either 0012 * version 2 of the License, or (at your option) any later version. 0013 * 0014 * This library is distributed in the hope that it will be useful, 0015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0017 * Library General Public License for more details. 0018 * 0019 * You should have received a copy of the GNU Library General Public License 0020 * along with this library; see the file COPYING.LIB. If not, write to 0021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0022 * Boston, MA 02110-1301, USA. 0023 * 0024 */ 0025 //#define DEBUG_LAYOUT 0026 0027 #include "render_image.h" 0028 #include "render_canvas.h" 0029 0030 #include <qdrawutil.h> 0031 #include <QPainter> 0032 #include <QApplication> 0033 0034 #include "khtml_debug.h" 0035 0036 #include "misc/loader.h" 0037 #include "html/html_formimpl.h" 0038 #include "html/html_imageimpl.h" 0039 #include "xml/dom2_eventsimpl.h" 0040 #include "html/html_documentimpl.h" 0041 #include "html/html_objectimpl.h" 0042 #include "khtmlview.h" 0043 #include "khtml_part.h" 0044 #include <math.h> 0045 #include "imload/imagepainter.h" 0046 0047 #include "loading_icon.cpp" 0048 0049 using namespace DOM; 0050 using namespace khtml; 0051 using namespace khtmlImLoad; 0052 0053 // ------------------------------------------------------------------------- 0054 0055 RenderImage::RenderImage(NodeImpl *_element) 0056 : RenderReplaced(_element) 0057 { 0058 m_cachedImage = nullptr; 0059 m_imagePainter = nullptr; 0060 0061 m_selectionState = SelectionNone; 0062 berrorPic = false; 0063 0064 const KHTMLSettings *settings = _element->document()->view()->part()->settings(); 0065 bUnfinishedImageFrame = settings->unfinishedImageFrame(); 0066 0067 setIntrinsicWidth(0); 0068 setIntrinsicHeight(0); 0069 } 0070 0071 RenderImage::~RenderImage() 0072 { 0073 delete m_imagePainter; 0074 if (m_cachedImage) { 0075 m_cachedImage->deref(this); 0076 } 0077 } 0078 0079 // QPixmap RenderImage::pixmap() const 0080 // { 0081 // return image ? image->pixmap() : QPixmap(); 0082 // } 0083 0084 void RenderImage::setStyle(RenderStyle *_style) 0085 { 0086 RenderReplaced::setStyle(_style); 0087 // init RenderObject attributes 0088 setShouldPaintBackgroundOrBorder(true); 0089 } 0090 0091 void RenderImage::setContentObject(CachedObject *co) 0092 { 0093 if (co && m_cachedImage != co) { 0094 updateImage(static_cast<CachedImage *>(co)); 0095 } 0096 } 0097 0098 void RenderImage::updatePixmap(const QRect &r, CachedImage *o) 0099 { 0100 if (o != m_cachedImage) { 0101 RenderReplaced::updatePixmap(r, o); 0102 return; 0103 } 0104 0105 bool iwchanged = false; 0106 0107 if (o->isErrorImage()) { 0108 int iw = o->pixmap_size().width() + 8; 0109 int ih = o->pixmap_size().height() + 8; 0110 0111 // we have an alt and the user meant it (its not a text we invented) 0112 if (element() && !alt.isEmpty() && !element()->getAttribute(ATTR_ALT).isNull()) { 0113 const QFontMetrics &fm = style()->fontMetrics(); 0114 QRect br = fm.boundingRect(0, 0, 1024, 256, Qt::AlignLeft | Qt::TextWordWrap, alt.string()); 0115 if (br.width() > iw) { 0116 iw = br.width(); 0117 } 0118 //#ifdef __GNUC__ 0119 // #warning "FIXME: hack for testregression, remove (use above instead)" 0120 //#endif 0121 // iw = br.width() + qMax(-fm.minLeftBearing(), 0) + qMax(-fm.minRightBearing(), 0); 0122 0123 if (br.height() > ih) { 0124 ih = br.height(); 0125 } 0126 } 0127 0128 if (iw != intrinsicWidth()) { 0129 setIntrinsicWidth(iw); 0130 iwchanged = true; 0131 } 0132 if (ih != intrinsicHeight()) { 0133 setIntrinsicHeight(ih); 0134 iwchanged = true; 0135 } 0136 if (element() && element()->id() == ID_OBJECT) { 0137 static_cast<HTMLObjectElementImpl *>(element())->renderAlternative(); 0138 return; 0139 } 0140 } 0141 berrorPic = o->isErrorImage(); 0142 0143 bool needlayout = false; 0144 0145 // Image dimensions have been changed, see what needs to be done 0146 if (o->pixmap_size().width() != intrinsicWidth() || 0147 o->pixmap_size().height() != intrinsicHeight() || iwchanged) { 0148 // qDebug("image dimensions have been changed, old: %d/%d new: %d/%d", 0149 // intrinsicWidth(), intrinsicHeight(), 0150 // o->pixmap_size().width(), o->pixmap_size().height()); 0151 0152 if (!o->isErrorImage()) { 0153 setIntrinsicWidth(o->pixmap_size().width()); 0154 setIntrinsicHeight(o->pixmap_size().height()); 0155 } 0156 0157 // lets see if we need to relayout at all.. 0158 int oldwidth = m_width; 0159 int oldheight = m_height; 0160 int oldminwidth = m_minWidth; 0161 m_minWidth = 0; 0162 0163 if (parent()) { 0164 calcWidth(); 0165 calcHeight(); 0166 } 0167 0168 if (iwchanged || m_width != oldwidth || m_height != oldheight) { 0169 needlayout = true; 0170 } 0171 0172 m_minWidth = oldminwidth; 0173 m_width = oldwidth; 0174 m_height = oldheight; 0175 } 0176 0177 // we're not fully integrated in the tree yet.. we'll come back. 0178 if (!parent()) { 0179 return; 0180 } 0181 0182 if (needlayout) { 0183 if (!selfNeedsLayout()) { 0184 setNeedsLayout(true); 0185 } 0186 if (minMaxKnown()) { 0187 setMinMaxKnown(false); 0188 } 0189 } else { 0190 if (intrinsicHeight() == 0 || intrinsicWidth() == 0) { 0191 return; 0192 } 0193 int scaledHeight = intrinsicHeight() ? ((r.height() * contentHeight()) / intrinsicHeight()) : 0; 0194 int scaledWidth = intrinsicWidth() ? ((r.width() * contentWidth()) / intrinsicWidth()) : 0; 0195 int scaledX = intrinsicWidth() ? ((r.x() * contentWidth()) / intrinsicWidth()) : 0; 0196 int scaledY = intrinsicHeight() ? ((r.y() * contentHeight()) / intrinsicHeight()) : 0; 0197 0198 repaintRectangle(scaledX + borderLeft() + paddingLeft(), scaledY + borderTop() + paddingTop(), 0199 scaledWidth, scaledHeight); 0200 } 0201 } 0202 0203 void RenderImage::paint(PaintInfo &paintInfo, int _tx, int _ty) 0204 { 0205 if (paintInfo.phase == PaintActionOutline && style()->outlineWidth() && style()->visibility() == VISIBLE) { 0206 paintOutline(paintInfo.p, _tx + m_x, _ty + m_y, width(), height(), style()); 0207 } 0208 0209 if (paintInfo.phase != PaintActionForeground && paintInfo.phase != PaintActionSelection) { 0210 return; 0211 } 0212 0213 // not visible or not even once layouted? 0214 if (style()->visibility() != VISIBLE || m_y <= -500000) { 0215 return; 0216 } 0217 0218 _tx += m_x; 0219 _ty += m_y; 0220 0221 if ((_ty > paintInfo.r.bottom()) || (_ty + m_height <= paintInfo.r.top())) { 0222 return; 0223 } 0224 0225 if (shouldPaintBackgroundOrBorder()) { 0226 paintBoxDecorations(paintInfo, _tx, _ty); 0227 } 0228 0229 if (!canvas()->printImages()) { 0230 return; 0231 } 0232 0233 int cWidth = contentWidth(); 0234 int cHeight = contentHeight(); 0235 int leftBorder = borderLeft(); 0236 int topBorder = borderTop(); 0237 int leftPad = paddingLeft(); 0238 int topPad = paddingTop(); 0239 0240 // paint frame around image and loading icon as long as it is not completely loaded from web. 0241 if (bUnfinishedImageFrame && paintInfo.phase == PaintActionForeground && cWidth > 2 && cHeight > 2 && !complete()) { 0242 static QPixmap *loadingIcon; 0243 QColor bg = khtml::retrieveBackgroundColor(this); 0244 QColor fg = khtml::hasSufficientContrast(Qt::gray, bg) ? Qt::gray : 0245 (hasSufficientContrast(Qt::white, bg) ? Qt::white : Qt::black); 0246 paintInfo.p->setPen(QPen(fg, 1)); 0247 paintInfo.p->setBrush(Qt::NoBrush); 0248 const int offsetX = _tx + leftBorder + leftPad; 0249 const int offsetY = _ty + topBorder + topPad; 0250 paintInfo.p->drawRect(offsetX, offsetY, cWidth - 1, cHeight - 1); 0251 if (!(m_width <= 5 || m_height <= 5)) { 0252 if (!loadingIcon) { 0253 loadingIcon = new QPixmap(); 0254 loadingIcon->loadFromData(loading_icon_data, loading_icon_len); 0255 } 0256 paintInfo.p->drawPixmap(offsetX + 4, offsetY + 4, *loadingIcon, 0, 0, cWidth - 5, cHeight - 5); 0257 } 0258 0259 } 0260 0261 CachedImage *i = m_cachedImage; 0262 0263 //qCDebug(KHTML_LOG) << " contents (" << contentWidth << "/" << contentHeight << ") border=" << borderLeft() << " padding=" << paddingLeft(); 0264 if (!i || berrorPic) { 0265 if (cWidth > 2 && cHeight > 2) { 0266 if (!berrorPic) { 0267 //qDebug("qDrawShadePanel %d/%d/%d/%d", _tx + leftBorder, _ty + topBorder, cWidth, cHeight); 0268 qDrawShadePanel(paintInfo.p, _tx + leftBorder + leftPad, _ty + topBorder + topPad, cWidth, cHeight, 0269 QApplication::palette(), true, 1); 0270 } 0271 0272 QPixmap pix = *Cache::brokenPixmap; 0273 if (berrorPic && (cWidth >= pix.width() + 4) && (cHeight >= pix.height() + 4)) { 0274 QRect r(pix.rect()); 0275 r = r.intersected(QRect(0, 0, cWidth - 4, cHeight - 4)); 0276 paintInfo.p->drawPixmap(QPoint(_tx + leftBorder + leftPad + 2, _ty + topBorder + topPad + 2), pix, r); 0277 } 0278 0279 if (!alt.isEmpty()) { 0280 QString text = alt.string(); 0281 paintInfo.p->setFont(style()->font()); 0282 paintInfo.p->setPen(style()->color()); 0283 int ax = _tx + leftBorder + leftPad + 2; 0284 int ay = _ty + topBorder + topPad + 2; 0285 const QFontMetrics &fm = style()->fontMetrics(); 0286 0287 //BEGIN HACK 0288 #if 0 0289 #ifdef __GNUC__ 0290 #warning "FIXME: hack for testregression, remove" 0291 #endif 0292 ax += qMax(-fm.minLeftBearing(), 0); 0293 cWidth -= qMax(-fm.minLeftBearing(), 0); 0294 0295 #endif 0296 //END HACK 0297 if (cWidth > 5 && cHeight >= fm.height()) { 0298 paintInfo.p->drawText(ax, ay + 1, cWidth - 4, cHeight - 4, Qt::TextWordWrap, text); 0299 } 0300 } 0301 } 0302 } else if (i && !i->isTransparent() && 0303 i->image()->size().width() && i->image()->size().height()) { 0304 paintInfo.p->setPen(Qt::black); // used for bitmaps 0305 //const QPixmap& pix = i->pixmap(); 0306 if (!m_imagePainter) { 0307 m_imagePainter = new ImagePainter(i->image()); 0308 } 0309 0310 // If we have a scaled painter we want to handle the resizing ourselves, so figure out the scaled size, 0311 QTransform painterTransform = paintInfo.p->transform(); 0312 0313 bool scaled = painterTransform.isScaling() && !painterTransform.isRotating(); 0314 0315 QRect scaledRect; // bounding box of the whole thing, transformed, so we also know where the origin goes. 0316 if (scaled) { 0317 scaledRect = painterTransform.mapRect(QRect(0, 0, contentWidth(), contentHeight())); 0318 m_imagePainter->setSize(QSize(scaledRect.width(), scaledRect.height())); 0319 } else { 0320 m_imagePainter->setSize(QSize(contentWidth(), contentHeight())); 0321 } 0322 0323 // Now, figure out the rectangle to paint (in painter coordinates), by interesting us with the painting clip rectangle. 0324 int x = _tx + leftBorder + leftPad; 0325 int y = _ty + topBorder + topPad; 0326 QRect imageGeom = QRect(0, 0, contentWidth(), contentHeight()); 0327 0328 QRect clipPortion = paintInfo.r.translated(-x, -y); 0329 imageGeom &= clipPortion; 0330 0331 QPoint destPos = QPoint(x + imageGeom.x(), y + imageGeom.y()); 0332 0333 // If we're scaling, reset the painters transform, and apply it ourselves; though 0334 // being careful not apply the translation to the source rect. 0335 if (scaled) { 0336 paintInfo.p->resetTransform(); 0337 destPos = painterTransform.map(destPos); 0338 imageGeom = painterTransform.mapRect(imageGeom).translated(-scaledRect.topLeft()); 0339 } 0340 0341 m_imagePainter->paint(destPos.x(), destPos.y(), paintInfo.p, 0342 imageGeom.x(), imageGeom.y(), 0343 imageGeom.width(), imageGeom.height()); 0344 0345 if (scaled) { 0346 paintInfo.p->setTransform(painterTransform); 0347 } 0348 0349 } 0350 if (m_selectionState != SelectionNone) { 0351 // qCDebug(KHTML_LOG) << "_tx " << _tx << " _ty " << _ty << " _x " << _x << " _y " << _y; 0352 // Draw in any case if inside selection. For selection borders, the 0353 // offset will decide whether to draw selection or not 0354 bool draw = true; 0355 if (m_selectionState != SelectionInside) { 0356 int startPos, endPos; 0357 selectionStartEnd(startPos, endPos); 0358 if (selectionState() == SelectionStart) { 0359 endPos = 1; 0360 } else if (selectionState() == SelectionEnd) { 0361 startPos = 0; 0362 } 0363 draw = endPos - startPos > 0; 0364 } 0365 if (draw) { 0366 // setting the brush origin is important for compatibility, 0367 // don't touch it unless you know what you're doing 0368 paintInfo.p->setBrushOrigin(_tx, _ty - paintInfo.r.y()); 0369 paintInfo.p->fillRect(_tx, _ty, width(), height(), 0370 QBrush(style()->palette().color(QPalette::Active, QPalette::Highlight), 0371 Qt::Dense4Pattern)); 0372 } 0373 } 0374 } 0375 0376 void RenderImage::layout() 0377 { 0378 KHTMLAssert(needsLayout()); 0379 KHTMLAssert(minMaxKnown()); 0380 0381 //short m_width = 0; 0382 0383 // minimum height 0384 m_height = m_cachedImage && m_cachedImage->isErrorImage() ? intrinsicHeight() : 0; 0385 0386 calcWidth(); 0387 calcHeight(); 0388 0389 setNeedsLayout(false); 0390 } 0391 0392 bool RenderImage::nodeAtPoint(NodeInfo &info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inside) 0393 { 0394 inside |= RenderReplaced::nodeAtPoint(info, _x, _y, _tx, _ty, hitTestAction, inside); 0395 0396 if (inside && element()) { 0397 int tx = _tx + m_x; 0398 int ty = _ty + m_y; 0399 0400 HTMLImageElementImpl *i = element()->id() == ID_IMG ? static_cast<HTMLImageElementImpl *>(element()) : nullptr; 0401 HTMLMapElementImpl *map; 0402 if (i && i->document()->isHTMLDocument() && 0403 (map = static_cast<HTMLDocumentImpl *>(i->document())->getMap(i->imageMap()))) { 0404 // we're a client side image map 0405 inside = map->mapMouseEvent(_x - tx, _y - ty, contentWidth(), contentHeight(), info); 0406 info.setInnerNonSharedNode(element()); 0407 } 0408 } 0409 0410 return inside; 0411 } 0412 0413 void RenderImage::updateImage(CachedImage *newImage) 0414 { 0415 if (newImage == m_cachedImage) { 0416 return; 0417 } 0418 0419 delete m_imagePainter; m_imagePainter = nullptr; 0420 0421 if (m_cachedImage) { 0422 m_cachedImage->deref(this); 0423 } 0424 0425 // Note: this must be up-to-date before we ref, since 0426 // ref can cause us to be notified of it being loaded 0427 m_cachedImage = newImage; 0428 if (m_cachedImage) { 0429 m_cachedImage->ref(this); 0430 } 0431 0432 // if the loading finishes we might get an error and then the image is deleted 0433 if (m_cachedImage) { 0434 berrorPic = m_cachedImage->isErrorImage(); 0435 } else { 0436 berrorPic = true; 0437 } 0438 } 0439 0440 void RenderImage::updateFromElement() 0441 { 0442 if (element()->id() == ID_INPUT) { 0443 alt = static_cast<HTMLInputElementImpl *>(element())->altText(); 0444 } else if (element()->id() == ID_IMG) { 0445 alt = static_cast<HTMLImageElementImpl *>(element())->altText(); 0446 } 0447 0448 const DOMString u = element()->id() == ID_OBJECT ? 0449 element()->getAttribute(ATTR_DATA).trimSpaces() : element()->getAttribute(ATTR_SRC).trimSpaces(); 0450 0451 if (!u.isEmpty()) { 0452 // Need to compute completeURL, as 'u' can be relative 0453 // while m_cachedImage->url() is always full url 0454 DocumentImpl *docImpl = element()->document(); 0455 const QString fullUrl = docImpl->completeURL(u.string()); 0456 if (!m_cachedImage || m_cachedImage->url() != fullUrl) { 0457 CachedImage *new_image = docImpl->docLoader()->requestImage(DOMString(fullUrl)); 0458 if (new_image && new_image != m_cachedImage) { 0459 updateImage(new_image); 0460 } 0461 } 0462 } 0463 } 0464 0465 bool RenderImage::complete() const 0466 { 0467 // "complete" means that the image has been loaded 0468 // but also that its width/height (contentWidth(),contentHeight()) have been calculated. 0469 return m_cachedImage && m_cachedImage->isComplete() && !needsLayout(); 0470 } 0471 0472 bool RenderImage::isWidthSpecified() const 0473 { 0474 switch (style()->width().type()) { 0475 case Fixed: 0476 case Percent: 0477 return true; 0478 default: 0479 return false; 0480 } 0481 assert(false); 0482 return false; 0483 } 0484 0485 bool RenderImage::isHeightSpecified() const 0486 { 0487 switch (style()->height().type()) { 0488 case Fixed: 0489 case Percent: 0490 return true; 0491 default: 0492 return false; 0493 } 0494 assert(false); 0495 return false; 0496 } 0497 0498 short RenderImage::calcAspectRatioWidth() const 0499 { 0500 if (intrinsicHeight() == 0) { 0501 return 0; 0502 } 0503 if (!m_cachedImage || m_cachedImage->isErrorImage()) { 0504 return intrinsicWidth(); // Don't bother scaling. 0505 } 0506 return RenderReplaced::calcReplacedHeight() * intrinsicWidth() / intrinsicHeight(); 0507 } 0508 0509 int RenderImage::calcAspectRatioHeight() const 0510 { 0511 if (intrinsicWidth() == 0) { 0512 return 0; 0513 } 0514 if (!m_cachedImage || m_cachedImage->isErrorImage()) { 0515 return intrinsicHeight(); // Don't bother scaling. 0516 } 0517 return RenderReplaced::calcReplacedWidth() * intrinsicHeight() / intrinsicWidth(); 0518 } 0519 0520 short RenderImage::calcReplacedWidth() const 0521 { 0522 int width; 0523 if (isWidthSpecified()) { 0524 width = calcReplacedWidthUsing(Width); 0525 } else { 0526 width = calcAspectRatioWidth(); 0527 } 0528 int minW = calcReplacedWidthUsing(MinWidth); 0529 int maxW = style()->maxWidth().isUndefined() ? width : calcReplacedWidthUsing(MaxWidth); 0530 0531 if (width > maxW) { 0532 width = maxW; 0533 } 0534 0535 if (width < minW) { 0536 width = minW; 0537 } 0538 0539 return width; 0540 } 0541 0542 int RenderImage::calcReplacedHeight() const 0543 { 0544 int height; 0545 if (isHeightSpecified()) { 0546 height = calcReplacedHeightUsing(Height); 0547 } else { 0548 height = calcAspectRatioHeight(); 0549 } 0550 0551 int minH = calcReplacedHeightUsing(MinHeight); 0552 int maxH = style()->maxHeight().isUndefined() ? height : calcReplacedHeightUsing(MaxHeight); 0553 0554 if (height > maxH) { 0555 height = maxH; 0556 } 0557 0558 if (height < minH) { 0559 height = minH; 0560 } 0561 0562 return height; 0563 } 0564 0565 #if 0 0566 void RenderImage::caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height) const 0567 { 0568 RenderReplaced::caretPos(offset, flags, _x, _y, width, height); 0569 0570 #if 0 // doesn't work reliably 0571 height = intrinsicHeight(); 0572 width = override && offset == 0 ? intrinsicWidth() : 0; 0573 _x = xPos(); 0574 _y = yPos(); 0575 if (offset > 0) { 0576 _x += intrinsicWidth(); 0577 } 0578 0579 RenderObject *cb = containingBlock(); 0580 0581 int absx, absy; 0582 if (cb && cb != this && cb->absolutePosition(absx, absy)) { 0583 _x += absx; 0584 _y += absy; 0585 } else { 0586 // we don't know our absolute position, and there is no point returning 0587 // just a relative one 0588 _x = _y = -1; 0589 } 0590 #endif 0591 } 0592 #endif