1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
5  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
6  *           (C) 2000-2003 Dirk Mueller (mueller@kde.org)
7  *           (C) 2003 Apple Computer, Inc.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB.  If not, write to
21  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  *
24  */
25 //#define DEBUG_LAYOUT
26 
27 #include "render_image.h"
28 #include "render_canvas.h"
29 
30 #include <qdrawutil.h>
31 #include <QPainter>
32 #include <QApplication>
33 
34 #include "khtml_debug.h"
35 
36 #include "misc/loader.h"
37 #include "html/html_formimpl.h"
38 #include "html/html_imageimpl.h"
39 #include "xml/dom2_eventsimpl.h"
40 #include "html/html_documentimpl.h"
41 #include "html/html_objectimpl.h"
42 #include "khtmlview.h"
43 #include "khtml_part.h"
44 #include <math.h>
45 #include "imload/imagepainter.h"
46 
47 #include "loading_icon.cpp"
48 
49 using namespace DOM;
50 using namespace khtml;
51 using namespace khtmlImLoad;
52 
53 // -------------------------------------------------------------------------
54 
RenderImage(NodeImpl * _element)55 RenderImage::RenderImage(NodeImpl *_element)
56     : RenderReplaced(_element)
57 {
58     m_cachedImage  = nullptr;
59     m_imagePainter = nullptr;
60 
61     m_selectionState = SelectionNone;
62     berrorPic = false;
63 
64     const KHTMLSettings *settings = _element->document()->view()->part()->settings();
65     bUnfinishedImageFrame = settings->unfinishedImageFrame();
66 
67     setIntrinsicWidth(0);
68     setIntrinsicHeight(0);
69 }
70 
~RenderImage()71 RenderImage::~RenderImage()
72 {
73     delete m_imagePainter;
74     if (m_cachedImage) {
75         m_cachedImage->deref(this);
76     }
77 }
78 
79 // QPixmap RenderImage::pixmap() const
80 // {
81 //     return image ? image->pixmap() : QPixmap();
82 // }
83 
setStyle(RenderStyle * _style)84 void RenderImage::setStyle(RenderStyle *_style)
85 {
86     RenderReplaced::setStyle(_style);
87     // init RenderObject attributes
88     setShouldPaintBackgroundOrBorder(true);
89 }
90 
setContentObject(CachedObject * co)91 void RenderImage::setContentObject(CachedObject *co)
92 {
93     if (co && m_cachedImage != co) {
94         updateImage(static_cast<CachedImage *>(co));
95     }
96 }
97 
updatePixmap(const QRect & r,CachedImage * o)98 void RenderImage::updatePixmap(const QRect &r, CachedImage *o)
99 {
100     if (o != m_cachedImage) {
101         RenderReplaced::updatePixmap(r, o);
102         return;
103     }
104 
105     bool iwchanged = false;
106 
107     if (o->isErrorImage()) {
108         int iw = o->pixmap_size().width() + 8;
109         int ih = o->pixmap_size().height() + 8;
110 
111         // we have an alt and the user meant it (its not a text we invented)
112         if (element() && !alt.isEmpty() && !element()->getAttribute(ATTR_ALT).isNull()) {
113             const QFontMetrics &fm = style()->fontMetrics();
114             QRect br = fm.boundingRect(0, 0, 1024, 256, Qt::AlignLeft | Qt::TextWordWrap, alt.string());
115             if (br.width() > iw) {
116                 iw = br.width();
117             }
118 //#ifdef __GNUC__
119 //  #warning "FIXME: hack for testregression, remove (use above instead)"
120 //#endif
121 //              iw = br.width() + qMax(-fm.minLeftBearing(), 0) + qMax(-fm.minRightBearing(), 0);
122 
123             if (br.height() > ih) {
124                 ih = br.height();
125             }
126         }
127 
128         if (iw != intrinsicWidth()) {
129             setIntrinsicWidth(iw);
130             iwchanged = true;
131         }
132         if (ih != intrinsicHeight()) {
133             setIntrinsicHeight(ih);
134             iwchanged = true;
135         }
136         if (element() && element()->id() == ID_OBJECT) {
137             static_cast<HTMLObjectElementImpl *>(element())->renderAlternative();
138             return;
139         }
140     }
141     berrorPic = o->isErrorImage();
142 
143     bool needlayout = false;
144 
145     // Image dimensions have been changed, see what needs to be done
146     if (o->pixmap_size().width() != intrinsicWidth() ||
147             o->pixmap_size().height() != intrinsicHeight() || iwchanged) {
148 //           qDebug("image dimensions have been changed, old: %d/%d  new: %d/%d",
149 //                  intrinsicWidth(), intrinsicHeight(),
150 //               o->pixmap_size().width(), o->pixmap_size().height());
151 
152         if (!o->isErrorImage()) {
153             setIntrinsicWidth(o->pixmap_size().width());
154             setIntrinsicHeight(o->pixmap_size().height());
155         }
156 
157         // lets see if we need to relayout at all..
158         int oldwidth = m_width;
159         int oldheight = m_height;
160         int oldminwidth = m_minWidth;
161         m_minWidth = 0;
162 
163         if (parent()) {
164             calcWidth();
165             calcHeight();
166         }
167 
168         if (iwchanged || m_width != oldwidth || m_height != oldheight) {
169             needlayout = true;
170         }
171 
172         m_minWidth = oldminwidth;
173         m_width = oldwidth;
174         m_height = oldheight;
175     }
176 
177     // we're not fully integrated in the tree yet.. we'll come back.
178     if (!parent()) {
179         return;
180     }
181 
182     if (needlayout) {
183         if (!selfNeedsLayout()) {
184             setNeedsLayout(true);
185         }
186         if (minMaxKnown()) {
187             setMinMaxKnown(false);
188         }
189     } else {
190         if (intrinsicHeight() == 0 || intrinsicWidth() == 0) {
191             return;
192         }
193         int scaledHeight = intrinsicHeight() ? ((r.height() * contentHeight()) / intrinsicHeight()) : 0;
194         int scaledWidth = intrinsicWidth() ? ((r.width() * contentWidth()) / intrinsicWidth()) : 0;
195         int scaledX = intrinsicWidth() ? ((r.x() * contentWidth()) / intrinsicWidth()) : 0;
196         int scaledY = intrinsicHeight() ? ((r.y() * contentHeight()) / intrinsicHeight()) : 0;
197 
198         repaintRectangle(scaledX + borderLeft() + paddingLeft(), scaledY + borderTop() + paddingTop(),
199                          scaledWidth, scaledHeight);
200     }
201 }
202 
paint(PaintInfo & paintInfo,int _tx,int _ty)203 void RenderImage::paint(PaintInfo &paintInfo, int _tx, int _ty)
204 {
205     if (paintInfo.phase == PaintActionOutline && style()->outlineWidth() && style()->visibility() == VISIBLE) {
206         paintOutline(paintInfo.p, _tx + m_x, _ty + m_y, width(), height(), style());
207     }
208 
209     if (paintInfo.phase != PaintActionForeground && paintInfo.phase != PaintActionSelection) {
210         return;
211     }
212 
213     // not visible or not even once layouted?
214     if (style()->visibility() != VISIBLE || m_y <=  -500000) {
215         return;
216     }
217 
218     _tx += m_x;
219     _ty += m_y;
220 
221     if ((_ty > paintInfo.r.bottom()) || (_ty + m_height <= paintInfo.r.top())) {
222         return;
223     }
224 
225     if (shouldPaintBackgroundOrBorder()) {
226         paintBoxDecorations(paintInfo, _tx, _ty);
227     }
228 
229     if (!canvas()->printImages()) {
230         return;
231     }
232 
233     int cWidth = contentWidth();
234     int cHeight = contentHeight();
235     int leftBorder = borderLeft();
236     int topBorder = borderTop();
237     int leftPad = paddingLeft();
238     int topPad = paddingTop();
239 
240     // paint frame around image and loading icon as long as it is not completely loaded from web.
241     if (bUnfinishedImageFrame && paintInfo.phase == PaintActionForeground && cWidth > 2 && cHeight > 2 && !complete()) {
242         static QPixmap *loadingIcon;
243         QColor bg = khtml::retrieveBackgroundColor(this);
244         QColor fg = khtml::hasSufficientContrast(Qt::gray, bg) ? Qt::gray :
245                     (hasSufficientContrast(Qt::white, bg) ? Qt::white : Qt::black);
246         paintInfo.p->setPen(QPen(fg, 1));
247         paintInfo.p->setBrush(Qt::NoBrush);
248         const int offsetX = _tx + leftBorder + leftPad;
249         const int offsetY = _ty + topBorder + topPad;
250         paintInfo.p->drawRect(offsetX, offsetY, cWidth - 1, cHeight - 1);
251         if (!(m_width <= 5 || m_height <= 5)) {
252             if (!loadingIcon) {
253                 loadingIcon = new QPixmap();
254                 loadingIcon->loadFromData(loading_icon_data, loading_icon_len);
255             }
256             paintInfo.p->drawPixmap(offsetX + 4, offsetY + 4, *loadingIcon, 0, 0, cWidth - 5, cHeight - 5);
257         }
258 
259     }
260 
261     CachedImage *i = m_cachedImage;
262 
263     //qCDebug(KHTML_LOG) << "    contents (" << contentWidth << "/" << contentHeight << ") border=" << borderLeft() << " padding=" << paddingLeft();
264     if (!i || berrorPic) {
265         if (cWidth > 2 && cHeight > 2) {
266             if (!berrorPic) {
267                 //qDebug("qDrawShadePanel %d/%d/%d/%d", _tx + leftBorder, _ty + topBorder, cWidth, cHeight);
268                 qDrawShadePanel(paintInfo.p, _tx + leftBorder + leftPad, _ty + topBorder + topPad, cWidth, cHeight,
269                                 QApplication::palette(), true, 1);
270             }
271 
272             QPixmap pix = *Cache::brokenPixmap;
273             if (berrorPic && (cWidth >= pix.width() + 4) && (cHeight >= pix.height() + 4)) {
274                 QRect r(pix.rect());
275                 r = r.intersected(QRect(0, 0, cWidth - 4, cHeight - 4));
276                 paintInfo.p->drawPixmap(QPoint(_tx + leftBorder + leftPad + 2, _ty + topBorder + topPad + 2), pix, r);
277             }
278 
279             if (!alt.isEmpty()) {
280                 QString text = alt.string();
281                 paintInfo.p->setFont(style()->font());
282                 paintInfo.p->setPen(style()->color());
283                 int ax = _tx + leftBorder + leftPad + 2;
284                 int ay = _ty + topBorder + topPad + 2;
285                 const QFontMetrics &fm = style()->fontMetrics();
286 
287 //BEGIN HACK
288 #if 0
289 #ifdef __GNUC__
290 #warning "FIXME: hack for testregression, remove"
291 #endif
292                 ax     += qMax(-fm.minLeftBearing(), 0);
293                 cWidth -= qMax(-fm.minLeftBearing(), 0);
294 
295 #endif
296 //END HACK
297                 if (cWidth > 5 && cHeight >= fm.height()) {
298                     paintInfo.p->drawText(ax, ay + 1, cWidth - 4, cHeight - 4, Qt::TextWordWrap, text);
299                 }
300             }
301         }
302     } else if (i && !i->isTransparent() &&
303                i->image()->size().width() && i->image()->size().height()) {
304         paintInfo.p->setPen(Qt::black);   // used for bitmaps
305         //const QPixmap& pix = i->pixmap();
306         if (!m_imagePainter) {
307             m_imagePainter = new ImagePainter(i->image());
308         }
309 
310         // If we have a scaled  painter we want to handle the resizing ourselves, so figure out the scaled size,
311         QTransform painterTransform = paintInfo.p->transform();
312 
313         bool scaled = painterTransform.isScaling() && !painterTransform.isRotating();
314 
315         QRect scaledRect; // bounding box of the whole thing, transformed, so we also know where the origin goes.
316         if (scaled) {
317             scaledRect = painterTransform.mapRect(QRect(0, 0, contentWidth(), contentHeight()));
318             m_imagePainter->setSize(QSize(scaledRect.width(), scaledRect.height()));
319         } else {
320             m_imagePainter->setSize(QSize(contentWidth(), contentHeight()));
321         }
322 
323         // Now, figure out the rectangle to paint (in painter coordinates), by interesting us with the painting clip rectangle.
324         int x = _tx + leftBorder + leftPad;
325         int y = _ty + topBorder + topPad;
326         QRect imageGeom   = QRect(0, 0, contentWidth(), contentHeight());
327 
328         QRect clipPortion = paintInfo.r.translated(-x, -y);
329         imageGeom &= clipPortion;
330 
331         QPoint destPos = QPoint(x + imageGeom.x(), y + imageGeom.y());
332 
333         // If we're scaling, reset the painters transform, and apply it ourselves; though
334         // being careful not apply the translation to the source rect.
335         if (scaled) {
336             paintInfo.p->resetTransform();
337             destPos   = painterTransform.map(destPos);
338             imageGeom = painterTransform.mapRect(imageGeom).translated(-scaledRect.topLeft());
339         }
340 
341         m_imagePainter->paint(destPos.x(), destPos.y(), paintInfo.p,
342                               imageGeom.x(),     imageGeom.y(),
343                               imageGeom.width(), imageGeom.height());
344 
345         if (scaled) {
346             paintInfo.p->setTransform(painterTransform);
347         }
348 
349     }
350     if (m_selectionState != SelectionNone) {
351 //    qCDebug(KHTML_LOG) << "_tx " << _tx << " _ty " << _ty << " _x " << _x << " _y " << _y;
352         // Draw in any case if inside selection. For selection borders, the
353         // offset will decide whether to draw selection or not
354         bool draw = true;
355         if (m_selectionState != SelectionInside) {
356             int startPos, endPos;
357             selectionStartEnd(startPos, endPos);
358             if (selectionState() == SelectionStart) {
359                 endPos = 1;
360             } else if (selectionState() == SelectionEnd) {
361                 startPos = 0;
362             }
363             draw = endPos - startPos > 0;
364         }
365         if (draw) {
366             // setting the brush origin is important for compatibility,
367             // don't touch it unless you know what you're doing
368             paintInfo.p->setBrushOrigin(_tx, _ty - paintInfo.r.y());
369             paintInfo.p->fillRect(_tx, _ty, width(), height(),
370                                   QBrush(style()->palette().color(QPalette::Active, QPalette::Highlight),
371                                          Qt::Dense4Pattern));
372         }
373     }
374 }
375 
layout()376 void RenderImage::layout()
377 {
378     KHTMLAssert(needsLayout());
379     KHTMLAssert(minMaxKnown());
380 
381     //short m_width = 0;
382 
383     // minimum height
384     m_height = m_cachedImage && m_cachedImage->isErrorImage() ? intrinsicHeight() : 0;
385 
386     calcWidth();
387     calcHeight();
388 
389     setNeedsLayout(false);
390 }
391 
nodeAtPoint(NodeInfo & info,int _x,int _y,int _tx,int _ty,HitTestAction hitTestAction,bool inside)392 bool RenderImage::nodeAtPoint(NodeInfo &info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inside)
393 {
394     inside |= RenderReplaced::nodeAtPoint(info, _x, _y, _tx, _ty, hitTestAction, inside);
395 
396     if (inside && element()) {
397         int tx = _tx + m_x;
398         int ty = _ty + m_y;
399 
400         HTMLImageElementImpl *i = element()->id() == ID_IMG ? static_cast<HTMLImageElementImpl *>(element()) : nullptr;
401         HTMLMapElementImpl *map;
402         if (i && i->document()->isHTMLDocument() &&
403                 (map = static_cast<HTMLDocumentImpl *>(i->document())->getMap(i->imageMap()))) {
404             // we're a client side image map
405             inside = map->mapMouseEvent(_x - tx, _y - ty, contentWidth(), contentHeight(), info);
406             info.setInnerNonSharedNode(element());
407         }
408     }
409 
410     return inside;
411 }
412 
updateImage(CachedImage * newImage)413 void RenderImage::updateImage(CachedImage *newImage)
414 {
415     if (newImage == m_cachedImage) {
416         return;
417     }
418 
419     delete m_imagePainter; m_imagePainter = nullptr;
420 
421     if (m_cachedImage) {
422         m_cachedImage->deref(this);
423     }
424 
425     // Note: this must be up-to-date before we ref, since
426     // ref can cause us to be notified of it being loaded
427     m_cachedImage  = newImage;
428     if (m_cachedImage) {
429         m_cachedImage->ref(this);
430     }
431 
432     // if the loading finishes we might get an error and then the image is deleted
433     if (m_cachedImage) {
434         berrorPic = m_cachedImage->isErrorImage();
435     } else {
436         berrorPic = true;
437     }
438 }
439 
updateFromElement()440 void RenderImage::updateFromElement()
441 {
442     if (element()->id() == ID_INPUT) {
443         alt = static_cast<HTMLInputElementImpl *>(element())->altText();
444     } else if (element()->id() == ID_IMG) {
445         alt = static_cast<HTMLImageElementImpl *>(element())->altText();
446     }
447 
448     const DOMString u = element()->id() == ID_OBJECT ?
449                   element()->getAttribute(ATTR_DATA).trimSpaces() : element()->getAttribute(ATTR_SRC).trimSpaces();
450 
451     if (!u.isEmpty()) {
452         // Need to compute completeURL, as 'u' can be relative
453         // while m_cachedImage->url() is always full url
454         DocumentImpl *docImpl = element()->document();
455         const QString fullUrl = docImpl->completeURL(u.string());
456         if (!m_cachedImage || m_cachedImage->url() != fullUrl) {
457             CachedImage *new_image = docImpl->docLoader()->requestImage(DOMString(fullUrl));
458             if (new_image && new_image != m_cachedImage) {
459                 updateImage(new_image);
460             }
461         }
462     }
463 }
464 
complete() const465 bool RenderImage::complete() const
466 {
467     // "complete" means that the image has been loaded
468     // but also that its width/height (contentWidth(),contentHeight()) have been calculated.
469     return m_cachedImage && m_cachedImage->isComplete() && !needsLayout();
470 }
471 
isWidthSpecified() const472 bool RenderImage::isWidthSpecified() const
473 {
474     switch (style()->width().type()) {
475     case Fixed:
476     case Percent:
477         return true;
478     default:
479         return false;
480     }
481     assert(false);
482     return false;
483 }
484 
isHeightSpecified() const485 bool RenderImage::isHeightSpecified() const
486 {
487     switch (style()->height().type()) {
488     case Fixed:
489     case Percent:
490         return true;
491     default:
492         return false;
493     }
494     assert(false);
495     return false;
496 }
497 
calcAspectRatioWidth() const498 short RenderImage::calcAspectRatioWidth() const
499 {
500     if (intrinsicHeight() == 0) {
501         return 0;
502     }
503     if (!m_cachedImage || m_cachedImage->isErrorImage()) {
504         return intrinsicWidth();    // Don't bother scaling.
505     }
506     return RenderReplaced::calcReplacedHeight() * intrinsicWidth() / intrinsicHeight();
507 }
508 
calcAspectRatioHeight() const509 int RenderImage::calcAspectRatioHeight() const
510 {
511     if (intrinsicWidth() == 0) {
512         return 0;
513     }
514     if (!m_cachedImage || m_cachedImage->isErrorImage()) {
515         return intrinsicHeight();    // Don't bother scaling.
516     }
517     return RenderReplaced::calcReplacedWidth() * intrinsicHeight() / intrinsicWidth();
518 }
519 
calcReplacedWidth() const520 short RenderImage::calcReplacedWidth() const
521 {
522     int width;
523     if (isWidthSpecified()) {
524         width = calcReplacedWidthUsing(Width);
525     } else {
526         width = calcAspectRatioWidth();
527     }
528     int minW = calcReplacedWidthUsing(MinWidth);
529     int maxW = style()->maxWidth().isUndefined() ? width : calcReplacedWidthUsing(MaxWidth);
530 
531     if (width > maxW) {
532         width = maxW;
533     }
534 
535     if (width < minW) {
536         width = minW;
537     }
538 
539     return width;
540 }
541 
calcReplacedHeight() const542 int RenderImage::calcReplacedHeight() const
543 {
544     int height;
545     if (isHeightSpecified()) {
546         height = calcReplacedHeightUsing(Height);
547     } else {
548         height = calcAspectRatioHeight();
549     }
550 
551     int minH = calcReplacedHeightUsing(MinHeight);
552     int maxH = style()->maxHeight().isUndefined() ? height : calcReplacedHeightUsing(MaxHeight);
553 
554     if (height > maxH) {
555         height = maxH;
556     }
557 
558     if (height < minH) {
559         height = minH;
560     }
561 
562     return height;
563 }
564 
565 #if 0
566 void RenderImage::caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height) const
567 {
568     RenderReplaced::caretPos(offset, flags, _x, _y, width, height);
569 
570 #if 0   // doesn't work reliably
571     height = intrinsicHeight();
572     width = override && offset == 0 ? intrinsicWidth() : 0;
573     _x = xPos();
574     _y = yPos();
575     if (offset > 0) {
576         _x += intrinsicWidth();
577     }
578 
579     RenderObject *cb = containingBlock();
580 
581     int absx, absy;
582     if (cb && cb != this && cb->absolutePosition(absx, absy)) {
583         _x += absx;
584         _y += absy;
585     } else {
586         // we don't know our absolute position, and there is no point returning
587         // just a relative one
588         _x = _y = -1;
589     }
590 #endif
591 }
592 #endif
593