1 /**
2 * This file is part of the HTML rendering engine for KDE.
3 *
4 * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
5 * (C) 1999 Antti Koivisto (koivisto@kde.org)
6 * (C) 2000-2002 Dirk Mueller (mueller@kde.org)
7 * (C) 2003 Apple Computer, Inc.
8 * (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
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
RenderListItem(DOM::NodeImpl * node)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
setStyle(RenderStyle * _style)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
detach()89 void RenderListItem::detach()
90 {
91 if (m_marker && m_deleteMarker) {
92 m_marker->detach();
93 }
94 RenderBlock::detach();
95 }
96
getParentOfFirstLineBox(RenderBlock * curr,RenderObject * marker)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
firstNonMarkerChild(RenderObject * parent)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
updateMarkerLocation()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
calcMinMaxWidth()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
layout()202 void RenderListItem::layout()
203 {
204 KHTMLAssert(needsLayout());
205 KHTMLAssert(minMaxKnown());
206
207 updateMarkerLocation();
208 RenderBlock::layout();
209 }
210
211 // -----------------------------------------------------------
212
RenderListMarker(DOM::NodeImpl * node)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
~RenderListMarker()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
setStyle(RenderStyle * s)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
paint(PaintInfo & paintInfo,int _tx,int _ty)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
layout()418 void RenderListMarker::layout()
419 {
420 KHTMLAssert(needsLayout());
421
422 if (!minMaxKnown()) {
423 calcMinMaxWidth();
424 }
425
426 setNeedsLayout(false);
427 }
428
updatePixmap(const QRect & r,CachedImage * o)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
calcMinMaxWidth()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
lineHeight(bool) const614 short RenderListMarker::lineHeight(bool /*b*/) const
615 {
616 return height();
617 }
618
baselinePosition(bool) const619 short RenderListMarker::baselinePosition(bool /*b*/) const
620 {
621 return height();
622 }
623
calcWidth()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
641