Reviewed by Darin.
[WebKit-https.git] / WebCore / khtml / rendering / render_text.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * (C) 1999 Lars Knoll (knoll@kde.org)
5  * (C) 2000 Dirk Mueller (mueller@kde.org)
6  * Copyright (C) 2004 Apple Computer, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  *
23  */
24 //#define DEBUG_LAYOUT
25 //#define BIDI_DEBUG
26
27 #include "rendering/render_canvas.h"
28 #include "rendering/render_object.h"
29 #include "rendering/render_text.h"
30 #include "rendering/break_lines.h"
31 #include "dom/dom2_range.h"
32 #include "xml/dom_nodeimpl.h"
33 #include "xml/dom_docimpl.h"
34 #include "xml/dom_position.h"
35 #include "render_arena.h"
36
37 #include "misc/loader.h"
38
39 #include "khtml_part.h"
40
41 #include <qpainter.h>
42 #include <kdebug.h>
43 #include <assert.h>
44
45 using namespace khtml;
46 using namespace DOM;
47
48 #ifndef NDEBUG
49 static bool inInlineTextBoxDetach;
50 #endif
51
52 void InlineTextBox::detach(RenderArena* renderArena)
53 {
54 #ifndef NDEBUG
55     inInlineTextBoxDetach = true;
56 #endif
57     delete this;
58 #ifndef NDEBUG
59     inInlineTextBoxDetach = false;
60 #endif
61     
62     // Recover the size left there for us by operator delete and free the memory.
63     renderArena->free(*(size_t *)this, this);
64 }
65
66 void* InlineTextBox::operator new(size_t sz, RenderArena* renderArena) throw()
67 {
68     return renderArena->allocate(sz);
69 }
70
71 void InlineTextBox::operator delete(void* ptr, size_t sz)
72 {
73     assert(inInlineTextBoxDetach);
74     
75     // Stash size where detach can find it.
76     *(size_t *)ptr = sz;
77 }
78
79 void InlineTextBox::deleteLine(RenderArena* arena)
80 {
81     static_cast<RenderText*>(m_object)->removeTextBox(this);
82     detach(arena);
83 }
84
85 void InlineTextBox::extractLine()
86 {
87     if (m_extracted)
88         return;
89
90     static_cast<RenderText*>(m_object)->extractTextBox(this);
91 }
92
93 void InlineTextBox::attachLine()
94 {
95     if (!m_extracted)
96         return;
97     
98     static_cast<RenderText*>(m_object)->attachTextBox(this);
99 }
100
101 int InlineTextBox::placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool& foundBox)
102 {
103     if (foundBox) {
104         m_truncation = cFullTruncation;
105         return -1;
106     }
107
108     int ellipsisX = ltr ? blockEdge - ellipsisWidth : blockEdge + ellipsisWidth;
109     
110     // For LTR, if the left edge of the ellipsis is to the left of our text run, then we are the run that will get truncated.
111     if (ltr) {
112         if (ellipsisX <= m_x) {
113             // Too far.  Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
114             m_truncation = cFullTruncation;
115             foundBox = true;
116             return -1;
117         }
118
119         if (ellipsisX < m_x + m_width) {
120             if (m_reversed)
121                 return -1; // FIXME: Support LTR truncation when the last run is RTL someday.
122
123             foundBox = true;
124
125             int offset = offsetForPosition(ellipsisX, false);
126             if (offset == 0) {
127                 // No characters should be rendered.  Set ourselves to full truncation and place the ellipsis at the min of our start
128                 // and the ellipsis edge.
129                 m_truncation = cFullTruncation;
130                 return kMin(ellipsisX, m_x);
131             }
132             
133             // Set the truncation index on the text run.  The ellipsis needs to be placed just after the last visible character.
134             m_truncation = offset + m_start;
135             return m_x + static_cast<RenderText*>(m_object)->width(m_start, offset, m_firstLine);
136         }
137     }
138     else {
139         // FIXME: Support RTL truncation someday, including both modes (when the leftmost run on the line is either RTL or LTR)
140     }
141     return -1;
142 }
143
144 void InlineTextBox::paintSelection(const Font *f, RenderText *text, QPainter *p, RenderStyle* style, int tx, int ty, int startPos, int endPos)
145 {
146     int offset = m_start;
147     int sPos = kMax(startPos - offset, 0);
148     int ePos = kMin(endPos - offset, (int)m_len);
149
150     if (sPos >= ePos)
151         return;
152
153     p->save();
154 #if APPLE_CHANGES
155     // Macintosh-style text highlighting is to draw with a particular background color, not invert.
156     QColor textColor = style->color();
157     QColor c = p->selectedTextBackgroundColor();
158     
159     // if text color and selection background color are identical, invert background color.
160     if (textColor == c)
161         c = QColor(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
162
163     RenderStyle* pseudoStyle = object()->getPseudoStyle(RenderStyle::SELECTION);
164     if (pseudoStyle && pseudoStyle->backgroundColor().isValid())
165         c = pseudoStyle->backgroundColor();
166     p->setPen(c); // Don't draw text at all!
167     
168 #else
169     QColor c = style->color();
170     p->setPen(QColor(0xff-c.red(),0xff-c.green(),0xff-c.blue()));
171     ty + m_baseline;
172 #endif
173     
174 #if APPLE_CHANGES
175     // Do the calculations to draw selections as tall as the line.
176     // Use the bottom of the line above as the y position (if there is one, 
177     // otherwise use the top of this renderer's line) and the height of the line as the height. 
178     // This mimics Cocoa.
179     RenderBlock *cb = object()->containingBlock();
180
181     int y = 0;
182     if (root()->prevRootBox())
183         y = root()->prevRootBox()->bottomOverflow();
184     else
185         y = root()->topOverflow();
186
187     int h = root()->bottomOverflow() - y;
188
189     int x = m_x;
190     int minX = x;
191     int maxX = x;
192
193     // Extend selection to the start of the line if:
194     // 1. The starting point of the selection is at or beyond the start of the text box; and
195     // 2. This box is the first box on the line; and
196     // 3. There is a another line before this one (first lines have special behavior;
197     //    the selection never extends on the first line); and 
198     // 4. The last leaf renderer on the previous line is selected.
199     RenderObject *prevLineLastLeaf = 0;
200     if (root()->prevRootBox() && root()->prevRootBox()->lastLeafChild())
201         prevLineLastLeaf = root()->prevRootBox()->lastLeafChild()->object();
202     if (startPos <= m_start && root()->firstLeafChild() == this && prevLineLastLeaf && 
203         prevLineLastLeaf->selectionState() != RenderObject::SelectionNone)
204         minX = kMax(cb->leftOffset(y), cb->leftOffset(root()->blockHeight()));
205         
206     // Extend selection to the end of the line if:
207     // 1. The ending point of the selection is at or beyond the end of the text box; and
208     // 2. There is a another line after this one (last lines have special behavior;
209     //    the selection never extends on the last line); and 
210     // 3. The last leaf renderer of the root box is this box.
211     if (endPos >= m_start + m_len && root()->nextRootBox() && root()->lastLeafChild() == this)
212         maxX = kMin(cb->rightOffset(y), cb->rightOffset(root()->blockHeight()));
213     
214     f->drawHighlightForText(p, x + tx, minX + tx, maxX + tx, y + ty, h, text->str->s, text->str->l, m_start, m_len,
215                 m_toAdd, m_reversed ? QPainter::RTL : QPainter::LTR, style->visuallyOrdered(), sPos, ePos, c);
216 #else
217     f->drawHighlightForText(p, m_x + tx, m_y + ty, text->str->s, text->str->l, m_start, m_len,
218                 m_toAdd, m_reversed ? QPainter::RTL : QPainter::LTR, sPos, ePos, c);
219 #endif
220     p->restore();
221 }
222
223 #if APPLE_CHANGES
224 void InlineTextBox::paintMarkedTextBackground(const Font *f, RenderText *text, QPainter *p, RenderStyle* style, int tx, int ty, int startPos, int endPos)
225 {
226     int offset = m_start;
227     int sPos = kMax(startPos - offset, 0);
228     int ePos = kMin(endPos - offset, (int)m_len);
229
230     if (sPos >= ePos)
231         return;
232
233     p->save();
234
235     QColor c = QColor(225, 221, 85);
236     
237     p->setPen(c); // Don't draw text at all!
238     
239     // Do the calculations to draw marked text background as tall as the line.
240     // Use the bottom of the line above as the y position (if there is one, 
241     // otherwise use the top of this renderer's line) and the height of the line as the height. 
242     // This mimics Cocoa.
243     RenderBlock *cb = object()->containingBlock();
244
245     int y = 0;
246     if (root()->prevRootBox())
247         y = root()->prevRootBox()->bottomOverflow();
248     else
249         y = root()->topOverflow();
250
251     int h = root()->bottomOverflow() - y;
252
253     int x = m_x;
254     int minX = x;
255     int maxX = x;
256
257     // Extend selection to the start of the line if:
258     // 1. The starting point of the selection is at or beyond the start of the text box; and
259     // 2. This box is the first box on the line; and
260     // 3. There is a another line before this one (first lines have special behavior;
261     //    the selection never extends on the first line); and 
262     // 4. The last leaf renderer on the previous line is selected.
263     RenderObject *prevLineLastLeaf = root()->prevRootBox() ? root()->prevRootBox()->lastLeafChild()->object() : 0;
264     if (startPos <= m_start && root()->firstLeafChild() == this && root()->prevRootBox() && prevLineLastLeaf && 
265         prevLineLastLeaf->selectionState() != RenderObject::SelectionNone)
266         minX = kMax(cb->leftOffset(y), cb->leftOffset(root()->blockHeight()));
267         
268     // Extend selection to the end of the line if:
269     // 1. The ending point of the selection is at or beyond the end of the text box; and
270     // 2. There is a another line after this one (last lines have special behavior;
271     //    the selection never extends on the last line); and 
272     // 3. The last leaf renderer of the root box is this box.
273     if (endPos >= m_start + m_len && root()->nextRootBox() && root()->lastLeafChild() == this)
274         maxX = kMin(cb->rightOffset(y), cb->rightOffset(root()->blockHeight()));
275     
276     f->drawHighlightForText(p, x + tx, minX + tx, maxX + tx, y + ty, h, text->str->s, text->str->l, m_start, m_len,
277                 m_toAdd, m_reversed ? QPainter::RTL : QPainter::LTR, style->visuallyOrdered(), sPos, ePos, c);
278     p->restore();
279 }
280 #endif
281
282 #ifdef APPLE_CHANGES
283 void InlineTextBox::paintDecoration( QPainter *pt, int _tx, int _ty, int deco)
284 {
285     _tx += m_x;
286     _ty += m_y;
287
288     if (m_truncation == cFullTruncation)
289         return;
290     
291     int width = (m_truncation == cNoTruncation) ? 
292                 m_width : static_cast<RenderText*>(m_object)->width(m_start, m_truncation - m_start, m_firstLine);
293     
294     // Get the text decoration colors.
295     QColor underline, overline, linethrough;
296     object()->getTextDecorationColors(deco, underline, overline, linethrough, true);
297     
298     // Use a special function for underlines to get the positioning exactly right.
299     if (deco & UNDERLINE) {
300         pt->setPen(underline);
301         pt->drawLineForText(_tx, _ty, m_baseline, width);
302     }
303     if (deco & OVERLINE) {
304         pt->setPen(overline);
305         pt->drawLineForText(_tx, _ty, 0, width);
306     }
307     if (deco & LINE_THROUGH) {
308         pt->setPen(linethrough);
309         pt->drawLineForText(_tx, _ty, 2*m_baseline/3, width);
310     }
311 }
312 #else
313 void InlineTextBox::paintDecoration( QPainter *pt, int _tx, int _ty, int decoration)
314 {
315     _tx += m_x;
316     _ty += m_y;
317
318     int width = m_width - 1;
319
320     QColor underline, overline, linethrough;
321     object()->getTextDecorationColors(decoration, underline, overline, linethrough, true);
322     
323     int underlineOffset = ( pt->fontMetrics().height() + m_baseline ) / 2;
324     if(underlineOffset <= m_baseline) underlineOffset = m_baseline+1;
325
326     if(deco & UNDERLINE){
327         pt->setPen(underline);
328         pt->drawLine(_tx, _ty + underlineOffset, _tx + width, _ty + underlineOffset );
329     }
330     if (deco & OVERLINE) {
331         pt->setPen(overline);
332         pt->drawLine(_tx, _ty, _tx + width, _ty );
333     }
334     if(deco & LINE_THROUGH) {
335         pt->setPen(linethrough);
336         pt->drawLine(_tx, _ty + 2*m_baseline/3, _tx + width, _ty + 2*m_baseline/3 );
337     }
338     // NO! Do NOT add BLINK! It is the most annouing feature of Netscape, and IE has a reason not to
339     // support it. Lars
340 }
341 #endif
342
343 void InlineTextBox::paintMarker(QPainter *pt, int _tx, int _ty, DocumentMarker marker)
344 {
345     _tx += m_x;
346     _ty += m_y;
347
348     if (m_truncation == cFullTruncation)
349         return;
350     
351     int start = 0;                  // start of line to draw, relative to _tx
352     int width = m_width;            // how much line to draw
353     bool useWholeWidth = true;
354     ulong paintStart = m_start;
355     ulong paintEnd = end()+1;      // end points at the last char, not past it
356     if (paintStart != marker.startOffset) {
357         paintStart = marker.startOffset;
358         useWholeWidth = false;
359         start = static_cast<RenderText*>(m_object)->width(m_start, paintStart - m_start, m_firstLine);
360     }
361     if (paintEnd != marker.endOffset) {      // end points at the last char, not past it
362         paintEnd = kMin(paintEnd, marker.endOffset);
363         useWholeWidth = false;
364     }
365     if (m_truncation != cNoTruncation) {
366         paintEnd = kMin(paintEnd, (ulong)m_truncation);
367         useWholeWidth = false;
368     }
369     if (!useWholeWidth) {
370         width = static_cast<RenderText*>(m_object)->width(paintStart, paintEnd - paintStart, m_firstLine);
371     }
372
373     // AppKit uses a more complicated formula to figure the offset from the baseline for the misspelling
374     // underline.  Using "+2" made the marker draw outside the selection rect in Times-16, using "+1"
375     // leaves it a pixel too high with Times-24.  The former is the lesser evil.
376     int underlineOffset = m_baseline + 1;
377     pt->drawLineForMisspelling(_tx + start, _ty + underlineOffset, width);
378 }
379
380 long InlineTextBox::caretMinOffset() const
381 {
382     return m_start;
383 }
384
385 long InlineTextBox::caretMaxOffset() const
386 {
387     return m_start + m_len;
388 }
389
390 unsigned long InlineTextBox::caretMaxRenderedOffset() const
391 {
392     return m_start + m_len;
393 }
394
395 #define LOCAL_WIDTH_BUF_SIZE    1024
396
397 int InlineTextBox::offsetForPosition(int _x, bool includePartialGlyphs)
398 {
399     RenderText* text = static_cast<RenderText*>(m_object);
400     const Font* f = text->htmlFont(m_firstLine);
401     return f->checkSelectionPoint(text->str->s, text->str->l, m_start, m_len, m_toAdd, _x - m_x, m_reversed, includePartialGlyphs);
402 }
403
404 // -------------------------------------------------------------------------------------
405
406 RenderText::RenderText(DOM::NodeImpl* node, DOMStringImpl *_str)
407     : RenderObject(node), m_linesDirty(false)
408 {
409     // init RenderObject attributes
410     setRenderText();   // our object inherits from RenderText
411
412     m_minWidth = -1;
413     m_maxWidth = -1;
414
415 #ifdef APPLE_CHANGES
416     m_monospaceCharacterWidth = 0;
417     m_allAsciiChecked = false;
418     m_allAscii = false;
419 #endif
420
421     str = _str;
422     if (str) {
423         str = str->replace('\\', backslashAsCurrencySymbol());
424         str->ref();
425     }
426     KHTMLAssert(!str || !str->l || str->s);
427
428     m_firstTextBox = m_lastTextBox = 0;
429     
430     m_selectionState = SelectionNone;
431
432 #ifdef DEBUG_LAYOUT
433     QConstString cstr(str->s, str->l);
434     kdDebug( 6040 ) << "RenderText ctr( "<< cstr.string().length() << " )  '" << cstr.string() << "'" << endl;
435 #endif
436 }
437
438 void RenderText::setStyle(RenderStyle *_style)
439 {
440     if ( style() != _style ) {
441         bool needToTransformText = (!style() && _style->textTransform() != TTNONE) ||
442                                    (style() && style()->textTransform() != _style->textTransform());
443
444         RenderObject::setStyle( _style );
445
446         if (needToTransformText) {
447             DOM::DOMStringImpl* textToTransform = originalString();
448             if (textToTransform)
449                 setText(textToTransform, true);
450         }
451 #if APPLE_CHANGES
452         // setText also calls cacheWidths(), so there is no need to call it again in that case.
453         else
454             cacheWidths();
455 #endif
456     }
457 }
458
459 RenderText::~RenderText()
460 {
461     if(str) str->deref();
462 }
463
464 void RenderText::detach()
465 {
466     if (!documentBeingDestroyed()) {
467         if (firstTextBox())
468             for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
469                 box->remove();
470         else if (parent() && isBR())
471             parent()->dirtyLinesFromChangedChild(this);
472     }
473     deleteTextBoxes();
474     RenderObject::detach();
475 }
476
477 void RenderText::extractTextBox(InlineTextBox* box)
478 {
479     m_lastTextBox = box->prevTextBox();
480     if (box == m_firstTextBox)
481         m_firstTextBox = 0;
482     if (box->prevTextBox())
483         box->prevTextBox()->setNextLineBox(0);
484     box->setPreviousLineBox(0);
485     for (InlineRunBox* curr = box; curr; curr = curr->nextLineBox())
486         curr->setExtracted();
487 }
488
489 void RenderText::attachTextBox(InlineTextBox* box)
490 {
491     if (m_lastTextBox) {
492         m_lastTextBox->setNextLineBox(box);
493         box->setPreviousLineBox(m_lastTextBox);
494     }
495     else
496         m_firstTextBox = box;
497     InlineTextBox* last = box;
498     for (InlineTextBox* curr = box; curr; curr = curr->nextTextBox()) {
499         curr->setExtracted(false);
500         last = curr;
501     }
502     m_lastTextBox = last;
503 }
504
505 void RenderText::removeTextBox(InlineTextBox* box)
506 {
507     if (box == m_firstTextBox)
508         m_firstTextBox = box->nextTextBox();
509     if (box == m_lastTextBox)
510         m_lastTextBox = box->prevTextBox();
511     if (box->nextTextBox())
512         box->nextTextBox()->setPreviousLineBox(box->prevTextBox());
513     if (box->prevTextBox())
514         box->prevTextBox()->setNextLineBox(box->nextTextBox());
515 }
516
517 void RenderText::deleteTextBoxes()
518 {
519     if (firstTextBox()) {
520         RenderArena* arena = renderArena();
521         InlineTextBox *curr = firstTextBox(), *next = 0;
522         while (curr) {
523             next = curr->nextTextBox();
524             curr->detach(arena);
525             curr = next;
526         }
527         m_firstTextBox = m_lastTextBox = 0;
528     }
529 }
530
531 bool RenderText::isTextFragment() const
532 {
533     return false;
534 }
535
536 DOM::DOMStringImpl* RenderText::originalString() const
537 {
538     return element() ? element()->string() : 0;
539 }
540
541 void RenderText::absoluteRects(QValueList<QRect>& rects, int _tx, int _ty)
542 {
543     for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
544         rects.append(QRect(_tx + box->xPos(), 
545                            _ty + box->yPos(), 
546                            box->width(), 
547                            box->height()));
548 }
549
550 InlineTextBox* RenderText::findNextInlineTextBox(int offset, int &pos)
551 {
552     // The text runs point to parts of the rendertext's str string
553     // (they don't include '\n')
554     // Find the text run that includes the character at @p offset
555     // and return pos, which is the position of the char in the run.
556
557     if (!m_firstTextBox)
558         return 0;
559     
560     InlineTextBox* s = m_firstTextBox;
561     int off = s->m_len;
562     while (offset > off && s->nextTextBox())
563     {
564         s = s->nextTextBox();
565         off = s->m_start + s->m_len;
566     }
567     // we are now in the correct text run
568     pos = (offset > off ? s->m_len : s->m_len - (off - offset) );
569     return s;
570 }
571
572 bool RenderText::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty,
573                              HitTestAction hitTestAction, bool inside)
574 {
575     assert(parent());
576
577     for (InlineTextBox *s = m_firstTextBox; s; s = s->nextTextBox()) {
578         if((_y >=_ty + s->m_y) && (_y < _ty + s->m_y + s->height()) &&
579            (_x >= _tx + s->m_x) && (_x <_tx + s->m_x + s->m_width) ) {
580             inside = true;
581             break;
582         }
583     }
584
585     if (inside && element()) {
586         if (info.innerNode() && info.innerNode()->renderer() && 
587             !info.innerNode()->renderer()->isInline()) {
588             // Within the same layer, inlines are ALWAYS fully above blocks.  Change inner node.
589             info.setInnerNode(element());
590             
591             // Clear everything else.
592             info.setInnerNonSharedNode(0);
593             info.setURLElement(0);
594         }
595         
596         if (!info.innerNode())
597             info.setInnerNode(element());
598
599         if (!info.innerNonSharedNode())
600             info.setInnerNonSharedNode(element());
601     }
602
603     return inside;
604 }
605
606 Position RenderText::positionForCoordinates(int _x, int _y)
607 {
608     if (!firstTextBox() || stringLength() == 0)
609         return Position(element(), 0);
610
611     int absx, absy;
612     containingBlock()->absolutePosition(absx, absy);
613
614     if (firstTextBox() && _y < absy + firstTextBox()->root()->bottomOverflow() && _x < absx + firstTextBox()->m_x) {
615         // at the y coordinate of the first line or above
616         // and the x coordinate is to the left than the first text box left edge
617         return Position(element(), firstTextBox()->m_start);
618     }
619
620     if (lastTextBox() && _y >= absy + lastTextBox()->root()->topOverflow() && _x >= absx + lastTextBox()->m_x + lastTextBox()->m_width) {
621         // at the y coordinate of the last line or below
622         // and the x coordinate is to the right than the last text box right edge
623         return Position(element(), lastTextBox()->m_start + lastTextBox()->m_len);
624     }
625
626     for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) {
627         if (_y >= absy + box->root()->topOverflow() && _y < absy + box->root()->bottomOverflow()) {
628             if (_x < absx + box->m_x + box->m_width) {
629                 // and the x coordinate is to the left of the right edge of this box
630                 // check to see if position goes in this box
631                 int offset = box->offsetForPosition(_x - absx);
632                 if (offset != -1) {
633                     return Position(element(), offset + box->m_start);
634                 }
635             }
636             else if (!box->prevOnLine() && _x < absx + box->m_x)
637                 // box is first on line
638                 // and the x coordinate is to the left than the first text box left edge
639                 return Position(element(), box->m_start);
640             else if (!box->nextOnLine() && _x >= absx + box->m_x + box->m_width)
641                 // box is last on line
642                 // and the x coordinate is to the right than the last text box right edge
643                 return Position(element(), box->m_start + box->m_len);
644         }
645     }
646     
647     return Position(element(), 0);
648 }
649
650 void RenderText::caretPos(int offset, bool override, int &_x, int &_y, int &width, int &height)
651 {
652     if (!firstTextBox() || stringLength() == 0) {
653         _x = _y = height = -1;
654         return;
655     }
656
657     // Find the text box for the given offset
658     InlineTextBox *box = 0;
659     for (box = firstTextBox(); box; box = box->nextTextBox()) {
660         if (offset <= box->m_start + box->m_len)
661             break;
662     }
663     
664     if (!box) {
665         _x = _y = height = -1;
666         return;
667     }
668
669     height = box->root()->bottomOverflow() - box->root()->topOverflow();
670     _y = box->root()->topOverflow();
671
672     const QFontMetrics &fm = metrics(box->isFirstLineStyle());
673     QString string(str->s + box->m_start, box->m_len);
674     long pos = offset - box->m_start; // the number of characters we are into the string
675     _x = box->m_x + (fm.boundingRect(string, pos)).right();
676
677 #if 0
678     // EDIT FIXME
679     if (pos)
680         _x += fm.rightBearing(*(str->s + box->m_start + offset));
681 #endif
682
683     int absx, absy;
684     absolutePosition(absx,absy);
685     _x += absx;
686     _y += absy;
687 }
688
689 void RenderText::posOfChar(int chr, int &x, int &y)
690 {
691     absolutePosition( x, y, false );
692
693     //if( chr > (int) str->l )
694     //chr = str->l;
695
696     int pos;
697     InlineTextBox * s = findNextInlineTextBox( chr, pos );
698
699     if ( s )
700     {
701         // s is the line containing the character
702         x += s->m_x; // this is the x of the beginning of the line, but it's good enough for now
703         y += s->m_y;
704     }
705 }
706
707 static int
708 simpleDifferenceBetweenColors(QColor c1, QColor c2)
709 {
710     // a distance could be computed by squaring the differences between components, but
711     // this is faster and so far seems good enough for our purposes.
712     return abs(c1.red() - c2.red()) + abs(c1.green() - c2.green()) + abs(c1.blue() - c2.blue());
713 }
714
715 static QColor 
716 correctedTextColor(QColor textColor, QColor backgroundColor) 
717 {
718     // Adjust the text color if it is too close to the background color,
719     // by darkening or lightening it to move it further away.
720     
721     int d = simpleDifferenceBetweenColors(textColor, backgroundColor);
722     // semi-arbitrarily chose 255 value here after a few tests; 
723     if (d > 255) {
724         return textColor;
725     }
726     
727     int distanceFromWhite = simpleDifferenceBetweenColors(textColor, Qt::white);
728     int distanceFromBlack = simpleDifferenceBetweenColors(textColor, Qt::black);
729
730     if (distanceFromWhite < distanceFromBlack) {
731         return textColor.dark();
732     }
733     
734     return textColor.light();
735 }
736
737 void RenderText::paint(PaintInfo& i, int tx, int ty)
738 {
739     if (i.phase != PaintActionForeground && i.phase != PaintActionSelection)
740         return;
741     
742     if (!shouldPaintWithinRoot(i))
743         return;
744         
745     if (style()->visibility() != VISIBLE || !firstTextBox())
746         return;
747     
748     if (ty + firstTextBox()->yPos() > i.r.y() + i.r.height()) 
749         return;
750
751     if (ty + lastTextBox()->yPos() + lastTextBox()->height() < i.r.y()) 
752         return;
753     
754     QPainter* p = i.p;
755     RenderStyle* pseudoStyle = style(true);
756     if (pseudoStyle == style()) pseudoStyle = 0;
757     int d = style()->textDecorationsInEffect();
758     bool isPrinting = (p->device()->devType() == QInternal::Printer);
759     
760     // Walk forward until we hit the first line that needs to be painted.
761     InlineTextBox* s = firstTextBox();
762     for (; s && !s->checkVerticalPoint(i.r.y(), ty, i.r.height()); s = s->nextTextBox());
763     if (!s) return;
764     
765     // Now calculate startPos and endPos, for painting selection.
766     // We paint selection while endPos > 0
767     int endPos, startPos;
768     if (!isPrinting && (selectionState() != SelectionNone)) {
769         if (selectionState() == SelectionInside) {
770             //kdDebug(6040) << this << " SelectionInside -> 0 to end" << endl;
771             startPos = 0;
772             endPos = str->l;
773         } else {
774             selectionStartEnd(startPos, endPos);
775             if(selectionState() == SelectionStart)
776                 endPos = str->l;
777             else if(selectionState() == SelectionEnd)
778                 startPos = 0;
779         }
780         //kdDebug(6040) << this << " Selection from " << startPos << " to " << endPos << endl;
781     }
782
783     const Font *font = &style()->htmlFont();
784
785 #if APPLE_CHANGES
786     // We will iterate over the text boxes in two passes. First we
787     // paint all the backgrounds, then the foregrounds.
788
789     InlineTextBox* startBox = s;
790
791     // Pass 1: paint backgrounds for slection or marked text, if we have any
792
793     bool haveSelection = startPos != endPos && !isPrinting && selectionState() != SelectionNone;
794     if (!haveSelection && i.phase == PaintActionSelection) {
795         // When only painting the selection, don't bother to paint if there is none.
796         return;
797     }
798
799     Range markedTextRange = KWQ(document()->part())->markedRange();
800     bool haveMarkedText = markedTextRange.handle() != 0 && markedTextRange.startContainer() == node();
801
802     if ((haveSelection || haveMarkedText) && i.phase != PaintActionSelection && !isPrinting) {
803         s = startBox;
804
805         // run until we find a text box that is outside the range, then we know we can stop
806         do {
807             if (s->m_truncation == cFullTruncation)
808                 continue;
809
810             RenderStyle* _style = pseudoStyle && s->m_firstLine ? pseudoStyle : style();
811
812             if (_style->font() != p->font())
813                 p->setFont(_style->font());
814
815             font = &_style->htmlFont(); // Always update, since smallCaps is not stored in the QFont.
816
817             if (haveMarkedText)
818                 s->paintMarkedTextBackground(font, this, p, _style, tx, ty, markedTextRange.startOffset(), markedTextRange.endOffset());
819
820             if (haveSelection)
821                 s->paintSelection(font, this, p, _style, tx, ty, startPos, endPos);
822
823         } while (((s = s->nextTextBox()) != 0) && s->checkVerticalPoint(i.r.y(), ty, i.r.height()));
824     }
825
826
827     // Pass 2: now paint the foreground, including text and decorations
828
829     QValueList<DocumentMarker> markers = document()->markersForNode(node());
830     QValueListIterator <DocumentMarker> markerIt = markers.begin();
831
832     s = startBox;
833
834     // run until we find a text box that is outside the range, then we know we can stop
835     do {
836         if (isPrinting) {
837             if (ty+s->m_y+s->height() > i.r.y() + i.r.height()) {
838                 RenderCanvas* canvasObj = canvas();
839                 if (ty+s->m_y < canvasObj->truncatedAt())
840                     canvasObj->setBestTruncatedAt(ty+s->m_y, this);
841                 // Let's stop here.
842                 break;
843             }
844         }
845
846         if (s->m_truncation == cFullTruncation)
847             continue;
848         
849         RenderStyle* _style = pseudoStyle && s->m_firstLine ? pseudoStyle : style();
850
851         if (_style->font() != p->font())
852             p->setFont(_style->font());
853
854         font = &_style->htmlFont(); // Always update, since smallCaps is not stored in the QFont.
855
856         QColor textColor = _style->color();
857         if (_style->shouldCorrectTextColor()) {
858             textColor = correctedTextColor(textColor, _style->backgroundColor());
859         }
860
861         if(textColor != p->pen().color())
862             p->setPen(textColor);
863
864         // Set a text shadow if we have one.
865         // FIXME: Support multiple shadow effects.  Need more from the CG API before
866         // we can do this.
867         bool setShadow = false;
868         if (_style->textShadow()) {
869             p->setShadow(_style->textShadow()->x, _style->textShadow()->y,
870                          _style->textShadow()->blur, _style->textShadow()->color);
871             setShadow = true;
872         }
873         
874         if (s->m_len > 0) {
875             bool paintSelectedTextOnly = (i.phase == PaintActionSelection);
876             bool paintSelectedTextSeparately = false; // Whether or not we have to do multiple paints.  Only
877             // necessary when a custom ::selection foreground color is applied.
878             QColor selectionColor = p->pen().color();
879             ShadowData* selectionTextShadow = 0;
880             if (haveSelection) {
881                 RenderStyle* pseudoStyle = getPseudoStyle(RenderStyle::SELECTION);
882                 if (pseudoStyle) {
883                     if (pseudoStyle->color() != selectionColor || pseudoStyle->textShadow()) {
884                         if (!paintSelectedTextOnly)
885                             paintSelectedTextSeparately = true;
886                         if (pseudoStyle->color() != selectionColor)
887                             selectionColor = pseudoStyle->color();
888                         if (pseudoStyle->textShadow())
889                             selectionTextShadow = pseudoStyle->textShadow();
890                     }
891                 }
892             }
893
894             if (!paintSelectedTextOnly && !paintSelectedTextSeparately) {
895                 // paint all the text
896
897                 // FIXME: Handle RTL direction, handle reversed strings.  For now truncation can only be turned on
898                 // for non-reversed LTR strings.
899                 int endPoint = s->m_len;
900                 if (s->m_truncation != cNoTruncation)
901                     endPoint = s->m_truncation - s->m_start;
902                 font->drawText(p, s->m_x + tx, s->m_y + ty + s->m_baseline,
903                                str->s, str->l, s->m_start, endPoint,
904                                s->m_toAdd, s->m_reversed ? QPainter::RTL : QPainter::LTR, style()->visuallyOrdered());
905             } else {
906                 int offset = s->m_start;
907                 int sPos = QMAX( startPos - offset, 0 );
908                 int ePos = QMIN( endPos - offset, s->m_len );
909                 if (paintSelectedTextSeparately) {
910                     // paint only the text that is not selected
911
912                     if (sPos >= ePos) {
913                         font->drawText(p, s->m_x + tx, s->m_y + ty + s->m_baseline,
914                                        str->s, str->l, s->m_start, s->m_len,
915                                        s->m_toAdd, s->m_reversed ? QPainter::RTL : QPainter::LTR, style()->visuallyOrdered());
916                     } else {
917                         if (sPos-1 >= 0) {
918                             font->drawText(p, s->m_x + tx, s->m_y + ty + s->m_baseline, str->s,
919                                            str->l, s->m_start, s->m_len,
920                                            s->m_toAdd, s->m_reversed ? QPainter::RTL : QPainter::LTR, style()->visuallyOrdered(), 0, sPos);
921                         }
922                         if (ePos < s->m_start+s->m_len) {
923                             font->drawText(p, s->m_x + tx, s->m_y + ty + s->m_baseline, str->s,
924                                            str->l, s->m_start, s->m_len,
925                                            s->m_toAdd, s->m_reversed ? QPainter::RTL : QPainter::LTR, style()->visuallyOrdered(), ePos, -1);
926                         }
927                     }
928                 }
929                 
930                 if ( sPos < ePos ) {
931                     // paint only the text that is selected
932                     if (selectionColor != p->pen().color())
933                         p->setPen(selectionColor);
934                     
935                     if (selectionTextShadow)
936                         p->setShadow(selectionTextShadow->x,
937                                      selectionTextShadow->y,
938                                      selectionTextShadow->blur,
939                                      selectionTextShadow->color);
940                     font->drawText(p, s->m_x + tx, s->m_y + ty + s->m_baseline, str->s,
941                                    str->l, s->m_start, s->m_len,
942                                    s->m_toAdd, s->m_reversed ? QPainter::RTL : QPainter::LTR, style()->visuallyOrdered(), sPos, ePos);
943                     if (selectionTextShadow)
944                         p->clearShadow();
945                 }
946             } 
947         }
948
949         // paint decorations
950
951         if (d != TDNONE && i.phase != PaintActionSelection &&
952             style()->htmlHacks()) {
953             p->setPen(_style->color());
954             s->paintDecoration(p, tx, ty, d);
955         }
956
957         // Draw any doc markers that touch this run
958         // Note s->end() points at the last char, not one past it like endOffset and ranges do
959         
960         if (i.phase != PaintActionSelection) {
961             for ( ; markerIt != markers.end(); markerIt++) {
962                 DocumentMarker marker = *markerIt;
963
964                 if (marker.endOffset <= s->start()) {
965                     // marker is completely before this run.  This might be a marker that sits before the
966                     // first run we draw, or markers that were within runs we skipped due to truncation.
967                     continue;
968                 }
969                 
970                 if (marker.startOffset <= s->end()) {
971                     // marker intersects this run.  Paint it.
972                     s->paintMarker(p, tx, ty, marker);
973                     if (marker.endOffset > s->end()+1) {
974                         // marker also runs into the next run. Bail now, no more marker advancement.
975                         break;
976                     }
977                 } else {
978                     // marker is completely after this run, bail.  A later run will paint it.
979                     break;
980                 }
981             }
982         }
983
984         if (setShadow)
985             p->clearShadow();
986         
987 #ifdef BIDI_DEBUG
988         {
989             int h = lineHeight( false ) + paddingTop() + paddingBottom() + borderTop() + borderBottom();
990             QColor c2 = QColor("#0000ff");
991             drawBorder(p, tx, ty, tx+1, ty + h,
992                        RenderObject::BSLeft, c2, c2, SOLID, 1, 1);
993             drawBorder(p, tx + s->m_width, ty, tx + s->m_width + 1, ty + h,
994                        RenderObject::BSRight, c2, c2, SOLID, 1, 1);
995         }
996 #endif
997     } while (((s = s->nextTextBox()) != 0) && s->checkVerticalPoint(i.r.y(), ty, i.r.height()));
998 #else
999 #error This file no longer works without Apple changes
1000 #endif
1001 }
1002
1003 #ifdef APPLE_CHANGES
1004
1005 bool RenderText::allAscii() const
1006 {
1007     if (m_allAsciiChecked)
1008         return m_allAscii;
1009     m_allAsciiChecked = true;
1010     
1011     unsigned int i;
1012     for (i = 0; i < str->l; i++){
1013         if (str->s[i].unicode() >= 0x7f){
1014             m_allAscii = false;
1015             return m_allAscii;
1016         }
1017     }
1018     
1019     m_allAscii = true;
1020     
1021     return m_allAscii;
1022 }
1023
1024 bool RenderText::shouldUseMonospaceCache(const Font *f) const
1025 {
1026     return (f && f->isFixedPitch() && allAscii() && !style()->htmlFont().isSmallCaps());
1027 }
1028
1029 // We cache the width of the ' ' character for <pre> text.  We could go futher
1030 // and cache a widths array for all styles, at the expense of increasing the size of the
1031 // RenderText.
1032 void RenderText::cacheWidths()
1033 {
1034     const Font *f = htmlFont( false );
1035     
1036     if (shouldUseMonospaceCache(f)){    
1037         float fw;
1038         QChar c(' ');
1039         f->floatCharacterWidths( &c, 1, 0, 1, 0, &fw);
1040         m_monospaceCharacterWidth = (int)fw;
1041     }
1042     else
1043         m_monospaceCharacterWidth = 0;
1044 }
1045
1046
1047 inline int RenderText::widthFromCache(const Font *f, int start, int len) const
1048 {
1049     if (m_monospaceCharacterWidth != 0){
1050         int i, w = 0;
1051         for (i = start; i < start+len; i++){
1052             int dir = str->s[i].direction();
1053             if (dir != QChar::DirNSM && dir != QChar::DirBN)
1054                 w += m_monospaceCharacterWidth;
1055         }
1056         return w;
1057     }
1058     
1059     return f->width(str->s, str->l, start, len);
1060 }
1061 #ifdef XXX
1062 inline int RenderText::widthFromCache(const Font *f, int start, int len) const
1063 {
1064     if (m_monospaceCharacterWidth != 0){
1065         return len * m_monospaceCharacterWidth;
1066     }
1067
1068     return f->width(str->s, str->l, start, len);
1069 }
1070 #endif
1071
1072 #endif
1073
1074 void RenderText::trimmedMinMaxWidth(int& beginMinW, bool& beginWS, 
1075                                     int& endMinW, bool& endWS,
1076                                     bool& hasBreakableChar, bool& hasBreak,
1077                                     int& beginMaxW, int& endMaxW,
1078                                     int& minW, int& maxW, bool& stripFrontSpaces)
1079 {
1080     bool isPre = style()->whiteSpace() == PRE;
1081     if (isPre)
1082         stripFrontSpaces = false;
1083     
1084     int len = str->l;
1085     if (len == 0 || (stripFrontSpaces && str->containsOnlyWhitespace())) {
1086         maxW = 0;
1087         hasBreak = false;
1088         return;
1089     }
1090     
1091     minW = m_minWidth;
1092     maxW = m_maxWidth;
1093     beginWS = stripFrontSpaces ? false : m_hasBeginWS;
1094     endWS = m_hasEndWS;
1095     
1096     beginMinW = m_beginMinWidth;
1097     endMinW = m_endMinWidth;
1098     
1099     hasBreakableChar = m_hasBreakableChar;
1100     hasBreak = m_hasBreak;
1101
1102     if (stripFrontSpaces && (str->s[0] == ' ' || (!isPre && str->s[0] == '\n'))) {
1103         const Font *f = htmlFont( false );
1104         QChar space[1]; space[0] = ' ';
1105         int spaceWidth = f->width(space, 1, 0);
1106         maxW -= spaceWidth;
1107     }
1108     
1109     stripFrontSpaces = !isPre && m_hasEndWS;
1110     
1111     if (style()->whiteSpace() == NOWRAP)
1112         minW = maxW;
1113     else if (minW > maxW)
1114         minW = maxW;
1115         
1116     // Compute our max widths by scanning the string for newlines.
1117     if (hasBreak) {
1118         const Font *f = htmlFont( false );
1119         bool firstLine = true;
1120         beginMaxW = endMaxW = maxW;
1121         for (int i = 0; i < len; i++)
1122         {
1123             int linelen = 0;
1124             while (i+linelen < len && str->s[i+linelen] != '\n')
1125                 linelen++;
1126                 
1127             if (linelen)
1128             {
1129 #if !APPLE_CHANGES
1130                 endMaxW = f->width(str->s, str->l, i, linelen);
1131 #else
1132                 endMaxW = widthFromCache(f, i, linelen);
1133 #endif
1134                 if (firstLine) {
1135                     firstLine = false;
1136                     beginMaxW = endMaxW;
1137                 }
1138                 i += linelen;
1139             }
1140             else if (firstLine) {
1141                 beginMaxW = 0;
1142                 firstLine = false;
1143             }
1144             
1145             if (i == len-1)
1146                 // A <pre> run that ends with a newline, as in, e.g.,
1147                 // <pre>Some text\n\n<span>More text</pre>
1148                 endMaxW = 0;
1149         }
1150     }
1151 }
1152
1153 void RenderText::calcMinMaxWidth()
1154 {
1155     KHTMLAssert( !minMaxKnown() );
1156
1157     // ### calc Min and Max width...
1158     m_minWidth = m_beginMinWidth = m_endMinWidth = 0;
1159     m_maxWidth = 0;
1160
1161     if (isBR())
1162         return;
1163         
1164     int currMinWidth = 0;
1165     int currMaxWidth = 0;
1166     m_hasBreakableChar = m_hasBreak = m_hasBeginWS = m_hasEndWS = false;
1167     
1168     // ### not 100% correct for first-line
1169     const Font *f = htmlFont( false );
1170     int wordSpacing = style()->wordSpacing();
1171     int len = str->l;
1172     bool ignoringSpaces = false;
1173     bool isSpace = false;
1174     bool isPre = style()->whiteSpace() == PRE;
1175     bool firstWord = true;
1176     bool firstLine = true;
1177     for(int i = 0; i < len; i++)
1178     {
1179         const QChar c = str->s[i];
1180         
1181         bool previousCharacterIsSpace = isSpace;
1182         
1183         bool isNewline = false;
1184         if (c == '\n') {
1185             if (isPre) {
1186                 m_hasBreak = true;
1187                 isNewline = true;
1188                 isSpace = false;
1189             }
1190             else
1191                 isSpace = true;
1192         } else {
1193             isSpace = c == ' ';
1194         }
1195         
1196         if ((isSpace || isNewline) && i == 0)
1197             m_hasBeginWS = true;
1198         if ((isSpace || isNewline) && i == len-1)
1199             m_hasEndWS = true;
1200             
1201         if (!ignoringSpaces && !isPre && previousCharacterIsSpace && isSpace)
1202             ignoringSpaces = true;
1203         
1204         if (ignoringSpaces && !isSpace)
1205             ignoringSpaces = false;
1206             
1207         if (ignoringSpaces || (i > 0 && c.unicode() == SOFT_HYPHEN)) // Ignore spaces and soft hyphens
1208             continue;
1209         
1210         int wordlen = 0;
1211         while (i+wordlen < len && str->s[i+wordlen] != '\n' && str->s[i+wordlen] != ' ' &&
1212                (i+wordlen == 0 || str->s[i+wordlen].unicode() != SOFT_HYPHEN) && // Skip soft hyphens
1213                (wordlen == 0 || !isBreakable( str->s, i+wordlen, str->l)))
1214             wordlen++;
1215             
1216         if (wordlen)
1217         {
1218 #if !APPLE_CHANGES
1219             int w = f->width(str->s, str->l, i, wordlen);
1220 #else
1221             int w = widthFromCache(f, i, wordlen);
1222 #endif
1223             currMinWidth += w;
1224             currMaxWidth += w;
1225             
1226             bool isBreakableCharSpace = (i+wordlen < len) ? ((!isPre && str->s[i+wordlen] == '\n') || 
1227                                                              str->s[i+wordlen] == ' ') : false;
1228
1229             if (i+wordlen < len && style()->whiteSpace() == NORMAL)
1230                 m_hasBreakableChar = true;
1231             
1232             // Add in wordspacing to our maxwidth, but not if this is the last word on a line or the
1233             // last word in the run.
1234             if (wordSpacing && isBreakableCharSpace && !containsOnlyWhitespace(i+wordlen, len-(i+wordlen)))
1235                 currMaxWidth += wordSpacing;
1236
1237             if (firstWord) {
1238                 firstWord = false;
1239                 // If the first character in the run is breakable, then we consider ourselves to have a beginning
1240                 // minimum width of 0, since a break could occur right before our run starts, preventing us from ever
1241                 // being appended to a previous text run when considering the total minimum width of the containing block.
1242                 bool hasBreak = isBreakable(str->s, i, str->l);
1243                 if (hasBreak)
1244                     m_hasBreakableChar = true;
1245                 m_beginMinWidth = hasBreak ? 0 : w;
1246             }
1247             m_endMinWidth = w;
1248             
1249             if (currMinWidth > m_minWidth) m_minWidth = currMinWidth;
1250             currMinWidth = 0;
1251             
1252             i += wordlen-1;
1253         }
1254         else {
1255             // Nowrap can never be broken, so don't bother setting the
1256             // breakable character boolean. Pre can only be broken if we encounter a newline.
1257             if (style()->whiteSpace() == NORMAL || isNewline)
1258                 m_hasBreakableChar = true;
1259
1260             if (currMinWidth > m_minWidth) m_minWidth = currMinWidth;
1261             currMinWidth = 0;
1262             
1263             if (isNewline) // Only set if isPre was true and we saw a newline.
1264             {
1265                 if (firstLine) {
1266                     firstLine = false;
1267                     m_beginMinWidth = currMaxWidth;
1268                 }
1269                 
1270                 if (currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
1271                 currMaxWidth = 0;
1272             }
1273             else
1274             {
1275                 currMaxWidth += f->width( str->s, str->l, i + wordlen );
1276             }
1277         }
1278     }
1279     
1280     if(currMinWidth > m_minWidth) m_minWidth = currMinWidth;
1281     if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
1282
1283     if (style()->whiteSpace() != NORMAL)
1284         m_minWidth = m_maxWidth;
1285
1286     if (isPre) {
1287         if (firstLine)
1288             m_beginMinWidth = m_maxWidth;
1289         m_endMinWidth = currMaxWidth;
1290     }
1291     
1292     setMinMaxKnown();
1293     //kdDebug( 6040 ) << "Text::calcMinMaxWidth(): min = " << m_minWidth << " max = " << m_maxWidth << endl;
1294 }
1295
1296 bool RenderText::containsOnlyWhitespace(unsigned int from, unsigned int len) const
1297 {
1298     unsigned int currPos;
1299     for (currPos = from; 
1300          currPos < from+len && (str->s[currPos] == '\n' || str->s[currPos].unicode() == ' '); 
1301          currPos++);
1302     return currPos >= (from+len);
1303 }
1304
1305 int RenderText::minXPos() const
1306 {
1307     if (!m_firstTextBox) return 0;
1308     int retval=6666666;
1309     for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
1310         retval = kMin(retval, (int)box->m_x);
1311     return retval;
1312 }
1313
1314 int RenderText::xPos() const
1315 {
1316     return m_firstTextBox ? m_firstTextBox->m_x : 0;
1317 }
1318
1319 int RenderText::yPos() const
1320 {
1321     return m_firstTextBox ? m_firstTextBox->m_y : 0;
1322 }
1323
1324 const QFont &RenderText::font()
1325 {
1326     return style()->font();
1327 }
1328
1329 void RenderText::setTextWithOffset(DOMStringImpl *text, uint offset, uint len, bool force)
1330 {
1331     uint oldLen = str ? str->l : 0;
1332     uint newLen = text ? text->l : 0;
1333     int delta = newLen - oldLen;
1334     uint end = len ? offset+len-1 : offset;
1335
1336     RootInlineBox* firstRootBox = 0;
1337     RootInlineBox* lastRootBox = 0;
1338     
1339     bool dirtiedLines = false;
1340     
1341     // Dirty all text boxes that include characters in between offset and offset+len.
1342     for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBox()) {
1343         // Text run is entirely before the affected range.
1344         if (curr->end() < offset)
1345             continue;
1346         
1347         // Text run is entirely after the affected range.
1348         if (curr->start() > end) {
1349             curr->offsetRun(delta);
1350             RootInlineBox* root = curr->root();
1351             if (!firstRootBox) {
1352                 firstRootBox = root;
1353                 if (!dirtiedLines) { // The affected area was in between two runs. Go ahead and mark the root box of the run after the affected area as dirty.
1354                     firstRootBox->markDirty();
1355                     dirtiedLines = true;
1356                 }
1357             }
1358             lastRootBox = root;
1359         }
1360         else if (curr->end() >= offset && curr->end() <= end) {
1361             curr->dirtyLineBoxes(); // Text run overlaps with the left end of the affected range.
1362             dirtiedLines = true;
1363         }
1364         else if (curr->start() <= offset && curr->end() >= end) {
1365             curr->dirtyLineBoxes(); // Text run subsumes the affected range.
1366             dirtiedLines = true;
1367         }
1368         else if (curr->start() <= end && curr->end() >= end) {
1369             curr->dirtyLineBoxes(); // Text run overlaps with right end of the affected range.
1370             dirtiedLines = true;
1371         }
1372     }
1373     
1374     // Now we have to walk all of the clean lines and adjust their cached line break information
1375     // to reflect our updated offsets.
1376     if (lastRootBox)
1377         lastRootBox = lastRootBox->nextRootBox();
1378     if (firstRootBox) {
1379         RootInlineBox* prev = firstRootBox->prevRootBox();
1380         if (prev)
1381             firstRootBox = prev;
1382     }
1383     for (RootInlineBox* curr = firstRootBox; curr && curr != lastRootBox; curr = curr->nextRootBox()) {
1384         if (!curr->isDirty() && curr->lineBreakObj() == this && curr->lineBreakPos() > end)
1385             curr->setLineBreakPos(curr->lineBreakPos()+delta);
1386     }
1387     
1388     m_linesDirty = dirtiedLines;
1389     setText(text, force);
1390 }
1391
1392 void RenderText::setText(DOMStringImpl *text, bool force)
1393 {
1394     if (!text)
1395         return;
1396     if (!force && str == text)
1397         return;
1398     if (str)
1399         str->deref();
1400
1401     str = text;
1402     if (str) {
1403         str = str->replace('\\', backslashAsCurrencySymbol());
1404         if ( style() ) {
1405             switch(style()->textTransform()) {
1406                 case CAPITALIZE:   str = str->capitalize();  break;
1407                 case UPPERCASE:   str = str->upper();       break;
1408                 case LOWERCASE:  str = str->lower();       break;
1409                 case NONE:
1410                 default:;
1411             }
1412         }
1413         str->ref();
1414     }
1415
1416 #if APPLE_CHANGES
1417     cacheWidths();
1418 #endif
1419
1420     // ### what should happen if we change the text of a
1421     // RenderBR object ?
1422     KHTMLAssert(!isBR() || (str->l == 1 && (*str->s) == '\n'));
1423     KHTMLAssert(!str->l || str->s);
1424
1425     setNeedsLayoutAndMinMaxRecalc();
1426     
1427 #ifdef BIDI_DEBUG
1428     QConstString cstr(str->s, str->l);
1429     kdDebug( 6040 ) << "RenderText::setText( " << cstr.string().length() << " ) '" << cstr.string() << "'" << endl;
1430 #endif
1431 }
1432
1433 int RenderText::height() const
1434 {
1435     // FIXME: Why use line-height? Shouldn't we be adding in the height of the last text box? -dwh
1436     int retval = 0;
1437     if (firstTextBox())
1438         retval = lastTextBox()->m_y + lineHeight(false) - firstTextBox()->m_y;
1439     return retval;
1440 }
1441
1442 short RenderText::lineHeight(bool firstLine, bool) const
1443 {
1444     // Always use the interior line height of the parent (e.g., if our parent is an inline block).
1445     return parent()->lineHeight(firstLine, true);
1446 }
1447
1448 short RenderText::baselinePosition( bool firstLine, bool ) const
1449 {
1450     const QFontMetrics &fm = metrics( firstLine );
1451     return fm.ascent() +
1452         ( lineHeight( firstLine ) - fm.height() ) / 2;
1453 }
1454
1455 void RenderText::dirtyLineBoxes(bool fullLayout, bool)
1456 {
1457     if (fullLayout)
1458         deleteTextBoxes();
1459     else if (!m_linesDirty) {
1460         for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
1461             box->dirtyLineBoxes();
1462     }
1463     m_linesDirty = false;
1464 }
1465
1466 InlineBox* RenderText::createInlineBox(bool, bool isRootLineBox, bool)
1467 {
1468     KHTMLAssert(!isRootLineBox);
1469     InlineTextBox* textBox = new (renderArena()) InlineTextBox(this);
1470     if (!m_firstTextBox)
1471         m_firstTextBox = m_lastTextBox = textBox;
1472     else {
1473         m_lastTextBox->setNextLineBox(textBox);
1474         textBox->setPreviousLineBox(m_lastTextBox);
1475         m_lastTextBox = textBox;
1476     }
1477     return textBox;
1478 }
1479
1480 void RenderText::position(InlineBox* box, int from, int len, bool reverse)
1481 {
1482     InlineTextBox *s = static_cast<InlineTextBox*>(box);
1483     
1484     // ### should not be needed!!!
1485     if (len == 0) {
1486         // We want the box to be destroyed.
1487         s->remove();
1488         s->detach(renderArena());
1489         m_firstTextBox = m_lastTextBox = 0;
1490         return;
1491     }
1492     
1493     reverse = reverse && !style()->visuallyOrdered();
1494
1495 #ifdef DEBUG_LAYOUT
1496     QChar *ch = str->s+from;
1497     QConstString cstr(ch, len);
1498     qDebug("setting run 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;
1499 #endif
1500
1501     s->m_reversed = reverse;
1502     s->m_start = from;
1503     s->m_len = len;
1504 }
1505
1506 unsigned int RenderText::width(unsigned int from, unsigned int len, bool firstLine) const
1507 {
1508     if(!str->s || from > str->l ) return 0;
1509     if ( from + len > str->l ) len = str->l - from;
1510
1511     const Font *f = htmlFont( firstLine );
1512     return width( from, len, f );
1513 }
1514
1515 unsigned int RenderText::width(unsigned int from, unsigned int len, const Font *f) const
1516 {
1517     if(!str->s || from > str->l ) return 0;
1518     if ( from + len > str->l ) len = str->l - from;
1519
1520     int w;
1521     if ( f == &style()->htmlFont() && from == 0 && len == str->l )
1522          w = m_maxWidth;
1523 #if APPLE_CHANGES
1524     else if (f == &style()->htmlFont())
1525         w = widthFromCache (f, from, len);
1526 #endif
1527     else
1528         w = f->width(str->s, str->l, from, len );
1529
1530     //kdDebug( 6040 ) << "RenderText::width(" << from << ", " << len << ") = " << w << endl;
1531     return w;
1532 }
1533
1534 int RenderText::width() const
1535 {
1536     int w;
1537     int minx = 100000000;
1538     int maxx = 0;
1539     // slooow
1540     for (InlineTextBox* s = firstTextBox(); s; s = s->nextTextBox()) {
1541         if(s->m_x < minx)
1542             minx = s->m_x;
1543         if(s->m_x + s->m_width > maxx)
1544             maxx = s->m_x + s->m_width;
1545     }
1546
1547     w = kMax(0, maxx-minx);
1548
1549     return w;
1550 }
1551
1552 QRect RenderText::getAbsoluteRepaintRect()
1553 {
1554     RenderObject *cb = containingBlock();
1555     return cb->getAbsoluteRepaintRect();
1556 }
1557
1558 short RenderText::verticalPositionHint( bool firstLine ) const
1559 {
1560     return parent()->verticalPositionHint( firstLine );
1561 }
1562
1563 const QFontMetrics &RenderText::metrics(bool firstLine) const
1564 {
1565     return style(firstLine)->fontMetrics();
1566 }
1567
1568 const Font *RenderText::htmlFont(bool firstLine) const
1569 {
1570     return &style(firstLine)->htmlFont();
1571 }
1572
1573 long RenderText::caretMinOffset() const
1574 {
1575     if (!firstTextBox()) 
1576         return 0;
1577     // EDIT FIXME: it is *not* guaranteed that the first run contains the lowest offset
1578     // Either make this a linear search (slow),
1579     // or maintain an index (needs much mem),
1580     // or calculate and store it in bidi.cpp (needs calculation even if not needed)
1581     return firstTextBox()->m_start;
1582 }
1583
1584 long RenderText::caretMaxOffset() const
1585 {
1586     if (!firstTextBox()) 
1587         return str->l;
1588     // EDIT FIXME: it is *not* guaranteed that the last run contains the highest offset
1589     // Either make this a linear search (slow),
1590     // or maintain an index (needs much mem),
1591     // or calculate and store it in bidi.cpp (needs calculation even if not needed)
1592     return lastTextBox()->m_start + lastTextBox()->m_len;
1593 }
1594
1595 unsigned long RenderText::caretMaxRenderedOffset() const
1596 {
1597     int l = 0;
1598     for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
1599         l += box->m_len;
1600     return l;
1601 }
1602
1603 InlineBox *RenderText::inlineBox(long offset)
1604 {
1605     for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) {
1606         if (offset >= box->m_start && offset <= box->m_start + box->m_len) {
1607             return box;
1608         }
1609         else if (offset < box->m_start) {
1610             // The offset we're looking for is before this node
1611             // this means the offset must be in content that is
1612             // not rendered.
1613             return box->prevTextBox() ? box->prevTextBox() : firstTextBox();
1614         }
1615     }
1616     
1617     return 0;
1618 }
1619
1620 RenderTextFragment::RenderTextFragment(DOM::NodeImpl* _node, DOM::DOMStringImpl* _str,
1621                                        int startOffset, int endOffset)
1622 :RenderText(_node, _str->substring(startOffset, endOffset)), 
1623 m_start(startOffset), m_end(endOffset), m_generatedContentStr(0)
1624 {}
1625
1626 RenderTextFragment::RenderTextFragment(DOM::NodeImpl* _node, DOM::DOMStringImpl* _str)
1627 :RenderText(_node, _str), m_start(0)
1628 {
1629     m_generatedContentStr = _str;
1630     if (_str) {
1631         _str->ref();
1632         m_end = _str->l;
1633     }
1634     else
1635         m_end = 0;
1636 }
1637     
1638 RenderTextFragment::~RenderTextFragment()
1639 {
1640     if (m_generatedContentStr)
1641         m_generatedContentStr->deref();
1642 }
1643
1644 bool RenderTextFragment::isTextFragment() const
1645 {
1646     return true;
1647 }
1648
1649 DOM::DOMStringImpl* RenderTextFragment::originalString() const
1650 {
1651     DOM::DOMStringImpl* result = 0;
1652     if (element())
1653         result = element()->string();
1654     else
1655         result = contentString();
1656     if (result && (start() > 0 || start() < result->l))
1657         result = result->substring(start(), end());
1658     return result;
1659 }
1660 #undef BIDI_DEBUG
1661 #undef DEBUG_LAYOUT