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