Fix for 3710721 and 3504114, crashes because of bad ownership model for list markers.
[WebKit-https.git] / WebCore / khtml / rendering / render_list.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
5  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
6  * Copyright (C) 2003 Apple Computer, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  *
23  */
24
25 #include "render_list.h"
26 #include "rendering/render_canvas.h"
27
28 #include "xml/dom_docimpl.h"
29 #include "misc/htmltags.h"
30
31 #include <qpainter.h>
32
33 #include "misc/helper.h"
34
35 #include <kdebug.h>
36
37 //#define BOX_DEBUG
38
39 using DOM::DocumentImpl;
40 using namespace khtml;
41
42 static QString toRoman( int number, bool upper )
43 {
44     QString roman;
45     QChar ldigits[] = { 'i', 'v', 'x', 'l', 'c', 'd', 'm' };
46     QChar udigits[] = { 'I', 'V', 'X', 'L', 'C', 'D', 'M' };
47     QChar *digits = upper ? udigits : ldigits;
48     int i, d = 0;
49
50     do
51     {
52         int num = number % 10;
53
54         if ( num % 5 < 4 )
55             for ( i = num % 5; i > 0; i-- )
56                 roman.insert( 0, digits[ d ] );
57
58         if ( num >= 4 && num <= 8)
59             roman.insert( 0, digits[ d+1 ] );
60
61         if ( num == 9 )
62             roman.insert( 0, digits[ d+2 ] );
63
64         if ( num % 5 == 4 )
65             roman.insert( 0, digits[ d ] );
66
67         number /= 10;
68         d += 2;
69     }
70     while ( number );
71
72     return roman;
73 }
74
75 static QString toLetter( int number, int base ) {
76     number--;
77     QString letter = (QChar) (base + (number % 26));
78     // Add a single quote at the end of the alphabet.
79     for (int i = 0; i < (number / 26); i++) {
80        letter += '\'';
81     }
82     return letter;
83 }
84
85 static QString toHebrew( int number ) {
86     const QChar tenDigit[] = {1497, 1499, 1500, 1502, 1504, 1505, 1506, 1508, 1510};
87
88     QString letter;
89     if (number>999) {
90         letter = toHebrew(number/1000) + QString::fromLatin1("'");
91         number = number%1000;
92     }
93
94     int hunderts = (number/400);
95     if (hunderts > 0) {
96         for(int i=0; i<hunderts; i++) {
97             letter += QChar(1511 + 3);
98         }
99     }
100     number = number % 400;
101     if ((number / 100) != 0) {
102         letter += QChar (1511 + (number / 100) -1);
103     }
104     number = number % 100;
105     int tens = number/10;
106     if (tens > 0 && !(number == 15 || number == 16)) {
107         letter += tenDigit[tens-1];
108     }
109     if (number == 15 || number == 16) { // special because of religious
110         letter += QChar(1487 + 9);       // reasons
111         letter += QChar(1487 + number - 9);
112     } else {
113         number = number % 10;
114         if (number != 0) {
115             letter += QChar (1487 + number);
116         }
117     }
118     return letter;
119 }
120
121 // -------------------------------------------------------------------------
122
123 RenderListItem::RenderListItem(DOM::NodeImpl* node)
124     : RenderBlock(node), _notInList(false)
125 {
126     // init RenderObject attributes
127     setInline(false);   // our object is not Inline
128
129     predefVal = -1;
130     m_marker = 0;
131 }
132
133 void RenderListItem::setStyle(RenderStyle *_style)
134 {
135     RenderBlock::setStyle(_style);
136
137     if (style()->listStyleType() != LNONE ||
138         (style()->listStyleImage() && !style()->listStyleImage()->isErrorImage())) {
139         RenderStyle *newStyle = new (renderArena()) RenderStyle();
140         newStyle->ref();
141         // The marker always inherits from the list item, regardless of where it might end
142         // up (e.g., in some deeply nested line box).  See CSS3 spec.
143         newStyle->inheritFrom(style()); 
144         if (!m_marker) {
145             m_marker = new (renderArena()) RenderListMarker(document());
146             m_marker->setStyle(newStyle);
147             m_marker->setListItem(this);
148         }
149         else
150             m_marker->setStyle(newStyle);
151         newStyle->deref(renderArena());
152     } else if (m_marker) {
153         m_marker->detach();
154         m_marker = 0;
155     }
156 }
157
158 RenderListItem::~RenderListItem()
159 {
160 }
161
162 void RenderListItem::detach()
163 {    
164     if (m_marker) {
165         m_marker->detach();
166         m_marker = 0;
167     }
168     RenderBlock::detach();
169 }
170
171 void RenderListItem::calcListValue()
172 {
173     // only called from the marker so..
174     KHTMLAssert(m_marker);
175
176     if(predefVal != -1)
177         m_marker->m_value = predefVal;
178     else if(!previousSibling())
179         m_marker->m_value = 1;
180     else {
181         RenderObject *o = previousSibling();
182         while ( o && (!o->isListItem() || o->style()->listStyleType() == LNONE) )
183             o = o->previousSibling();
184         if( o && o->isListItem() && o->style()->listStyleType() != LNONE ) {
185             RenderListItem *item = static_cast<RenderListItem *>(o);
186             m_marker->m_value = item->value() + 1;
187         }
188         else
189             m_marker->m_value = 1;
190     }
191 }
192
193 static RenderObject* getParentOfFirstLineBox(RenderObject* curr, RenderObject* marker)
194 {
195     RenderObject* firstChild = curr->firstChild();
196     if (!firstChild)
197         return 0;
198         
199     for (RenderObject* currChild = firstChild;
200          currChild; currChild = currChild->nextSibling()) {
201         if (currChild == marker)
202             continue;
203             
204         if (currChild->isInline())
205             return curr;
206         
207         if (currChild->isFloating() || currChild->isPositioned())
208             continue;
209             
210         if (currChild->isTable() || !currChild->isRenderBlock())
211             break;
212         
213         if (currChild->style()->htmlHacks() && currChild->element() &&
214             (currChild->element()->id() == ID_UL || currChild->element()->id() == ID_OL))
215             break;
216             
217         RenderObject* lineBox = getParentOfFirstLineBox(currChild, marker);
218         if (lineBox)
219             return lineBox;
220     }
221     
222     return 0;
223 }
224
225 void RenderListItem::updateMarkerLocation()
226 {
227     // Sanity check the location of our marker.
228     if (m_marker) {
229         RenderObject* markerPar = m_marker->parent();
230         RenderObject* lineBoxParent = getParentOfFirstLineBox(this, m_marker);
231         if (!lineBoxParent) {
232             // If the marker is currently contained inside an anonymous box,
233             // then we are the only item in that anonymous box (since no line box
234             // parent was found).  It's ok to just leave the marker where it is
235             // in this case.
236             if (markerPar && markerPar->isAnonymousBlock())
237                 lineBoxParent = markerPar;
238             else
239                 lineBoxParent = this;
240         }
241         if (markerPar != lineBoxParent)
242         {
243             if (markerPar)
244                 markerPar->removeChild(m_marker);
245             if (!lineBoxParent)
246                 lineBoxParent = this;
247             lineBoxParent->addChild(m_marker, lineBoxParent->firstChild());
248             if (!m_marker->minMaxKnown())
249                 m_marker->calcMinMaxWidth();
250             recalcMinMaxWidths();
251         }
252     }
253 }
254
255 void RenderListItem::calcMinMaxWidth()
256 {
257     // Make sure our marker is in the correct location.
258     updateMarkerLocation();
259     if (!minMaxKnown())
260         RenderBlock::calcMinMaxWidth();
261 }
262
263 void RenderListItem::layout( )
264 {
265     KHTMLAssert( needsLayout() );
266     KHTMLAssert( minMaxKnown() );
267
268     updateMarkerLocation();    
269     RenderBlock::layout();
270 }
271
272 void RenderListItem::paint(PaintInfo& i, int _tx, int _ty)
273 {
274     if (!m_height)
275         return;
276
277 #ifdef DEBUG_LAYOUT
278     kdDebug( 6040 ) << nodeName().string() << "(LI)::paint()" << endl;
279 #endif
280     RenderBlock::paint(i, _tx, _ty);
281 }
282
283 QRect RenderListItem::getAbsoluteRepaintRect()
284 {
285     QRect result = RenderBlock::getAbsoluteRepaintRect();
286     if (m_marker && !m_marker->isInside()) {
287         // This can be a sloppy and imprecise offset as long as it's always too big.
288         int pixHeight = style()->htmlFont().getFontDef().computedPixelSize();
289         int offset = pixHeight*2/3;
290         int xoff = 0;
291         if (style()->direction() == LTR)
292             xoff = -7 - offset;
293         else
294             xoff = offset;
295
296         if (m_marker->listImage() && !m_marker->listImage()->isErrorImage()) {
297             // For OUTSIDE bullets shrink back to only a 0.3em margin. 0.67 em is too
298             // much.  This brings the margin back to MacIE/Gecko/WinIE levels.
299             // For LTR don't forget to add in the width of the image to the offset as
300             // well (you are moving the image left, so you have to also add in the width
301             // of the image's border box as well). -dwh
302             if (style()->direction() == LTR)
303                 xoff -= m_marker->listImage()->pixmap().width() - pixHeight*1/3;
304             else
305                 xoff -= pixHeight*1/3;
306         }
307
308         if (xoff < 0) {
309             result.setX(result.x() + xoff);
310             result.setWidth(result.width() - xoff);
311         }
312         else
313             result.setWidth(result.width() + xoff);
314     }
315     return result;
316 }
317
318 // -----------------------------------------------------------
319
320 RenderListMarker::RenderListMarker(DocumentImpl* document)
321     : RenderBox(document), m_listImage(0), m_value(-1)
322 {
323     // init RenderObject attributes
324     setInline(true);   // our object is Inline
325     setReplaced(true); // pretend to be replaced
326     // val = -1;
327     // m_listImage = 0;
328 }
329
330 RenderListMarker::~RenderListMarker()
331 {
332     if(m_listImage)
333         m_listImage->deref(this);
334 }
335
336 void RenderListMarker::setStyle(RenderStyle *s)
337 {
338     if ( s && style() && s->listStylePosition() != style()->listStylePosition() )
339         setNeedsLayoutAndMinMaxRecalc();
340     
341     RenderBox::setStyle(s);
342
343     if ( m_listImage != style()->listStyleImage() ) {
344         if(m_listImage)  m_listImage->deref(this);
345         m_listImage = style()->listStyleImage();
346         if(m_listImage)  m_listImage->ref(this);
347     }
348 }
349
350
351 void RenderListMarker::paint(PaintInfo& i, int _tx, int _ty)
352 {
353     if (i.phase != PaintActionForeground)
354         return;
355     
356     if (style()->visibility() != VISIBLE)  return;
357
358     _tx += m_x;
359     _ty += m_y;
360
361     if ((_ty > i.r.y() + i.r.height()) || (_ty + m_height < i.r.y()))
362         return;
363
364     if (shouldPaintBackgroundOrBorder()) 
365         paintBoxDecorations(i, _tx, _ty);
366
367 #ifdef DEBUG_LAYOUT
368     kdDebug( 6040 ) << nodeName().string() << "(ListMarker)::paintObject(" << _tx << ", " << _ty << ")" << endl;
369 #endif
370
371     QPainter* p = i.p;
372     p->setFont(style()->font());
373     const QFontMetrics fm = p->fontMetrics();
374     int offset = fm.ascent()*2/3;
375
376     // The marker needs to adjust its tx, for the case where it's an outside marker.
377     RenderObject* listItem = 0;
378     int leftLineOffset = 0;
379     int rightLineOffset = 0;
380     if (!isInside()) {
381         listItem = this;
382         int yOffset = 0;
383         int xOffset = 0;
384         while (listItem && listItem != m_listItem) {
385             yOffset += listItem->yPos();
386             xOffset += listItem->xPos();
387             listItem = listItem->parent();
388         }
389         
390         // Now that we have our xoffset within the listbox, we need to adjust ourselves by the delta
391         // between our current xoffset and our desired position (which is just outside the border box
392         // of the list item).
393         if (style()->direction() == LTR) {
394             leftLineOffset = m_listItem->leftRelOffset(yOffset, m_listItem->leftOffset(yOffset));
395             _tx -= (xOffset - leftLineOffset) + m_listItem->paddingLeft() + m_listItem->borderLeft();
396         }
397         else {
398             rightLineOffset = m_listItem->rightRelOffset(yOffset, m_listItem->rightOffset(yOffset));
399             _tx += (rightLineOffset-xOffset) + m_listItem->paddingRight() + m_listItem->borderRight();
400         }
401     }
402
403     bool isPrinting = (p->device()->devType() == QInternal::Printer);
404     if (isPrinting)
405     {
406         if (_ty < i.r.y())
407         {
408             // This has been printed already we suppose.
409             return;
410         }
411         if (_ty + m_height + paddingBottom() + borderBottom() >= i.r.y() + i.r.height())
412         {
413             RenderCanvas *rootObj = canvas();
414             if (_ty < rootObj->truncatedAt())
415 #if APPLE_CHANGES
416                 rootObj->setBestTruncatedAt(_ty, this);
417 #else
418                 rootObj->setTruncatedAt(_ty);
419 #endif
420             // Let's print this on the next page.
421             return; 
422         }
423     }
424     
425
426     int xoff = 0;
427     int yoff = fm.ascent() - offset;
428
429     if (!isInside())
430         if (listItem->style()->direction() == LTR)
431             xoff = -7 - offset;
432         else 
433             xoff = offset;
434         
435             
436     if ( m_listImage && !m_listImage->isErrorImage()) {
437         // For OUTSIDE bullets shrink back to only a 0.3em margin. 0.67 em is too
438         // much.  This brings the margin back to MacIE/Gecko/WinIE levels.  
439         // For LTR don't forget to add in the width of the image to the offset as
440         // well (you are moving the image left, so you have to also add in the width
441         // of the image's border box as well). -dwh
442         if (!isInside()) {
443             if (style()->direction() == LTR)
444                 xoff -= m_listImage->pixmap().width() - fm.ascent()*1/3;
445             else
446                 xoff -= fm.ascent()*1/3;
447         }
448         
449         p->drawPixmap( QPoint( _tx + xoff, _ty ), m_listImage->pixmap());
450         return;
451     }
452
453 #ifdef BOX_DEBUG
454     p->setPen( Qt::red );
455     p->drawRect( _tx + xoff, _ty + yoff, offset, offset );
456 #endif
457
458     const QColor color( style()->color() );
459     p->setPen( color );
460
461     switch(style()->listStyleType()) {
462     case DISC:
463         p->setBrush( color );
464         p->drawEllipse( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1)+1, (offset>>1)+1 );
465         return;
466     case CIRCLE:
467         p->setBrush( Qt::NoBrush );
468         p->drawEllipse( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1)+1, (offset>>1)+1 );
469         return;
470     case SQUARE:
471         p->setBrush( color );
472         p->drawRect( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1)+1, (offset>>1)+1 );
473         return;
474     case LNONE:
475         return;
476     default:
477         if (!m_item.isEmpty()) {
478 #if APPLE_CHANGES
479             // Text should be drawn on the baseline, so we add in the ascent of the font. 
480             // For some inexplicable reason, this works in Konqueror.  I'm not sure why.
481             // - dwh
482             _ty += fm.ascent();
483 #else
484             //_ty += fm.ascent() - fm.height()/2 + 1;
485 #endif
486             if (isInside()) {
487                 if( style()->direction() == LTR)
488                     p->drawText(_tx, _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, m_item);
489                 else
490                     p->drawText(_tx, _ty, 0, 0, Qt::AlignRight|Qt::DontClip, m_item);
491             } else {
492                 if(style()->direction() == LTR)
493                     p->drawText(_tx-offset/2, _ty, 0, 0, Qt::AlignRight|Qt::DontClip, m_item);
494                 else
495                     p->drawText(_tx+offset/2, _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, m_item);
496             }
497         }
498     }
499 }
500
501 void RenderListMarker::layout()
502 {
503     KHTMLAssert( needsLayout() );
504     // ### KHTMLAssert( minMaxKnown() );
505     if ( !minMaxKnown() )
506         calcMinMaxWidth();
507     setNeedsLayout(false);
508 }
509
510 void RenderListMarker::setPixmap( const QPixmap &p, const QRect& r, CachedImage *o)
511 {
512     if(o != m_listImage) {
513         RenderBox::setPixmap(p, r, o);
514         return;
515     }
516
517     if (m_width != m_listImage->pixmap_size().width() || m_height != m_listImage->pixmap_size().height())
518         setNeedsLayoutAndMinMaxRecalc();
519     else
520         repaint();
521 }
522
523 void RenderListMarker::calcMinMaxWidth()
524 {
525     KHTMLAssert( !minMaxKnown() );
526
527     m_width = 0;
528
529     if(m_listImage) {
530         if (isInside())
531             m_width = m_listImage->pixmap().width() + 5;
532         m_height = m_listImage->pixmap().height();
533         m_minWidth = m_maxWidth = m_width;
534         setMinMaxKnown();
535         return;
536     }
537
538     if (m_value < 0) // not yet calculated
539         m_listItem->calcListValue();
540
541     const QFontMetrics &fm = style()->fontMetrics();
542     m_height = fm.ascent();
543
544     switch(style()->listStyleType())
545     {
546     case DISC:
547     case CIRCLE:
548     case SQUARE:
549         if (isInside()) {
550             m_width = m_height; //fm.ascent();
551         }
552         goto end;
553     case ARMENIAN:
554     case GEORGIAN:
555     case CJK_IDEOGRAPHIC:
556     case HIRAGANA:
557     case KATAKANA:
558     case HIRAGANA_IROHA:
559     case KATAKANA_IROHA:
560     case DECIMAL_LEADING_ZERO:
561         // ### unsupported, we use decimal instead
562     case LDECIMAL:
563         m_item.sprintf( "%ld", m_value );
564         break;
565     case LOWER_ROMAN:
566         m_item = toRoman( m_value, false );
567         break;
568     case UPPER_ROMAN:
569         m_item = toRoman( m_value, true );
570         break;
571     case LOWER_GREEK:
572      {
573         int number = m_value - 1;
574         int l = (number % 24);
575
576         if (l>16) {l++;} // Skip GREEK SMALL LETTER FINAL SIGMA
577
578         m_item = QChar(945 + l);
579         for (int i = 0; i < (number / 24); i++) {
580             m_item += QString::fromLatin1("'");
581         }
582         break;
583      }
584     case HEBREW:
585         m_item = toHebrew( m_value );
586         break;
587     case LOWER_ALPHA:
588     case LOWER_LATIN:
589         m_item = toLetter( m_value, 'a' );
590         break;
591     case UPPER_ALPHA:
592     case UPPER_LATIN:
593         m_item = toLetter( m_value, 'A' );
594         break;
595     case LNONE:
596         break;
597     }
598
599     m_item += QString::fromLatin1(". ");
600
601     if (isInside())
602         m_width = fm.width(m_item);
603
604 end:
605
606     m_minWidth = m_width;
607     m_maxWidth = m_width;
608
609     setMinMaxKnown();
610 }
611
612 void RenderListMarker::calcWidth()
613 {
614     RenderBox::calcWidth();
615 }
616
617 short RenderListMarker::lineHeight(bool, bool) const
618 {
619     return height();
620 }
621
622 short RenderListMarker::baselinePosition(bool, bool) const
623 {
624     return height();
625 }
626
627 bool RenderListMarker::isInside() const
628 {
629     return m_listItem->notInList() || style()->listStylePosition() == INSIDE;
630 }
631
632 #undef BOX_DEBUG