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