KHtml

render_list.cpp
1 /**
2  * This file is part of the HTML rendering engine for KDE.
3  *
4  * Copyright (C) 1999-2003 Lars Knoll ([email protected])
5  * (C) 1999 Antti Koivisto ([email protected])
6  * (C) 2000-2002 Dirk Mueller ([email protected])
7  * (C) 2003 Apple Computer, Inc.
8  * (C) 2004-2005 Allan Sandfeld Jensen ([email protected])
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 "rendering/render_list.h"
28 #include "rendering/render_canvas.h"
29 #include "rendering/enumerate.h"
30 #include "rendering/counter_tree.h"
31 #include "html/html_listimpl.h"
32 #include "imload/imagepainter.h"
33 #include "misc/helper.h"
34 #include "misc/loader.h"
35 #include "xml/dom_docimpl.h"
36 
37 #include "khtml_debug.h"
38 
39 //#define BOX_DEBUG
40 
41 using namespace khtml;
42 using namespace Enumerate;
43 using namespace khtmlImLoad;
44 
45 const int cMarkerPadding = 7;
46 
47 // -------------------------------------------------------------------------
48 
49 RenderListItem::RenderListItem(DOM::NodeImpl *node)
50  : RenderBlock(node)
51 {
52  // init RenderObject attributes
53  setInline(false); // our object is not Inline
54 
55  predefVal = -1;
56  m_marker = nullptr;
57  m_counter = nullptr;
58  m_insideList = false;
59  m_deleteMarker = false;
60 }
61 
62 void RenderListItem::setStyle(RenderStyle *_style)
63 {
64  RenderBlock::setStyle(_style);
65 
66  RenderStyle *newStyle = new RenderStyle();
67  newStyle->ref();
68 
69  newStyle->inheritFrom(style());
70 
71  const bool showListMarker = style()->listStyleImage() || style()->listStyleType() != LNONE;
72 
73  if (!m_marker && showListMarker) {
74  m_marker = new(renderArena()) RenderListMarker(element());
75  m_marker->setIsAnonymous(true);
76  m_marker->setStyle(newStyle);
77  m_marker->setListItem(this);
78  m_deleteMarker = true;
79  } else if (m_marker && !showListMarker) {
80  m_marker->detach();
81  m_marker = nullptr;
82  } else if (m_marker) {
83  m_marker->setStyle(newStyle);
84  }
85 
86  newStyle->deref();
87 }
88 
89 void RenderListItem::detach()
90 {
91  if (m_marker && m_deleteMarker) {
92  m_marker->detach();
93  }
94  RenderBlock::detach();
95 }
96 
97 static RenderObject *getParentOfFirstLineBox(RenderBlock *curr, RenderObject *marker)
98 {
99  RenderObject *firstChild = curr->firstChild();
100  if (!firstChild) {
101  return nullptr;
102  }
103 
104  for (RenderObject *currChild = firstChild;
105  currChild; currChild = currChild->nextSibling()) {
106  if (currChild == marker) {
107  continue;
108  }
109 
110  if (currChild->isInline() && (!currChild->isInlineFlow() || curr->inlineChildNeedsLineBox(currChild))) {
111  return curr;
112  }
113 
114  if (currChild->isFloating() || currChild->isPositioned()) {
115  continue;
116  }
117 
118  if (currChild->isTable() || !currChild->isRenderBlock()) {
119  break;
120  }
121 
122  if (curr->isListItem() && currChild->style()->htmlHacks() && currChild->element() &&
123  (currChild->element()->id() == ID_UL || currChild->element()->id() == ID_OL ||
124  currChild->element()->id() == ID_DIR || currChild->element()->id() == ID_MENU)) {
125  break;
126  }
127 
128  RenderObject *lineBox = getParentOfFirstLineBox(static_cast<RenderBlock *>(currChild), marker);
129  if (lineBox) {
130  return lineBox;
131  }
132  }
133 
134  return nullptr;
135 }
136 
137 static RenderObject *firstNonMarkerChild(RenderObject *parent)
138 {
139  RenderObject *result = parent->firstChild();
140  while (result && result->isListMarker()) {
141  result = result->nextSibling();
142  }
143  return result;
144 }
145 
146 void RenderListItem::updateMarkerLocation()
147 {
148  // Sanity check the location of our marker.
149  if (m_marker) {
150  RenderObject *markerPar = m_marker->parent();
151  RenderObject *lineBoxParent = getParentOfFirstLineBox(this, m_marker);
152  if (!lineBoxParent) {
153  // If the marker is currently contained inside an anonymous box,
154  // then we are the only item in that anonymous box (since no line box
155  // parent was found). It's ok to just leave the marker where it is
156  // in this case.
157  if (markerPar && markerPar->isAnonymousBlock()) {
158  lineBoxParent = markerPar;
159  } else {
160  lineBoxParent = this;
161  }
162  }
163  if (markerPar != lineBoxParent) {
164  if (markerPar) {
165  markerPar->removeChild(m_marker);
166  }
167  if (!lineBoxParent) {
168  lineBoxParent = this;
169  }
170  lineBoxParent->addChild(m_marker, firstNonMarkerChild(lineBoxParent));
171  m_deleteMarker = false;
172  if (!m_marker->minMaxKnown()) {
173  m_marker->calcMinMaxWidth();
174  }
175  recalcMinMaxWidths();
176  }
177  }
178 }
179 
180 void RenderListItem::calcMinMaxWidth()
181 {
182  // Make sure our marker is in the correct location.
183  updateMarkerLocation();
184  if (!minMaxKnown()) {
185  RenderBlock::calcMinMaxWidth();
186  }
187 }
188 /*
189 short RenderListItem::marginLeft() const
190 {
191  if (m_insideList)
192  return RenderBlock::marginLeft();
193  else
194  return qMax(m_marker->markerWidth(), RenderBlock::marginLeft());
195 }
196 
197 short RenderListItem::marginRight() const
198 {
199  return RenderBlock::marginRight();
200 }*/
201 
202 void RenderListItem::layout()
203 {
204  KHTMLAssert(needsLayout());
205  KHTMLAssert(minMaxKnown());
206 
207  updateMarkerLocation();
208  RenderBlock::layout();
209 }
210 
211 // -----------------------------------------------------------
212 
213 RenderListMarker::RenderListMarker(DOM::NodeImpl *node)
214  : RenderBox(node), m_listImage(nullptr), m_markerWidth(0)
215 {
216  // init RenderObject attributes
217  setInline(true); // our object is Inline
218  setReplaced(true); // pretend to be replaced
219  // val = -1;
220  // m_listImage = 0;
221 }
222 
223 RenderListMarker::~RenderListMarker()
224 {
225  if (m_listImage) {
226  m_listImage->deref(this);
227  }
228  if (m_listItem) {
229  m_listItem->resetListMarker();
230  }
231 }
232 
233 void RenderListMarker::setStyle(RenderStyle *s)
234 {
235  if (style() && (s->listStylePosition() != style()->listStylePosition() || s->listStyleType() != style()->listStyleType())) {
236  setNeedsLayoutAndMinMaxRecalc();
237  }
238 
239  RenderBox::setStyle(s);
240 
241  if (m_listImage != style()->listStyleImage()) {
242  if (m_listImage) {
243  m_listImage->deref(this);
244  }
245  m_listImage = style()->listStyleImage();
246  if (m_listImage) {
247  m_listImage->ref(this);
248  }
249  }
250 }
251 
252 void RenderListMarker::paint(PaintInfo &paintInfo, int _tx, int _ty)
253 {
254  if (paintInfo.phase != PaintActionForeground) {
255  return;
256  }
257 
258  if (style()->visibility() != VISIBLE) {
259  return;
260  }
261 
262  _tx += m_x;
263  _ty += m_y;
264 
265  if ((_ty > paintInfo.r.bottom()) || (_ty + m_height <= paintInfo.r.top())) {
266  return;
267  }
268 
269  if (shouldPaintBackgroundOrBorder()) {
270  paintBoxDecorations(paintInfo, _tx, _ty);
271  }
272 
273  QPainter *p = paintInfo.p;
274 #ifdef DEBUG_LAYOUT
275  qCDebug(KHTML_LOG) << nodeName().string() << "(ListMarker)::paintObject(" << _tx << ", " << _ty << ")";
276 #endif
277  p->setFont(style()->font());
278  const QFontMetrics fm = p->fontMetrics();
279 
280  // The marker needs to adjust its tx, for the case where it's an outside marker.
281  RenderObject *listItem = nullptr;
282  int leftLineOffset = 0;
283  int rightLineOffset = 0;
284  if (!listPositionInside()) {
285  listItem = this;
286  int yOffset = 0;
287  int xOffset = 0;
288  while (listItem && listItem != m_listItem) {
289  yOffset += listItem->yPos();
290  xOffset += listItem->xPos();
291  listItem = listItem->parent();
292  }
293 
294  // Now that we have our xoffset within the listbox, we need to adjust ourselves by the delta
295  // between our current xoffset and our desired position (which is just outside the border box
296  // of the list item).
297  if (style()->direction() == LTR) {
298  leftLineOffset = m_listItem->leftRelOffset(yOffset, m_listItem->leftOffset(yOffset));
299  _tx -= (xOffset - leftLineOffset) + m_listItem->paddingLeft() + m_listItem->borderLeft();
300  } else {
301  rightLineOffset = m_listItem->rightRelOffset(yOffset, m_listItem->rightOffset(yOffset));
302  _tx += (rightLineOffset - xOffset) + m_listItem->paddingRight() + m_listItem->borderRight();
303  }
304  }
305 
306  int offset = fm.ascent() * 2 / 3;
307  bool haveImage = m_listImage && !m_listImage->isErrorImage();
308  if (haveImage) {
309  offset = m_listImage->pixmap_size().width();
310  }
311 
312  int xoff = 0;
313  int yoff = fm.ascent() - offset;
314 
315  int bulletWidth = offset / 2;
316  if (offset % 2) {
317  bulletWidth++;
318  }
319  if (!listPositionInside()) {
320  if (listItem && listItem->style()->direction() == LTR) {
321  xoff = -cMarkerPadding - offset;
322  } else {
323  xoff = cMarkerPadding + (haveImage ? 0 : (offset - bulletWidth));
324  }
325  } else if (style()->direction() == RTL) {
326  xoff += haveImage ? cMarkerPadding : (m_width - bulletWidth);
327  }
328 
329  if (m_listImage && !m_listImage->isErrorImage()) {
330  ImagePainter painter(m_listImage->image());
331  painter.paint(_tx + xoff, _ty, p);
332  return;
333  }
334 
335 #ifdef BOX_DEBUG
336  p->setPen(Qt::red);
337  p->drawRect(_tx + xoff, _ty + yoff, offset, offset);
338 #endif
339 
340  const QColor color(style()->color());
341  p->setPen(color);
342 
343  switch (style()->listStyleType()) {
344  case LDISC:
345  p->setBrush(color);
346  p->drawEllipse(_tx + xoff, _ty + (3 * yoff) / 2, (offset >> 1), (offset >> 1));
347  return;
348  case LCIRCLE:
349  p->setBrush(Qt::NoBrush);
350  p->drawEllipse(_tx + xoff, _ty + (3 * yoff) / 2, (offset >> 1), (offset >> 1));
351  return;
352  case LSQUARE:
353  p->setBrush(color);
354  p->drawRect(_tx + xoff, _ty + (3 * yoff) / 2, (offset >> 1), (offset >> 1));
355  return;
356  case LBOX:
357  p->setBrush(Qt::NoBrush);
358  p->drawRect(_tx + xoff, _ty + (3 * yoff) / 2, (offset >> 1), (offset >> 1));
359  return;
360  case LDIAMOND: {
361  static QPolygon diamond(4);
362  int x = _tx + xoff;
363  int y = _ty + (3 * yoff) / 2 - 1;
364  int s = (offset >> 2) + 1;
365  diamond[0] = QPoint(x + s, y);
366  diamond[1] = QPoint(x + 2 * s, y + s);
367  diamond[2] = QPoint(x + s, y + 2 * s);
368  diamond[3] = QPoint(x, y + s);
369  p->setBrush(color);
370 
371  p->drawConvexPolygon(diamond.constData(), 4);
372  return;
373  }
374  case LNONE:
375  return;
376  default:
377  if (!m_item.isEmpty()) {
378  if (listPositionInside()) {
379  //BEGIN HACK
380 #ifdef __GNUC__
381 #warning "FIXME: hack for testregression, remove"
382 #endif
383  _tx += qMax(-fm.minLeftBearing(), 0);
384  //END HACK
385  if (style()->direction() == LTR) {
386  p->drawText(_tx, _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, m_item);
387  p->drawText(_tx + fm.width(m_item), _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip,
388  QLatin1String(". "));
389  } else {
390  const QString &punct(QLatin1String(" ."));
391  p->drawText(_tx, _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, punct);
392  p->drawText(_tx + fm.width(punct), _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, m_item);
393  }
394  } else {
395  if (style()->direction() == LTR) {
396  const QString &punct(QLatin1String(". "));
397  int itemWidth = fm.width(m_item);
398  int punctWidth = fm.width(punct);
399  p->drawText(_tx - offset / 2 - punctWidth, _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, punct);
400  p->drawText(_tx - offset / 2 - punctWidth - itemWidth, _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, m_item);
401  } else {
402  //BEGIN HACK
403 #ifdef __GNUC__
404 #warning "FIXME: hack for testregression, remove"
405 #endif
406  _tx += qMax(-fm.minLeftBearing(), 0);
407  //END HACK
408 
409  const QString &punct(QLatin1String(" ."));
410  p->drawText(_tx + offset / 2, _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, punct);
411  p->drawText(_tx + offset / 2 + fm.width(punct), _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, m_item);
412  }
413  }
414  }
415  }
416 }
417 
418 void RenderListMarker::layout()
419 {
420  KHTMLAssert(needsLayout());
421 
422  if (!minMaxKnown()) {
423  calcMinMaxWidth();
424  }
425 
426  setNeedsLayout(false);
427 }
428 
429 void RenderListMarker::updatePixmap(const QRect &r, CachedImage *o)
430 {
431  if (o != m_listImage) {
432  RenderBox::updatePixmap(r, o);
433  return;
434  }
435 
436  if (m_width != m_listImage->pixmap_size().width() || m_height != m_listImage->pixmap_size().height()) {
437  setNeedsLayoutAndMinMaxRecalc();
438  } else {
439  repaintRectangle(0, 0, m_width, m_height);
440  }
441 }
442 
443 void RenderListMarker::calcMinMaxWidth()
444 {
445  KHTMLAssert(!minMaxKnown());
446 
447  m_markerWidth = m_width = 0;
448 
449  if (m_listImage && !m_listImage->isErrorImage()) {
450  m_markerWidth = m_listImage->pixmap_size().width() + cMarkerPadding;
451  if (listPositionInside()) {
452  m_width = m_markerWidth;
453  }
454  m_height = m_listImage->pixmap_size().height();
455  m_minWidth = m_maxWidth = m_width;
456  setMinMaxKnown();
457  return;
458  }
459 
460  const QFontMetrics &fm = style()->fontMetrics();
461  m_height = fm.ascent();
462 
463  // Skip uncounted elements
464  switch (style()->listStyleType()) {
465  // Glyphs:
466  case LDISC:
467  case LCIRCLE:
468  case LSQUARE:
469  case LBOX:
470  case LDIAMOND:
471  m_markerWidth = fm.ascent();
472  goto end;
473  default:
474  break;
475  }
476 
477  {
478  // variable scope
479  CounterNode *counter = m_listItem->m_counter;
480  if (!counter) {
481  counter = m_listItem->getCounter("list-item", true);
482  counter->setRenderer(this);
483  m_listItem->m_counter = counter;
484  }
485 
486  assert(counter);
487  int value = counter->count();
488  if (counter->isReset()) {
489  value = counter->value();
490  }
491  int total = value;
492  if (counter->parent()) {
493  total = counter->parent()->total();
494  }
495 
496  switch (style()->listStyleType()) {
497 // Numeric:
498  case LDECIMAL:
499  m_item.setNum(value);
500  break;
501  case DECIMAL_LEADING_ZERO: {
502  int decimals = 2;
503  int t = total / 100;
504  while (t > 0) {
505  t = t / 10;
506  decimals++;
507  }
508  decimals = qMax(decimals, 2);
509  QString num = QString::number(value);
510  m_item.fill('0', decimals - num.length());
511  m_item.append(num);
512  break;
513  }
514  case ARABIC_INDIC:
515  m_item = toArabicIndic(value);
516  break;
517  case LAO:
518  m_item = toLao(value);
519  break;
520  case PERSIAN:
521  case URDU:
522  m_item = toPersianUrdu(value);
523  break;
524  case THAI:
525  m_item = toThai(value);
526  break;
527  case TIBETAN:
528  m_item = toTibetan(value);
529  break;
530 // Algoritmic:
531  case LOWER_ROMAN:
532  m_item = toRoman(value, false);
533  break;
534  case UPPER_ROMAN:
535  m_item = toRoman(value, true);
536  break;
537  case HEBREW:
538  m_item = toHebrew(value);
539  break;
540  case ARMENIAN:
541  m_item = toArmenian(value);
542  break;
543  case GEORGIAN:
544  m_item = toGeorgian(value);
545  break;
546 // Alphabetic:
547  case LOWER_ALPHA:
548  case LOWER_LATIN:
549  m_item = toLowerLatin(value);
550  break;
551  case UPPER_ALPHA:
552  case UPPER_LATIN:
553  m_item = toUpperLatin(value);
554  break;
555  case LOWER_GREEK:
556  m_item = toLowerGreek(value);
557  break;
558  case UPPER_GREEK:
559  m_item = toUpperGreek(value);
560  break;
561  case HIRAGANA:
562  m_item = toHiragana(value);
563  break;
564  case HIRAGANA_IROHA:
565  m_item = toHiraganaIroha(value);
566  break;
567  case KATAKANA:
568  m_item = toKatakana(value);
569  break;
570  case KATAKANA_IROHA:
571  m_item = toKatakanaIroha(value);
572  break;
573 // Ideographic:
574  case JAPANESE_FORMAL:
575  m_item = toJapaneseFormal(value);
576  break;
577  case JAPANESE_INFORMAL:
578  m_item = toJapaneseInformal(value);
579  break;
580  case SIMP_CHINESE_FORMAL:
581  m_item = toSimpChineseFormal(value);
582  break;
583  case SIMP_CHINESE_INFORMAL:
584  m_item = toSimpChineseInformal(value);
585  break;
586  case TRAD_CHINESE_FORMAL:
587  m_item = toTradChineseFormal(value);
588  break;
589  case CJK_IDEOGRAPHIC:
590  // CSS 3 List says treat as trad-chinese-informal
591  case TRAD_CHINESE_INFORMAL:
592  m_item = toTradChineseInformal(value);
593  break;
594 // special:
595  case LNONE:
596  break;
597  default:
598  KHTMLAssert(false);
599  }
600  m_markerWidth = fm.width(m_item) + fm.width(QLatin1String(". "));
601  }
602 
603 end:
604  if (listPositionInside()) {
605  m_width = m_markerWidth;
606  }
607 
608  m_minWidth = m_width;
609  m_maxWidth = m_width;
610 
611  setMinMaxKnown();
612 }
613 
614 short RenderListMarker::lineHeight(bool /*b*/) const
615 {
616  return height();
617 }
618 
619 short RenderListMarker::baselinePosition(bool /*b*/) const
620 {
621  return height();
622 }
623 
624 void RenderListMarker::calcWidth()
625 {
626  RenderBox::calcWidth();
627 }
628 
629 /*
630 int CounterListItem::recount() const
631 {
632  static_cast<RenderListItem*>(m_renderer)->m_marker->setNeedsLayoutAndMinMaxRecalc();
633 }
634 
635 void CounterListItem::setSelfDirty()
636 {
637 
638 }*/
639 
640 #undef BOX_DEBUG
int ascent() const const
This file is part of the HTML rendering engine for KDE.
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
void paint(int dx, int dy, QPainter *p, int sx=0, int sy=0, int width=-1, int height=-1)
Paints a portion of the image frame on the painter &#39;p&#39; at dx and dy.
void drawRect(const QRectF &rectangle)
void setFont(const QFont &font)
QString number(int n, int base)
void setPen(const QColor &color)
void drawEllipse(const QRectF &rectangle)
void setBrush(const QBrush &brush)
void drawText(const QPointF &position, const QString &text)
const QList< QKeySequence > & end()
int minLeftBearing() const const
QFontMetrics fontMetrics() const const
void drawConvexPolygon(const QPointF *points, int pointCount)
TextDontClip
Base Class for all rendering tree objects.
int length() const const
int width(const QString &text, int len) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Oct 26 2021 22:48:08 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.