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