render_text.cpp 36.3 KB
Newer Older
kocienda's avatar
kocienda committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/**
 * This file is part of the DOM implementation for KDE.
 *
 * (C) 1999 Lars Knoll (knoll@kde.org)
 * (C) 2000 Dirk Mueller (mueller@kde.org)
 *
 * 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
26
#include "rendering/render_root.h"
27
#include "rendering/render_text.h"
darin's avatar
darin committed
28
#include "rendering/render_root.h"
29 30
#include "rendering/break_lines.h"
#include "xml/dom_nodeimpl.h"
31 32
#include "xml/dom_docimpl.h"
#include "render_arena.h"
kocienda's avatar
kocienda committed
33 34 35 36 37

#include "misc/loader.h"

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

using namespace khtml;
using namespace DOM;

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
void TextSlave::detach(RenderArena* renderArena)
{
    delete this;
    
    // Now perform the destroy.
    size_t* sz = (size_t*)this;
    renderArena->free(*sz, (void*)this);
}

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

void TextSlave::operator delete(void* ptr, size_t sz) {
    size_t* szPtr = (size_t*)ptr;
    *szPtr = sz;
}

darin's avatar
darin committed
62
void TextSlave::printSelection(const Font *f, RenderText *text, QPainter *p, RenderStyle* style, int tx, int ty, int startPos, int endPos)
kocienda's avatar
kocienda committed
63 64 65 66 67
{
    if(startPos > m_len) return;
    if(startPos < 0) startPos = 0;

    p->save();
68
#if APPLE_CHANGES
darin's avatar
darin committed
69
    // Macintosh-style text highlighting is to draw with a particular background color, not invert.
70 71 72
    QColor textColor = style->color();
    QColor c = QPainter::selectedTextBackgroundColor();
    if (textColor == c)
darin's avatar
darin committed
73
        c = QColor(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
74
    p->setPen(c); // Don't draw text at all!
75
#else
kocienda's avatar
kocienda committed
76 77
    QColor c = style->color();
    p->setPen(QColor(0xff-c.red(),0xff-c.green(),0xff-c.blue()));
78
#endif
kocienda's avatar
kocienda committed
79 80 81
    ty += m_baseline;

    //kdDebug( 6040 ) << "textSlave::printing(" << s.string() << ") at(" << x+_tx << "/" << y+_ty << ")" << endl;
darin's avatar
darin committed
82 83
    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
84 85 86 87 88 89 90 91
    p->restore();
}

void TextSlave::printDecoration( QPainter *pt, RenderText* p, int _tx, int _ty, int deco, bool begin, bool end)
{
    _tx += m_x;
    _ty += m_y;

92
    int width = m_width - 1;
kocienda's avatar
kocienda committed
93 94

    if( begin )
95
 	width -= p->paddingLeft() + p->borderLeft();
kocienda's avatar
kocienda committed
96 97 98 99

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

100
#if APPLE_CHANGES
darin's avatar
darin committed
101
    // Use a special function for underlines to get the positioning exactly right.
darin's avatar
darin committed
102 103
    if(deco & UNDERLINE)
        pt->drawUnderlineForText(_tx, _ty + m_baseline, p->str->s + m_start, m_len);
104
#else
kocienda's avatar
kocienda committed
105 106
    int underlineOffset = ( pt->fontMetrics().height() + m_baseline ) / 2;
    if(underlineOffset <= m_baseline) underlineOffset = m_baseline+1;
kocienda's avatar
kocienda committed
107

darin's avatar
darin committed
108
    if(deco & UNDERLINE){
109
        pt->drawLine(_tx, _ty + underlineOffset, _tx + width, _ty + underlineOffset );
darin's avatar
darin committed
110
    }
111
#endif
kocienda's avatar
kocienda committed
112 113 114 115 116 117 118 119 120 121 122 123
    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 );
    // NO! Do NOT add BLINK! It is the most annouing feature of Netscape, and IE has a reason not to
    // support it. Lars
}

void TextSlave::printBoxDecorations(QPainter *pt, RenderStyle* style, RenderText *p, int _tx, int _ty, bool begin, bool end)
{
    int topExtra = p->borderTop() + p->paddingTop();
    int bottomExtra = p->borderBottom() + p->paddingBottom();
124 125
    // ### firstline
    int halfleading = (p->m_lineHeight - style->font().pixelSize() ) / 2;
kocienda's avatar
kocienda committed
126 127

    _tx += m_x;
128
    _ty += m_y + halfleading - topExtra;
kocienda's avatar
kocienda committed
129 130 131

    int width = m_width;

132 133
    // 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
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148

    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));
    }

149 150
#ifdef DEBUG_VALIGN
    pt->fillRect(_tx, _ty, width, height, Qt::cyan );
kocienda's avatar
kocienda committed
151 152
#endif

153 154
    if(style->hasBorder())
        p->printBorder(pt, _tx, _ty, width, height, style, begin, end);
kocienda's avatar
kocienda committed
155 156
}

darin's avatar
darin committed
157
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
158
{
darin's avatar
darin committed
159 160
//     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
161 162 163 164 165
    offset = 0;

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

darin's avatar
darin committed
166 167
    if ( _y > _ty + m_y + lineHeight ) {
        // below -> after
kocienda's avatar
kocienda committed
168 169 170 171
        // Set the offset to the max
        offset = m_len;
        return SelectionPointAfter;
    }
darin's avatar
darin committed
172 173 174 175
    if ( _x > _tx + m_x + m_width ) {
	// to the right
	return m_reversed ? SelectionPointBeforeInLine : SelectionPointAfterInLine;
    }
kocienda's avatar
kocienda committed
176 177

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

182
#if APPLE_CHANGES
darin's avatar
darin committed
183
    // Floating point version needed for best results with Mac OS X text.
rjw's avatar
 
rjw committed
184
    float delta = _x - (_tx + m_x);
185
    float widths[text->str->l]; 
186 187 188
    
    // 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
189 190 191 192
    int pos = 0;
    if ( m_reversed ) {
	delta -= m_width;
	while(pos < m_len) {
193
	    float w = widths[pos+m_start];
rjw's avatar
 
rjw committed
194 195 196 197 198 199 200 201 202 203
	    float w2 = w/2;
	    w -= w2;
	    delta += w2;
	    if(delta >= 0)
	        break;
	    pos++;
	    delta += w;
	}
    } else {
	while(pos < m_len) {
204
	    float w = widths[pos+m_start];
rjw's avatar
 
rjw committed
205 206 207 208 209 210 211 212 213 214
	    float w2 = w/2;
	    w -= w2;
	    delta -= w2;
	    if(delta <= 0) 
	        break;
	    pos++;
	    delta -= w;
	}
    }
#else
kocienda's avatar
kocienda committed
215 216 217
    int delta = _x - (_tx + m_x);
    //kdDebug(6040) << "TextSlave::checkSelectionPoint delta=" << delta << endl;
    int pos = 0;
darin's avatar
darin committed
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
    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
239
    }
rjw's avatar
 
rjw committed
240
#endif
darin's avatar
darin committed
241
//     kdDebug( 6040 ) << " Text  --> inside at position " << pos << endl;
kocienda's avatar
kocienda committed
242 243 244 245 246 247 248 249
    offset = pos;
    return SelectionPointInside;
}

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

TextSlaveArray::TextSlaveArray()
{
250
    setAutoDelete(false);
kocienda's avatar
kocienda committed
251 252 253 254
}

int TextSlaveArray::compareItems( Item d1, Item d2 )
{
255 256
    assert(d1);
    assert(d2);
kocienda's avatar
kocienda committed
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279

    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
280
	    res = ((QGVector*)this)->compareItems( d, (*this)[mid] );
kocienda's avatar
kocienda committed
281 282 283 284 285 286 287 288 289 290 291 292
	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
293
    while ( found && (mid > 0) && !((QGVector*)this)->compareItems(d, (*this)[mid-1]) )
kocienda's avatar
kocienda committed
294 295 296 297 298 299
	mid--;
    return mid;
}

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

300 301
RenderText::RenderText(DOM::NodeImpl* node, DOMStringImpl *_str)
    : RenderObject(node)
kocienda's avatar
kocienda committed
302 303 304 305 306 307 308 309
{
    // init RenderObject attributes
    setRenderText();   // our object inherits from RenderText

    m_minWidth = -1;
    m_maxWidth = -1;
    str = _str;
    if(str) str->ref();
310
    KHTMLAssert(!str || !str->l || str->s);
kocienda's avatar
kocienda committed
311 312 313 314 315

    m_selectionState = SelectionNone;

#ifdef DEBUG_LAYOUT
    QConstString cstr(str->s, str->l);
316
    kdDebug( 6040 ) << "RenderText ctr( "<< cstr.string().length() << " )  '" << cstr.string() << "'" << endl;
kocienda's avatar
kocienda committed
317 318 319 320 321
#endif
}

void RenderText::setStyle(RenderStyle *_style)
{
322
    if ( style() != _style ) {
darin's avatar
darin committed
323 324 325 326 327 328 329 330
        // ### 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
331
        if (changedText && element() && element()->string())
darin's avatar
darin committed
332
            setText(element()->string(), changedText);
kocienda's avatar
kocienda committed
333 334 335 336 337 338 339 340
    }
}

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

341 342
void RenderText::detach(RenderArena* renderArena)
{
343
    deleteSlaves(renderArena);
344 345 346
    RenderObject::detach(renderArena);
}

347
void RenderText::deleteSlaves(RenderArena *arena)
kocienda's avatar
kocienda committed
348 349 350 351 352 353
{
    // 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();
354
    if (len) {
355 356
        if (!arena)
            arena = renderArena();
357 358 359 360 361 362
        for(unsigned int i=0; i < len; i++) {
            TextSlave* s = m_lines.at(i);
            if (s)
                s->detach(arena);
            m_lines.remove(i);
        }
363 364
    }
    
365
    KHTMLAssert(m_lines.count() == 0);
kocienda's avatar
kocienda committed
366 367 368 369 370 371 372 373 374 375 376 377 378
}

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
379
    uint si = 1;
kocienda's avatar
kocienda committed
380 381 382
    int off = s->m_len;
    while(offset > off && si < m_lines.count())
    {
rjw's avatar
rjw committed
383
        s = m_lines[si++];
darin's avatar
darin committed
384
        off = s->m_start + s->m_len;
kocienda's avatar
kocienda committed
385 386 387 388 389 390
    }
    // we are now in the correct text slave
    pos = (offset > off ? s->m_len : s->m_len - (off - offset) );
    return s;
}

darin's avatar
darin committed
391
bool RenderText::nodeAtPoint(NodeInfo& /*info*/, int _x, int _y, int _tx, int _ty)
kocienda's avatar
kocienda committed
392
{
393 394 395 396 397 398 399 400 401
    assert(parent());

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

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

    bool inside = false;
kocienda's avatar
kocienda committed
402 403
    TextSlave *s = m_lines.count() ? m_lines[0] : 0;
    int si = 0;
404 405 406 407 408 409 410
    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
411
        s = si < (int) m_lines.count()-1 ? m_lines[++si] : 0;
kocienda's avatar
kocienda committed
412
    }
413 414 415 416

    setMouseInside(inside);

    return inside;
kocienda's avatar
kocienda committed
417 418
}

419
FindSelectionResult RenderText::checkSelectionPoint(int _x, int _y, int _tx, int _ty, DOM::NodeImpl*& node, int &offset)
kocienda's avatar
kocienda committed
420
{
421 422
//     kdDebug(6040) << "RenderText::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y
//                   << " _tx=" << _tx << " _ty=" << _ty << endl;
darin's avatar
darin committed
423 424
    TextSlave *lastPointAfterInline=0;

kocienda's avatar
kocienda committed
425 426 427 428
    for(unsigned int si = 0; si < m_lines.count(); si++)
    {
        TextSlave* s = m_lines[si];
        int result;
darin's avatar
darin committed
429 430
        const Font *f = htmlFont( si==0 );
        result = s->checkSelectionPoint(_x, _y, _tx, _ty, f, this, offset, m_lineHeight);
431

darin's avatar
darin committed
432
//         kdDebug(6040) << "RenderText::checkSelectionPoint " << this << " line " << si << " result=" << result << " offset=" << offset << endl;
kocienda's avatar
kocienda committed
433 434
        if ( result == SelectionPointInside ) // x,y is inside the textslave
        {
darin's avatar
darin committed
435
            offset += s->m_start; // add the offset from the previous lines
kocienda's avatar
kocienda committed
436
            //kdDebug(6040) << "RenderText::checkSelectionPoint inside -> " << offset << endl;
437
            node = element();
kocienda's avatar
kocienda committed
438
            return SelectionPointInside;
darin's avatar
darin committed
439
        } else if ( result == SelectionPointBefore ) {
kocienda's avatar
kocienda committed
440
            // x,y is before the textslave -> stop here
darin's avatar
darin committed
441 442
            if ( si > 0 && lastPointAfterInline ) {
                offset = lastPointAfterInline->m_start + lastPointAfterInline->m_len;
kocienda's avatar
kocienda committed
443
                //kdDebug(6040) << "RenderText::checkSelectionPoint before -> " << offset << endl;
444
                node = element();
kocienda's avatar
kocienda committed
445
                return SelectionPointInside;
darin's avatar
darin committed
446
            } else {
kocienda's avatar
kocienda committed
447
                offset = 0;
448 449
                //kdDebug(6040) << "RenderText::checkSelectionPoint " << this << "before us -> returning Before" << endl;
                node = element();
kocienda's avatar
kocienda committed
450 451
                return SelectionPointBefore;
            }
darin's avatar
darin committed
452 453 454 455
        } else if ( result == SelectionPointAfterInLine ) {
	    lastPointAfterInline = s;
	}

kocienda's avatar
kocienda committed
456 457 458 459
    }

    // set offset to max
    offset = str->l;
460 461
    //qDebug("setting node to %p", element());
    node = element();
kocienda's avatar
kocienda committed
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
    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;

477
  const QFontMetrics &fm = metrics( false ); // #### wrong for first-line!
darin's avatar
darin committed
478
  QString tekst(str->s + s->m_start, s->m_len);
kocienda's avatar
kocienda committed
479 480
  _x = s->m_x + (fm.boundingRect(tekst, pos)).right();
  if(pos)
darin's avatar
darin committed
481
      _x += fm.rightBearing( *(str->s + s->m_start + pos - 1 ) );
kocienda's avatar
kocienda committed
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499

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

kocienda's avatar
kocienda committed
502
    if(parent() && parent()->absolutePosition(xPos, yPos, false)) {
503 504
        xPos -= paddingLeft() + borderLeft();
        yPos -= borderTop() + paddingTop();
kocienda's avatar
kocienda committed
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
        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;
    }
}

535 536 537 538 539 540 541 542
int RenderText::rightmostPosition() const
{
    if (style()->whiteSpace() != NORMAL)
        return maxWidth();

    return 0;
}

kocienda's avatar
kocienda committed
543 544 545
void RenderText::printObject( QPainter *p, int /*x*/, int y, int /*w*/, int h,
                      int tx, int ty)
{
546
    int ow = style()->outlineWidth();
kocienda's avatar
kocienda committed
547 548 549 550 551
    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
552
    bool isPrinting = (p->device()->devType() == QInternal::Printer);
kocienda's avatar
kocienda committed
553 554 555 556 557 558 559 560 561
    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
562
        if (!isPrinting && (selectionState() != SelectionNone)) {
darin's avatar
darin committed
563
            if (selectionState() == SelectionInside) {
kocienda's avatar
kocienda committed
564 565 566
                //kdDebug(6040) << this << " SelectionInside -> 0 to end" << endl;
                startPos = 0;
                endPos = str->l;
darin's avatar
darin committed
567
            } else {
kocienda's avatar
kocienda committed
568 569 570 571 572 573 574 575 576 577 578 579 580
                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;
581 582 583
	QPtrList <QRect> linerects;
        linerects.setAutoDelete(true);
        linerects.append(new QRect());
kocienda's avatar
kocienda committed
584

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

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

589 590 591 592 593 594 595 596 597 598 599 600
#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
601 602 603 604
        // 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
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624

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

darin's avatar
darin committed
627
            if(_style->font() != p->font()) {
kocienda's avatar
kocienda committed
628
                p->setFont(_style->font());
darin's avatar
darin committed
629 630
		font = &_style->htmlFont();
	    }
darin's avatar
darin committed
631

632 633 634
#if APPLE_CHANGES
            if (drawDecorations)
#endif
kocienda's avatar
kocienda committed
635 636 637 638 639
            if((hasSpecialObjects()  &&
                (parent()->isInline() || pseudoStyle)) &&
               (!pseudoStyle || s->m_firstLine))
                s->printBoxDecorations(p, _style, this, tx, ty, si == 0, si == (int)m_lines.count()-1);

640

641 642 643 644
#if APPLE_CHANGES
            if (drawText) {
#endif

kocienda's avatar
kocienda committed
645 646 647
            if(_style->color() != p->pen().color())
                p->setPen(_style->color());

darin's avatar
darin committed
648 649 650
	    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
651 652 653 654 655 656 657

            if(d != TDNONE)
            {
                p->setPen(_style->textDecorationColor());
                s->printDecoration(p, this, tx, ty, d, si == 0, si == ( int ) m_lines.count()-1);
            }

658 659 660 661 662 663 664 665 666
#if APPLE_CHANGES
            } // drawText
#endif

#if APPLE_CHANGES
            if (drawSelectionBackground)
#endif
            if (!isPrinting && (selectionState() != SelectionNone))
            {
darin's avatar
darin committed
667 668 669 670 671 672 673
		int offset = s->m_start;
		int sPos = QMAX( startPos - offset, 0 );
		int ePos = QMIN( endPos - offset, s->m_len );
                //kdDebug(6040) << this << " printSelection with startPos=" << sPos << " endPos=" << ePos << endl;
		if ( sPos < ePos )
		    s->printSelection(font, this, p, _style, tx, ty, sPos, ePos);

kocienda's avatar
kocienda committed
674
            }
675 676 677 678

#if APPLE_CHANGES
            if (drawText)
#endif
679
            if(renderOutline) {
kocienda's avatar
kocienda committed
680 681
                if(outlinebox_y == s->m_y) {
                    if(minx > s->m_x)  minx = s->m_x;
682 683 684 685
                    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
686 687
                }
                else {
688 689 690
                    QRect *curLine = new QRect(minx, outlinebox_y, maxx-minx, m_lineHeight);
                    linerects.append(curLine);

kocienda's avatar
kocienda committed
691 692 693
                    outlinebox_y = s->m_y;
                    minx = s->m_x;
                    maxx = s->m_x+s->m_width;
694 695
                    //if (parent()->isInline() && si==0) maxx-=paddingLeft();
                    if (parent()->isInline() && si==int(m_lines.count())-1) maxx-=paddingRight();
kocienda's avatar
kocienda committed
696 697
                }
            }
698 699 700 701 702 703 704 705 706 707 708 709
#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
710

711 712 713 714
#if APPLE_CHANGES
        } // end of for loop
#endif

715 716 717 718 719 720 721
        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++)
                printTextOutline(p, tx, ty, *linerects.at(i-1), *linerects.at(i), *linerects.at(i+1));
	  }
kocienda's avatar
kocienda committed
722 723 724 725 726 727
    }
}

void RenderText::print( QPainter *p, int x, int y, int w, int h,
                      int tx, int ty)
{
728
    if (style()->visibility() != VISIBLE) return;
kocienda's avatar
kocienda committed
729 730 731

    int s = m_lines.count() - 1;
    if ( s < 0 ) return;
732 733 734 735

    // ### 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
736 737 738 739

    printObject(p, x, y, w, h, tx, ty);
}

740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
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;
761
    hasBreak = m_hasBreak;
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
    
    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) {
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
        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;
            }
        }
807 808 809
    }
}

kocienda's avatar
kocienda committed
810 811
void RenderText::calcMinMaxWidth()
{
812
    KHTMLAssert( !minMaxKnown() );
kocienda's avatar
kocienda committed
813 814

    // ### calc Min and Max width...
815
    m_minWidth = m_beginMinWidth = m_endMinWidth = 0;
kocienda's avatar
kocienda committed
816 817
    m_maxWidth = 0;

818 819 820
    if (isBR())
        return;
        
kocienda's avatar
kocienda committed
821 822
    int currMinWidth = 0;
    int currMaxWidth = 0;
823 824
    m_hasBreakableChar = m_hasBreak = m_hasBeginWS = m_hasEndWS = false;
    
kocienda's avatar
kocienda committed
825
    // ### not 100% correct for first-line
darin's avatar
darin committed
826
    const Font *f = htmlFont( false );
kocienda's avatar
kocienda committed
827
    int len = str->l;
828 829 830 831
    bool ignoringSpaces = false;
    bool isSpace = false;
    bool isPre = style()->whiteSpace() == PRE;
    bool firstWord = true;
kocienda's avatar
kocienda committed
832 833
    for(int i = 0; i < len; i++)
    {
834
        bool isNewline = false;
835 836 837
        // XXXdwh Wrong in the first stage.  Will stop mutating newlines
        // in a second stage.
        if (str->s[i] == '\n') {
838
            if (isPre) {
839
                m_hasBreak = true;
840 841
                isNewline = true;
            }
842 843 844 845 846 847 848
            else
                str->s[i] = ' ';
        }
        
        bool oldSpace = isSpace;
        isSpace = str->s[i].direction() == QChar::DirWS;
        
849
        if ((isSpace || isNewline) && i == 0)
850
            m_hasBeginWS = true;
851
        if ((isSpace || isNewline) && i == len-1)
852 853 854 855 856 857 858 859 860 861 862
            m_hasEndWS = true;
            
        if (!ignoringSpaces && !isPre && oldSpace && isSpace)
            ignoringSpaces = true;
        
        if (ignoringSpaces && !isSpace)
            ignoringSpaces = false;
            
        if (ignoringSpaces)
            continue;
        
kocienda's avatar
kocienda committed
863
        int wordlen = 0;
darin's avatar
darin committed
864
        while( i+wordlen < len && !(isBreakable( str->s, i+wordlen, str->l )) )
kocienda's avatar
kocienda committed
865
            wordlen++;
866
            
kocienda's avatar
kocienda committed
867 868
        if (wordlen)
        {
darin's avatar
darin committed
869
            int w = f->width(str->s, str->l, i, wordlen);
kocienda's avatar
kocienda committed
870 871
            currMinWidth += w;
            currMaxWidth += w;
872 873 874 875 876 877 878 879 880 881
            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
882
        }
883 884 885 886 887 888 889 890 891 892
        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
893 894 895 896 897 898
            {
                if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
                currMaxWidth = 0;
            }
            else
            {
darin's avatar
darin committed
899
                currMaxWidth += f->width( str->s, str->l, i + wordlen );
kocienda's avatar
kocienda committed
900 901 902
            }
        }
    }
903
    
kocienda's avatar
kocienda committed
904 905
    if(currMinWidth > m_minWidth) m_minWidth = currMinWidth;
    if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
906 907 908 909

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

kocienda's avatar
kocienda committed
910
    setMinMaxKnown();
911
    //kdDebug( 6040 ) << "Text::calcMinMaxWidth(): min = " << m_minWidth << " max = " << m_maxWidth << endl;
kocienda's avatar
kocienda committed
912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943
}

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()
{
944
    return style()->font();
kocienda's avatar
kocienda committed
945 946
}

darin's avatar
darin committed
947
void RenderText::setText(DOMStringImpl *text, bool force)
kocienda's avatar
kocienda committed
948
{
949
#if APPLE_CHANGES
950 951 952
    if (!text)
        return;
#endif
darin's avatar
darin committed
953
    if( !force && str == text ) return;
kocienda's avatar
kocienda committed
954 955
    if(str) str->deref();
    str = text;
darin's avatar
darin committed
956

darin's avatar
darin committed
957
    if ( str && style() ) {
darin's avatar
darin committed
958 959 960 961 962 963 964 965 966 967
        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
968
        str->ref();
darin's avatar
darin committed
969
    }
kocienda's avatar
kocienda committed
970 971 972

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

    setLayouted(false);
977
#ifdef BIDI_DEBUG
kocienda's avatar
kocienda committed
978
    QConstString cstr(str->s, str->l);
979
    kdDebug( 6040 ) << "RenderText::setText( " << cstr.string().length() << " ) '" << cstr.string() << "'" << endl;
kocienda's avatar
kocienda committed
980 981 982 983 984
#endif
}

int RenderText::height() const
{
985 986 987 988 989 990 991 992
    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
993 994 995
    return retval;
}

996
short RenderText::lineHeight( bool firstLine ) const
kocienda's avatar
kocienda committed
997
{
998 999 1000
    if ( firstLine )
 	return RenderObject::lineHeight( firstLine );

kocienda's avatar
kocienda committed
1001 1002 1003 1004 1005
    return m_lineHeight;
}

short RenderText::baselinePosition( bool firstLine ) const
{
1006 1007 1008
    const QFontMetrics &fm = metrics( firstLine );
    return fm.ascent() +
        ( lineHeight( firstLine ) - fm.height() ) / 2;
kocienda's avatar
kocienda committed
1009 1010
}

darin's avatar
darin committed
1011
void RenderText::position(int x, int y, int from, int len, int width, bool reverse, bool firstLine, int spaceAdd)
kocienda's avatar
kocienda committed
1012 1013
{
    // ### should not be needed!!!
1014 1015
    if (len == 0 || (str->l && len == 1 && *(str->s+from) == '\n'))
        return;
kocienda's avatar
kocienda committed
1016 1017 1018 1019 1020 1021 1022 1023 1024 1025

    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
1026
    if(from + len >= int(str->l) && parent()->isInline() && parent()->lastChild()==this)
kocienda's avatar
kocienda committed
1027 1028 1029
        width -= marginRight();

#ifdef DEBUG_LAYOUT
darin's avatar
darin committed
1030
    QChar *ch = str->s+from;
kocienda's avatar
kocienda committed
1031
    QConstString cstr(ch, len);
1032
    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
1033 1034
#endif

darin's avatar
darin committed
1035
    TextSlave *s = new (renderArena()) TextSlave(x, y, from, len,
kocienda's avatar
kocienda committed
1036
                                 baselinePosition( firstLine ),
1037
                                 width+spaceAdd, reverse, spaceAdd, firstLine);
kocienda's avatar
kocienda committed
1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049

    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
1050 1051
    const Font *f = htmlFont( firstLine );
    return width( from, len, f );
kocienda's avatar
kocienda committed
1052 1053
}

darin's avatar
darin committed
1054
unsigned int RenderText::width(unsigned int from, unsigned int len, const Font *f) const
kocienda's avatar
kocienda committed
1055 1056 1057 1058 1059
{
    if(!str->s || from > str->l ) return 0;
    if ( from + len > str->l ) len = str->l - from;

    int w;
darin's avatar
darin committed
1060
    if ( f == &style()->htmlFont() && from == 0 && len == str->l )
kocienda's avatar
kocienda committed
1061 1062
 	 w = m_maxWidth;
    else
darin's avatar
darin committed
1063
	w = f->width(str->s, str->l, from, len );
kocienda's avatar
kocienda committed
1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 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 1120 1121 1122 1123

    // ### add margins and support for RTL

    if(parent()->isInline())
    {
        if(from == 0 && parent()->firstChild() == static_cast<const RenderObject*>(this))
            w += borderLeft() + paddingLeft() + marginLeft();
        if(from + len == str->l &&
           parent()->lastChild() == static_cast<const RenderObject*>(this))
            w += borderRight() + paddingRight() +marginRight();
    }

    //kdDebug( 6040 ) << "RenderText::width(" << from << ", " << len << ") = " << w << endl;
    return w;
}

short RenderText::width() const
{
    int w;
    int minx = 100000000;
    int maxx = 0;
    // slooow
    for(unsigned int si = 0; si < m_lines.count(); si++) {
        TextSlave* s = m_lines[si];
        if(s->m_x < minx)
            minx = s->m_x;
        if(s->m_x + s->m_width > maxx)
            maxx = s->m_x + s->m_width;
    }

    w = QMAX(0, maxx-minx);

    if(parent()->isInline())
    {
        if(parent()->firstChild() == static_cast<const RenderObject*>(this))
            w += borderLeft() + paddingLeft();
        if(parent()->lastChild() == static_cast<const RenderObject*>(this))
            w += borderRight() + paddingRight();
    }

    return w;
}

void RenderText::repaint()
{
    RenderObject *cb = containingBlock();
    if(cb != this)
        cb->repaint();
}

bool RenderText::isFixedWidthFont() const
{
    return QFontInfo(style()->font()).fixedPitch();
}

short RenderText::verticalPositionHint( bool firstLine ) const
{
    return parent()->verticalPositionHint( firstLine );
}

1124
const QFontMetrics &RenderText::metrics(bool firstLine) const
kocienda's avatar
kocienda committed
1125 1126 1127 1128
{
    if( firstLine && hasFirstLine() ) {
	RenderStyle *pseudoStyle  = style()->getPseudoStyle(RenderStyle::FIRST_LINE);
	if ( pseudoStyle )
1129
	    return pseudoStyle->fontMetrics();
kocienda's avatar
kocienda committed
1130
    }
1131 1132 1133
    return style()->fontMetrics();
}

darin's avatar
darin committed
1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146
const Font *RenderText::htmlFont(bool firstLine) const
{
    const Font *f = 0;
    if( firstLine && hasFirstLine() ) {
	RenderStyle *pseudoStyle  = style()->getPseudoStyle(RenderStyle::FIRST_LINE);
	if ( pseudoStyle )
	    f = &pseudoStyle->htmlFont();
    } else {
	f = &style()->htmlFont();
    }
    return f;
}

1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225
void RenderText::printTextOutline(QPainter *p, int tx, int ty, const QRect &lastline, const QRect &thisline, const QRect &nextline)
{
  int ow = style()->outlineWidth();
  EBorderStyle os = style()->outlineStyle();
  QColor oc = style()->outlineColor();

  int t = ty + thisline.top();
  int l = tx + thisline.left();
  int b = ty + thisline.bottom() + 1;
  int r = tx + thisline.right() + 1;

  // left edge
  drawBorder(p,
	     l - ow,
	     t - (lastline.isEmpty() || thisline.left() < lastline.left() || lastline.right() <= thisline.left() ? ow : 0),
	     l,
	     b + (nextline.isEmpty() || thisline.left() <= nextline.left() || nextline.right() <= thisline.left() ? ow : 0),
	     BSLeft,
	     oc, style()->color(), os,
	     (lastline.isEmpty() || thisline.left() < lastline.left() || lastline.right() <= thisline.left() ? ow : -ow),
	     (nextline.isEmpty() || thisline.left() <= nextline