File indexing completed on 2024-12-08 12:19:46

0001 /**
0002  * This file is part of the HTML rendering engine for KDE.
0003  *
0004  * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
0005  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
0006  *           (C) 2000-2002 Dirk Mueller (mueller@kde.org)
0007  *           (C) 2003 Apple Computer, Inc.
0008  *           (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
0009  *
0010  * This library is free software; you can redistribute it and/or
0011  * modify it under the terms of the GNU Library General Public
0012  * License as published by the Free Software Foundation; either
0013  * version 2 of the License, or (at your option) any later version.
0014  *
0015  * This library is distributed in the hope that it will be useful,
0016  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0017  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0018  * Library General Public License for more details.
0019  *
0020  * You should have received a copy of the GNU Library General Public License
0021  * along with this library; see the file COPYING.LIB.  If not, write to
0022  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0023  * Boston, MA 02110-1301, USA.
0024  *
0025  */
0026 
0027 #include "rendering/render_list.h"
0028 #include "rendering/render_canvas.h"
0029 #include "rendering/enumerate.h"
0030 #include "rendering/counter_tree.h"
0031 #include "html/html_listimpl.h"
0032 #include "imload/imagepainter.h"
0033 #include "misc/helper.h"
0034 #include "misc/loader.h"
0035 #include "xml/dom_docimpl.h"
0036 
0037 #include "khtml_debug.h"
0038 
0039 //#define BOX_DEBUG
0040 
0041 using namespace khtml;
0042 using namespace Enumerate;
0043 using namespace khtmlImLoad;
0044 
0045 const int cMarkerPadding = 7;
0046 
0047 // -------------------------------------------------------------------------
0048 
0049 RenderListItem::RenderListItem(DOM::NodeImpl *node)
0050     : RenderBlock(node)
0051 {
0052     // init RenderObject attributes
0053     setInline(false);   // our object is not Inline
0054 
0055     predefVal = -1;
0056     m_marker = nullptr;
0057     m_counter = nullptr;
0058     m_insideList = false;
0059     m_deleteMarker = false;
0060 }
0061 
0062 void RenderListItem::setStyle(RenderStyle *_style)
0063 {
0064     RenderBlock::setStyle(_style);
0065 
0066     RenderStyle *newStyle = new RenderStyle();
0067     newStyle->ref();
0068 
0069     newStyle->inheritFrom(style());
0070 
0071     const bool showListMarker = style()->listStyleImage() || style()->listStyleType() != LNONE;
0072 
0073     if (!m_marker && showListMarker) {
0074         m_marker = new(renderArena()) RenderListMarker(element());
0075         m_marker->setIsAnonymous(true);
0076         m_marker->setStyle(newStyle);
0077         m_marker->setListItem(this);
0078         m_deleteMarker = true;
0079     } else if (m_marker && !showListMarker) {
0080         m_marker->detach();
0081         m_marker = nullptr;
0082     } else if (m_marker) {
0083         m_marker->setStyle(newStyle);
0084     }
0085 
0086     newStyle->deref();
0087 }
0088 
0089 void RenderListItem::detach()
0090 {
0091     if (m_marker && m_deleteMarker) {
0092         m_marker->detach();
0093     }
0094     RenderBlock::detach();
0095 }
0096 
0097 static RenderObject *getParentOfFirstLineBox(RenderBlock *curr, RenderObject *marker)
0098 {
0099     RenderObject *firstChild = curr->firstChild();
0100     if (!firstChild) {
0101         return nullptr;
0102     }
0103 
0104     for (RenderObject *currChild = firstChild;
0105             currChild; currChild = currChild->nextSibling()) {
0106         if (currChild == marker) {
0107             continue;
0108         }
0109 
0110         if (currChild->isInline() && (!currChild->isInlineFlow() || curr->inlineChildNeedsLineBox(currChild))) {
0111             return curr;
0112         }
0113 
0114         if (currChild->isFloating() || currChild->isPositioned()) {
0115             continue;
0116         }
0117 
0118         if (currChild->isTable() || !currChild->isRenderBlock()) {
0119             break;
0120         }
0121 
0122         if (curr->isListItem() && currChild->style()->htmlHacks() && currChild->element() &&
0123                 (currChild->element()->id() == ID_UL || currChild->element()->id() == ID_OL ||
0124                  currChild->element()->id() == ID_DIR || currChild->element()->id() == ID_MENU)) {
0125             break;
0126         }
0127 
0128         RenderObject *lineBox = getParentOfFirstLineBox(static_cast<RenderBlock *>(currChild), marker);
0129         if (lineBox) {
0130             return lineBox;
0131         }
0132     }
0133 
0134     return nullptr;
0135 }
0136 
0137 static RenderObject *firstNonMarkerChild(RenderObject *parent)
0138 {
0139     RenderObject *result = parent->firstChild();
0140     while (result && result->isListMarker()) {
0141         result = result->nextSibling();
0142     }
0143     return result;
0144 }
0145 
0146 void RenderListItem::updateMarkerLocation()
0147 {
0148     // Sanity check the location of our marker.
0149     if (m_marker) {
0150         RenderObject *markerPar = m_marker->parent();
0151         RenderObject *lineBoxParent = getParentOfFirstLineBox(this, m_marker);
0152         if (!lineBoxParent) {
0153             // If the marker is currently contained inside an anonymous box,
0154             // then we are the only item in that anonymous box (since no line box
0155             // parent was found).  It's ok to just leave the marker where it is
0156             // in this case.
0157             if (markerPar && markerPar->isAnonymousBlock()) {
0158                 lineBoxParent = markerPar;
0159             } else {
0160                 lineBoxParent = this;
0161             }
0162         }
0163         if (markerPar != lineBoxParent) {
0164             if (markerPar) {
0165                 markerPar->removeChild(m_marker);
0166             }
0167             if (!lineBoxParent) {
0168                 lineBoxParent = this;
0169             }
0170             lineBoxParent->addChild(m_marker, firstNonMarkerChild(lineBoxParent));
0171             m_deleteMarker = false;
0172             if (!m_marker->minMaxKnown()) {
0173                 m_marker->calcMinMaxWidth();
0174             }
0175             recalcMinMaxWidths();
0176         }
0177     }
0178 }
0179 
0180 void RenderListItem::calcMinMaxWidth()
0181 {
0182     // Make sure our marker is in the correct location.
0183     updateMarkerLocation();
0184     if (!minMaxKnown()) {
0185         RenderBlock::calcMinMaxWidth();
0186     }
0187 }
0188 /*
0189 short RenderListItem::marginLeft() const
0190 {
0191     if (m_insideList)
0192         return RenderBlock::marginLeft();
0193     else
0194         return qMax(m_marker->markerWidth(), RenderBlock::marginLeft());
0195 }
0196 
0197 short RenderListItem::marginRight() const
0198 {
0199     return RenderBlock::marginRight();
0200 }*/
0201 
0202 void RenderListItem::layout()
0203 {
0204     KHTMLAssert(needsLayout());
0205     KHTMLAssert(minMaxKnown());
0206 
0207     updateMarkerLocation();
0208     RenderBlock::layout();
0209 }
0210 
0211 // -----------------------------------------------------------
0212 
0213 RenderListMarker::RenderListMarker(DOM::NodeImpl *node)
0214     : RenderBox(node), m_listImage(nullptr), m_markerWidth(0)
0215 {
0216     // init RenderObject attributes
0217     setInline(true);   // our object is Inline
0218     setReplaced(true); // pretend to be replaced
0219     // val = -1;
0220     // m_listImage = 0;
0221 }
0222 
0223 RenderListMarker::~RenderListMarker()
0224 {
0225     if (m_listImage) {
0226         m_listImage->deref(this);
0227     }
0228     if (m_listItem) {
0229         m_listItem->resetListMarker();
0230     }
0231 }
0232 
0233 void RenderListMarker::setStyle(RenderStyle *s)
0234 {
0235     if (style() && (s->listStylePosition() != style()->listStylePosition()  || s->listStyleType() != style()->listStyleType())) {
0236         setNeedsLayoutAndMinMaxRecalc();
0237     }
0238 
0239     RenderBox::setStyle(s);
0240 
0241     if (m_listImage != style()->listStyleImage()) {
0242         if (m_listImage) {
0243             m_listImage->deref(this);
0244         }
0245         m_listImage = style()->listStyleImage();
0246         if (m_listImage) {
0247             m_listImage->ref(this);
0248         }
0249     }
0250 }
0251 
0252 void RenderListMarker::paint(PaintInfo &paintInfo, int _tx, int _ty)
0253 {
0254     if (paintInfo.phase != PaintActionForeground) {
0255         return;
0256     }
0257 
0258     if (style()->visibility() != VISIBLE) {
0259         return;
0260     }
0261 
0262     _tx += m_x;
0263     _ty += m_y;
0264 
0265     if ((_ty > paintInfo.r.bottom()) || (_ty + m_height <= paintInfo.r.top())) {
0266         return;
0267     }
0268 
0269     if (shouldPaintBackgroundOrBorder()) {
0270         paintBoxDecorations(paintInfo, _tx, _ty);
0271     }
0272 
0273     QPainter *p = paintInfo.p;
0274 #ifdef DEBUG_LAYOUT
0275     qCDebug(KHTML_LOG) << nodeName().string() << "(ListMarker)::paintObject(" << _tx << ", " << _ty << ")";
0276 #endif
0277     p->setFont(style()->font());
0278     const QFontMetrics fm = p->fontMetrics();
0279 
0280     // The marker needs to adjust its tx, for the case where it's an outside marker.
0281     RenderObject *listItem = nullptr;
0282     int leftLineOffset = 0;
0283     int rightLineOffset = 0;
0284     if (!listPositionInside()) {
0285         listItem = this;
0286         int yOffset = 0;
0287         int xOffset = 0;
0288         while (listItem && listItem != m_listItem) {
0289             yOffset += listItem->yPos();
0290             xOffset += listItem->xPos();
0291             listItem = listItem->parent();
0292         }
0293 
0294         // Now that we have our xoffset within the listbox, we need to adjust ourselves by the delta
0295         // between our current xoffset and our desired position (which is just outside the border box
0296         // of the list item).
0297         if (style()->direction() == LTR) {
0298             leftLineOffset = m_listItem->leftRelOffset(yOffset, m_listItem->leftOffset(yOffset));
0299             _tx -= (xOffset - leftLineOffset) + m_listItem->paddingLeft() + m_listItem->borderLeft();
0300         } else {
0301             rightLineOffset = m_listItem->rightRelOffset(yOffset, m_listItem->rightOffset(yOffset));
0302             _tx += (rightLineOffset - xOffset) + m_listItem->paddingRight() + m_listItem->borderRight();
0303         }
0304     }
0305 
0306     int offset = fm.ascent() * 2 / 3;
0307     bool haveImage = m_listImage && !m_listImage->isErrorImage();
0308     if (haveImage) {
0309         offset = m_listImage->pixmap_size().width();
0310     }
0311 
0312     int xoff = 0;
0313     int yoff = fm.ascent() - offset;
0314 
0315     int bulletWidth = offset / 2;
0316     if (offset % 2) {
0317         bulletWidth++;
0318     }
0319     if (!listPositionInside()) {
0320         if (listItem && listItem->style()->direction() == LTR) {
0321             xoff = -cMarkerPadding - offset;
0322         } else {
0323             xoff = cMarkerPadding + (haveImage ? 0 : (offset - bulletWidth));
0324         }
0325     } else if (style()->direction() == RTL) {
0326         xoff += haveImage ? cMarkerPadding : (m_width - bulletWidth);
0327     }
0328 
0329     if (m_listImage && !m_listImage->isErrorImage()) {
0330         ImagePainter painter(m_listImage->image());
0331         painter.paint(_tx + xoff, _ty, p);
0332         return;
0333     }
0334 
0335 #ifdef BOX_DEBUG
0336     p->setPen(Qt::red);
0337     p->drawRect(_tx + xoff, _ty + yoff, offset, offset);
0338 #endif
0339 
0340     const QColor color(style()->color());
0341     p->setPen(color);
0342 
0343     switch (style()->listStyleType()) {
0344     case LDISC:
0345         p->setBrush(color);
0346         p->drawEllipse(_tx + xoff, _ty + (3 * yoff) / 2, (offset >> 1), (offset >> 1));
0347         return;
0348     case LCIRCLE:
0349         p->setBrush(Qt::NoBrush);
0350         p->drawEllipse(_tx + xoff, _ty + (3 * yoff) / 2, (offset >> 1), (offset >> 1));
0351         return;
0352     case LSQUARE:
0353         p->setBrush(color);
0354         p->drawRect(_tx + xoff, _ty + (3 * yoff) / 2, (offset >> 1), (offset >> 1));
0355         return;
0356     case LBOX:
0357         p->setBrush(Qt::NoBrush);
0358         p->drawRect(_tx + xoff, _ty + (3 * yoff) / 2, (offset >> 1), (offset >> 1));
0359         return;
0360     case LDIAMOND: {
0361         static QPolygon diamond(4);
0362         int x = _tx + xoff;
0363         int y = _ty + (3 * yoff) / 2 - 1;
0364         int s = (offset >> 2) + 1;
0365         diamond[0] = QPoint(x + s,   y);
0366         diamond[1] = QPoint(x + 2 * s, y + s);
0367         diamond[2] = QPoint(x + s,   y + 2 * s);
0368         diamond[3] = QPoint(x,     y + s);
0369         p->setBrush(color);
0370 
0371         p->drawConvexPolygon(diamond.constData(), 4);
0372         return;
0373     }
0374     case LNONE:
0375         return;
0376     default:
0377         if (!m_item.isEmpty()) {
0378             if (listPositionInside()) {
0379                 //BEGIN HACK
0380 #ifdef __GNUC__
0381 #warning "FIXME: hack for testregression, remove"
0382 #endif
0383                 _tx += qMax(-fm.minLeftBearing(), 0);
0384                 //END HACK
0385                 if (style()->direction() == LTR) {
0386                     p->drawText(_tx, _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, m_item);
0387                     p->drawText(_tx + fm.width(m_item), _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip,
0388                                 QLatin1String(". "));
0389                 } else {
0390                     const QString &punct(QLatin1String(" ."));
0391                     p->drawText(_tx, _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, punct);
0392                     p->drawText(_tx + fm.width(punct), _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, m_item);
0393                 }
0394             } else {
0395                 if (style()->direction() == LTR) {
0396                     const QString &punct(QLatin1String(". "));
0397                     int itemWidth = fm.width(m_item);
0398                     int punctWidth = fm.width(punct);
0399                     p->drawText(_tx - offset / 2 - punctWidth, _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, punct);
0400                     p->drawText(_tx - offset / 2 - punctWidth - itemWidth, _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, m_item);
0401                 } else {
0402                     //BEGIN HACK
0403 #ifdef __GNUC__
0404 #warning "FIXME: hack for testregression, remove"
0405 #endif
0406                     _tx += qMax(-fm.minLeftBearing(), 0);
0407                     //END HACK
0408 
0409                     const QString &punct(QLatin1String(" ."));
0410                     p->drawText(_tx + offset / 2, _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, punct);
0411                     p->drawText(_tx + offset / 2 + fm.width(punct), _ty, 0, 0, Qt::AlignLeft | Qt::TextDontClip, m_item);
0412                 }
0413             }
0414         }
0415     }
0416 }
0417 
0418 void RenderListMarker::layout()
0419 {
0420     KHTMLAssert(needsLayout());
0421 
0422     if (!minMaxKnown()) {
0423         calcMinMaxWidth();
0424     }
0425 
0426     setNeedsLayout(false);
0427 }
0428 
0429 void RenderListMarker::updatePixmap(const QRect &r, CachedImage *o)
0430 {
0431     if (o != m_listImage) {
0432         RenderBox::updatePixmap(r, o);
0433         return;
0434     }
0435 
0436     if (m_width != m_listImage->pixmap_size().width() || m_height != m_listImage->pixmap_size().height()) {
0437         setNeedsLayoutAndMinMaxRecalc();
0438     } else {
0439         repaintRectangle(0, 0, m_width, m_height);
0440     }
0441 }
0442 
0443 void RenderListMarker::calcMinMaxWidth()
0444 {
0445     KHTMLAssert(!minMaxKnown());
0446 
0447     m_markerWidth = m_width = 0;
0448 
0449     if (m_listImage && !m_listImage->isErrorImage()) {
0450         m_markerWidth = m_listImage->pixmap_size().width() + cMarkerPadding;
0451         if (listPositionInside()) {
0452             m_width = m_markerWidth;
0453         }
0454         m_height = m_listImage->pixmap_size().height();
0455         m_minWidth = m_maxWidth = m_width;
0456         setMinMaxKnown();
0457         return;
0458     }
0459 
0460     const QFontMetrics &fm = style()->fontMetrics();
0461     m_height = fm.ascent();
0462 
0463     // Skip uncounted elements
0464     switch (style()->listStyleType()) {
0465     // Glyphs:
0466     case LDISC:
0467     case LCIRCLE:
0468     case LSQUARE:
0469     case LBOX:
0470     case LDIAMOND:
0471         m_markerWidth = fm.ascent();
0472         goto end;
0473     default:
0474         break;
0475     }
0476 
0477     {
0478         // variable scope
0479         CounterNode *counter = m_listItem->m_counter;
0480         if (!counter) {
0481             counter = m_listItem->getCounter("list-item", true);
0482             counter->setRenderer(this);
0483             m_listItem->m_counter = counter;
0484         }
0485 
0486         assert(counter);
0487         int value = counter->count();
0488         if (counter->isReset()) {
0489             value = counter->value();
0490         }
0491         int total = value;
0492         if (counter->parent()) {
0493             total = counter->parent()->total();
0494         }
0495 
0496         switch (style()->listStyleType()) {
0497 // Numeric:
0498         case LDECIMAL:
0499             m_item.setNum(value);
0500             break;
0501         case DECIMAL_LEADING_ZERO: {
0502             int decimals = 2;
0503             int t = total / 100;
0504             while (t > 0) {
0505                 t = t / 10;
0506                 decimals++;
0507             }
0508             decimals = qMax(decimals, 2);
0509             QString num = QString::number(value);
0510             m_item.fill('0', decimals - num.length());
0511             m_item.append(num);
0512             break;
0513         }
0514         case ARABIC_INDIC:
0515             m_item = toArabicIndic(value);
0516             break;
0517         case LAO:
0518             m_item = toLao(value);
0519             break;
0520         case PERSIAN:
0521         case URDU:
0522             m_item = toPersianUrdu(value);
0523             break;
0524         case THAI:
0525             m_item = toThai(value);
0526             break;
0527         case TIBETAN:
0528             m_item = toTibetan(value);
0529             break;
0530 // Algoritmic:
0531         case LOWER_ROMAN:
0532             m_item = toRoman(value, false);
0533             break;
0534         case UPPER_ROMAN:
0535             m_item = toRoman(value, true);
0536             break;
0537         case HEBREW:
0538             m_item = toHebrew(value);
0539             break;
0540         case ARMENIAN:
0541             m_item = toArmenian(value);
0542             break;
0543         case GEORGIAN:
0544             m_item = toGeorgian(value);
0545             break;
0546 // Alphabetic:
0547         case LOWER_ALPHA:
0548         case LOWER_LATIN:
0549             m_item = toLowerLatin(value);
0550             break;
0551         case UPPER_ALPHA:
0552         case UPPER_LATIN:
0553             m_item = toUpperLatin(value);
0554             break;
0555         case LOWER_GREEK:
0556             m_item = toLowerGreek(value);
0557             break;
0558         case UPPER_GREEK:
0559             m_item = toUpperGreek(value);
0560             break;
0561         case HIRAGANA:
0562             m_item = toHiragana(value);
0563             break;
0564         case HIRAGANA_IROHA:
0565             m_item = toHiraganaIroha(value);
0566             break;
0567         case KATAKANA:
0568             m_item = toKatakana(value);
0569             break;
0570         case KATAKANA_IROHA:
0571             m_item = toKatakanaIroha(value);
0572             break;
0573 // Ideographic:
0574         case JAPANESE_FORMAL:
0575             m_item = toJapaneseFormal(value);
0576             break;
0577         case JAPANESE_INFORMAL:
0578             m_item = toJapaneseInformal(value);
0579             break;
0580         case SIMP_CHINESE_FORMAL:
0581             m_item = toSimpChineseFormal(value);
0582             break;
0583         case SIMP_CHINESE_INFORMAL:
0584             m_item = toSimpChineseInformal(value);
0585             break;
0586         case TRAD_CHINESE_FORMAL:
0587             m_item = toTradChineseFormal(value);
0588             break;
0589         case CJK_IDEOGRAPHIC:
0590         // CSS 3 List says treat as trad-chinese-informal
0591         case TRAD_CHINESE_INFORMAL:
0592             m_item = toTradChineseInformal(value);
0593             break;
0594 // special:
0595         case LNONE:
0596             break;
0597         default:
0598             KHTMLAssert(false);
0599         }
0600         m_markerWidth = fm.width(m_item) + fm.width(QLatin1String(". "));
0601     }
0602 
0603 end:
0604     if (listPositionInside()) {
0605         m_width = m_markerWidth;
0606     }
0607 
0608     m_minWidth = m_width;
0609     m_maxWidth = m_width;
0610 
0611     setMinMaxKnown();
0612 }
0613 
0614 short RenderListMarker::lineHeight(bool /*b*/) const
0615 {
0616     return height();
0617 }
0618 
0619 short RenderListMarker::baselinePosition(bool /*b*/) const
0620 {
0621     return height();
0622 }
0623 
0624 void RenderListMarker::calcWidth()
0625 {
0626     RenderBox::calcWidth();
0627 }
0628 
0629 /*
0630 int CounterListItem::recount() const
0631 {
0632     static_cast<RenderListItem*>(m_renderer)->m_marker->setNeedsLayoutAndMinMaxRecalc();
0633 }
0634 
0635 void CounterListItem::setSelfDirty()
0636 {
0637 
0638 }*/
0639 
0640 #undef BOX_DEBUG