render_text.cpp 39.6 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
}

rjw's avatar
rjw committed
178 179
#define LOCAL_WIDTH_BUF_SIZE	1024

darin's avatar
darin committed
180
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
181
{
darin's avatar
darin committed
182 183
//     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
184 185 186 187 188
    offset = 0;

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

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

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

205
#if APPLE_CHANGES
darin's avatar
darin committed
206
    // Floating point version needed for best results with Mac OS X text.
rjw's avatar
 
rjw committed
207
    float delta = _x - (_tx + m_x);
rjw's avatar
rjw committed
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
    float _widths[LOCAL_WIDTH_BUF_SIZE]; 
    float *widths = 0;
    float monospaceWidth = 0;

    if (text->shouldUseMonospaceCache(f)){
        monospaceWidth = text->widthFromCache (f, m_start, 1);
    }
    else {
        if (text->str->l > LOCAL_WIDTH_BUF_SIZE)
            widths = (float *)malloc(text->str->l * sizeof(float));
        else
            widths = &_widths[0];
        // 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
224 225 226 227
    int pos = 0;
    if ( m_reversed ) {
	delta -= m_width;
	while(pos < m_len) {
rjw's avatar
rjw committed
228
	    float w = (monospaceWidth != 0 ? monospaceWidth : widths[pos+m_start]);
rjw's avatar
 
rjw committed
229 230 231 232 233 234 235 236 237 238
	    float w2 = w/2;
	    w -= w2;
	    delta += w2;
	    if(delta >= 0)
	        break;
	    pos++;
	    delta += w;
	}
    } else {
	while(pos < m_len) {
rjw's avatar
rjw committed
239
	    float w = (monospaceWidth != 0 ? monospaceWidth : widths[pos+m_start]);
rjw's avatar
 
rjw committed
240 241 242 243 244 245 246 247 248
	    float w2 = w/2;
	    w -= w2;
	    delta -= w2;
	    if(delta <= 0) 
	        break;
	    pos++;
	    delta -= w;
	}
    }
rjw's avatar
rjw committed
249 250 251
    
    if (widths != _widths)
        free (widths);
rjw's avatar
 
rjw committed
252
#else
kocienda's avatar
kocienda committed
253 254 255
    int delta = _x - (_tx + m_x);
    //kdDebug(6040) << "TextSlave::checkSelectionPoint delta=" << delta << endl;
    int pos = 0;
darin's avatar
darin committed
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
    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
277
    }
rjw's avatar
 
rjw committed
278
#endif
darin's avatar
darin committed
279
//     kdDebug( 6040 ) << " Text  --> inside at position " << pos << endl;
kocienda's avatar
kocienda committed
280 281 282 283 284 285 286 287
    offset = pos;
    return SelectionPointInside;
}

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

TextSlaveArray::TextSlaveArray()
{
288
    setAutoDelete(false);
kocienda's avatar
kocienda committed
289 290 291 292
}

int TextSlaveArray::compareItems( Item d1, Item d2 )
{
293 294
    assert(d1);
    assert(d2);
kocienda's avatar
kocienda committed
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317

    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
318
	    res = ((QGVector*)this)->compareItems( d, (*this)[mid] );
kocienda's avatar
kocienda committed
319 320 321 322 323 324 325 326 327 328 329 330
	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
331
    while ( found && (mid > 0) && !((QGVector*)this)->compareItems(d, (*this)[mid-1]) )
kocienda's avatar
kocienda committed
332 333 334 335 336 337
	mid--;
    return mid;
}

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

338 339
RenderText::RenderText(DOM::NodeImpl* node, DOMStringImpl *_str)
    : RenderObject(node)
kocienda's avatar
kocienda committed
340 341 342 343 344 345
{
    // init RenderObject attributes
    setRenderText();   // our object inherits from RenderText

    m_minWidth = -1;
    m_maxWidth = -1;
rjw's avatar
rjw committed
346 347 348

#ifdef APPLE_CHANGES
    m_monospaceCharacterWidth = 0;
rjw's avatar
rjw committed
349
#endif
rjw's avatar
rjw committed
350

kocienda's avatar
kocienda committed
351 352
    str = _str;
    if(str) str->ref();
353
    KHTMLAssert(!str || !str->l || str->s);
kocienda's avatar
kocienda committed
354 355 356 357 358

    m_selectionState = SelectionNone;

#ifdef DEBUG_LAYOUT
    QConstString cstr(str->s, str->l);
359
    kdDebug( 6040 ) << "RenderText ctr( "<< cstr.string().length() << " )  '" << cstr.string() << "'" << endl;
kocienda's avatar
kocienda committed
360 361 362 363 364
#endif
}

void RenderText::setStyle(RenderStyle *_style)
{
365
    if ( style() != _style ) {
darin's avatar
darin committed
366 367 368 369 370 371 372 373
        // ### 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
374
        if (changedText && element() && element()->string())
darin's avatar
darin committed
375
            setText(element()->string(), changedText);
rjw's avatar
rjw committed
376
#if APPLE_CHANGES
rjw's avatar
rjw committed
377
        // set also call cacheWidths(), so no need to recache if that has already been done.
rjw's avatar
rjw committed
378
        else
rjw's avatar
rjw committed
379
            cacheWidths();
rjw's avatar
rjw committed
380
#endif
kocienda's avatar
kocienda committed
381 382 383 384 385 386 387 388
    }
}

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

389 390
void RenderText::detach(RenderArena* renderArena)
{
391
    deleteSlaves(renderArena);
392 393 394
    RenderObject::detach(renderArena);
}

395
void RenderText::deleteSlaves(RenderArena *arena)
kocienda's avatar
kocienda committed
396 397 398 399 400 401
{
    // 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();
402
    if (len) {
403 404
        if (!arena)
            arena = renderArena();
405 406 407 408 409 410
        for(unsigned int i=0; i < len; i++) {
            TextSlave* s = m_lines.at(i);
            if (s)
                s->detach(arena);
            m_lines.remove(i);
        }
411 412
    }
    
413
    KHTMLAssert(m_lines.count() == 0);
kocienda's avatar
kocienda committed
414 415 416 417 418 419 420 421 422 423 424 425 426
}

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
427
    uint si = 1;
kocienda's avatar
kocienda committed
428 429 430
    int off = s->m_len;
    while(offset > off && si < m_lines.count())
    {
rjw's avatar
rjw committed
431
        s = m_lines[si++];
darin's avatar
darin committed
432
        off = s->m_start + s->m_len;
kocienda's avatar
kocienda committed
433 434 435 436 437 438
    }
    // we are now in the correct text slave
    pos = (offset > off ? s->m_len : s->m_len - (off - offset) );
    return s;
}

439
bool RenderText::nodeAtPoint(NodeInfo& /*info*/, int _x, int _y, int _tx, int _ty, bool inside)
kocienda's avatar
kocienda committed
440
{
441 442 443 444 445 446 447 448
    assert(parent());

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

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

kocienda's avatar
kocienda committed
449 450
    TextSlave *s = m_lines.count() ? m_lines[0] : 0;
    int si = 0;
451 452 453 454 455 456 457
    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
458
        s = si < (int) m_lines.count()-1 ? m_lines[++si] : 0;
kocienda's avatar
kocienda committed
459
    }
460 461 462 463

    setMouseInside(inside);

    return inside;
kocienda's avatar
kocienda committed
464 465
}

rjw's avatar
rjw committed
466
FindSelectionResult RenderText::checkSelectionPoint(const khtml::MouseEvent *event, int _tx, int _ty, DOM::NodeImpl*& node, int &offset)
kocienda's avatar
kocienda committed
467
{
468 469
//     kdDebug(6040) << "RenderText::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y
//                   << " _tx=" << _tx << " _ty=" << _ty << endl;
rjw's avatar
rjw committed
470 471
    int _x = event->x();
    int _y = event->y();
darin's avatar
darin committed
472 473
    TextSlave *lastPointAfterInline=0;

kocienda's avatar
kocienda committed
474 475 476 477
    for(unsigned int si = 0; si < m_lines.count(); si++)
    {
        TextSlave* s = m_lines[si];
        int result;
darin's avatar
darin committed
478 479
        const Font *f = htmlFont( si==0 );
        result = s->checkSelectionPoint(_x, _y, _tx, _ty, f, this, offset, m_lineHeight);
480

darin's avatar
darin committed
481
//         kdDebug(6040) << "RenderText::checkSelectionPoint " << this << " line " << si << " result=" << result << " offset=" << offset << endl;
kocienda's avatar
kocienda committed
482 483
        if ( result == SelectionPointInside ) // x,y is inside the textslave
        {
darin's avatar
darin committed
484
            offset += s->m_start; // add the offset from the previous lines
kocienda's avatar
kocienda committed
485
            //kdDebug(6040) << "RenderText::checkSelectionPoint inside -> " << offset << endl;
486
            node = element();
kocienda's avatar
kocienda committed
487
            return SelectionPointInside;
darin's avatar
darin committed
488
        } else if ( result == SelectionPointBefore ) {
kocienda's avatar
kocienda committed
489
            // x,y is before the textslave -> stop here
darin's avatar
darin committed
490 491
            if ( si > 0 && lastPointAfterInline ) {
                offset = lastPointAfterInline->m_start + lastPointAfterInline->m_len;
kocienda's avatar
kocienda committed
492
                //kdDebug(6040) << "RenderText::checkSelectionPoint before -> " << offset << endl;
493
                node = element();
kocienda's avatar
kocienda committed
494
                return SelectionPointInside;
darin's avatar
darin committed
495
            } else {
kocienda's avatar
kocienda committed
496
                offset = 0;
497 498
                //kdDebug(6040) << "RenderText::checkSelectionPoint " << this << "before us -> returning Before" << endl;
                node = element();
kocienda's avatar
kocienda committed
499 500
                return SelectionPointBefore;
            }
darin's avatar
darin committed
501 502 503 504
        } else if ( result == SelectionPointAfterInLine ) {
	    lastPointAfterInline = s;
	}

kocienda's avatar
kocienda committed
505 506 507 508
    }

    // set offset to max
    offset = str->l;
509 510
    //qDebug("setting node to %p", element());
    node = element();
kocienda's avatar
kocienda committed
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
    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;

526
  const QFontMetrics &fm = metrics( false ); // #### wrong for first-line!
darin's avatar
darin committed
527
  QString tekst(str->s + s->m_start, s->m_len);
kocienda's avatar
kocienda committed
528 529
  _x = s->m_x + (fm.boundingRect(tekst, pos)).right();
  if(pos)
darin's avatar
darin committed
530
      _x += fm.rightBearing( *(str->s + s->m_start + pos - 1 ) );
kocienda's avatar
kocienda committed
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548

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

kocienda's avatar
kocienda committed
551
    if(parent() && parent()->absolutePosition(xPos, yPos, false)) {
552 553
        xPos -= paddingLeft() + borderLeft();
        yPos -= borderTop() + paddingTop();
kocienda's avatar
kocienda committed
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
        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;
    }
}

584 585 586 587 588 589 590 591
int RenderText::rightmostPosition() const
{
    if (style()->whiteSpace() != NORMAL)
        return maxWidth();

    return 0;
}

592 593
void RenderText::paintObject(QPainter *p, int /*x*/, int y, int /*w*/, int h,
                             int tx, int ty, int paintPhase)
kocienda's avatar
kocienda committed
594
{
595
    int ow = style()->outlineWidth();
kocienda's avatar
kocienda committed
596 597 598 599 600
    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
601
    bool isPrinting = (p->device()->devType() == QInternal::Printer);
kocienda's avatar
kocienda committed
602 603 604 605 606 607 608 609 610
    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
611
        if (!isPrinting && (selectionState() != SelectionNone)) {
darin's avatar
darin committed
612
            if (selectionState() == SelectionInside) {
kocienda's avatar
kocienda committed
613 614 615
                //kdDebug(6040) << this << " SelectionInside -> 0 to end" << endl;
                startPos = 0;
                endPos = str->l;
darin's avatar
darin committed
616
            } else {
kocienda's avatar
kocienda committed
617 618 619 620 621 622 623 624 625 626 627 628 629
                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;
630 631 632
	QPtrList <QRect> linerects;
        linerects.setAutoDelete(true);
        linerects.append(new QRect());
kocienda's avatar
kocienda committed
633

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

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

638 639 640 641 642 643 644 645 646 647 648 649
#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
650 651 652 653
        // 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
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673

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

darin's avatar
darin committed
676
            if(_style->font() != p->font()) {
kocienda's avatar
kocienda committed
677
                p->setFont(_style->font());
darin's avatar
darin committed
678 679
		font = &_style->htmlFont();
	    }
darin's avatar
darin committed
680

681 682 683
#if APPLE_CHANGES
            if (drawDecorations)
#endif
684
            if((shouldPaintBackgroundOrBorder()  &&
kocienda's avatar
kocienda committed
685 686
                (parent()->isInline() || pseudoStyle)) &&
               (!pseudoStyle || s->m_firstLine))
687
                s->paintBoxDecorations(p, _style, this, tx, ty, si == 0, si == (int)m_lines.count()-1);
kocienda's avatar
kocienda committed
688

689

690 691 692 693
#if APPLE_CHANGES
            if (drawText) {
#endif

kocienda's avatar
kocienda committed
694 695 696
            if(_style->color() != p->pen().color())
                p->setPen(_style->color());

darin's avatar
darin committed
697 698 699
	    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
700 701 702 703

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

707 708 709 710 711 712 713 714 715
#if APPLE_CHANGES
            } // drawText
#endif

#if APPLE_CHANGES
            if (drawSelectionBackground)
#endif
            if (!isPrinting && (selectionState() != SelectionNone))
            {
darin's avatar
darin committed
716 717 718
		int offset = s->m_start;
		int sPos = QMAX( startPos - offset, 0 );
		int ePos = QMIN( endPos - offset, s->m_len );
719
                //kdDebug(6040) << this << " paintSelection with startPos=" << sPos << " endPos=" << ePos << endl;
darin's avatar
darin committed
720
		if ( sPos < ePos )
721
		    s->paintSelection(font, this, p, _style, tx, ty, sPos, ePos);
darin's avatar
darin committed
722

kocienda's avatar
kocienda committed
723
            }
724 725 726 727

#if APPLE_CHANGES
            if (drawText)
#endif
728
            if(renderOutline) {
kocienda's avatar
kocienda committed
729 730
                if(outlinebox_y == s->m_y) {
                    if(minx > s->m_x)  minx = s->m_x;
731 732 733 734
                    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
735 736
                }
                else {
737 738 739
                    QRect *curLine = new QRect(minx, outlinebox_y, maxx-minx, m_lineHeight);
                    linerects.append(curLine);

kocienda's avatar
kocienda committed
740 741 742
                    outlinebox_y = s->m_y;
                    minx = s->m_x;
                    maxx = s->m_x+s->m_width;
743 744
                    //if (parent()->isInline() && si==0) maxx-=paddingLeft();
                    if (parent()->isInline() && si==int(m_lines.count())-1) maxx-=paddingRight();
kocienda's avatar
kocienda committed
745 746
                }
            }
747 748 749 750 751 752 753 754 755 756 757 758
#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
759

760 761 762 763
#if APPLE_CHANGES
        } // end of for loop
#endif

764 765 766 767 768
        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++)
769
            paintTextOutline(p, tx, ty, *linerects.at(i-1), *linerects.at(i), *linerects.at(i+1));
770
	  }
kocienda's avatar
kocienda committed
771 772 773
    }
}

774 775
void RenderText::paint(QPainter *p, int x, int y, int w, int h,
                       int tx, int ty, int paintPhase)
kocienda's avatar
kocienda committed
776
{
777 778
    if (paintPhase != FOREGROUND_PHASE || style()->visibility() != VISIBLE) 
        return;
kocienda's avatar
kocienda committed
779 780 781

    int s = m_lines.count() - 1;
    if ( s < 0 ) return;
782 783 784 785

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

787
    paintObject(p, x, y, w, h, tx, ty, paintPhase);
kocienda's avatar
kocienda committed
788 789
}

rjw's avatar
rjw committed
790 791
#ifdef APPLE_CHANGES

rjw's avatar
rjw committed
792 793 794 795 796 797 798 799 800
bool RenderText::shouldUseMonospaceCache(const Font *f) const
{
    return (f && f->isFixedPitch());
}

// We cache the width of the ' ' character for <pre> text.  We could go futher
// and cache a widths array for all styles, at the expense of increasing the size of the
// RenderText.
void RenderText::cacheWidths()
rjw's avatar
rjw committed
801 802 803
{
    const Font *f = htmlFont( false );
    
rjw's avatar
rjw committed
804 805 806 807 808
    if (shouldUseMonospaceCache(f)){	
        float fw;
        QChar c(' ');
        f->floatCharacterWidths( &c, 1, 0, 1, 0, &fw);
        m_monospaceCharacterWidth = (int)fw;
rjw's avatar
rjw committed
809
    }
rjw's avatar
rjw committed
810 811
    else
        m_monospaceCharacterWidth = 0;
rjw's avatar
rjw committed
812 813
}

814

rjw's avatar
rjw committed
815
inline int RenderText::widthFromCache(const Font *f, int start, int len) const
rjw's avatar
rjw committed
816
{
rjw's avatar
rjw committed
817
    if (m_monospaceCharacterWidth != 0){
818 819 820 821 822 823 824
        int i, w = 0;
        for (i = start; i < start+len; i++){
            int dir = str->s[i].direction();
            if (dir != QChar::DirNSM && dir != QChar::DirBN)
                w += m_monospaceCharacterWidth;
        }
        return w;
rjw's avatar
rjw committed
825
    }
rjw's avatar
rjw committed
826 827
    
    return f->width(str->s, str->l, start, len);
rjw's avatar
rjw committed
828
}
829 830 831 832 833 834 835 836 837 838 839
#ifdef XXX
inline int RenderText::widthFromCache(const Font *f, int start, int len) const
{
    if (m_monospaceCharacterWidth != 0){
        return len * m_monospaceCharacterWidth;
    }

    return f->width(str->s, str->l, start, len);
}
#endif

rjw's avatar
rjw committed
840 841
#endif

842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
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;
863
    hasBreak = m_hasBreak;
864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880
    
    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
881
        
882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897
    // 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
rjw's avatar
rjw committed
898
                endMaxW = widthFromCache(f, i, linelen);
899 900 901 902 903 904 905 906 907 908 909 910 911 912 913
#endif
                if (firstLine) {
                    firstLine = false;
                    beginMaxW = endMaxW;
                }
                i += linelen;
                if (i == len-1)
                    endMaxW = 0;
            }
            else if (firstLine) {
                beginMaxW = 0;
                firstLine = false;
            }
        }
    }
914 915
}

kocienda's avatar
kocienda committed
916 917
void RenderText::calcMinMaxWidth()
{
918
    KHTMLAssert( !minMaxKnown() );
kocienda's avatar
kocienda committed
919 920

    // ### calc Min and Max width...
921
    m_minWidth = m_beginMinWidth = m_endMinWidth = 0;
922
    m_maxWidth = 0;
kocienda's avatar
kocienda committed
923

924 925 926
    if (isBR())
        return;
        
kocienda's avatar
kocienda committed
927 928
    int currMinWidth = 0;
    int currMaxWidth = 0;
929 930
    m_hasBreakableChar = m_hasBreak = m_hasBeginWS = m_hasEndWS = false;
    
kocienda's avatar
kocienda committed
931
    // ### not 100% correct for first-line
darin's avatar
darin committed
932
    const Font *f = htmlFont( false );
kocienda's avatar
kocienda committed
933
    int len = str->l;
934 935 936 937
    bool ignoringSpaces = false;
    bool isSpace = false;
    bool isPre = style()->whiteSpace() == PRE;
    bool firstWord = true;
kocienda's avatar
kocienda committed
938 939
    for(int i = 0; i < len; i++)
    {
940
        bool isNewline = false;
941 942 943
        // XXXdwh Wrong in the first stage.  Will stop mutating newlines
        // in a second stage.
        if (str->s[i] == '\n') {
944
            if (isPre) {
945
                m_hasBreak = true;
946 947
                isNewline = true;
            }
948 949 950 951 952 953 954
            else
                str->s[i] = ' ';
        }
        
        bool oldSpace = isSpace;
        isSpace = str->s[i].direction() == QChar::DirWS;
        
955
        if ((isSpace || isNewline) && i == 0)
956
            m_hasBeginWS = true;
957
        if ((isSpace || isNewline) && i == len-1)
958 959 960 961 962 963 964 965 966 967 968
            m_hasEndWS = true;
            
        if (!ignoringSpaces && !isPre && oldSpace && isSpace)
            ignoringSpaces = true;
        
        if (ignoringSpaces && !isSpace)
            ignoringSpaces = false;
            
        if (ignoringSpaces)
            continue;
        
kocienda's avatar
kocienda committed
969
        int wordlen = 0;
darin's avatar
darin committed
970
        while( i+wordlen < len && !(isBreakable( str->s, i+wordlen, str->l )) )
kocienda's avatar
kocienda committed
971
            wordlen++;
972
            
kocienda's avatar
kocienda committed
973 974
        if (wordlen)
        {
rjw's avatar
rjw committed
975
#if !APPLE_CHANGES
darin's avatar
darin committed
976
            int w = f->width(str->s, str->l, i, wordlen);
rjw's avatar
rjw committed
977
#else
rjw's avatar
rjw committed
978
            int w = widthFromCache(f, i, wordlen);
rjw's avatar
rjw committed
979
#endif
kocienda's avatar
kocienda committed
980 981
            currMinWidth += w;
            currMaxWidth += w;
982 983 984 985 986 987 988 989 990 991
            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
992
        }
993 994 995 996 997 998 999 1000 1001 1002
        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
1003 1004 1005 1006 1007 1008
            {
                if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
                currMaxWidth = 0;
            }
            else
            {
darin's avatar
darin committed
1009
                currMaxWidth += f->width( str->s, str->l, i + wordlen );
kocienda's avatar
kocienda committed
1010 1011 1012
            }
        }
    }
1013
    
kocienda's avatar
kocienda committed
1014 1015
    if(currMinWidth > m_minWidth) m_minWidth = currMinWidth;
    if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
1016 1017 1018 1019

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

kocienda's avatar
kocienda committed
1020
    setMinMaxKnown();
1021
    //kdDebug( 6040 ) << "Text::calcMinMaxWidth(): min = " << m_minWidth << " max = " << m_maxWidth << endl;
kocienda's avatar
kocienda committed
1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053
}

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()
{
1054
    return style()->font();
kocienda's avatar
kocienda committed
1055 1056
}

darin's avatar
darin committed
1057
void RenderText::setText(DOMStringImpl *text, bool force)
kocienda's avatar
kocienda committed
1058
{
1059
#if APPLE_CHANGES
1060 1061 1062
    if (!text)
        return;
#endif
darin's avatar
darin committed
1063
    if( !force && str == text ) return;
kocienda's avatar
kocienda committed
1064 1065
    if(str) str->deref();
    str = text;
darin's avatar
darin committed
1066

darin's avatar
darin committed
1067
    if ( str && style() ) {
darin's avatar
darin committed
1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
        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
1078
        str->ref();
darin's avatar
darin committed
1079
    }
kocienda's avatar
kocienda committed
1080

rjw's avatar
rjw committed
1081
#if APPLE_CHANGES
rjw's avatar
rjw committed
1082
    cacheWidths();
rjw's avatar
rjw committed
1083
#endif
kocienda's avatar
kocienda committed
1084 1085
    // ### what should happen if we change the text of a
    // RenderBR object ?
1086 1087
    KHTMLAssert(!isBR() || (str->l == 1 && (*str->s) == '\n'));
    KHTMLAssert(!str->l || str->s);
kocienda's avatar
kocienda committed
1088 1089

    setLayouted(false);
1090
#ifdef BIDI_DEBUG
kocienda's avatar
kocienda committed
1091
    QConstString cstr(str->s, str->l);
1092