render_text.cpp 37.2 KB
Newer Older
kocienda's avatar
kocienda committed
1 2 3 4 5
/**
 * This file is part of the DOM implementation for KDE.
 *
 * (C) 1999 Lars Knoll (knoll@kde.org)
 * (C) 2000 Dirk Mueller (mueller@kde.org)
darin's avatar
darin committed
6
 * Copyright (C) 2002 Apple Computer, Inc.
kocienda's avatar
kocienda committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 */
//#define DEBUG_LAYOUT
//#define BIDI_DEBUG

darin's avatar
darin committed
27
#include "rendering/render_root.h"
28
#include "rendering/render_text.h"
darin's avatar
darin committed
29
#include "rendering/render_root.h"
30 31
#include "rendering/break_lines.h"
#include "xml/dom_nodeimpl.h"
32 33
#include "xml/dom_docimpl.h"
#include "render_arena.h"
kocienda's avatar
kocienda committed
34 35 36 37 38

#include "misc/loader.h"

#include <qpainter.h>
#include <kdebug.h>
39
#include <assert.h>
kocienda's avatar
kocienda committed
40 41 42 43

using namespace khtml;
using namespace DOM;

darin's avatar
darin committed
44 45 46 47
#ifndef NDEBUG
static bool inTextSlaveDetach;
#endif

48 49
void TextSlave::detach(RenderArena* renderArena)
{
darin's avatar
darin committed
50 51 52
#ifndef NDEBUG
    inTextSlaveDetach = true;
#endif
53
    delete this;
darin's avatar
darin committed
54 55 56
#ifndef NDEBUG
    inTextSlaveDetach = false;
#endif
57
    
darin's avatar
darin committed
58 59
    // Recover the size left there for us by operator delete and free the memory.
    renderArena->free(*(size_t *)this, this);
60 61 62 63 64 65 66
}

void* TextSlave::operator new(size_t sz, RenderArena* renderArena) throw()
{
    return renderArena->allocate(sz);
}

darin's avatar
darin committed
67 68 69 70 71 72
void TextSlave::operator delete(void* ptr, size_t sz)
{
    assert(inTextSlaveDetach);
    
    // Stash size where detach can find it.
    *(size_t *)ptr = sz;
73 74
}

75
void TextSlave::paintSelection(const Font *f, RenderText *text, QPainter *p, RenderStyle* style, int tx, int ty, int startPos, int endPos)
kocienda's avatar
kocienda committed
76 77 78 79 80
{
    if(startPos > m_len) return;
    if(startPos < 0) startPos = 0;

    p->save();
81
#if APPLE_CHANGES
darin's avatar
darin committed
82
    // Macintosh-style text highlighting is to draw with a particular background color, not invert.
83 84 85
    QColor textColor = style->color();
    QColor c = QPainter::selectedTextBackgroundColor();
    if (textColor == c)
darin's avatar
darin committed
86
        c = QColor(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
87
    p->setPen(c); // Don't draw text at all!
88
#else
kocienda's avatar
kocienda committed
89 90
    QColor c = style->color();
    p->setPen(QColor(0xff-c.red(),0xff-c.green(),0xff-c.blue()));
91
#endif
kocienda's avatar
kocienda committed
92 93
    ty += m_baseline;

94
    //kdDebug( 6040 ) << "textSlave::painting(" << s.string() << ") at(" << x+_tx << "/" << y+_ty << ")" << endl;
darin's avatar
darin committed
95 96
    f->drawText(p, m_x + tx, m_y + ty, text->str->s, text->str->l, m_start, m_len,
		m_toAdd, m_reversed ? QPainter::RTL : QPainter::LTR, startPos, endPos, c);
kocienda's avatar
kocienda committed
97 98 99
    p->restore();
}

rjw's avatar
WebKit:  
rjw committed
100
void TextSlave::paintDecoration( QPainter *pt, const Font *f, RenderText* p, int _tx, int _ty, int deco, bool begin, bool end)
kocienda's avatar
kocienda committed
101 102 103 104
{
    _tx += m_x;
    _ty += m_y;

105
    int width = m_width - 1;
kocienda's avatar
kocienda committed
106 107

    if( begin )
108
 	width -= p->paddingLeft() + p->borderLeft();
kocienda's avatar
kocienda committed
109 110 111 112

    if ( end )
        width -= p->paddingRight() + p->borderRight();

113
#if APPLE_CHANGES
darin's avatar
darin committed
114
    // Use a special function for underlines to get the positioning exactly right.
rjw's avatar
WebKit:  
rjw committed
115 116 117 118 119 120 121 122 123 124
    if(deco & UNDERLINE){
        f->drawLineForText(pt, _tx, _ty, p->str->s, p->str->l, m_start, m_len,
                    m_toAdd, m_baseline, m_reversed ? QPainter::RTL : QPainter::LTR);
    }
    if(deco & OVERLINE)
        f->drawLineForText(pt, _tx, _ty, p->str->s, p->str->l, m_start, m_len,
                              m_toAdd, 0, m_reversed ? QPainter::RTL : QPainter::LTR);
    if(deco & LINE_THROUGH)
        f->drawLineForText(pt, _tx, _ty, p->str->s, p->str->l, m_start, m_len,
                              m_toAdd, 2*m_baseline/3, m_reversed ? QPainter::RTL : QPainter::LTR);
125
#else
kocienda's avatar
kocienda committed
126 127
    int underlineOffset = ( pt->fontMetrics().height() + m_baseline ) / 2;
    if(underlineOffset <= m_baseline) underlineOffset = m_baseline+1;
kocienda's avatar
kocienda committed
128

darin's avatar
darin committed
129
    if(deco & UNDERLINE){
130
        pt->drawLine(_tx, _ty + underlineOffset, _tx + width, _ty + underlineOffset );
darin's avatar
darin committed
131
    }
kocienda's avatar
kocienda committed
132 133 134 135
    if(deco & OVERLINE)
        pt->drawLine(_tx, _ty, _tx + width, _ty );
    if(deco & LINE_THROUGH)
        pt->drawLine(_tx, _ty + 2*m_baseline/3, _tx + width, _ty + 2*m_baseline/3 );
rjw's avatar
WebKit:  
rjw committed
136
#endif
kocienda's avatar
kocienda committed
137 138 139 140
    // NO! Do NOT add BLINK! It is the most annouing feature of Netscape, and IE has a reason not to
    // support it. Lars
}

141
void TextSlave::paintBoxDecorations(QPainter *pt, RenderStyle* style, RenderText *p, int _tx, int _ty, bool begin, bool end)
kocienda's avatar
kocienda committed
142 143 144
{
    int topExtra = p->borderTop() + p->paddingTop();
    int bottomExtra = p->borderBottom() + p->paddingBottom();
145 146
    // ### firstline
    int halfleading = (p->m_lineHeight - style->font().pixelSize() ) / 2;
kocienda's avatar
kocienda committed
147 148

    _tx += m_x;
149
    _ty += m_y + halfleading - topExtra;
kocienda's avatar
kocienda committed
150 151 152

    int width = m_width;

153 154
    // the height of the decorations is:  topBorder + topPadding + CSS font-size + bottomPadding + bottomBorder
    int height = style->font().pixelSize() + topExtra + bottomExtra;
kocienda's avatar
kocienda committed
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169

    if( begin )
	_tx -= p->paddingLeft() + p->borderLeft();

    QColor c = style->backgroundColor();
    CachedImage *i = style->backgroundImage();
    if(c.isValid() && (!i || i->tiled_pixmap(c).mask()))
         pt->fillRect(_tx, _ty, width, height, c);

    if(i) {
        // ### might need to add some correct offsets
        // ### use paddingX/Y
        pt->drawTiledPixmap(_tx, _ty, width, height, i->tiled_pixmap(c));
    }

170 171
#ifdef DEBUG_VALIGN
    pt->fillRect(_tx, _ty, width, height, Qt::cyan );
kocienda's avatar
kocienda committed
172 173
#endif

174
    if(style->hasBorder())
175
        p->paintBorder(pt, _tx, _ty, width, height, style, begin, end);
kocienda's avatar
kocienda committed
176 177
}

darin's avatar
darin committed
178
FindSelectionResult TextSlave::checkSelectionPoint(int _x, int _y, int _tx, int _ty, const Font *f, RenderText *text, int & offset, short lineHeight)
kocienda's avatar
kocienda committed
179
{
darin's avatar
darin committed
180 181
//     kdDebug(6040) << "TextSlave::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y
//                   << " _tx+m_x=" << _tx+m_x << " _ty+m_y=" << _ty+m_y << endl;
kocienda's avatar
kocienda committed
182 183 184 185 186
    offset = 0;

    if ( _y < _ty + m_y )
        return SelectionPointBefore; // above -> before

darin's avatar
darin committed
187 188
    if ( _y > _ty + m_y + lineHeight ) {
        // below -> after
kocienda's avatar
kocienda committed
189 190 191 192
        // Set the offset to the max
        offset = m_len;
        return SelectionPointAfter;
    }
darin's avatar
darin committed
193 194 195 196
    if ( _x > _tx + m_x + m_width ) {
	// to the right
	return m_reversed ? SelectionPointBeforeInLine : SelectionPointAfterInLine;
    }
kocienda's avatar
kocienda committed
197 198

    // The Y matches, check if we're on the left
darin's avatar
darin committed
199 200 201
    if ( _x < _tx + m_x ) {
        return m_reversed ? SelectionPointAfterInLine : SelectionPointBeforeInLine;
    }
kocienda's avatar
kocienda committed
202

203
#if APPLE_CHANGES
darin's avatar
darin committed
204
    // Floating point version needed for best results with Mac OS X text.
rjw's avatar
 
rjw committed
205
    float delta = _x - (_tx + m_x);
206
    float widths[text->str->l]; 
207 208 209
    
    // Do width calculations for whole run once.
    f->floatCharacterWidths( text->str->s, text->str->l, m_start, m_len, m_toAdd, &widths[0]);
rjw's avatar
 
rjw committed
210 211 212 213
    int pos = 0;
    if ( m_reversed ) {
	delta -= m_width;
	while(pos < m_len) {
214
	    float w = widths[pos+m_start];
rjw's avatar
 
rjw committed
215 216 217 218 219 220 221 222 223 224
	    float w2 = w/2;
	    w -= w2;
	    delta += w2;
	    if(delta >= 0)
	        break;
	    pos++;
	    delta += w;
	}
    } else {
	while(pos < m_len) {
225
	    float w = widths[pos+m_start];
rjw's avatar
 
rjw committed
226 227 228 229 230 231 232 233 234 235
	    float w2 = w/2;
	    w -= w2;
	    delta -= w2;
	    if(delta <= 0) 
	        break;
	    pos++;
	    delta -= w;
	}
    }
#else
kocienda's avatar
kocienda committed
236 237 238
    int delta = _x - (_tx + m_x);
    //kdDebug(6040) << "TextSlave::checkSelectionPoint delta=" << delta << endl;
    int pos = 0;
darin's avatar
darin committed
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
    if ( m_reversed ) {
	delta -= m_width;
	while(pos < m_len) {
	    int w = f->width( text->str->s, text->str->l, m_start + pos);
	    int w2 = w/2;
	    w -= w2;
	    delta += w2;
	    if(delta >= 0) break;
	    pos++;
	    delta += w;
	}
    } else {
	while(pos < m_len) {
	    int w = f->width( text->str->s, text->str->l, m_start + pos);
	    int w2 = w/2;
	    w -= w2;
	    delta -= w2;
	    if(delta <= 0) break;
	    pos++;
	    delta -= w;
	}
kocienda's avatar
kocienda committed
260
    }
rjw's avatar
 
rjw committed
261
#endif
darin's avatar
darin committed
262
//     kdDebug( 6040 ) << " Text  --> inside at position " << pos << endl;
kocienda's avatar
kocienda committed
263 264 265 266 267 268 269 270
    offset = pos;
    return SelectionPointInside;
}

// -----------------------------------------------------------------------------

TextSlaveArray::TextSlaveArray()
{
271
    setAutoDelete(false);
kocienda's avatar
kocienda committed
272 273 274 275
}

int TextSlaveArray::compareItems( Item d1, Item d2 )
{
276 277
    assert(d1);
    assert(d2);
kocienda's avatar
kocienda committed
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

    return static_cast<TextSlave*>(d1)->m_y - static_cast<TextSlave*>(d2)->m_y;
}

// remove this once QVector::bsearch is fixed
int TextSlaveArray::findFirstMatching(Item d) const
{
    int len = count();

    if ( !len )
	return -1;
    if ( !d )
	return -1;
    int n1 = 0;
    int n2 = len - 1;
    int mid = 0;
    bool found = FALSE;
    while ( n1 <= n2 ) {
	int  res;
	mid = (n1 + n2)/2;
	if ( (*this)[mid] == 0 )			// null item greater
	    res = -1;
	else
301
	    res = ((QGVector*)this)->compareItems( d, (*this)[mid] );
kocienda's avatar
kocienda committed
302 303 304 305 306 307 308 309 310 311 312 313
	if ( res < 0 )
	    n2 = mid - 1;
	else if ( res > 0 )
	    n1 = mid + 1;
	else {					// found it
	    found = TRUE;
	    break;
	}
    }
    /* if ( !found )
	return -1; */
    // search to first one equal or bigger
314
    while ( found && (mid > 0) && !((QGVector*)this)->compareItems(d, (*this)[mid-1]) )
kocienda's avatar
kocienda committed
315 316 317 318 319 320
	mid--;
    return mid;
}

// -------------------------------------------------------------------------------------

321 322
RenderText::RenderText(DOM::NodeImpl* node, DOMStringImpl *_str)
    : RenderObject(node)
kocienda's avatar
kocienda committed
323 324 325 326 327 328 329 330
{
    // init RenderObject attributes
    setRenderText();   // our object inherits from RenderText

    m_minWidth = -1;
    m_maxWidth = -1;
    str = _str;
    if(str) str->ref();
331
    KHTMLAssert(!str || !str->l || str->s);
kocienda's avatar
kocienda committed
332 333 334 335 336

    m_selectionState = SelectionNone;

#ifdef DEBUG_LAYOUT
    QConstString cstr(str->s, str->l);
337
    kdDebug( 6040 ) << "RenderText ctr( "<< cstr.string().length() << " )  '" << cstr.string() << "'" << endl;
kocienda's avatar
kocienda committed
338 339 340 341 342
#endif
}

void RenderText::setStyle(RenderStyle *_style)
{
343
    if ( style() != _style ) {
darin's avatar
darin committed
344 345 346 347 348 349 350 351
        // ### fontvariant being implemented as text-transform: upper. sucks!
        bool changedText = (!style() && (_style->fontVariant() != FVNORMAL || _style->textTransform() != TTNONE)) ||
            ((style() && style()->textTransform() != _style->textTransform()) ||
             (style() && style()->fontVariant() != _style->fontVariant()));

        RenderObject::setStyle( _style );
        m_lineHeight = RenderObject::lineHeight(false);

darin's avatar
darin committed
352
        if (changedText && element() && element()->string())
darin's avatar
darin committed
353
            setText(element()->string(), changedText);
kocienda's avatar
kocienda committed
354 355 356 357 358 359 360 361
    }
}

RenderText::~RenderText()
{
    if(str) str->deref();
}

362 363
void RenderText::detach(RenderArena* renderArena)
{
364
    deleteSlaves(renderArena);
365 366 367
    RenderObject::detach(renderArena);
}

368
void RenderText::deleteSlaves(RenderArena *arena)
kocienda's avatar
kocienda committed
369 370 371 372 373 374
{
    // this is a slight variant of QArray::clear().
    // We don't delete the array itself here because its
    // likely to be used in the same size later again, saves
    // us resize() calls
    unsigned int len = m_lines.size();
375
    if (len) {
376 377
        if (!arena)
            arena = renderArena();
378 379 380 381 382 383
        for(unsigned int i=0; i < len; i++) {
            TextSlave* s = m_lines.at(i);
            if (s)
                s->detach(arena);
            m_lines.remove(i);
        }
384 385
    }
    
386
    KHTMLAssert(m_lines.count() == 0);
kocienda's avatar
kocienda committed
387 388 389 390 391 392 393 394 395 396 397 398 399
}

TextSlave * RenderText::findTextSlave( int offset, int &pos )
{
    // The text slaves point to parts of the rendertext's str string
    // (they don't include '\n')
    // Find the text slave that includes the character at @p offset
    // and return pos, which is the position of the char in the slave.

    if ( m_lines.isEmpty() )
        return 0L;

    TextSlave* s = m_lines[0];
rjw's avatar
rjw committed
400
    uint si = 1;
kocienda's avatar
kocienda committed
401 402 403
    int off = s->m_len;
    while(offset > off && si < m_lines.count())
    {
rjw's avatar
rjw committed
404
        s = m_lines[si++];
darin's avatar
darin committed
405
        off = s->m_start + s->m_len;
kocienda's avatar
kocienda committed
406 407 408 409 410 411
    }
    // we are now in the correct text slave
    pos = (offset > off ? s->m_len : s->m_len - (off - offset) );
    return s;
}

412
bool RenderText::nodeAtPoint(NodeInfo& /*info*/, int _x, int _y, int _tx, int _ty, bool inside)
kocienda's avatar
kocienda committed
413
{
414 415 416 417 418 419 420 421
    assert(parent());

    _tx -= paddingLeft() + borderLeft();
    _ty -= borderTop() + paddingTop();

    int height = m_lineHeight + borderTop() + paddingTop() +
                 borderBottom() + paddingBottom();

kocienda's avatar
kocienda committed
422 423
    TextSlave *s = m_lines.count() ? m_lines[0] : 0;
    int si = 0;
424 425 426 427 428 429 430
    while(s) {
        if((_y >=_ty + s->m_y) && (_y < _ty + s->m_y + height) &&
           (_x >= _tx + s->m_x) && (_x <_tx + s->m_x + s->m_width) ) {
            inside = true;
            break;
        }

darin's avatar
darin committed
431
        s = si < (int) m_lines.count()-1 ? m_lines[++si] : 0;
kocienda's avatar
kocienda committed
432
    }
433 434 435 436

    setMouseInside(inside);

    return inside;
kocienda's avatar
kocienda committed
437 438
}

rjw's avatar
rjw committed
439
FindSelectionResult RenderText::checkSelectionPoint(const khtml::MouseEvent *event, int _tx, int _ty, DOM::NodeImpl*& node, int &offset)
kocienda's avatar
kocienda committed
440
{
441 442
//     kdDebug(6040) << "RenderText::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y
//                   << " _tx=" << _tx << " _ty=" << _ty << endl;
rjw's avatar
rjw committed
443 444
    int _x = event->x();
    int _y = event->y();
darin's avatar
darin committed
445 446
    TextSlave *lastPointAfterInline=0;

kocienda's avatar
kocienda committed
447 448 449 450
    for(unsigned int si = 0; si < m_lines.count(); si++)
    {
        TextSlave* s = m_lines[si];
        int result;
darin's avatar
darin committed
451 452
        const Font *f = htmlFont( si==0 );
        result = s->checkSelectionPoint(_x, _y, _tx, _ty, f, this, offset, m_lineHeight);
453

darin's avatar
darin committed
454
//         kdDebug(6040) << "RenderText::checkSelectionPoint " << this << " line " << si << " result=" << result << " offset=" << offset << endl;
kocienda's avatar
kocienda committed
455 456
        if ( result == SelectionPointInside ) // x,y is inside the textslave
        {
darin's avatar
darin committed
457
            offset += s->m_start; // add the offset from the previous lines
kocienda's avatar
kocienda committed
458
            //kdDebug(6040) << "RenderText::checkSelectionPoint inside -> " << offset << endl;
459
            node = element();
kocienda's avatar
kocienda committed
460
            return SelectionPointInside;
darin's avatar
darin committed
461
        } else if ( result == SelectionPointBefore ) {
kocienda's avatar
kocienda committed
462
            // x,y is before the textslave -> stop here
darin's avatar
darin committed
463 464
            if ( si > 0 && lastPointAfterInline ) {
                offset = lastPointAfterInline->m_start + lastPointAfterInline->m_len;
kocienda's avatar
kocienda committed
465
                //kdDebug(6040) << "RenderText::checkSelectionPoint before -> " << offset << endl;
466
                node = element();
kocienda's avatar
kocienda committed
467
                return SelectionPointInside;
darin's avatar
darin committed
468
            } else {
kocienda's avatar
kocienda committed
469
                offset = 0;
470 471
                //kdDebug(6040) << "RenderText::checkSelectionPoint " << this << "before us -> returning Before" << endl;
                node = element();
kocienda's avatar
kocienda committed
472 473
                return SelectionPointBefore;
            }
darin's avatar
darin committed
474 475 476 477
        } else if ( result == SelectionPointAfterInLine ) {
	    lastPointAfterInline = s;
	}

kocienda's avatar
kocienda committed
478 479 480 481
    }

    // set offset to max
    offset = str->l;
482 483
    //qDebug("setting node to %p", element());
    node = element();
kocienda's avatar
kocienda committed
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
    return SelectionPointAfter;
}

void RenderText::cursorPos(int offset, int &_x, int &_y, int &height)
{
  if (!m_lines.count()) {
    _x = _y = height = -1;
    return;
  }

  int pos;
  TextSlave * s = findTextSlave( offset, pos );
  _y = s->m_y;
  height = m_lineHeight; // ### firstLine!!! s->m_height;

499
  const QFontMetrics &fm = metrics( false ); // #### wrong for first-line!
darin's avatar
darin committed
500
  QString tekst(str->s + s->m_start, s->m_len);
kocienda's avatar
kocienda committed
501 502
  _x = s->m_x + (fm.boundingRect(tekst, pos)).right();
  if(pos)
darin's avatar
darin committed
503
      _x += fm.rightBearing( *(str->s + s->m_start + pos - 1 ) );
kocienda's avatar
kocienda committed
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521

  int absx, absy;

  RenderObject *cb = containingBlock();

  if (cb && cb != this && cb->absolutePosition(absx,absy))
  {
    _x += absx;
    _y += absy;
  } else {
    // we don't know our absolute position, and there is not point returning
    // just a relative one
    _x = _y = -1;
  }
}

bool RenderText::absolutePosition(int &xPos, int &yPos, bool)
{
522 523
    return RenderObject::absolutePosition(xPos, yPos, false);

kocienda's avatar
kocienda committed
524
    if(parent() && parent()->absolutePosition(xPos, yPos, false)) {
525 526
        xPos -= paddingLeft() + borderLeft();
        yPos -= borderTop() + paddingTop();
kocienda's avatar
kocienda committed
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
        return true;
    }
    xPos = yPos = 0;
    return false;
}

void RenderText::posOfChar(int chr, int &x, int &y)
{
    if (!parent())
    {
       x = -1;
       y = -1;
       return;
    }
    parent()->absolutePosition( x, y, false );

    //if( chr > (int) str->l )
    //chr = str->l;

    int pos;
    TextSlave * s = findTextSlave( chr, pos );

    if ( s )
    {
        // s is the line containing the character
        x += s->m_x; // this is the x of the beginning of the line, but it's good enough for now
        y += s->m_y;
    }
}

557 558 559 560 561 562 563 564
int RenderText::rightmostPosition() const
{
    if (style()->whiteSpace() != NORMAL)
        return maxWidth();

    return 0;
}

565 566
void RenderText::paintObject(QPainter *p, int /*x*/, int y, int /*w*/, int h,
                             int tx, int ty, int paintPhase)
kocienda's avatar
kocienda committed
567
{
568
    int ow = style()->outlineWidth();
kocienda's avatar
kocienda committed
569 570 571 572 573
    RenderStyle* pseudoStyle = style()->getPseudoStyle(RenderStyle::FIRST_LINE);
    int d = style()->textDecoration();
    TextSlave f(0, y-ty);
    int si = m_lines.findFirstMatching(&f);
    // something matching found, find the first one to print
darin's avatar
darin committed
574
    bool isPrinting = (p->device()->devType() == QInternal::Printer);
kocienda's avatar
kocienda committed
575 576 577 578 579 580 581 582 583
    if(si >= 0)
    {
        // Move up until out of area to be printed
        while(si > 0 && m_lines[si-1]->checkVerticalPoint(y, ty, h, m_lineHeight))
            si--;

        // Now calculate startPos and endPos, for printing selection.
        // We print selection while endPos > 0
        int endPos, startPos;
darin's avatar
darin committed
584
        if (!isPrinting && (selectionState() != SelectionNone)) {
darin's avatar
darin committed
585
            if (selectionState() == SelectionInside) {
kocienda's avatar
kocienda committed
586 587 588
                //kdDebug(6040) << this << " SelectionInside -> 0 to end" << endl;
                startPos = 0;
                endPos = str->l;
darin's avatar
darin committed
589
            } else {
kocienda's avatar
kocienda committed
590 591 592 593 594 595 596 597 598 599 600 601 602
                selectionStartEnd(startPos, endPos);
                if(selectionState() == SelectionStart)
                    endPos = str->l;
                else if(selectionState() == SelectionEnd)
                    startPos = 0;
            }
            //kdDebug(6040) << this << " Selection from " << startPos << " to " << endPos << endl;
        }

        TextSlave* s;
        int minx =  1000000;
        int maxx = -1000000;
        int outlinebox_y = m_lines[si]->m_y;
603 604 605
	QPtrList <QRect> linerects;
        linerects.setAutoDelete(true);
        linerects.append(new QRect());
kocienda's avatar
kocienda committed
606

607
	bool renderOutline = style()->outlineWidth()!=0;
kocienda's avatar
kocienda committed
608

darin's avatar
darin committed
609 610
	const Font *font = &style()->htmlFont();

611 612 613 614 615 616 617 618 619 620 621 622
#if APPLE_CHANGES
        // Do one pass for the selection, then another for the rest.
        bool haveSelection = startPos != endPos && !isPrinting && selectionState() != SelectionNone;
        int startLine = si;
        for (int pass = 0; pass < (haveSelection ? 2 : 1); pass++) {
            si = startLine;
            
            bool drawDecorations = !haveSelection || pass == 0;
            bool drawSelectionBackground = haveSelection && pass == 0;
            bool drawText = !haveSelection || pass == 1;
#endif

kocienda's avatar
kocienda committed
623 624 625 626
        // run until we find one that is outside the range, then we
        // know we can stop
        do {
            s = m_lines[si];
darin's avatar
darin committed
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646

	    if (isPrinting)
	    {
                int lh = lineHeight( false ) + paddingBottom() + borderBottom();
                if (ty+s->m_y < y)
                {
                   // This has been printed already we suppose.
                   continue;
                }

                if (ty+lh+s->m_y > y+h)
                {
                   RenderRoot *rootObj = root();
                   if (ty+s->m_y < rootObj->truncatedAt())
                      rootObj->setTruncatedAt(ty+s->m_y);
                   // Let's stop here.
                   break;
                }
            }

kocienda's avatar
kocienda committed
647 648
            RenderStyle* _style = pseudoStyle && s->m_firstLine ? pseudoStyle : style();

darin's avatar
darin committed
649
            if(_style->font() != p->font()) {
kocienda's avatar
kocienda committed
650
                p->setFont(_style->font());
darin's avatar
darin committed
651 652
		font = &_style->htmlFont();
	    }
darin's avatar
darin committed
653

654 655 656
#if APPLE_CHANGES
            if (drawDecorations)
#endif
657
            if((shouldPaintBackgroundOrBorder()  &&
kocienda's avatar
kocienda committed
658 659
                (parent()->isInline() || pseudoStyle)) &&
               (!pseudoStyle || s->m_firstLine))
660
                s->paintBoxDecorations(p, _style, this, tx, ty, si == 0, si == (int)m_lines.count()-1);
kocienda's avatar
kocienda committed
661

662

663 664 665 666
#if APPLE_CHANGES
            if (drawText) {
#endif

kocienda's avatar
kocienda committed
667 668 669
            if(_style->color() != p->pen().color())
                p->setPen(_style->color());

darin's avatar
darin committed
670 671 672
	    if (s->m_len > 0)
		font->drawText(p, s->m_x + tx, s->m_y + ty + s->m_baseline, str->s, str->l, s->m_start, s->m_len,
			       s->m_toAdd, s->m_reversed ? QPainter::RTL : QPainter::LTR);
kocienda's avatar
kocienda committed
673 674 675 676

            if(d != TDNONE)
            {
                p->setPen(_style->textDecorationColor());
rjw's avatar
WebKit:  
rjw committed
677
                s->paintDecoration(p, font, this, tx, ty, d, si == 0, si == ( int ) m_lines.count()-1);
kocienda's avatar
kocienda committed
678 679
            }

680 681 682 683 684 685 686 687 688
#if APPLE_CHANGES
            } // drawText
#endif

#if APPLE_CHANGES
            if (drawSelectionBackground)
#endif
            if (!isPrinting && (selectionState() != SelectionNone))
            {
darin's avatar
darin committed
689 690 691
		int offset = s->m_start;
		int sPos = QMAX( startPos - offset, 0 );
		int ePos = QMIN( endPos - offset, s->m_len );
692
                //kdDebug(6040) << this << " paintSelection with startPos=" << sPos << " endPos=" << ePos << endl;
darin's avatar
darin committed
693
		if ( sPos < ePos )
694
		    s->paintSelection(font, this, p, _style, tx, ty, sPos, ePos);
darin's avatar
darin committed
695

kocienda's avatar
kocienda committed
696
            }
697 698 699 700

#if APPLE_CHANGES
            if (drawText)
#endif
701
            if(renderOutline) {
kocienda's avatar
kocienda committed
702 703
                if(outlinebox_y == s->m_y) {
                    if(minx > s->m_x)  minx = s->m_x;
704 705 706 707
                    int newmaxx = s->m_x+s->m_width;
                    //if (parent()->isInline() && si==0) newmaxx-=paddingLeft();
                    if (parent()->isInline() && si==int(m_lines.count())-1) newmaxx-=paddingRight();
                    if(maxx < newmaxx) maxx = newmaxx;
kocienda's avatar
kocienda committed
708 709
                }
                else {
710 711 712
                    QRect *curLine = new QRect(minx, outlinebox_y, maxx-minx, m_lineHeight);
                    linerects.append(curLine);

kocienda's avatar
kocienda committed
713 714 715
                    outlinebox_y = s->m_y;
                    minx = s->m_x;
                    maxx = s->m_x+s->m_width;
716 717
                    //if (parent()->isInline() && si==0) maxx-=paddingLeft();
                    if (parent()->isInline() && si==int(m_lines.count())-1) maxx-=paddingRight();
kocienda's avatar
kocienda committed
718 719
                }
            }
720 721 722 723 724 725 726 727 728 729 730 731
#ifdef BIDI_DEBUG
            {
                int h = lineHeight( false ) + paddingTop() + paddingBottom() + borderTop() + borderBottom();
                QColor c2 = QColor("#0000ff");
                drawBorder(p, tx, ty, tx+1, ty + h,
                              RenderObject::BSLeft, c2, c2, SOLID, 1, 1);
                drawBorder(p, tx + s->m_width, ty, tx + s->m_width + 1, ty + h,
                              RenderObject::BSRight, c2, c2, SOLID, 1, 1);
            }
#endif

        } while (++si < (int)m_lines.count() && m_lines[si]->checkVerticalPoint(y-ow, ty, h, m_lineHeight));
kocienda's avatar
kocienda committed
732

733 734 735 736
#if APPLE_CHANGES
        } // end of for loop
#endif

737 738 739 740 741
        if(renderOutline)
	  {
	    linerects.append(new QRect(minx, outlinebox_y, maxx-minx, m_lineHeight));
	    linerects.append(new QRect());
	    for (unsigned int i = 1; i < linerects.count() - 1; i++)
742
            paintTextOutline(p, tx, ty, *linerects.at(i-1), *linerects.at(i), *linerects.at(i+1));
743
	  }
kocienda's avatar
kocienda committed
744 745 746
    }
}

747 748
void RenderText::paint(QPainter *p, int x, int y, int w, int h,
                       int tx, int ty, int paintPhase)
kocienda's avatar
kocienda committed
749
{
750 751
    if (paintPhase != FOREGROUND_PHASE || style()->visibility() != VISIBLE) 
        return;
kocienda's avatar
kocienda committed
752 753 754

    int s = m_lines.count() - 1;
    if ( s < 0 ) return;
755 756 757 758

    // ### incorporate padding/border here!
    if ( ty + m_lines[0]->m_y > y + h + 64 ) return;
    if ( ty + m_lines[s]->m_y + m_lines[s]->m_baseline + m_lineHeight + 64 < y ) return;
kocienda's avatar
kocienda committed
759

760
    paintObject(p, x, y, w, h, tx, ty, paintPhase);
kocienda's avatar
kocienda committed
761 762
}

763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
void RenderText::trimmedMinMaxWidth(short& beginMinW, bool& beginWS, 
                                    short& endMinW, bool& endWS,
                                    bool& hasBreakableChar, bool& hasBreak,
                                    short& beginMaxW, short& endMaxW,
                                    short& minW, short& maxW, bool& stripFrontSpaces)
{
    int len = str->l;
    bool isPre = style()->whiteSpace() == PRE;
    if (isPre)
        stripFrontSpaces = false;
    
    minW = m_minWidth;
    maxW = m_maxWidth;
    beginWS = stripFrontSpaces ? false : m_hasBeginWS;
    // Handle the case where all space got stripped.
    endWS = stripFrontSpaces && len > 0 && str->containsOnlyWhitespace() ? false : m_hasEndWS;
    
    beginMinW = m_beginMinWidth;
    endMinW = m_endMinWidth;
    
    hasBreakableChar = m_hasBreakableChar;
784
    hasBreak = m_hasBreak;
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804
    
    if (len == 0)
        return;
        
    if (stripFrontSpaces && str->s[0].direction() == QChar::DirWS) {
        const Font *f = htmlFont( false );
        QChar space[1]; space[0] = ' ';
        int spaceWidth = f->width(space, 1, 0);
        maxW -= spaceWidth;
    }
    
    stripFrontSpaces = !isPre && endWS;
    
    if (style()->whiteSpace() == NOWRAP)
        minW = maxW;
    else if (minW > maxW)
        minW = maxW;

    // Compute our max widths by scanning the string for newlines.
    if (hasBreak) {
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829
        const Font *f = htmlFont( false );
        bool firstLine = true;
        beginMaxW = endMaxW = maxW;
        for(int i = 0; i < len; i++)
        {
            int linelen = 0;
            while( i+linelen < len && str->s[i+linelen] != '\n')
                linelen++;
                
            if (linelen)
            {
                endMaxW = f->width(str->s, str->l, i, linelen);
                if (firstLine) {
                    firstLine = false;
                    beginMaxW = endMaxW;
                }
                i += linelen;
                if (i == len-1)
                    endMaxW = 0;
            }
            else if (firstLine) {
                beginMaxW = 0;
                firstLine = false;
            }
        }
830 831 832
    }
}

kocienda's avatar
kocienda committed
833 834
void RenderText::calcMinMaxWidth()
{
835
    KHTMLAssert( !minMaxKnown() );
kocienda's avatar
kocienda committed
836 837

    // ### calc Min and Max width...
838
    m_minWidth = m_beginMinWidth = m_endMinWidth = 0;
kocienda's avatar
kocienda committed
839 840
    m_maxWidth = 0;

841 842 843
    if (isBR())
        return;
        
kocienda's avatar
kocienda committed
844 845
    int currMinWidth = 0;
    int currMaxWidth = 0;
846 847
    m_hasBreakableChar = m_hasBreak = m_hasBeginWS = m_hasEndWS = false;
    
kocienda's avatar
kocienda committed
848
    // ### not 100% correct for first-line
darin's avatar
darin committed
849
    const Font *f = htmlFont( false );
kocienda's avatar
kocienda committed
850
    int len = str->l;
851 852 853 854
    bool ignoringSpaces = false;
    bool isSpace = false;
    bool isPre = style()->whiteSpace() == PRE;
    bool firstWord = true;
kocienda's avatar
kocienda committed
855 856
    for(int i = 0; i < len; i++)
    {
857
        bool isNewline = false;
858 859 860
        // XXXdwh Wrong in the first stage.  Will stop mutating newlines
        // in a second stage.
        if (str->s[i] == '\n') {
861
            if (isPre) {
862
                m_hasBreak = true;
863 864
                isNewline = true;
            }
865 866 867 868 869 870 871
            else
                str->s[i] = ' ';
        }
        
        bool oldSpace = isSpace;
        isSpace = str->s[i].direction() == QChar::DirWS;
        
872
        if ((isSpace || isNewline) && i == 0)
873
            m_hasBeginWS = true;
874
        if ((isSpace || isNewline) && i == len-1)
875 876 877 878 879 880 881 882 883 884 885
            m_hasEndWS = true;
            
        if (!ignoringSpaces && !isPre && oldSpace && isSpace)
            ignoringSpaces = true;
        
        if (ignoringSpaces && !isSpace)
            ignoringSpaces = false;
            
        if (ignoringSpaces)
            continue;
        
kocienda's avatar
kocienda committed
886
        int wordlen = 0;
darin's avatar
darin committed
887
        while( i+wordlen < len && !(isBreakable( str->s, i+wordlen, str->l )) )
kocienda's avatar
kocienda committed
888
            wordlen++;
889
            
kocienda's avatar
kocienda committed
890 891
        if (wordlen)
        {
darin's avatar
darin committed
892
            int w = f->width(str->s, str->l, i, wordlen);
kocienda's avatar
kocienda committed
893 894
            currMinWidth += w;
            currMaxWidth += w;
895 896 897 898 899 900 901 902 903 904
            if (firstWord) {
                firstWord = false;
                m_beginMinWidth = w;
            }
            m_endMinWidth = w;
            
            if(currMinWidth > m_minWidth) m_minWidth = currMinWidth;
            currMinWidth = 0;
                
            i += wordlen-1;
kocienda's avatar
kocienda committed
905
        }
906 907 908 909 910 911 912 913 914 915
        else {
            // Nowrap can never be broken, so don't bother setting the
            // breakable character boolean.
            if (style()->whiteSpace() != NOWRAP)
                m_hasBreakableChar = true;

            if(currMinWidth > m_minWidth) m_minWidth = currMinWidth;
            currMinWidth = 0;
                
            if (str->s[i] == '\n')
kocienda's avatar
kocienda committed
916 917 918 919 920 921
            {
                if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
                currMaxWidth = 0;
            }
            else
            {
darin's avatar
darin committed
922
                currMaxWidth += f->width( str->s, str->l, i + wordlen );
kocienda's avatar
kocienda committed
923 924 925
            }
        }
    }
926
    
kocienda's avatar
kocienda committed
927 928
    if(currMinWidth > m_minWidth) m_minWidth = currMinWidth;
    if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
929 930 931 932

    if (style()->whiteSpace() == NOWRAP)
        m_minWidth = m_maxWidth;

kocienda's avatar
kocienda committed
933
    setMinMaxKnown();
934
    //kdDebug( 6040 ) << "Text::calcMinMaxWidth(): min = " << m_minWidth << " max = " << m_maxWidth << endl;
kocienda's avatar
kocienda committed
935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966
}

int RenderText::minXPos() const
{
    if (!m_lines.count())
	return 0;
    int retval=6666666;
    for (unsigned i=0;i < m_lines.count(); i++)
    {
	retval = QMIN ( retval, m_lines[i]->m_x);
    }
    return retval;
}

int RenderText::xPos() const
{
    if (m_lines.count())
	return m_lines[0]->m_x;
    else
	return 0;
}

int RenderText::yPos() const
{
    if (m_lines.count())
        return m_lines[0]->m_y;
    else
        return 0;
}

const QFont &RenderText::font()
{
967
    return style()->font();
kocienda's avatar
kocienda committed
968 969
}

darin's avatar
darin committed
970
void RenderText::setText(DOMStringImpl *text, bool force)
kocienda's avatar
kocienda committed
971
{
972
#if APPLE_CHANGES
973 974 975
    if (!text)
        return;
#endif
darin's avatar
darin committed
976
    if( !force && str == text ) return;
kocienda's avatar
kocienda committed
977 978
    if(str) str->deref();
    str = text;
darin's avatar
darin committed
979

darin's avatar
darin committed
980
    if ( str && style() ) {
darin's avatar
darin committed
981 982 983 984 985 986 987 988 989 990
        if ( style()->fontVariant() == SMALL_CAPS )
            str = str->upper();
        else
            switch(style()->textTransform()) {
            case CAPITALIZE:   str = str->capitalize();  break;
            case UPPERCASE:   str = str->upper();       break;
            case LOWERCASE:  str = str->lower();       break;
            case NONE:
            default:;
            }
darin's avatar
darin committed
991
        str->ref();
darin's avatar
darin committed
992
    }
kocienda's avatar
kocienda committed
993 994 995

    // ### what should happen if we change the text of a
    // RenderBR object ?
996 997
    KHTMLAssert(!isBR() || (str->l == 1 && (*str->s) == '\n'));
    KHTMLAssert(!str->l || str->s);
kocienda's avatar
kocienda committed
998 999

    setLayouted(false);
1000
#ifdef BIDI_DEBUG
kocienda's avatar
kocienda committed
1001
    QConstString cstr(str->s, str->l);
1002
    kdDebug( 6040 ) << "RenderText::setText( " << cstr.string().length() << " ) '" << cstr.string() << "'" << endl;
kocienda's avatar
kocienda committed
1003 1004 1005 1006 1007
#endif
}

int RenderText::height() const
{
1008 1009 1010 1011 1012 1013 1014 1015
    int retval;
    if ( m_lines.count() )
        retval = m_lines[m_lines.count()-1]->m_y + m_lineHeight - m_lines[0]->m_y;
    else
        retval = metrics( false ).height();

    retval += paddingTop() + paddingBottom() + borderTop() + borderBottom();

kocienda's avatar
kocienda committed
1016 1017 1018
    return retval;
}

1019
short RenderText::lineHeight( bool firstLine ) const
kocienda's avatar
kocienda committed
1020
{
1021 1022 1023
    if ( firstLine )
 	return RenderObject::lineHeight( firstLine );

kocienda's avatar
kocienda committed
1024 1025 1026 1027 1028
    return m_lineHeight;
}

short RenderText::baselinePosition( bool firstLine ) const
{
1029 1030 1031
    const QFontMetrics &fm = metrics( firstLine );
    return fm.ascent() +
        ( lineHeight( firstLine ) - fm.height() ) / 2;
kocienda's avatar
kocienda committed
1032 1033
}

darin's avatar
darin committed
1034
void RenderText::position(int x, int y, int from, int len, int width, bool reverse, bool firstLine, int spaceAdd)
kocienda's avatar
kocienda committed
1035 1036
{
    // ### should not be needed!!!
1037 1038
    if (len == 0 || (str->l && len == 1 && *(str->s+from) == '\n'))
        return;
kocienda's avatar
kocienda committed
1039 1040 1041 1042 1043 1044 1045 1046 1047 1048

    reverse = reverse && !style()->visuallyOrdered();

    // ### margins and RTL
    if(from == 0 && parent()->isInline() && parent()->firstChild()==this)
    {
        x += paddingLeft() + borderLeft() + marginLeft();
        width -= marginLeft();
    }

darin's avatar
darin committed
1049
    if(from + len >= int(str->l) && parent()->isInline() && parent()->lastChild()==this)
kocienda's avatar
kocienda committed
1050 1051 1052
        width -= marginRight();

#ifdef DEBUG_LAYOUT
darin's avatar
darin committed
1053
    QChar *ch = str->s+from;
kocienda's avatar
kocienda committed
1054
    QConstString cstr(ch, len);
1055
    qDebug("setting slave text to *%s*, len=%d, w)=%d" , cstr.string().latin1(), len, width );//" << y << ")" << " height=" << lineHeight(false) << " fontHeight=" << metrics(false).height() << " ascent =" << metrics(false).ascent() << endl;
kocienda's avatar
kocienda committed
1056 1057
#endif

darin's avatar
darin committed
1058
    TextSlave *s = new (renderArena()) TextSlave(x, y, from, len,
kocienda's avatar
kocienda committed
1059
                                 baselinePosition( firstLine ),
1060
                                 width+spaceAdd, reverse, spaceAdd, firstLine);
kocienda's avatar
kocienda committed
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072

    if(m_lines.count() == m_lines.size())
        m_lines.resize(m_lines.size()*2+1);

    m_lines.insert(m_lines.count(), s);
}

unsigned int RenderText::width(unsigned int from, unsigned int len, bool firstLine) const
{
    if(!str->s || from > str->l ) return 0;
    if ( from + len > str->l ) len = str->l - from;

darin's avatar
darin committed
1073 1074
    const Font *f = htmlFont( firstLine );
    return width( from, len, f );
kocienda's avatar
kocienda committed
1075 1076
}

darin's avatar
darin committed
1077
unsigned int RenderText::width(unsigned int from, unsigned int len, const Font *f) const
kocienda's avatar
kocienda committed
1078 1079 1080 1081 1082
{
    if(!str->s || from > str->l ) return 0;
    if ( from + len > str->l ) len = str->l - from;

    int w;
darin's avatar
darin committed
1083
    if ( f == &style()->htmlFont() && from == 0 && len == str->l )
kocienda's avatar
kocienda committed
1084 1085
 	 w = m_maxWidth;
    else
darin's avatar
darin committed
1086
	w = f->width(str->s, str->l, from, len );
kocienda's avatar
kocienda committed
1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119