summaryrefslogtreecommitdiffstats
path: root/khtml/rendering/render_text.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'khtml/rendering/render_text.cpp')
-rw-r--r--khtml/rendering/render_text.cpp1546
1 files changed, 1546 insertions, 0 deletions
diff --git a/khtml/rendering/render_text.cpp b/khtml/rendering/render_text.cpp
new file mode 100644
index 000000000..a862c3ad5
--- /dev/null
+++ b/khtml/rendering/render_text.cpp
@@ -0,0 +1,1546 @@
+/**
+ * This file is part of the DOM implementation for KDE.
+ *
+ * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
+ * (C) 2000-2003 Dirk Mueller (mueller@kde.org)
+ * (C) 2003 Apple Computer, Inc.
+ * (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
+ *
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+//#define DEBUG_LAYOUT
+//#define BIDI_DEBUG
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "rendering/render_text.h"
+#include "rendering/render_canvas.h"
+#include "rendering/break_lines.h"
+#include "rendering/render_arena.h"
+#include "xml/dom_nodeimpl.h"
+
+#include "misc/loader.h"
+#include "misc/helper.h"
+
+#include <qbitmap.h>
+#include <qimage.h>
+#include <qpainter.h>
+#include <kdebug.h>
+#include <kglobal.h>
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+
+#ifdef HAVE_ALLOCA_H
+// explicitly included for systems that don't provide it in stdlib.h
+#include <alloca.h>
+#else
+#include <stdlib.h>
+#endif
+
+using namespace khtml;
+using namespace DOM;
+
+#ifndef NDEBUG
+static bool inInlineTextBoxDetach;
+#endif
+
+void InlineTextBox::detach(RenderArena* renderArena)
+{
+ if (m_parent)
+ m_parent->removeFromLine(this);
+
+#ifndef NDEBUG
+ inInlineTextBoxDetach = true;
+#endif
+ delete this;
+#ifndef NDEBUG
+ inInlineTextBoxDetach = false;
+#endif
+
+ // Recover the size left there for us by operator delete and free the memory.
+ renderArena->free(*(size_t *)this, this);
+}
+
+void* InlineTextBox::operator new(size_t sz, RenderArena* renderArena) throw()
+{
+ return renderArena->allocate(sz);
+}
+
+void InlineTextBox::operator delete(void* ptr, size_t sz)
+{
+ assert(inInlineTextBoxDetach);
+
+ // Stash size where detach can find it.
+ *(size_t *)ptr = sz;
+}
+
+void InlineTextBox::selectionStartEnd(int& sPos, int& ePos)
+{
+ int startPos, endPos;
+ if (object()->selectionState() == RenderObject::SelectionInside) {
+ startPos = 0;
+ endPos = renderText()->string()->l;
+ } else {
+ renderText()->selectionStartEnd(startPos, endPos);
+ if (object()->selectionState() == RenderObject::SelectionStart)
+ endPos = renderText()->string()->l;
+ else if (object()->selectionState() == RenderObject::SelectionEnd)
+ startPos = 0;
+ }
+
+ sPos = kMax(startPos - m_start, 0);
+ ePos = kMin(endPos - m_start, (int)m_len);
+}
+
+RenderObject::SelectionState InlineTextBox::selectionState()
+{
+ RenderObject::SelectionState state = object()->selectionState();
+ if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd ||
+ state == RenderObject::SelectionBoth) {
+ int startPos, endPos;
+ renderText()->selectionStartEnd(startPos, endPos);
+
+ bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len);
+ bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= m_start + m_len);
+ if (start && end)
+ state = RenderObject::SelectionBoth;
+ else if (start)
+ state = RenderObject::SelectionStart;
+ else if (end)
+ state = RenderObject::SelectionEnd;
+ else if ((state == RenderObject::SelectionEnd || startPos < m_start) &&
+ (state == RenderObject::SelectionStart || endPos > m_start + m_len))
+ state = RenderObject::SelectionInside;
+ }
+ return state;
+}
+
+void InlineTextBox::paint(RenderObject::PaintInfo& i, int tx, int ty)
+{
+ if (object()->isBR() || object()->style()->visibility() != VISIBLE ||
+ m_truncation == cFullTruncation || i.phase == PaintActionOutline)
+ return;
+
+ if (i.phase == PaintActionSelection && object()->selectionState() == RenderObject::SelectionNone)
+ // When only painting the selection, don't bother to paint if there is none.
+ return;
+
+ int xPos = tx + m_x;
+ int w = width();
+ if ((xPos >= i.r.x() + i.r.width()) || (xPos + w <= i.r.x()))
+ return;
+
+ // Set our font.
+ RenderStyle* styleToUse = object()->style(m_firstLine);
+ int d = styleToUse->textDecorationsInEffect();
+ if (styleToUse->font() != i.p->font())
+ i.p->setFont(styleToUse->font());
+ const Font *font = &styleToUse->htmlFont();
+ bool haveSelection = selectionState() != RenderObject::SelectionNone;
+
+ // Now calculate startPos and endPos, for painting selection.
+ // We paint selection while endPos > 0
+ int ePos = 0, sPos = 0;
+ if (haveSelection && !object()->canvas()->staticMode()) {
+ selectionStartEnd(sPos, ePos);
+ }
+ if (styleToUse->color() != i.p->pen().color())
+ i.p->setPen(styleToUse->color());
+
+ if (m_len > 0 && i.phase != PaintActionSelection) {
+ int endPoint = m_len;
+ if (m_truncation != cNoTruncation)
+ endPoint = m_truncation - m_start;
+ if (styleToUse->textShadow())
+ paintShadow(i.p, font, tx, ty, styleToUse->textShadow());
+ if (!haveSelection || sPos != 0 || ePos != m_len) {
+ font->drawText(i.p, m_x + tx, m_y + ty + m_baseline, renderText()->string()->s, renderText()->string()->l, m_start, endPoint,
+ m_toAdd, m_reversed ? QPainter::RTL : QPainter::LTR);
+ }
+ }
+
+ if (d != TDNONE && i.phase != PaintActionSelection && styleToUse->htmlHacks()) {
+ i.p->setPen(styleToUse->color());
+ paintDecoration(i.p, font, tx, ty, d);
+ }
+
+ if (haveSelection && i.phase == PaintActionSelection) {
+ //kdDebug(6040) << this << " paintSelection with startPos=" << sPos << " endPos=" << ePos << endl;
+ if ( sPos < ePos )
+ paintSelection(font, renderText(), i.p, styleToUse, tx, ty, sPos, ePos, d);
+ }
+}
+
+/** returns the proper ::selection pseudo style for the given element
+ * @return the style or 0 if no ::selection pseudo applies.
+ */
+inline const RenderStyle *retrieveSelectionPseudoStyle(const RenderObject *obj)
+{
+ // http://www.w3.org/Style/CSS/Test/CSS3/Selectors/20021129/html/tests/css3-modsel-162.html
+ // is of the opinion that ::selection of parent elements is also to be applied
+ // to children, so let's do it.
+ while (obj) {
+ const RenderStyle *style = obj->style()->getPseudoStyle(RenderStyle::SELECTION);
+ if (style) return style;
+
+ obj = obj->parent();
+ }/*wend*/
+ return 0;
+}
+
+void InlineTextBox::paintSelection(const Font *f, RenderText *text, QPainter *p, RenderStyle* style, int tx, int ty, int startPos, int endPos, int deco)
+{
+ if(startPos > m_len) return;
+ if(startPos < 0) startPos = 0;
+
+ QColor hc;
+ QColor hbg;
+ const RenderStyle* pseudoStyle = retrieveSelectionPseudoStyle(text);
+ if (pseudoStyle) {
+ // ### support outline (mandated by CSS3)
+ // ### support background-image? (optional by CSS3)
+ if (pseudoStyle->backgroundColor().isValid())
+ hbg = pseudoStyle->backgroundColor();
+ hc = pseudoStyle->color();
+ } else {
+ const QColorGroup &grp = style->palette().active();
+ hc = grp.highlightedText();
+ hbg = grp.highlight();
+ // ### should be at most retrieved once per render text
+ QColor bg = khtml::retrieveBackgroundColor(text);
+ // It may happen that the contrast is -- well -- virtually non existent.
+ // In this case, simply swap the colors, thus in compliance with
+ // NN4 (win32 only), IE, and Mozilla.
+ if (!khtml::hasSufficientContrast(hbg, bg))
+ qSwap(hc, hbg);
+ }
+
+ p->setPen(hc);
+
+ //kdDebug( 6040 ) << "textRun::painting(" << QConstString(text->str->s + m_start, m_len).string().left(30) << ") at(" << m_x+tx << "/" << m_y+ty << ")" << endl;
+
+ const bool needClipping = startPos != 0 || endPos != m_len;
+
+ if (needClipping) {
+ p->save();
+
+ int visualSelectionStart = f->width(text->str->s, text->str->l, m_start, startPos, m_start, m_start + m_len, m_toAdd);
+ int visualSelectionEnd = f->width(text->str->s, text->str->l, m_start, endPos, m_start, m_start + m_len, m_toAdd);
+ int visualSelectionWidth = visualSelectionEnd - visualSelectionStart;
+ if (m_reversed) {
+ visualSelectionStart = f->width(text->str->s, text->str->l, m_start, m_len) - visualSelectionEnd;
+ }
+
+ QRect selectionRect(m_x + tx + visualSelectionStart, m_y + ty, visualSelectionWidth, height());
+ QRegion r(selectionRect);
+ if (p->hasClipping())
+ r &= p->clipRegion(QPainter::CoordPainter);
+ p->setClipRegion(r, QPainter::CoordPainter);
+ }
+
+ f->drawText(p, m_x + tx, m_y + ty + m_baseline, text->str->s, text->str->l,
+ m_start, m_len, m_toAdd,
+ m_reversed ? QPainter::RTL : QPainter::LTR,
+ needClipping ? 0 : startPos, needClipping ? m_len : endPos,
+ hbg, m_y + ty, height(), deco);
+
+ if (needClipping) p->restore();
+}
+
+void InlineTextBox::paintDecoration( QPainter *pt, const Font *f, int _tx, int _ty, int deco)
+{
+ _tx += m_x;
+ _ty += m_y;
+
+ if (m_truncation == cFullTruncation)
+ return;
+
+ int width = m_width - 1;
+ if (m_truncation != cNoTruncation) {
+ width = static_cast<RenderText*>(m_object)->width(m_start, m_truncation - m_start, m_firstLine);
+ }
+
+ RenderObject *p = object();
+
+ QColor underline, overline, linethrough;
+ p->getTextDecorationColors(deco, underline, overline, linethrough, p->style()->htmlHacks());
+
+ if(deco & UNDERLINE){
+ pt->setPen(underline);
+ f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::UNDERLINE);
+ }
+ if (deco & OVERLINE) {
+ pt->setPen(overline);
+ f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::OVERLINE);
+ }
+ if(deco & LINE_THROUGH) {
+ pt->setPen(linethrough);
+ f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::LINE_THROUGH);
+ }
+ // NO! Do NOT add BLINK! It is the most annouing feature of Netscape, and IE has a reason not to
+ // support it. Lars
+}
+
+void InlineTextBox::paintShadow(QPainter *pt, const Font *f, int _tx, int _ty, const ShadowData *shadow )
+{
+ int x = m_x + _tx + shadow->x;
+ int y = m_y + _ty + shadow->y;
+ const RenderText* text = renderText();
+
+ if (shadow->blur <= 0) {
+ QColor c = pt->pen().color();
+ pt->setPen(shadow->color);
+ f->drawText(pt, x, y+m_baseline, text->str->s, text->str->l,
+ m_start, m_len, m_toAdd,
+ m_reversed ? QPainter::RTL : QPainter::LTR);
+ pt->setPen(c);
+
+ }
+ else {
+ const int thickness = shadow->blur;
+ const int w = m_width+2*thickness;
+ const int h = m_height+2*thickness;
+ const QRgb color = shadow->color.rgb();
+ const int gray = qGray(color);
+ const bool inverse = (gray < 100);
+ const QRgb bgColor = (inverse) ? qRgb(255,255,255) : qRgb(0,0,0);
+ QPixmap pixmap(w, h);
+ pixmap.fill(bgColor);
+ QPainter p;
+
+ p.begin(&pixmap);
+ p.setPen(shadow->color);
+ p.setFont(pt->font());
+ f->drawText(&p, thickness, thickness+m_baseline, text->str->s, text->str->l,
+ m_start, m_len, m_toAdd,
+ m_reversed ? QPainter::RTL : QPainter::LTR);
+
+ p.end();
+ QImage img = pixmap.convertToImage().convertDepth(32);
+
+ int md = thickness*thickness; // max-dist^2
+
+ // blur map (division cache)
+ float *bmap = (float*)alloca(sizeof(float)*(md+1));
+ for(int n=0; n<=md; n++) {
+ float f;
+ f = n/(float)(md+1);
+ f = 1.0 - f*f;
+ bmap[n] = f;
+ }
+
+ float factor = 0.0; // maximal potential opacity-sum
+ for(int n=-thickness; n<=thickness; n++)
+ for(int m=-thickness; m<=thickness; m++) {
+ int d = n*n+m*m;
+ if (d<=md)
+ factor += bmap[d];
+ }
+
+ // arbitratry factor adjustment to make shadows solid.
+ factor = factor/1.333;
+
+ // alpha map
+ float* amap = (float*)alloca(sizeof(float)*(h*w));
+ memset(amap, 0, h*w*(sizeof(float)));
+ for(int j=thickness; j<h-thickness; j++) {
+ for(int i=thickness; i<w-thickness; i++) {
+ QRgb col= img.pixel(i,j);
+ if (col == bgColor) continue;
+ float g = qGray(col);
+ if (inverse)
+ g = (255-g)/(255-gray);
+ else
+ g = g/gray;
+ for(int n=-thickness; n<=thickness; n++) {
+ for(int m=-thickness; m<=thickness; m++) {
+ int d = n*n+m*m;
+ if (d>md) continue;
+ float f = bmap[d];
+ amap[(i+m)+(j+n)*w] += (g*f);
+ }
+ }
+ }
+ }
+
+ QImage res(w,h,32);
+ res.setAlphaBuffer(true);
+ int r = qRed(color);
+ int g = qGreen(color);
+ int b = qBlue(color);
+
+ // divide by factor
+ factor = 1.0/factor;
+
+ for(int j=0; j<h; j++) {
+ for(int i=0; i<w; i++) {
+ int a = (int)(amap[i+j*w] * factor * 255.0);
+ if (a > 255) a = 255;
+ res.setPixel(i,j, qRgba(r,g,b,a));
+ }
+ }
+
+ pt->drawImage(x-thickness, y-thickness, res, 0, 0, -1, -1, Qt::DiffuseAlphaDither | Qt::ColorOnly | Qt::PreferDither);
+ }
+ // Paint next shadow effect
+ if (shadow->next) paintShadow(pt, f, _tx, _ty, shadow->next);
+}
+
+/**
+ * Distributes pixels to justify text.
+ * @param numSpaces spaces left, will be decremented by one
+ * @param toAdd number of pixels left to be distributed, will have the
+ * amount of pixels distributed during this call subtracted.
+ * @return number of pixels to distribute
+ */
+static inline int justifyWidth(int &numSpaces, int &toAdd) {
+ int a = 0;
+ if ( numSpaces ) {
+ a = toAdd/numSpaces;
+ toAdd -= a;
+ numSpaces--;
+ }/*end if*/
+ return a;
+}
+
+FindSelectionResult InlineTextBox::checkSelectionPoint(int _x, int _y, int _tx, int _ty, const Font *f, RenderText *text, int & offset, short lineHeight)
+{
+// kdDebug(6040) << "InlineTextBox::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y
+// << " _tx+m_x=" << _tx+m_x << " _ty+m_y=" << _ty+m_y << endl;
+ offset = 0;
+
+ if ( _y < _ty + m_y )
+ return SelectionPointBefore; // above -> before
+
+ if ( _y > _ty + m_y + lineHeight ) {
+ // below -> after
+ // Set the offset to the max
+ offset = m_len;
+ return SelectionPointAfter;
+ }
+ if ( _x > _tx + m_x + m_width ) {
+ // to the right
+ return SelectionPointAfterInLine;
+ }
+
+ // The Y matches, check if we're on the left
+ if ( _x < _tx + m_x ) {
+ return SelectionPointBeforeInLine;
+ }
+
+ // consider spacing for justified text
+ int toAdd = m_toAdd;
+ bool justified = text->style()->textAlign() == JUSTIFY && toAdd > 0;
+ int numSpaces = 0;
+ if (justified) {
+
+ for( int i = 0; i < m_len; i++ )
+ if ( text->str->s[m_start+i].category() == QChar::Separator_Space )
+ numSpaces++;
+
+ }/*end if*/
+
+ int delta = _x - (_tx + m_x);
+ //kdDebug(6040) << "InlineTextBox::checkSelectionPoint delta=" << delta << endl;
+ int pos = 0;
+ if ( m_reversed ) {
+ delta -= m_width;
+ while(pos < m_len) {
+ int w = f->width( text->str->s, text->str->l, m_start + pos);
+ if (justified && text->str->s[m_start + pos].category() == QChar::Separator_Space)
+ w += justifyWidth(numSpaces, toAdd);
+ 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);
+ if (justified && text->str->s[m_start + pos].category() == QChar::Separator_Space)
+ w += justifyWidth(numSpaces, toAdd);
+ int w2 = w/2;
+ w -= w2;
+ delta -= w2;
+ if(delta <= 0) break;
+ pos++;
+ delta -= w;
+ }
+ }
+// kdDebug( 6040 ) << " Text --> inside at position " << pos << endl;
+ offset = pos;
+ return SelectionPointInside;
+}
+
+int InlineTextBox::offsetForPoint(int _x, int &ax) const
+{
+ // Do binary search for finding out offset, saves some time for long
+ // runs.
+ int start = 0;
+ int end = m_len;
+ ax = m_x;
+ int offset = (start + end) / 2;
+ while (end - start > 0) {
+ // always snap to the right column. This makes up for "jumpy" vertical
+ // navigation.
+ if (end - start == 1) start = end;
+
+ offset = (start + end) / 2;
+ ax = m_x + widthFromStart(offset);
+ if (ax > _x) end = offset;
+ else if (ax < _x) start = offset;
+ else break;
+ }
+ return m_start + offset;
+}
+
+int InlineTextBox::widthFromStart(int pos) const
+{
+ // gasp! sometimes pos is i < 0 which crashes Font::width
+ pos = kMax(pos, 0);
+
+ const RenderText *t = renderText();
+ Q_ASSERT(t->isText());
+ const Font *f = t->htmlFont(m_firstLine);
+ const QFontMetrics &fm = t->fontMetrics(m_firstLine);
+
+ int numSpaces = 0;
+ // consider spacing for justified text
+ bool justified = t->style()->textAlign() == JUSTIFY;
+ //kdDebug(6000) << "InlineTextBox::width(int)" << endl;
+ if (justified && m_toAdd > 0) do {
+ //kdDebug(6000) << "justify" << endl;
+
+// QConstString cstr = QConstString(t->str->s + m_start, m_len);
+ for( int i = 0; i < m_len; i++ )
+ if ( t->str->s[m_start+i].category() == QChar::Separator_Space )
+ numSpaces++;
+ if (numSpaces == 0) break;
+
+ int toAdd = m_toAdd;
+ int w = 0; // accumulated width
+ int start = 0; // start of non-space sequence
+ int current = 0; // current position
+ while (current < pos) {
+ // add spacing
+ while (current < pos && t->str->s[m_start + current].category() == QChar::Separator_Space) {
+ w += f->getWordSpacing();
+ w += f->getLetterSpacing();
+ w += justifyWidth(numSpaces, toAdd);
+ w += fm.width(' '); // ### valid assumption? (LS)
+ current++; start++;
+ }/*wend*/
+ if (current >= pos) break;
+
+ // seek next space
+ while (current < pos && t->str->s[m_start + current].category() != QChar::Separator_Space)
+ current++;
+
+ // check run without spaces
+ if ( current > start ) {
+ w += f->width(t->str->s + m_start, m_len, start, current - start);
+ start = current;
+ }
+ }
+
+ return w;
+
+ } while(false);/*end if*/
+
+ //kdDebug(6000) << "default" << endl;
+ // else use existing width function
+ return f->width(t->str->s + m_start, m_len, 0, pos);
+
+}
+
+long InlineTextBox::minOffset() const
+{
+ return m_start;
+}
+
+long InlineTextBox::maxOffset() const
+{
+ return m_start + m_len;
+}
+
+int InlineTextBox::placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool& foundBox)
+{
+ if (foundBox) {
+ m_truncation = cFullTruncation;
+ return -1;
+ }
+
+ int ellipsisX = ltr ? blockEdge - ellipsisWidth : blockEdge + ellipsisWidth;
+
+ // 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.
+ if (ltr) {
+ if (ellipsisX <= m_x) {
+ // Too far. Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box.
+ m_truncation = cFullTruncation;
+ foundBox = true;
+ return -1;
+ }
+
+ if (ellipsisX < m_x + m_width) {
+ if (m_reversed)
+ return -1; // FIXME: Support LTR truncation when the last run is RTL someday.
+
+ foundBox = true;
+
+ int ax;
+ int offset = offsetForPoint(ellipsisX, ax) - 1;
+ if (offset <= m_start) {
+ // No characters should be rendered. Set ourselves to full truncation and place the ellipsis at the min of our start
+ // and the ellipsis edge.
+ m_truncation = cFullTruncation;
+ return kMin(ellipsisX, (int)m_x);
+ }
+
+ // Set the truncation index on the text run. The ellipsis needs to be placed just after the last visible character.
+ m_truncation = offset;
+ return widthFromStart(offset - m_start);
+ }
+ }
+ else {
+ // FIXME: Support RTL truncation someday, including both modes (when the leftmost run on the line is either RTL or LTR)
+ }
+ return -1;
+}
+
+// -----------------------------------------------------------------------------
+
+InlineTextBoxArray::InlineTextBoxArray()
+{
+ setAutoDelete(false);
+}
+
+int InlineTextBoxArray::compareItems( Item d1, Item d2 )
+{
+ assert(d1);
+ assert(d2);
+
+ return static_cast<InlineTextBox*>(d1)->m_y - static_cast<InlineTextBox*>(d2)->m_y;
+}
+
+// remove this once QVector::bsearch is fixed
+int InlineTextBoxArray::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
+ res = ((QGVector*)this)->compareItems( d, (*this)[mid] );
+ 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
+ while ( found && (mid > 0) && !((QGVector*)this)->compareItems(d, (*this)[mid-1]) )
+ mid--;
+ return mid;
+}
+
+// -------------------------------------------------------------------------------------
+
+RenderText::RenderText(DOM::NodeImpl* node, DOMStringImpl *_str)
+ : RenderObject(node)
+{
+ // init RenderObject attributes
+ setRenderText(); // our object inherits from RenderText
+
+ m_minWidth = -1;
+ m_maxWidth = -1;
+ str = _str;
+ if(str) str->ref();
+ KHTMLAssert(!str || !str->l || str->s);
+
+ m_selectionState = SelectionNone;
+ m_hasReturn = true;
+
+#ifdef DEBUG_LAYOUT
+ QConstString cstr(str->s, str->l);
+ kdDebug( 6040 ) << "RenderText ctr( "<< cstr.string().length() << " ) '" << cstr.string() << "'" << endl;
+#endif
+}
+
+void RenderText::setStyle(RenderStyle *_style)
+{
+ if ( style() != _style ) {
+ bool changedText = ((!style() && ( _style->textTransform() != TTNONE ||
+ !_style->preserveLF() || !_style->preserveWS() )) ||
+ (style() && (style()->textTransform() != _style->textTransform() ||
+ style()->whiteSpace() != _style->whiteSpace())));
+
+ RenderObject::setStyle( _style );
+ m_lineHeight = RenderObject::lineHeight(false);
+
+ if (!isBR() && changedText) {
+ DOM::DOMStringImpl* textToTransform = originalString();
+ if (textToTransform)
+ setText(textToTransform, true);
+ }
+ }
+}
+
+RenderText::~RenderText()
+{
+ KHTMLAssert(m_lines.count() == 0);
+ if(str) str->deref();
+}
+
+void RenderText::deleteInlineBoxes(RenderArena* arena)
+{
+ // 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();
+ if (len) {
+ if (!arena)
+ arena = renderArena();
+ for(unsigned int i=0; i < len; i++) {
+ InlineTextBox* s = m_lines.at(i);
+ if (s)
+ s->detach(arena);
+ m_lines.remove(i);
+ }
+ }
+
+ KHTMLAssert(m_lines.count() == 0);
+}
+
+bool RenderText::isTextFragment() const
+{
+ return false;
+}
+
+DOM::DOMStringImpl* RenderText::originalString() const
+{
+ return element() ? element()->string() : 0;
+}
+
+InlineTextBox * RenderText::findInlineTextBox( int offset, int &pos, bool checkFirstLetter )
+{
+ // The text boxes point to parts of the rendertext's str string
+ // (they don't include '\n')
+ // Find the text box that includes the character at @p offset
+ // and return pos, which is the position of the char in the run.
+
+ // FIXME: make this use binary search? Dirk says it won't work :-( (LS)
+ (void)checkFirstLetter;
+#if 0
+ if (checkFirstLetter && forcedMinOffset()) {
+// kdDebug(6040) << "checkFirstLetter: forcedMinOffset: " << forcedMinOffset() << endl;
+ RenderFlow *firstLetter = static_cast<RenderFlow *>(previousSibling());
+ if (firstLetter && firstLetter->isFlow() && firstLetter->isFirstLetter()) {
+ RenderText *letterText = static_cast<RenderText *>(firstLetter->firstChild());
+ //kdDebug(6040) << "lettertext: " << letterText << " minOfs: " << letterText->minOffset() << " maxOfs: " << letterText->maxOffset() << endl;
+ if (offset >= letterText->minOffset() && offset <= letterText->maxOffset()) {
+ InlineTextBox *result = letterText->findInlineTextBox(offset, pos, false);
+ //kdDebug(6040) << "result: " << result << endl;
+ if (result) return result;
+ }
+ }
+ }
+#endif
+
+ if ( m_lines.isEmpty() )
+ return 0L;
+
+ // The text boxes don't resemble a contiguous coverage of the text, there
+ // may be holes. Therefore, we snap to the nearest previous text box if
+ // the given offset happens to point to such a hole.
+
+ InlineTextBox* s = m_lines[0];
+ uint count = m_lines.count();
+ uint si = 0;
+ uint nearest_idx = 0; // index of nearest text box
+ int nearest = INT_MAX; // nearest distance
+//kdDebug(6040) << "s[" << si << "] m_start " << s->m_start << " m_end " << (s->m_start + s->m_len) << endl;
+ while(!(offset >= s->m_start && offset <= s->m_start + s->m_len)
+ && ++si < count)
+ {
+ int dist = offset - (s->m_start + s->m_len);
+//kdDebug(6040) << "dist " << dist << " nearest " << nearest << endl;
+ if (dist >= 0 && dist <= nearest) {
+ nearest = dist;
+ nearest_idx = si - 1;
+ }/*end if*/
+ s = m_lines[si];
+//kdDebug(6040) << "s[" << si << "] m_start " << s->m_start << " m_end " << (s->m_start + s->m_len) << endl;
+ }
+//kdDebug(6040) << "nearest_idx " << nearest_idx << " count " << count << endl;
+ if (si >= count) s = m_lines[nearest_idx];
+ // we are now in the correct text box
+ pos = kMin(offset - s->m_start, int( s->m_len ));
+ //kdDebug(6040) << "offset=" << offset << " s->m_start=" << s->m_start << endl;
+ return s;
+}
+
+bool RenderText::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction /*hitTestAction*/, bool /*inBox*/)
+{
+ assert(parent());
+
+ bool inside = false;
+ if (style()->visibility() != HIDDEN) {
+ InlineTextBox *s = m_lines.count() ? m_lines[0] : 0;
+ int si = 0;
+ while(s) {
+ if((_y >=_ty + s->m_y) && (_y < _ty + s->m_y + s->m_height) &&
+ (_x >= _tx + s->m_x) && (_x <_tx + s->m_x + s->m_width) ) {
+ inside = true;
+ break;
+ }
+
+ s = si < (int) m_lines.count()-1 ? m_lines[++si] : 0;
+ }
+ }
+
+ // #### ported over from Safari. Can this happen at all? (lars)
+
+ if (inside && element()) {
+ if (info.innerNode() && info.innerNode()->renderer() &&
+ !info.innerNode()->renderer()->isInline()) {
+ // Within the same layer, inlines are ALWAYS fully above blocks. Change inner node.
+ info.setInnerNode(element());
+
+ // Clear everything else.
+ info.setInnerNonSharedNode(0);
+ info.setURLElement(0);
+ }
+
+ if (!info.innerNode())
+ info.setInnerNode(element());
+
+ if(!info.innerNonSharedNode())
+ info.setInnerNonSharedNode(element());
+ }
+
+ return inside;
+}
+
+FindSelectionResult RenderText::checkSelectionPoint(int _x, int _y, int _tx, int _ty, DOM::NodeImpl*& node, int &offset, SelPointState &)
+{
+// kdDebug(6040) << "RenderText::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y
+// << " _tx=" << _tx << " _ty=" << _ty << endl;
+//kdDebug(6040) << renderName() << "::checkSelectionPoint x=" << xPos() << " y=" << yPos() << " w=" << width() << " h=" << height() << " m_lines.count=" << m_lines.count() << endl;
+
+ NodeImpl *lastNode = 0;
+ int lastOffset = 0;
+ FindSelectionResult lastResult = SelectionPointAfter;
+
+ for(unsigned int si = 0; si < m_lines.count(); si++)
+ {
+ InlineTextBox* s = m_lines[si];
+ FindSelectionResult result;
+ const Font *f = htmlFont( si==0 );
+ result = s->checkSelectionPoint(_x, _y, _tx, _ty, f, this, offset, m_lineHeight);
+
+// kdDebug(6040) << "RenderText::checkSelectionPoint " << this << " line " << si << " result=" << result << " offset=" << offset << endl;
+ if ( result == SelectionPointInside ) // x,y is inside the textrun
+ {
+ offset += s->m_start; // add the offset from the previous lines
+// kdDebug(6040) << "RenderText::checkSelectionPoint inside -> " << offset << endl;
+ node = element();
+ return SelectionPointInside;
+ } else if ( result == SelectionPointBefore ) {
+ if (!lastNode) {
+ // x,y is before the textrun -> stop here
+ offset = 0;
+// kdDebug(6040) << "RenderText::checkSelectionPoint " << this << "before us -> returning Before" << endl;
+ node = element();
+ return SelectionPointBefore;
+ }
+ } else if ( result == SelectionPointBeforeInLine ) {
+ offset = s->m_start;
+ node = element();
+ return SelectionPointInside;
+ } else if ( result == SelectionPointAfterInLine ) {
+ lastOffset = s->m_start + s->m_len;
+ lastNode = element();
+ lastResult = result;
+ // no return here
+ }
+
+ }
+
+ if (lastNode) {
+ offset = lastOffset;
+ node = lastNode;
+// kdDebug(6040) << "RenderText::checkSelectionPoint: lastNode " << lastNode << " lastOffset " << lastOffset << endl;
+ return lastResult;
+ }
+
+ // set offset to max
+ offset = str->l;
+ //qDebug("setting node to %p", element());
+ node = element();
+// kdDebug(6040) << "RenderText::checkSelectionPoint: node " << node << " offset " << offset << endl;
+ return SelectionPointAfter;
+}
+
+void RenderText::caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height)
+{
+ if (!m_lines.count()) {
+ _x = _y = height = -1;
+ width = 1;
+ return;
+ }
+
+ int pos;
+ InlineTextBox * s = findInlineTextBox( offset, pos, true );
+ RenderText *t = s->renderText();
+// kdDebug(6040) << "offset="<<offset << " pos="<<pos << endl;
+
+ const QFontMetrics &fm = t->metrics( s->m_firstLine );
+ height = fm.height(); // s->m_height;
+
+ _x = s->m_x + s->widthFromStart(pos);
+ _y = s->m_y + s->baseline() - fm.ascent();
+ width = 1;
+ if (flags & CFOverride) {
+ width = offset < maxOffset() ? fm.width(str->s[offset]) : 1;
+ }/*end if*/
+#if 0
+ kdDebug(6040) << "_x="<<_x << " s->m_x="<<s->m_x
+ << " s->m_start"<<s->m_start
+ << " s->m_len" << s->m_len << " _y=" << _y << endl;
+#endif
+
+ int absx, absy;
+
+ if (absolutePosition(absx,absy))
+ {
+ //kdDebug(6040) << "absx=" << absx << " absy=" << absy << endl;
+ _x += absx;
+ _y += absy;
+ } else {
+ // we don't know our absolute position, and there is no point returning
+ // just a relative one
+ _x = _y = -1;
+ }
+}
+
+long RenderText::minOffset() const
+{
+ if (!m_lines.count()) return 0;
+ // FIXME: it is *not* guaranteed that the first run contains the lowest offset
+ // Either make this a linear search (slow),
+ // or maintain an index (needs much mem),
+ // or calculate and store it in bidi.cpp (needs calculation even if not needed)
+ // (LS)
+ return m_lines[0]->m_start;
+}
+
+long RenderText::maxOffset() const
+{
+ int count = m_lines.count();
+ if (!count) return str->l;
+ // FIXME: it is *not* guaranteed that the last run contains the highest offset
+ // Either make this a linear search (slow),
+ // or maintain an index (needs much mem),
+ // or calculate and store it in bidi.cpp (needs calculation even if not needed)
+ // (LS)
+ return m_lines[count - 1]->m_start + m_lines[count - 1]->m_len;
+}
+
+bool RenderText::absolutePosition(int &xPos, int &yPos, bool) const
+{
+ return RenderObject::absolutePosition(xPos, yPos, false);
+}
+
+bool RenderText::posOfChar(int chr, int &x, int &y)
+{
+ if (!parent())
+ return false;
+ parent()->absolutePosition( x, y, false );
+
+ int pos;
+ InlineTextBox * s = findInlineTextBox( 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;
+ return true;
+ }
+
+ return false;
+}
+
+void RenderText::paint( PaintInfo& /*pI*/, int /*tx*/, int /*ty*/)
+{
+ KHTMLAssert( false );
+}
+
+void RenderText::calcMinMaxWidth()
+{
+ KHTMLAssert( !minMaxKnown() );
+
+ // ### calc Min and Max width...
+ m_minWidth = m_beginMinWidth = m_endMinWidth = 0;
+ m_maxWidth = 0;
+
+ if (isBR())
+ return;
+
+ int currMinWidth = 0;
+ int currMaxWidth = 0;
+ m_hasBreakableChar = m_hasBreak = m_hasBeginWS = m_hasEndWS = false;
+
+ // ### not 100% correct for first-line
+ const Font *f = htmlFont( false );
+ int wordSpacing = style()->wordSpacing();
+ int len = str->l;
+ bool isSpace = false;
+ bool firstWord = true;
+ bool firstLine = true;
+ for(int i = 0; i < len; i++)
+ {
+ unsigned short c = str->s[i].unicode();
+ bool isNewline = false;
+
+ // If line-breaks survive to here they are preserved
+ if ( c == '\n' ) {
+ m_hasBreak = true;
+ isNewline = true;
+ isSpace = false;
+ } else
+ isSpace = c == ' ';
+
+ if ((isSpace || isNewline) && i == 0)
+ m_hasBeginWS = true;
+ if ((isSpace || isNewline) && i == len-1)
+ m_hasEndWS = true;
+
+ if (i && c == SOFT_HYPHEN)
+ continue;
+
+ int wordlen = 0;
+ while( i+wordlen < len && (i+wordlen == 0 || str->s[i+wordlen].unicode() != SOFT_HYPHEN) &&
+ !(isBreakable( str->s, i+wordlen, str->l )) )
+ wordlen++;
+
+ if (wordlen)
+ {
+#ifndef APPLE_CHANGES
+ int w = f->width(str->s, str->l, i, wordlen);
+#else
+ int w = widthFromCache(f, i, wordlen);
+#endif
+ currMinWidth += w;
+ currMaxWidth += w;
+
+ // Add in wordspacing to our maxwidth, but not if this is the last word.
+ if (wordSpacing && !containsOnlyWhitespace(i+wordlen, len-(i+wordlen)))
+ currMaxWidth += wordSpacing;
+
+ if (firstWord) {
+ firstWord = false;
+ m_beginMinWidth = w;
+ }
+ m_endMinWidth = w;
+
+ if(currMinWidth > m_minWidth) m_minWidth = currMinWidth;
+ currMinWidth = 0;
+
+ i += wordlen-1;
+ }
+ else {
+ // Nowrap can never be broken, so don't bother setting the
+ // breakable character boolean. Pre can only be broken if we encounter a newline.
+ if (style()->autoWrap() || isNewline)
+ m_hasBreakableChar = true;
+
+ if(currMinWidth > m_minWidth) m_minWidth = currMinWidth;
+ currMinWidth = 0;
+
+ if (isNewline) // Only set if isPre was true and we saw a newline.
+ {
+ if ( firstLine ) {
+ firstLine = false;
+ m_beginMinWidth = currMaxWidth;
+ }
+
+ if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
+ currMaxWidth = 0;
+ }
+ else
+ {
+ currMaxWidth += f->width( str->s, str->l, i + wordlen );
+ }
+ }
+ }
+
+ if(currMinWidth > m_minWidth) m_minWidth = currMinWidth;
+ if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth;
+
+ if (!style()->autoWrap()) {
+ m_minWidth = m_maxWidth;
+ if (style()->preserveLF()) {
+ if (firstLine)
+ m_beginMinWidth = m_maxWidth;
+ m_endMinWidth = currMaxWidth;
+ }
+ }
+
+ setMinMaxKnown();
+ //kdDebug( 6040 ) << "Text::calcMinMaxWidth(): min = " << m_minWidth << " max = " << m_maxWidth << endl;
+
+}
+
+int RenderText::minXPos() const
+{
+ if (!m_lines.count())
+ return 0;
+ int retval=6666666;
+ for (unsigned i=0;i < m_lines.count(); i++)
+ {
+ retval = kMin ( retval, int( m_lines[i]->m_x ));
+ }
+ return retval;
+}
+
+int RenderText::inlineXPos() const
+{
+ return minXPos();
+}
+
+int RenderText::inlineYPos() const
+{
+ return m_lines.isEmpty() ? 0 : m_lines[0]->yPos();
+}
+
+const QFont &RenderText::font()
+{
+ return style()->font();
+}
+
+void RenderText::setText(DOMStringImpl *text, bool force)
+{
+ if( !force && str == text ) return;
+
+ DOMStringImpl *oldstr = str;
+ if(text && style())
+ str = text->collapseWhiteSpace(style()->preserveLF(), style()->preserveWS());
+ else
+ str = text;
+ if(str) str->ref();
+ if(oldstr) oldstr->deref();
+
+ if ( str && style() ) {
+ oldstr = str;
+ switch(style()->textTransform()) {
+ case CAPITALIZE:
+ {
+ RenderObject *o;
+ bool runOnString = false;
+
+ // find previous non-empty text renderer if one exists
+ for (o = previousRenderer(); o; o = o->previousRenderer()) {
+ if (!o->isInlineFlow()) {
+ if (!o->isText())
+ break;
+
+ DOMStringImpl *prevStr = static_cast<RenderText*>(o)->string();
+ // !prevStr can happen with css like "content:open-quote;"
+ if (!prevStr)
+ break;
+
+ if (prevStr->length() == 0)
+ continue;
+ QChar c = (*prevStr)[prevStr->length() - 1];
+ if (!c.isSpace())
+ runOnString = true;
+
+ break;
+ }
+ }
+
+ str = str->capitalize(runOnString);
+ }
+ break;
+
+
+
+ case UPPERCASE: str = str->upper(); break;
+ case LOWERCASE: str = str->lower(); break;
+ case NONE:
+ default:;
+ }
+ str->ref();
+ oldstr->deref();
+ }
+
+ // ### what should happen if we change the text of a
+ // RenderBR object ?
+ KHTMLAssert(!isBR() || (str->l == 1 && (*str->s) == '\n'));
+ KHTMLAssert(!str->l || str->s);
+
+ setNeedsLayoutAndMinMaxRecalc();
+#ifdef BIDI_DEBUG
+ QConstString cstr(str->s, str->l);
+ kdDebug( 6040 ) << "RenderText::setText( " << cstr.string().length() << " ) '" << cstr.string() << "'" << endl;
+#endif
+}
+
+int RenderText::height() const
+{
+ 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();
+
+ return retval;
+}
+
+short RenderText::lineHeight( bool firstLine ) const
+{
+ if ( firstLine )
+ return RenderObject::lineHeight( firstLine );
+
+ return m_lineHeight;
+}
+
+short RenderText::baselinePosition( bool firstLine ) const
+{
+ const QFontMetrics &fm = metrics( firstLine );
+ return fm.ascent() +
+ ( lineHeight( firstLine ) - fm.height() ) / 2;
+}
+
+InlineBox* RenderText::createInlineBox(bool, bool isRootLineBox)
+{
+ KHTMLAssert( !isRootLineBox );
+ return new (renderArena()) InlineTextBox(this);
+}
+
+void RenderText::position(InlineBox* box, int from, int len, bool reverse)
+{
+//kdDebug(6040) << "position: from="<<from<<" len="<<len<<endl;
+ // ### should not be needed!!!
+ // asserts sometimes with pre (that unibw-hamburg testcase). ### find out why
+ //KHTMLAssert(!(len == 0 || (str->l && len == 1 && *(str->s+from) == '\n') ));
+ // It is now needed. BRs need text boxes too otherwise caret navigation
+ // gets stuck (LS)
+ // if (len == 0 || (str->l && len == 1 && *(str->s+from) == '\n') ) return;
+
+ reverse = reverse && !style()->visuallyOrdered();
+
+#ifdef DEBUG_LAYOUT
+ QChar *ch = str->s+from;
+ QConstString cstr(ch, len);
+#endif
+
+ KHTMLAssert(box->isInlineTextBox());
+ InlineTextBox *s = static_cast<InlineTextBox *>(box);
+ s->m_start = from;
+ s->m_len = len;
+ s->m_reversed = reverse;
+ //kdDebug(6040) << "m_start: " << s->m_start << " m_len: " << s->m_len << endl;
+
+ if(m_lines.count() == m_lines.size())
+ m_lines.resize(m_lines.size()*2+1);
+
+ m_lines.insert(m_lines.count(), s);
+ //kdDebug(6040) << this << " " << renderName() << "::position inserted" << endl;
+}
+
+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;
+
+ const Font *f = htmlFont( firstLine );
+ return width( from, len, f );
+}
+
+unsigned int RenderText::width(unsigned int from, unsigned int len, const Font *f) const
+{
+ if(!str->s || from > str->l ) return 0;
+ if ( from + len > str->l ) len = str->l - from;
+
+ if ( f == &style()->htmlFont() && from == 0 && len == str->l )
+ return m_maxWidth;
+
+ int w = f->width(str->s, str->l, from, len );
+
+ //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++) {
+ InlineTextBox* 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 = kMax(0, maxx-minx);
+
+ return w;
+}
+
+void RenderText::repaint(Priority p)
+{
+ RenderObject *cb = containingBlock();
+ if(cb)
+ cb->repaint(p);
+}
+
+bool RenderText::isFixedWidthFont() const
+{
+ return QFontInfo(style()->font()).fixedPitch();
+}
+
+short RenderText::verticalPositionHint( bool firstLine ) const
+{
+ return parent()->verticalPositionHint( firstLine );
+}
+
+const QFontMetrics &RenderText::metrics(bool firstLine) const
+{
+ if( firstLine && hasFirstLine() ) {
+ RenderStyle *pseudoStyle = style()->getPseudoStyle(RenderStyle::FIRST_LINE);
+ if ( pseudoStyle )
+ return pseudoStyle->fontMetrics();
+ }
+ return style()->fontMetrics();
+}
+
+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;
+}
+
+bool RenderText::containsOnlyWhitespace(unsigned int from, unsigned int len) const
+{
+ unsigned int currPos;
+ for (currPos = from;
+ currPos < from+len && (str->s[currPos] == '\n' || str->s[currPos].direction() == QChar::DirWS);
+ currPos++);
+ return currPos >= (from+len);
+}
+
+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)
+{
+ bool preserveWS = style()->preserveWS();
+ bool preserveLF = style()->preserveLF();
+ bool autoWrap = style()->autoWrap();
+ if (preserveWS)
+ stripFrontSpaces = false;
+
+ int len = str->l;
+ if (len == 0 || (stripFrontSpaces && str->containsOnlyWhitespace())) {
+ maxW = 0;
+ hasBreak = false;
+ return;
+ }
+
+ minW = m_minWidth;
+ maxW = m_maxWidth;
+ beginWS = stripFrontSpaces ? false : m_hasBeginWS;
+ endWS = m_hasEndWS;
+
+ beginMinW = m_beginMinWidth;
+ endMinW = m_endMinWidth;
+
+ hasBreakableChar = m_hasBreakableChar;
+ hasBreak = m_hasBreak;
+
+ if (stripFrontSpaces && (str->s[0].direction() == QChar::DirWS || (!preserveLF && str->s[0] == '\n'))) {
+ const Font *f = htmlFont( false );
+ QChar space[1]; space[0] = ' ';
+ int spaceWidth = f->width(space, 1, 0);
+ maxW -= spaceWidth;
+ }
+
+ stripFrontSpaces = !preserveWS && m_hasEndWS;
+
+ if (!autoWrap)
+ minW = maxW;
+ else if (minW > maxW)
+ minW = maxW;
+
+ // Compute our max widths by scanning the string for newlines.
+ if (hasBreak) {
+ const Font *f = htmlFont( false );
+ bool firstLine = true;
+ beginMaxW = endMaxW = maxW;
+ for(int i = 0; i < len; i++)
+ {
+ int linelen = 0;
+ while( i+linelen < len && str->s[i+linelen] != '\n')
+ linelen++;
+
+ if (linelen)
+ {
+#ifndef APPLE_CHANGES
+ endMaxW = f->width(str->s, str->l, i, linelen);
+#else
+ endMaxW = widthFromCache(f, i, linelen);
+#endif
+ if (firstLine) {
+ firstLine = false;
+ beginMaxW = endMaxW;
+ }
+ i += linelen;
+ }
+ else if (firstLine) {
+ beginMaxW = 0;
+ firstLine = false;
+ }
+ if (i == len-1)
+ // A <pre> run that ends with a newline, as in, e.g.,
+ // <pre>Some text\n\n<span>More text</pre>
+ endMaxW = 0;
+ }
+ }
+}
+
+#ifdef ENABLE_DUMP
+
+static QString quoteAndEscapeNonPrintables(const QString &s)
+{
+ QString result;
+ result += '"';
+ for (uint i = 0; i != s.length(); ++i) {
+ QChar c = s.at(i);
+ if (c == '\\') {
+ result += "\\\\";
+ } else if (c == '"') {
+ result += "\\\"";
+ } else {
+ ushort u = c.unicode();
+ if (u >= 0x20 && u < 0x7F) {
+ result += c;
+ } else {
+ QString hex;
+ hex.sprintf("\\x{%X}", u);
+ result += hex;
+ }
+ }
+ }
+ result += '"';
+ return result;
+}
+
+static void writeTextRun(QTextStream &ts, const RenderText &o, const InlineTextBox &run)
+{
+ ts << "text run at (" << run.m_x << "," << run.m_y << ") width " << run.m_width << ": "
+ << quoteAndEscapeNonPrintables(o.data().string().mid(run.m_start, run.m_len));
+}
+
+void RenderText::dump(QTextStream &stream, const QString &ind) const
+{
+ RenderObject::dump( stream, ind );
+
+ for (unsigned int i = 0; i < m_lines.count(); i++) {
+ stream << endl << ind << " ";
+ writeTextRun(stream, *this, *m_lines[i]);
+ }
+}
+#endif
+
+RenderTextFragment::RenderTextFragment(DOM::NodeImpl* _node, DOM::DOMStringImpl* _str,
+ int startOffset, int endOffset)
+:RenderText(_node, _str->substring(startOffset, endOffset)),
+m_start(startOffset), m_end(endOffset), m_generatedContentStr(0)
+{}
+
+RenderTextFragment::RenderTextFragment(DOM::NodeImpl* _node, DOM::DOMStringImpl* _str)
+:RenderText(_node, _str), m_start(0)
+{
+ m_generatedContentStr = _str;
+ if (_str) {
+ _str->ref();
+ m_end = _str->l;
+ }
+ else
+ m_end = 0;
+}
+
+RenderTextFragment::~RenderTextFragment()
+{
+ if (m_generatedContentStr)
+ m_generatedContentStr->deref();
+}
+
+bool RenderTextFragment::isTextFragment() const
+{
+ return true;
+}
+
+DOM::DOMStringImpl* RenderTextFragment::originalString() const
+{
+ DOM::DOMStringImpl* result = 0;
+ if (element())
+ result = element()->string();
+ else
+ result = contentString();
+ if (result && (start() > 0 || start() < result->l))
+ result = result->substring(start(), end());
+ return result;
+}
+
+#undef BIDI_DEBUG
+#undef DEBUG_LAYOUT