KHtml

render_image.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999-2003 Lars Knoll ([email protected])
5  * (C) 1999 Antti Koivisto ([email protected])
6  * (C) 2000-2003 Dirk Mueller ([email protected])
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 
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 
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 
84 void RenderImage::setStyle(RenderStyle *_style)
85 {
86  RenderReplaced::setStyle(_style);
87  // init RenderObject attributes
88  setShouldPaintBackgroundOrBorder(true);
89 }
90 
91 void RenderImage::setContentObject(CachedObject *co)
92 {
93  if (co && m_cachedImage != co) {
94  updateImage(static_cast<CachedImage *>(co));
95  }
96 }
97 
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 
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;
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),
372  }
373  }
374 }
375 
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 
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 
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 
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 
465 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 
472 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 
485 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 
498 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 
509 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 
520 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 
542 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
Settings for the HTML view.
int width() const const
int width() const const
This file is part of the HTML rendering engine for KDE.
bool hasSufficientContrast(const QColor &c1, const QColor &c2)
checks whether the given colors have enough contrast
Definition: helper.cpp:267
QColor retrieveBackgroundColor(const RenderObject *obj)
finds out the background color of an element
Definition: helper.cpp:248
QSize size() const
Returns the image&#39;s size.
Definition: image.cpp:353
bool isScaling() const const
int height() const const
bool loadFromData(const uchar *data, uint len, const char *format, Qt::ImageConversionFlags flags)
int x() const const
int y() const const
An image painter let&#39;s one paint an image at the given size.
Definition: imagepainter.h:40
AlignLeft
a cached image
Definition: loader.h:359
int x() const const
int y() const const
QRect boundingRect(QChar ch) const const
Type type(const QSqlDatabase &db)
QPalette palette()
This class implements the basic string we use in the DOM.
Definition: dom_string.h:44
QRect translated(int dx, int dy) const const
QRect intersected(const QRect &rectangle) const const
int height() const const
This library provides a full-featured HTML parser and widget.
int minLeftBearing() const const
int width() const const
int height() const const
int height() const const
TextWordWrap
Base Class for all rendering tree objects.
QPoint topLeft() const const
void setSize(const QSize &size)
QRect rect() const const
QFuture< void > map(Sequence &sequence, MapFunctor function)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Oct 26 2021 22:48:07 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.