render_text.cpp 39.1 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
{
    // init RenderObject attributes
    setRenderText();   // our object inherits from RenderText

    m_minWidth = -1;
    m_maxWidth = -1;
rjw's avatar
rjw committed
329 330 331
#if APPLE_CHANGES
    m_widths = 0;
#endif
kocienda's avatar
kocienda committed
332 333
    str = _str;
    if(str) str->ref();
334
    KHTMLAssert(!str || !str->l || str->s);
kocienda's avatar
kocienda committed
335 336 337 338 339

    m_selectionState = SelectionNone;

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

void RenderText::setStyle(RenderStyle *_style)
{
346
    if ( style() != _style ) {
darin's avatar
darin committed
347 348 349 350 351 352 353 354
        // ### 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
355
        if (changedText && element() && element()->string())
darin's avatar
darin committed
356
            setText(element()->string(), changedText);
rjw's avatar
rjw committed
357 358 359 360 361
#if APPLE_CHANGES
        // set also call computeWidths(), so no need to recache if that has already been done.
        else
            computeWidths();
#endif
kocienda's avatar
kocienda committed
362 363 364 365 366 367
    }
}

RenderText::~RenderText()
{
    if(str) str->deref();
rjw's avatar
rjw committed
368 369 370 371
#if APPLE_CHANGES
    if (m_widths)
        free (m_widths);
#endif
kocienda's avatar
kocienda committed
372 373
}

374 375
void RenderText::detach(RenderArena* renderArena)
{
376
    deleteSlaves(renderArena);
377 378 379
    RenderObject::detach(renderArena);
}

380
void RenderText::deleteSlaves(RenderArena *arena)
kocienda's avatar
kocienda committed
381 382 383 384 385 386
{
    // 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();
387
    if (len) {
388 389
        if (!arena)
            arena = renderArena();
390 391 392 393 394 395
        for(unsigned int i=0; i < len; i++) {
            TextSlave* s = m_lines.at(i);
            if (s)
                s->detach(arena);
            m_lines.remove(i);
        }
396 397
    }
    
398
    KHTMLAssert(m_lines.count() == 0);
kocienda's avatar
kocienda committed
399 400 401 402 403 404 405 406 407 408 409 410 411
}

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
412
    uint si = 1;
kocienda's avatar
kocienda committed
413 414 415
    int off = s->m_len;
    while(offset > off && si < m_lines.count())
    {
rjw's avatar
rjw committed
416
        s = m_lines[si++];
darin's avatar
darin committed
417
        off = s->m_start + s->m_len;
kocienda's avatar
kocienda committed
418 419 420 421 422 423
    }
    // we are now in the correct text slave
    pos = (offset > off ? s->m_len : s->m_len - (off - offset) );
    return s;
}

424
bool RenderText::nodeAtPoint(NodeInfo& /*info*/, int _x, int _y, int _tx, int _ty, bool inside)
kocienda's avatar
kocienda committed
425
{
426 427 428 429 430 431 432 433
    assert(parent());

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

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

kocienda's avatar
kocienda committed
434 435
    TextSlave *s = m_lines.count() ? m_lines[0] : 0;
    int si = 0;
436 437 438 439 440 441 442
    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
443
        s = si < (int) m_lines.count()-1 ? m_lines[++si] : 0;
kocienda's avatar
kocienda committed
444
    }
445 446 447 448

    setMouseInside(inside);

    return inside;
kocienda's avatar
kocienda committed
449 450
}

rjw's avatar
rjw committed
451
FindSelectionResult RenderText::checkSelectionPoint(const khtml::MouseEvent *event, int _tx, int _ty, DOM::NodeImpl*& node, int &offset)
kocienda's avatar
kocienda committed
452
{
453 454
//     kdDebug(6040) << "RenderText::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y
//                   << " _tx=" << _tx << " _ty=" << _ty << endl;
rjw's avatar
rjw committed
455 456
    int _x = event->x();
    int _y = event->y();
darin's avatar
darin committed
457 458
    TextSlave *lastPointAfterInline=0;

kocienda's avatar
kocienda committed
459 460 461 462
    for(unsigned int si = 0; si < m_lines.count(); si++)
    {
        TextSlave* s = m_lines[si];
        int result;
darin's avatar
darin committed
463 464
        const Font *f = htmlFont( si==0 );
        result = s->checkSelectionPoint(_x, _y, _tx, _ty, f, this, offset, m_lineHeight);
465

darin's avatar
darin committed
466
//         kdDebug(6040) << "RenderText::checkSelectionPoint " << this << " line " << si << " result=" << result << " offset=" << offset << endl;
kocienda's avatar
kocienda committed
467 468
        if ( result == SelectionPointInside ) // x,y is inside the textslave
        {
darin's avatar
darin committed
469
            offset += s->m_start; // add the offset from the previous lines
kocienda's avatar
kocienda committed
470
            //kdDebug(6040) << "RenderText::checkSelectionPoint inside -> " << offset << endl;
471
            node = element();
kocienda's avatar
kocienda committed
472
            return SelectionPointInside;
darin's avatar
darin committed
473
        } else if ( result == SelectionPointBefore ) {
kocienda's avatar
kocienda committed
474
            // x,y is before the textslave -> stop here
darin's avatar
darin committed
475 476
            if ( si > 0 && lastPointAfterInline ) {
                offset = lastPointAfterInline->m_start + lastPointAfterInline->m_len;
kocienda's avatar
kocienda committed
477
                //kdDebug(6040) << "RenderText::checkSelectionPoint before -> " << offset << endl;
478
                node = element();
kocienda's avatar
kocienda committed
479
                return SelectionPointInside;
darin's avatar
darin committed
480
            } else {
kocienda's avatar
kocienda committed
481
                offset = 0;
482 483
                //kdDebug(6040) << "RenderText::checkSelectionPoint " << this << "before us -> returning Before" << endl;
                node = element();
kocienda's avatar
kocienda committed
484 485
                return SelectionPointBefore;
            }
darin's avatar
darin committed
486 487 488 489
        } else if ( result == SelectionPointAfterInLine ) {
	    lastPointAfterInline = s;
	}

kocienda's avatar
kocienda committed
490 491 492 493
    }

    // set offset to max
    offset = str->l;
494 495
    //qDebug("setting node to %p", element());
    node = element();
kocienda's avatar
kocienda committed
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
    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;

511
  const QFontMetrics &fm = metrics( false ); // #### wrong for first-line!
darin's avatar
darin committed
512
  QString tekst(str->s + s->m_start, s->m_len);
kocienda's avatar
kocienda committed
513 514
  _x = s->m_x + (fm.boundingRect(tekst, pos)).right();
  if(pos)
darin's avatar
darin committed
515
      _x += fm.rightBearing( *(str->s + s->m_start + pos - 1 ) );
kocienda's avatar
kocienda committed
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533

  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)
{
534 535
    return RenderObject::absolutePosition(xPos, yPos, false);

kocienda's avatar
kocienda committed
536
    if(parent() && parent()->absolutePosition(xPos, yPos, false)) {
537 538
        xPos -= paddingLeft() + borderLeft();
        yPos -= borderTop() + paddingTop();
kocienda's avatar
kocienda committed
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
        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;
    }
}

569 570 571 572 573 574 575 576
int RenderText::rightmostPosition() const
{
    if (style()->whiteSpace() != NORMAL)
        return maxWidth();

    return 0;
}

577 578
void RenderText::paintObject(QPainter *p, int /*x*/, int y, int /*w*/, int h,
                             int tx, int ty, int paintPhase)
kocienda's avatar
kocienda committed
579
{
580
    int ow = style()->outlineWidth();
kocienda's avatar
kocienda committed
581 582 583 584 585
    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
586
    bool isPrinting = (p->device()->devType() == QInternal::Printer);
kocienda's avatar
kocienda committed
587 588 589 590 591 592 593 594 595
    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
596
        if (!isPrinting && (selectionState() != SelectionNone)) {
darin's avatar
darin committed
597
            if (selectionState() == SelectionInside) {
kocienda's avatar
kocienda committed
598 599 600
                //kdDebug(6040) << this << " SelectionInside -> 0 to end" << endl;
                startPos = 0;
                endPos = str->l;
darin's avatar
darin committed
601
            } else {
kocienda's avatar
kocienda committed
602 603 604 605 606 607 608 609 610 611 612 613 614
                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;
615 616 617
	QPtrList <QRect> linerects;
        linerects.setAutoDelete(true);
        linerects.append(new QRect());
kocienda's avatar
kocienda committed
618

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

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

623 624 625 626 627 628 629 630 631 632 633 634
#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
635 636 637 638
        // 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
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658

	    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
659 660
            RenderStyle* _style = pseudoStyle && s->m_firstLine ? pseudoStyle : style();

darin's avatar
darin committed
661
            if(_style->font() != p->font()) {
kocienda's avatar
kocienda committed
662
                p->setFont(_style->font());
darin's avatar
darin committed
663 664
		font = &_style->htmlFont();
	    }
darin's avatar
darin committed
665

666 667 668
#if APPLE_CHANGES
            if (drawDecorations)
#endif
669
            if((shouldPaintBackgroundOrBorder()  &&
kocienda's avatar
kocienda committed
670 671
                (parent()->isInline() || pseudoStyle)) &&
               (!pseudoStyle || s->m_firstLine))
672
                s->paintBoxDecorations(p, _style, this, tx, ty, si == 0, si == (int)m_lines.count()-1);
kocienda's avatar
kocienda committed
673

674

675 676 677 678
#if APPLE_CHANGES
            if (drawText) {
#endif

kocienda's avatar
kocienda committed
679 680 681
            if(_style->color() != p->pen().color())
                p->setPen(_style->color());

darin's avatar
darin committed
682 683 684
	    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
685 686 687 688

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

692 693 694 695 696 697 698 699 700
#if APPLE_CHANGES
            } // drawText
#endif

#if APPLE_CHANGES
            if (drawSelectionBackground)
#endif
            if (!isPrinting && (selectionState() != SelectionNone))
            {
darin's avatar
darin committed
701 702 703
		int offset = s->m_start;
		int sPos = QMAX( startPos - offset, 0 );
		int ePos = QMIN( endPos - offset, s->m_len );
704
                //kdDebug(6040) << this << " paintSelection with startPos=" << sPos << " endPos=" << ePos << endl;
darin's avatar
darin committed
705
		if ( sPos < ePos )
706
		    s->paintSelection(font, this, p, _style, tx, ty, sPos, ePos);
darin's avatar
darin committed
707

kocienda's avatar
kocienda committed
708
            }
709 710 711 712

#if APPLE_CHANGES
            if (drawText)
#endif
713
            if(renderOutline) {
kocienda's avatar
kocienda committed
714 715
                if(outlinebox_y == s->m_y) {
                    if(minx > s->m_x)  minx = s->m_x;
716 717 718 719
                    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
720 721
                }
                else {
722 723 724
                    QRect *curLine = new QRect(minx, outlinebox_y, maxx-minx, m_lineHeight);
                    linerects.append(curLine);

kocienda's avatar
kocienda committed
725 726 727
                    outlinebox_y = s->m_y;
                    minx = s->m_x;
                    maxx = s->m_x+s->m_width;
728 729
                    //if (parent()->isInline() && si==0) maxx-=paddingLeft();
                    if (parent()->isInline() && si==int(m_lines.count())-1) maxx-=paddingRight();
kocienda's avatar
kocienda committed
730 731
                }
            }
732 733 734 735 736 737 738 739 740 741 742 743
#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
744

745 746 747 748
#if APPLE_CHANGES
        } // end of for loop
#endif

749 750 751 752 753
        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++)
754
            paintTextOutline(p, tx, ty, *linerects.at(i-1), *linerects.at(i), *linerects.at(i+1));
755
	  }
kocienda's avatar
kocienda committed
756 757 758
    }
}

759 760
void RenderText::paint(QPainter *p, int x, int y, int w, int h,
                       int tx, int ty, int paintPhase)
kocienda's avatar
kocienda committed
761
{
762 763
    if (paintPhase != FOREGROUND_PHASE || style()->visibility() != VISIBLE) 
        return;
kocienda's avatar
kocienda committed
764 765 766

    int s = m_lines.count() - 1;
    if ( s < 0 ) return;
767 768 769 770

    // ### 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
771

772
    paintObject(p, x, y, w, h, tx, ty, paintPhase);
kocienda's avatar
kocienda committed
773 774
}

rjw's avatar
rjw committed
775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818
#ifdef APPLE_CHANGES

// We cache the widths array for the characters in the string only
// if the string is very large.  This is a short term performance
// optimization.  It has the downside of increasing the size
// of each render text by float * str->l.  The performance
// gained is huge though, so this is justifiable.  The long term
// fix is to rework the word break/line break/bidi mechanisms
// to reduce the measurement performed, and to correctly account
// for unicode break points and surrogate pairs.
#define MIN_STRING_LENGTH_FOR_WIDTH_CACHE 1024

void RenderText::computeWidths()
{
    const Font *f = htmlFont( false );
    
    if (f){
        if (m_widths){
            free (m_widths);
            m_widths = 0;
        }

        if (str->l > MIN_STRING_LENGTH_FOR_WIDTH_CACHE){
            m_widths = (float *)malloc(str->l * sizeof(float));
            f->floatCharacterWidths( str->s, str->l, 0, str->l, 0, m_widths);
        }
    }
}

inline int RenderText::widthFromBuffer(const Font *f, int start, int len) const
{
    float width = 0;
    int i;
    
    if (m_widths == 0)
        return f->width(str->s, str->l, start, len);
        
    for (i = start; i < start+len; i++){
        width += m_widths[i];
    }
    return (int)width;
}
#endif

819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839
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;
840
    hasBreak = m_hasBreak;
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857
    
    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;
rjw's avatar
rjw committed
858
        
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890
    // Compute our max widths by scanning the string for newlines.
    if (hasBreak) {
        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)
            {
#if !APPLE_CHANGES
                endMaxW = f->width(str->s, str->l, i, linelen);
#else
                endMaxW = widthFromBuffer(f, i, linelen);
#endif
                if (firstLine) {
                    firstLine = false;
                    beginMaxW = endMaxW;
                }
                i += linelen;
                if (i == len-1)
                    endMaxW = 0;
            }
            else if (firstLine) {
                beginMaxW = 0;
                firstLine = false;
            }
        }
    }
891 892
}

kocienda's avatar
kocienda committed
893 894
void RenderText::calcMinMaxWidth()
{
895
    KHTMLAssert( !minMaxKnown() );
kocienda's avatar
kocienda committed
896 897

    // ### calc Min and Max width...
898
    m_minWidth = m_beginMinWidth = m_endMinWidth = 0;
899
    m_maxWidth = 0;
kocienda's avatar
kocienda committed
900

901 902 903
    if (isBR())
        return;
        
kocienda's avatar
kocienda committed
904 905
    int currMinWidth = 0;
    int currMaxWidth = 0;
906 907
    m_hasBreakableChar = m_hasBreak = m_hasBeginWS = m_hasEndWS = false;
    
kocienda's avatar
kocienda committed
908
    // ### not 100% correct for first-line
darin's avatar
darin committed
909
    const Font *f = htmlFont( false );
kocienda's avatar
kocienda committed
910
    int len = str->l;
911 912 913 914
    bool ignoringSpaces = false;
    bool isSpace = false;
    bool isPre = style()->whiteSpace() == PRE;
    bool firstWord = true;
kocienda's avatar
kocienda committed
915 916
    for(int i = 0; i < len; i++)
    {
917
        bool isNewline = false;
918 919 920
        // XXXdwh Wrong in the first stage.  Will stop mutating newlines
        // in a second stage.
        if (str->s[i] == '\n') {
921
            if (isPre) {
922
                m_hasBreak = true;
923 924
                isNewline = true;
            }
925 926 927 928 929 930 931
            else
                str->s[i] = ' ';
        }
        
        bool oldSpace = isSpace;
        isSpace = str->s[i].direction() == QChar::DirWS;
        
932
        if ((isSpace || isNewline) && i == 0)
933
            m_hasBeginWS = true;
934
        if ((isSpace || isNewline) && i == len-1)
935 936 937 938 939 940 941 942 943 944 945
            m_hasEndWS = true;
            
        if (!ignoringSpaces && !isPre && oldSpace && isSpace)
            ignoringSpaces = true;
        
        if (ignoringSpaces && !isSpace)
            ignoringSpaces = false;
            
        if (ignoringSpaces)
            continue;
        
kocienda's avatar
kocienda committed
946
        int wordlen = 0;
darin's avatar
darin committed
947
        while( i+wordlen < len && !(isBreakable( str->s, i+wordlen, str->l )) )
kocienda's avatar
kocienda committed
948
            wordlen++;
949
            
kocienda's avatar
kocienda committed
950 951
        if (wordlen)
        {
rjw's avatar
rjw committed
952
#if !APPLE_CHANGES
darin's avatar
darin committed
953
            int w = f->width(str->s, str->l, i, wordlen);
rjw's avatar
rjw committed
954 955 956
#else
            int w = widthFromBuffer(f, i, wordlen);
#endif
kocienda's avatar
kocienda committed
957 958
            currMinWidth += w;
            currMaxWidth += w;
959 960 961 962 963 964 965 966 967 968
            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
969
        }
970 971 972 973 974 975 976 977 978 979
        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
980 981 982 983 984 985
            {
                if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
                currMaxWidth = 0;
            }
            else
            {
darin's avatar
darin committed
986
                currMaxWidth += f->width( str->s, str->l, i + wordlen );
kocienda's avatar
kocienda committed
987 988 989
            }
        }
    }
990
    
kocienda's avatar
kocienda committed
991 992
    if(currMinWidth > m_minWidth) m_minWidth = currMinWidth;
    if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
993 994 995 996

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

kocienda's avatar
kocienda committed
997
    setMinMaxKnown();
998
    //kdDebug( 6040 ) << "Text::calcMinMaxWidth(): min = " << m_minWidth << " max = " << m_maxWidth << endl;
kocienda's avatar
kocienda committed
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
}

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()
{
1031
    return style()->font();
kocienda's avatar
kocienda committed
1032 1033
}

darin's avatar
darin committed
1034
void RenderText::setText(DOMStringImpl *text, bool force)
kocienda's avatar
kocienda committed
1035
{
1036
#if APPLE_CHANGES
1037 1038 1039
    if (!text)
        return;
#endif
darin's avatar
darin committed
1040
    if( !force && str == text ) return;
kocienda's avatar
kocienda committed
1041 1042
    if(str) str->deref();
    str = text;
darin's avatar
darin committed
1043

darin's avatar
darin committed
1044
    if ( str && style() ) {
darin's avatar
darin committed
1045 1046 1047 1048 1049 1050 1051 1052 1053 1054
        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
1055
        str->ref();
darin's avatar
darin committed
1056
    }
kocienda's avatar
kocienda committed
1057

rjw's avatar
rjw committed
1058 1059 1060
#if APPLE_CHANGES
    computeWidths();
#endif
kocienda's avatar
kocienda committed
1061 1062
    // ### what should happen if we change the text of a
    // RenderBR object ?
1063 1064
    KHTMLAssert(!isBR() || (str->l == 1 && (*str->s) == '\n'));
    KHTMLAssert(!str->l || str->s);
kocienda's avatar
kocienda committed
1065 1066

    setLayouted(false);
1067
#ifdef BIDI_DEBUG
kocienda's avatar
kocienda committed
1068
    QConstString cstr(str->s, str->l);
1069
    kdDebug( 6040 ) << "RenderText::setText( " << cstr.string().length() << " ) '" << cstr.string() << "'" << endl;
kocienda's avatar
kocienda committed
1070 1071 1072 1073 1074
#endif
}

int RenderText::height() const
{
1075 1076 1077 1078 1079 1080 1081 1082
    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
1083 1084 1085
    return retval;
}

1086
short RenderText::lineHeight( bool firstLine ) const
kocienda's avatar
kocienda committed
1087
{
1088 1089 1090
    if ( firstLine )
 	return RenderObject::lineHeight( firstLine );

kocienda's avatar
kocienda committed
1091 1092 1093 1094 1095
    return m_lineHeight;
}

short RenderText::baselinePosition( bool firstLine ) const
{
1096 1097 1098
    const QFontMetrics &fm = metrics( firstLine );
    return fm.ascent() +
        ( lineHeight( firstLine ) - fm.height() ) / 2;
kocienda's avatar
kocienda committed
1099 1100
}

darin's avatar
darin committed
1101
void RenderText::position(int x, int y, int from, int len, int width, bool reverse, bool firstLine, int spaceAdd)
kocienda's avatar
kocienda committed
1102 1103
{
    // ### should not be needed!!!
1104 1105
    if (len == 0 || (str->l && len == 1 && *(str->s+from) == '\n'))
        return;
kocienda's avatar
kocienda committed
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115

    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
1116
    if(from + len >= int(str->l) && parent()->isInline() && parent()->lastChild()==this)
kocienda's avatar
kocienda committed
1117 1118 1119
        width -= marginRight();

#ifdef DEBUG_LAYOUT
darin's avatar
darin committed
1120
    QChar *ch = str->s+from;
kocienda's avatar
kocienda committed
1121
    QConstString cstr(ch, len);