src/gui/text/qtextlayout.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/text/qtextlayout.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,2544 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextlayout.h"
+#include "qtextengine_p.h"
+
+#include <qfont.h>
+#include <qapplication.h>
+#include <qpainter.h>
+#include <qvarlengtharray.h>
+#include <qtextformat.h>
+#include <qabstracttextdocumentlayout.h>
+#include "qtextdocument_p.h"
+#include "qtextformat_p.h"
+#include "qstyleoption.h"
+#include "qpainterpath.h"
+#include <limits.h>
+
+#include <qdebug.h>
+
+#include "qfontengine_p.h"
+
+QT_BEGIN_NAMESPACE
+
+#define ObjectSelectionBrush (QTextFormat::ForegroundBrush + 1)
+#define SuppressText 0x5012
+#define SuppressBackground 0x513
+
+static inline QFixed leadingSpaceWidth(QTextEngine *eng, const QScriptLine &line)
+{
+    if (!line.hasTrailingSpaces
+        || (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
+        || !(eng->option.alignment() & Qt::AlignRight)
+        || (eng->option.textDirection() != Qt::RightToLeft))
+        return QFixed();
+
+    int pos = line.length;
+    const HB_CharAttributes *attributes = eng->attributes();
+    while (pos > 0 && attributes[line.from + pos - 1].whiteSpace)
+        --pos;
+    return eng->width(line.from + pos, line.length - pos);
+}
+
+static QFixed alignLine(QTextEngine *eng, const QScriptLine &line)
+{
+    QFixed x = 0;
+    eng->justify(line);
+    // if width is QFIXED_MAX that means we used setNumColumns() and that implicitly makes this line left aligned.
+    if (!line.justified && line.width != QFIXED_MAX) {
+        int align = eng->option.alignment();
+        if (align & Qt::AlignJustify && eng->option.textDirection() == Qt::RightToLeft)
+            align = Qt::AlignRight;
+        if (align & Qt::AlignRight)
+            x = line.width - (line.textWidth + leadingSpaceWidth(eng, line));
+        else if (align & Qt::AlignHCenter)
+            x = (line.width - line.textWidth)/2;
+    }
+    return x;
+}
+
+/*!
+    \class QTextLayout::FormatRange
+    \reentrant
+
+    \brief The QTextLayout::FormatRange structure is used to apply extra formatting information
+    for a specified area in the text layout's content.
+
+    \sa QTextLayout::setAdditionalFormats(), QTextLayout::draw()
+*/
+
+/*!
+    \variable QTextLayout::FormatRange::start
+    Specifies the beginning of the format range within the text layout's text.
+*/
+
+/*!
+    \variable QTextLayout::FormatRange::length
+    Specifies the numer of characters the format range spans.
+*/
+
+/*!
+    \variable QTextLayout::FormatRange::format
+    Specifies the format to apply.
+*/
+
+/*!
+    \class QTextInlineObject
+    \reentrant
+
+    \brief The QTextInlineObject class represents an inline object in
+    a QTextLayout.
+
+    \ingroup richtext-processing
+
+    This class is only used if the text layout is used to lay out
+    parts of a QTextDocument.
+
+    The inline object has various attributes that can be set, for
+    example using, setWidth(), setAscent(), and setDescent(). The
+    rectangle it occupies is given by rect(), and its direction by
+    isRightToLeft(). Its position in the text layout is given by at(),
+    and its format is given by format().
+*/
+
+/*!
+    \fn QTextInlineObject::QTextInlineObject(int i, QTextEngine *e)
+
+    Creates a new inline object for the item at position \a i in the
+    text engine \a e.
+*/
+
+/*!
+    \fn QTextInlineObject::QTextInlineObject()
+
+    \internal
+*/
+
+/*!
+    \fn bool QTextInlineObject::isValid() const
+
+    Returns true if this inline object is valid; otherwise returns
+    false.
+*/
+
+/*!
+    Returns the inline object's rectangle.
+
+    \sa ascent() descent() width()
+*/
+QRectF QTextInlineObject::rect() const
+{
+    QScriptItem& si = eng->layoutData->items[itm];
+    return QRectF(0, -si.ascent.toReal(), si.width.toReal(), si.height().toReal());
+}
+
+/*!
+    Returns the inline object's width.
+
+    \sa ascent() descent() rect()
+*/
+qreal QTextInlineObject::width() const
+{
+    return eng->layoutData->items[itm].width.toReal();
+}
+
+/*!
+    Returns the inline object's ascent.
+
+    \sa descent() width() rect()
+*/
+qreal QTextInlineObject::ascent() const
+{
+    return eng->layoutData->items[itm].ascent.toReal();
+}
+
+/*!
+    Returns the inline object's descent.
+
+    \sa ascent() width() rect()
+*/
+qreal QTextInlineObject::descent() const
+{
+    return eng->layoutData->items[itm].descent.toReal();
+}
+
+/*!
+    Returns the inline object's total height. This is equal to
+    ascent() + descent() + 1.
+
+    \sa ascent() descent() width() rect()
+*/
+qreal QTextInlineObject::height() const
+{
+    return eng->layoutData->items[itm].height().toReal();
+}
+
+
+/*!
+    Sets the inline object's width to \a w.
+
+    \sa width() ascent() descent() rect()
+*/
+void QTextInlineObject::setWidth(qreal w)
+{
+    eng->layoutData->items[itm].width = QFixed::fromReal(w);
+}
+
+/*!
+    Sets the inline object's ascent to \a a.
+
+    \sa ascent() setDescent() width() rect()
+*/
+void QTextInlineObject::setAscent(qreal a)
+{
+    eng->layoutData->items[itm].ascent = QFixed::fromReal(a);
+}
+
+/*!
+    Sets the inline object's decent to \a d.
+
+    \sa descent() setAscent() width() rect()
+*/
+void QTextInlineObject::setDescent(qreal d)
+{
+    eng->layoutData->items[itm].descent = QFixed::fromReal(d);
+}
+
+/*!
+  The position of the inline object within the text layout.
+*/
+int QTextInlineObject::textPosition() const
+{
+    return eng->layoutData->items[itm].position;
+}
+
+/*!
+  Returns an integer describing the format of the inline object
+  within the text layout.
+*/
+int QTextInlineObject::formatIndex() const
+{
+    return eng->formatIndex(&eng->layoutData->items[itm]);
+}
+
+/*!
+  Returns format of the inline object within the text layout.
+*/
+QTextFormat QTextInlineObject::format() const
+{
+    if (!eng->block.docHandle())
+        return QTextFormat();
+    return eng->formats()->format(eng->formatIndex(&eng->layoutData->items[itm]));
+}
+
+/*!
+  Returns if the object should be laid out right-to-left or left-to-right.
+*/
+Qt::LayoutDirection QTextInlineObject::textDirection() const
+{
+    return (eng->layoutData->items[itm].analysis.bidiLevel % 2 ? Qt::RightToLeft : Qt::LeftToRight);
+}
+
+/*!
+    \class QTextLayout
+    \reentrant
+
+    \brief The QTextLayout class is used to lay out and paint a single
+    paragraph of text.
+
+    \ingroup richtext-processing
+
+    It offers most features expected from a modern text layout
+    engine, including Unicode compliant rendering, line breaking and
+    handling of cursor positioning. It can also produce and render
+    device independent layout, something that is important for WYSIWYG
+    applications.
+
+    The class has a rather low level API and unless you intend to
+    implement your own text rendering for some specialized widget, you
+    probably won't need to use it directly.
+
+    QTextLayout can currently deal with plain text and rich text
+    paragraphs that are part of a QTextDocument.
+
+    QTextLayout can be used to create a sequence of QTextLine's with
+    given widths and can position them independently on the screen.
+    Once the layout is done, these lines can be drawn on a paint
+    device.
+
+    Here's some pseudo code that presents the layout phase:
+    \snippet doc/src/snippets/code/src_gui_text_qtextlayout.cpp 0
+
+    The text can be drawn by calling the layout's draw() function:
+    \snippet doc/src/snippets/code/src_gui_text_qtextlayout.cpp 1
+
+    The text layout's text is set in the constructor or with
+    setText(). The layout can be seen as a sequence of QTextLine
+    objects; use lineAt() or lineForTextPosition() to get a QTextLine,
+    createLine() to create one. For a given position in the text you
+    can find a valid cursor position with isValidCursorPosition(),
+    nextCursorPosition(), and previousCursorPosition(). The layout
+    itself can be positioned with setPosition(); it has a
+    boundingRect(), and a minimumWidth() and a maximumWidth(). A text
+    layout can be drawn on a painter device using draw().
+
+*/
+
+/*!
+    \enum QTextLayout::CursorMode
+
+    \value SkipCharacters
+    \value SkipWords
+*/
+
+/*!
+    \fn QTextEngine *QTextLayout::engine() const
+    \internal
+
+    Returns the text engine used to render the text layout.
+*/
+
+/*!
+    Constructs an empty text layout.
+
+    \sa setText()
+*/
+QTextLayout::QTextLayout()
+{ d = new QTextEngine(); }
+
+/*!
+    Constructs a text layout to lay out the given \a text.
+*/
+QTextLayout::QTextLayout(const QString& text)
+{
+    d = new QTextEngine();
+    d->text = text;
+}
+
+/*!
+    Constructs a text layout to lay out the given \a text with the specified
+    \a font.
+
+    All the metric and layout calculations will be done in terms of
+    the paint device, \a paintdevice. If \a paintdevice is 0 the
+    calculations will be done in screen metrics.
+*/
+QTextLayout::QTextLayout(const QString& text, const QFont &font, QPaintDevice *paintdevice)
+{
+    QFont f(font);
+    if (paintdevice)
+        f = QFont(font, paintdevice);
+    d = new QTextEngine((text.isNull() ? (const QString&)QString::fromLatin1("") : text), f.d.data());
+}
+
+/*!
+    \internal
+    Constructs a text layout to lay out the given \a block.
+*/
+QTextLayout::QTextLayout(const QTextBlock &block)
+{
+    d = new QTextEngine();
+    d->block = block;
+}
+
+/*!
+    Destructs the layout.
+*/
+QTextLayout::~QTextLayout()
+{
+    if (!d->stackEngine)
+        delete d;
+}
+
+/*!
+    Sets the layout's font to the given \a font. The layout is
+    invalidated and must be laid out again.
+
+    \sa text()
+*/
+void QTextLayout::setFont(const QFont &font)
+{
+    d->fnt = font;
+}
+
+/*!
+    Returns the current font that is used for the layout, or a default
+    font if none is set.
+*/
+QFont QTextLayout::font() const
+{
+    return d->font();
+}
+
+/*!
+    Sets the layout's text to the given \a string. The layout is
+    invalidated and must be laid out again.
+
+    Notice that when using this QTextLayout as part of a QTextDocument this
+    method will have no effect.
+
+    \sa text()
+*/
+void QTextLayout::setText(const QString& string)
+{
+    d->invalidate();
+    d->clearLineData();
+    d->text = string;
+}
+
+/*!
+    Returns the layout's text.
+
+    \sa setText()
+*/
+QString QTextLayout::text() const
+{
+    return d->text;
+}
+
+/*!
+  Sets the text option structure that controls the layout process to the
+  given \a option.
+
+  \sa textOption() QTextOption
+*/
+void QTextLayout::setTextOption(const QTextOption &option)
+{
+    d->option = option;
+}
+
+/*!
+  Returns the current text option used to control the layout process.
+
+  \sa setTextOption() QTextOption
+*/
+QTextOption QTextLayout::textOption() const
+{
+    return d->option;
+}
+
+/*!
+    Sets the \a position and \a text of the area in the layout that is
+    processed before editing occurs.
+*/
+void QTextLayout::setPreeditArea(int position, const QString &text)
+{
+    if (text.isEmpty()) {
+        if (!d->specialData)
+            return;
+        if (d->specialData->addFormats.isEmpty()) {
+            delete d->specialData;
+            d->specialData = 0;
+        } else {
+            d->specialData->preeditText = QString();
+            d->specialData->preeditPosition = -1;
+        }
+    } else {
+        if (!d->specialData)
+            d->specialData = new QTextEngine::SpecialData;
+        d->specialData->preeditPosition = position;
+        d->specialData->preeditText = text;
+    }
+    d->invalidate();
+    d->clearLineData();
+    if (d->block.docHandle())
+        d->block.docHandle()->documentChange(d->block.position(), d->block.length());
+}
+
+/*!
+    Returns the position of the area in the text layout that will be
+    processed before editing occurs.
+*/
+int QTextLayout::preeditAreaPosition() const
+{
+    return d->specialData ? d->specialData->preeditPosition : -1;
+}
+
+/*!
+    Returns the text that is inserted in the layout before editing occurs.
+*/
+QString QTextLayout::preeditAreaText() const
+{
+    return d->specialData ? d->specialData->preeditText : QString();
+}
+
+
+/*!
+    Sets the additional formats supported by the text layout to \a
+    formatList.
+
+    \sa additionalFormats(), clearAdditionalFormats()
+*/
+void QTextLayout::setAdditionalFormats(const QList<FormatRange> &formatList)
+{
+    if (formatList.isEmpty()) {
+        if (!d->specialData)
+            return;
+        if (d->specialData->preeditText.isEmpty()) {
+            delete d->specialData;
+            d->specialData = 0;
+        } else {
+            d->specialData->addFormats = formatList;
+            d->specialData->addFormatIndices.clear();
+        }
+    } else {
+        if (!d->specialData) {
+            d->specialData = new QTextEngine::SpecialData;
+            d->specialData->preeditPosition = -1;
+        }
+        d->specialData->addFormats = formatList;
+        d->indexAdditionalFormats();
+    }
+    if (d->block.docHandle())
+        d->block.docHandle()->documentChange(d->block.position(), d->block.length());
+}
+
+/*!
+    Returns the list of additional formats supported by the text layout.
+
+    \sa setAdditionalFormats(), clearAdditionalFormats()
+*/
+QList<QTextLayout::FormatRange> QTextLayout::additionalFormats() const
+{
+    QList<FormatRange> formats;
+    if (!d->specialData)
+        return formats;
+
+    formats = d->specialData->addFormats;
+
+    if (d->specialData->addFormatIndices.isEmpty())
+        return formats;
+
+    const QTextFormatCollection *collection = d->formats();
+
+    for (int i = 0; i < d->specialData->addFormatIndices.count(); ++i)
+        formats[i].format = collection->charFormat(d->specialData->addFormatIndices.at(i));
+
+    return formats;
+}
+
+/*!
+    Clears the list of additional formats supported by the text layout.
+
+    \sa additionalFormats(), setAdditionalFormats()
+*/
+void QTextLayout::clearAdditionalFormats()
+{
+    setAdditionalFormats(QList<FormatRange>());
+}
+
+/*!
+    Enables caching of the complete layout information if \a enable is
+    true; otherwise disables layout caching. Usually
+    QTextLayout throws most of the layouting information away after a
+    call to endLayout() to reduce memory consumption. If you however
+    want to draw the laid out text directly afterwards enabling caching
+    might speed up drawing significantly.
+
+    \sa cacheEnabled()
+*/
+void QTextLayout::setCacheEnabled(bool enable)
+{
+    d->cacheGlyphs = enable;
+}
+
+/*!
+    Returns true if the complete layout information is cached; otherwise
+    returns false.
+
+    \sa setCacheEnabled()
+*/
+bool QTextLayout::cacheEnabled() const
+{
+    return d->cacheGlyphs;
+}
+
+/*!
+    Begins the layout process.
+*/
+void QTextLayout::beginLayout()
+{
+#ifndef QT_NO_DEBUG
+    if (d->layoutData && d->layoutData->inLayout) {
+        qWarning("QTextLayout::beginLayout: Called while already doing layout");
+        return;
+    }
+#endif
+    d->invalidate();
+    d->clearLineData();
+    d->itemize();
+    d->layoutData->inLayout = true;
+}
+
+/*!
+    Ends the layout process.
+*/
+void QTextLayout::endLayout()
+{
+#ifndef QT_NO_DEBUG
+    if (!d->layoutData || !d->layoutData->inLayout) {
+        qWarning("QTextLayout::endLayout: Called without beginLayout()");
+        return;
+    }
+#endif
+    int l = d->lines.size();
+    if (l && d->lines.at(l-1).length < 0) {
+        QTextLine(l-1, d).setNumColumns(INT_MAX);
+    }
+    d->layoutData->inLayout = false;
+    if (!d->cacheGlyphs)
+        d->freeMemory();
+}
+
+/*!  \since 4.4
+
+Clears the line information in the layout. After having called
+this function, lineCount() returns 0.
+ */
+void QTextLayout::clearLayout()
+{
+    d->clearLineData();
+}
+
+
+/*!
+    Returns the next valid cursor position after \a oldPos that
+    respects the given cursor \a mode.
+
+    \sa isValidCursorPosition() previousCursorPosition()
+*/
+int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const
+{
+//      qDebug("looking for next cursor pos for %d", oldPos);
+    const HB_CharAttributes *attributes = d->attributes();
+    if (!attributes)
+        return 0;
+    int len = d->block.isValid() ?
+              (d->block.length() - 1)
+              : d->layoutData->string.length();
+
+    if (oldPos >= len)
+        return oldPos;
+    if (mode == SkipCharacters) {
+        oldPos++;
+        while (oldPos < len && !attributes[oldPos].charStop)
+            oldPos++;
+    } else {
+        if (oldPos < len && d->atWordSeparator(oldPos)) {
+            oldPos++;
+            while (oldPos < len && d->atWordSeparator(oldPos))
+                oldPos++;
+        } else {
+            while (oldPos < len && !d->atSpace(oldPos) && !d->atWordSeparator(oldPos))
+                oldPos++;
+        }
+        while (oldPos < len && d->atSpace(oldPos))
+            oldPos++;
+    }
+//      qDebug("  -> %d", oldPos);
+    return oldPos;
+}
+
+/*!
+    Returns the first valid cursor position before \a oldPos that
+    respects the given cursor \a mode.
+
+    \sa isValidCursorPosition() nextCursorPosition()
+*/
+int QTextLayout::previousCursorPosition(int oldPos, CursorMode mode) const
+{
+//     qDebug("looking for previous cursor pos for %d", oldPos);
+    const HB_CharAttributes *attributes = d->attributes();
+    if (!attributes || oldPos <= 0)
+        return 0;
+    if (mode == SkipCharacters) {
+        oldPos--;
+        while (oldPos && !attributes[oldPos].charStop)
+            oldPos--;
+    } else {
+        while (oldPos && d->atSpace(oldPos-1))
+            oldPos--;
+
+        if (oldPos && d->atWordSeparator(oldPos-1)) {
+            oldPos--;
+            while (oldPos && d->atWordSeparator(oldPos-1))
+                oldPos--;
+        } else {
+            while (oldPos && !d->atSpace(oldPos-1) && !d->atWordSeparator(oldPos-1))
+                oldPos--;
+        }
+    }
+//     qDebug("  -> %d", oldPos);
+    return oldPos;
+}
+
+/*!
+    Returns true if position \a pos is a valid cursor position.
+
+    In a Unicode context some positions in the text are not valid
+    cursor positions, because the position is inside a Unicode
+    surrogate or a grapheme cluster.
+
+    A grapheme cluster is a sequence of two or more Unicode characters
+    that form one indivisible entity on the screen. For example the
+    latin character `\Auml' can be represented in Unicode by two
+    characters, `A' (0x41), and the combining diaresis (0x308). A text
+    cursor can only validly be positioned before or after these two
+    characters, never between them since that wouldn't make sense. In
+    indic languages every syllable forms a grapheme cluster.
+*/
+bool QTextLayout::isValidCursorPosition(int pos) const
+{
+    const HB_CharAttributes *attributes = d->attributes();
+    if (!attributes || pos < 0 || pos > (int)d->layoutData->string.length())
+        return false;
+    return attributes[pos].charStop;
+}
+
+
+/*!
+    Returns a new text line to be laid out if there is text to be
+    inserted into the layout; otherwise returns an invalid text line.
+
+    The text layout creates a new line object that starts after the
+    last line in the layout, or at the beginning if the layout is empty.
+    The layout maintains an internal cursor, and each line is filled
+    with text from the cursor position onwards when the
+    QTextLine::setLineWidth() function is called.
+
+    Once QTextLine::setLineWidth() is called, a new line can be created and
+    filled with text. Repeating this process will lay out the whole block
+    of text contained in the QTextLayout. If there is no text left to be
+    inserted into the layout, the QTextLine returned will not be valid
+    (isValid() will return false).
+*/
+QTextLine QTextLayout::createLine()
+{
+#ifndef QT_NO_DEBUG
+    if (!d->layoutData || !d->layoutData->inLayout) {
+        qWarning("QTextLayout::createLine: Called without layouting");
+        return QTextLine();
+    }
+#endif
+    int l = d->lines.size();
+    if (l && d->lines.at(l-1).length < 0) {
+        QTextLine(l-1, d).setNumColumns(INT_MAX);
+    }
+    int from = l > 0 ? d->lines.at(l-1).from + d->lines.at(l-1).length : 0;
+    int strlen = d->layoutData->string.length();
+    if (l && from >= strlen) {
+        if (!d->lines.at(l-1).length || d->layoutData->string.at(strlen - 1) != QChar::LineSeparator)
+            return QTextLine();
+    }
+
+    QScriptLine line;
+    line.from = from;
+    line.length = -1;
+    line.justified = false;
+    line.gridfitted = false;
+
+    d->lines.append(line);
+    return QTextLine(l, d);
+}
+
+/*!
+    Returns the number of lines in this text layout.
+
+    \sa lineAt()
+*/
+int QTextLayout::lineCount() const
+{
+    return d->lines.size();
+}
+
+/*!
+    Returns the \a{i}-th line of text in this text layout.
+
+    \sa lineCount() lineForTextPosition()
+*/
+QTextLine QTextLayout::lineAt(int i) const
+{
+    return QTextLine(i, d);
+}
+
+/*!
+    Returns the line that contains the cursor position specified by \a pos.
+
+    \sa isValidCursorPosition() lineAt()
+*/
+QTextLine QTextLayout::lineForTextPosition(int pos) const
+{
+    for (int i = 0; i < d->lines.size(); ++i) {
+        const QScriptLine& line = d->lines[i];
+        if (line.from + (int)line.length > pos)
+            return QTextLine(i, d);
+    }
+    if (!d->layoutData)
+        d->itemize();
+    if (pos == d->layoutData->string.length() && d->lines.size())
+        return QTextLine(d->lines.size()-1, d);
+    return QTextLine();
+}
+
+/*!
+    \since 4.2
+
+    The global position of the layout. This is independent of the
+    bounding rectangle and of the layout process.
+
+    \sa setPosition()
+*/
+QPointF QTextLayout::position() const
+{
+    return d->position;
+}
+
+/*!
+    Moves the text layout to point \a p.
+
+    \sa position()
+*/
+void QTextLayout::setPosition(const QPointF &p)
+{
+    d->position = p;
+}
+
+/*!
+    The smallest rectangle that contains all the lines in the layout.
+*/
+QRectF QTextLayout::boundingRect() const
+{
+    if (d->lines.isEmpty())
+        return QRectF();
+
+    QFixed xmax, ymax;
+    QFixed xmin = d->lines.at(0).x;
+    QFixed ymin = d->lines.at(0).y;
+
+    for (int i = 0; i < d->lines.size(); ++i) {
+        const QScriptLine &si = d->lines[i];
+        xmin = qMin(xmin, si.x);
+        ymin = qMin(ymin, si.y);
+        xmax = qMax(xmax, si.x+qMax(si.width, si.textWidth));
+        // ### shouldn't the ascent be used in ymin???
+        ymax = qMax(ymax, si.y+si.ascent+si.descent+1);
+    }
+    return QRectF(xmin.toReal(), ymin.toReal(), (xmax-xmin).toReal(), (ymax-ymin).toReal());
+}
+
+/*!
+    The minimum width the layout needs. This is the width of the
+    layout's smallest non-breakable substring.
+
+    \warning This function only returns a valid value after the layout
+    has been done.
+
+    \sa maximumWidth()
+*/
+qreal QTextLayout::minimumWidth() const
+{
+    return d->minWidth.toReal();
+}
+
+/*!
+    The maximum width the layout could expand to; this is essentially
+    the width of the entire text.
+
+    \warning This function only returns a valid value after the layout
+    has been done.
+
+    \sa minimumWidth()
+*/
+qreal QTextLayout::maximumWidth() const
+{
+    return d->maxWidth.toReal();
+}
+
+/*!
+  \internal
+*/
+void QTextLayout::setFlags(int flags)
+{
+    if (flags & Qt::TextJustificationForced) {
+        d->option.setAlignment(Qt::AlignJustify);
+        d->forceJustification = true;
+    }
+
+    if (flags & (Qt::TextForceLeftToRight|Qt::TextForceRightToLeft)) {
+        d->ignoreBidi = true;
+        d->option.setTextDirection((flags & Qt::TextForceLeftToRight) ? Qt::LeftToRight : Qt::RightToLeft);
+    }
+}
+
+struct QTextLineItemIterator
+{
+    QTextLineItemIterator(QTextEngine *eng, int lineNum, const QPointF &pos = QPointF(),
+                          const QTextLayout::FormatRange *_selection = 0);
+
+    inline bool atEnd() const { return logicalItem >= nItems - 1; }
+    QScriptItem &next();
+
+    bool getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const;
+    inline bool isOutsideSelection() const {
+        QFixed tmp1, tmp2;
+        return !getSelectionBounds(&tmp1, &tmp2);
+    }
+
+    QTextEngine *eng;
+
+    QFixed x;
+    QFixed pos_x;
+    const QScriptLine &line;
+    QScriptItem *si;
+
+    int lineEnd;
+    int firstItem;
+    int lastItem;
+    int nItems;
+    int logicalItem;
+    int item;
+    int itemLength;
+
+    int glyphsStart;
+    int glyphsEnd;
+    int itemStart;
+    int itemEnd;
+
+    QFixed itemWidth;
+
+    QVarLengthArray<int> visualOrder;
+    QVarLengthArray<uchar> levels;
+
+    const QTextLayout::FormatRange *selection;
+};
+
+QTextLineItemIterator::QTextLineItemIterator(QTextEngine *_eng, int lineNum, const QPointF &pos,
+                                             const QTextLayout::FormatRange *_selection)
+    : eng(_eng),
+      line(eng->lines[lineNum]),
+      si(0),
+      lineEnd(line.from + line.length),
+      firstItem(eng->findItem(line.from)),
+      lastItem(eng->findItem(lineEnd - 1)),
+      nItems((firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0),
+      logicalItem(-1),
+      item(-1),
+      visualOrder(nItems),
+      levels(nItems),
+      selection(_selection)
+{
+    pos_x = x = QFixed::fromReal(pos.x());
+
+    x += line.x;
+
+    x += alignLine(eng, line);
+
+    for (int i = 0; i < nItems; ++i)
+        levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
+    QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
+
+    eng->shapeLine(line);
+}
+
+QScriptItem &QTextLineItemIterator::next()
+{
+    x += itemWidth;
+
+    ++logicalItem;
+    item = visualOrder[logicalItem] + firstItem;
+    itemLength = eng->length(item);
+    si = &eng->layoutData->items[item];
+    if (!si->num_glyphs)
+        eng->shape(item);
+
+    if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
+        itemWidth = si->width;
+        return *si;
+    }
+
+    unsigned short *logClusters = eng->logClusters(si);
+    QGlyphLayout glyphs = eng->shapedGlyphs(si);
+
+    itemStart = qMax(line.from, si->position);
+    glyphsStart = logClusters[itemStart - si->position];
+    if (lineEnd < si->position + itemLength) {
+        itemEnd = lineEnd;
+        glyphsEnd = logClusters[itemEnd-si->position];
+    } else {
+        itemEnd = si->position + itemLength;
+        glyphsEnd = si->num_glyphs;
+    }
+    // show soft-hyphen at line-break
+    if (si->position + itemLength >= lineEnd
+        && eng->layoutData->string.at(lineEnd - 1) == 0x00ad)
+        glyphs.attributes[glyphsEnd - 1].dontPrint = false;
+
+    itemWidth = 0;
+    for (int g = glyphsStart; g < glyphsEnd; ++g)
+        itemWidth += glyphs.effectiveAdvance(g);
+
+    return *si;
+}
+
+bool QTextLineItemIterator::getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const
+{
+    *selectionX = *selectionWidth = 0;
+
+    if (!selection)
+        return false;
+
+    if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
+        if (si->position >= selection->start + selection->length
+            || si->position + itemLength <= selection->start)
+            return false;
+
+        *selectionX = x;
+        *selectionWidth = itemWidth;
+    } else {
+        unsigned short *logClusters = eng->logClusters(si);
+        QGlyphLayout glyphs = eng->shapedGlyphs(si);
+
+        int from = qMax(itemStart, selection->start) - si->position;
+        int to = qMin(itemEnd, selection->start + selection->length) - si->position;
+        if (from >= to)
+            return false;
+
+        int start_glyph = logClusters[from];
+        int end_glyph = (to == eng->length(item)) ? si->num_glyphs : logClusters[to];
+        QFixed soff;
+        QFixed swidth;
+        if (si->analysis.bidiLevel %2) {
+            for (int g = glyphsEnd - 1; g >= end_glyph; --g)
+                soff += glyphs.effectiveAdvance(g);
+            for (int g = end_glyph - 1; g >= start_glyph; --g)
+                swidth += glyphs.effectiveAdvance(g);
+        } else {
+            for (int g = glyphsStart; g < start_glyph; ++g)
+                soff += glyphs.effectiveAdvance(g);
+            for (int g = start_glyph; g < end_glyph; ++g)
+                swidth += glyphs.effectiveAdvance(g);
+        }
+
+        *selectionX = x + soff;
+        *selectionWidth = swidth;
+    }
+    return true;
+}
+
+static void addSelectedRegionsToPath(QTextEngine *eng, int lineNumber, const QPointF &pos, QTextLayout::FormatRange *selection,
+                                     QPainterPath *region, QRectF boundingRect)
+{
+    const QScriptLine &line = eng->lines[lineNumber];
+
+    QTextLineItemIterator iterator(eng, lineNumber, pos, selection);
+
+    const QFixed y = QFixed::fromReal(pos.y()) + line.y + line.ascent;
+
+    const qreal lineHeight = line.height().toReal();
+    const qreal selectionY = (y - line.ascent).toReal();
+
+    QFixed lastSelectionX = iterator.x;
+    QFixed lastSelectionWidth;
+
+    while (!iterator.atEnd()) {
+        iterator.next();
+
+        QFixed selectionX, selectionWidth;
+        if (iterator.getSelectionBounds(&selectionX, &selectionWidth)) {
+            if (selectionX == lastSelectionX + lastSelectionWidth) {
+                lastSelectionWidth += selectionWidth;
+                continue;
+            }
+
+            if (lastSelectionWidth > 0)
+                region->addRect(boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight));
+
+            lastSelectionX = selectionX;
+            lastSelectionWidth = selectionWidth;
+        }
+    }
+    if (lastSelectionWidth > 0)
+        region->addRect(boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight));
+}
+
+static inline QRectF clipIfValid(const QRectF &rect, const QRectF &clip)
+{
+    return clip.isValid() ? (rect & clip) : rect;
+}
+
+/*!
+    Draws the whole layout on the painter \a p at the position specified by
+    \a pos.
+    The rendered layout includes the given \a selections and is clipped within
+    the rectangle specified by \a clip.
+*/
+void QTextLayout::draw(QPainter *p, const QPointF &pos, const QVector<FormatRange> &selections, const QRectF &clip) const
+{
+    if (d->lines.isEmpty())
+        return;
+
+    if (!d->layoutData)
+        d->itemize();
+
+    QPointF position = pos + d->position;
+
+    QFixed clipy = (INT_MIN/256);
+    QFixed clipe = (INT_MAX/256);
+    if (clip.isValid()) {
+        clipy = QFixed::fromReal(clip.y() - position.y());
+        clipe = clipy + QFixed::fromReal(clip.height());
+    }
+
+    int firstLine = 0;
+    int lastLine = d->lines.size();
+    for (int i = 0; i < d->lines.size(); ++i) {
+        QTextLine l(i, d);
+        const QScriptLine &sl = d->lines[i];
+
+        if (sl.y > clipe) {
+            lastLine = i;
+            break;
+        }
+        if ((sl.y + sl.height()) < clipy) {
+            firstLine = i;
+            continue;
+        }
+    }
+
+    QPainterPath excludedRegion;
+    QPainterPath textDoneRegion;
+    for (int i = 0; i < selections.size(); ++i) {
+        FormatRange selection = selections.at(i);
+        const QBrush bg = selection.format.background();
+
+        QPainterPath region;
+        region.setFillRule(Qt::WindingFill);
+
+        for (int line = firstLine; line < lastLine; ++line) {
+            const QScriptLine &sl = d->lines[line];
+            QTextLine tl(line, d);
+
+            QRectF lineRect(tl.naturalTextRect());
+            lineRect.translate(position);
+
+            bool isLastLineInBlock = (line == d->lines.size()-1);
+            int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline
+
+
+            if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start)
+                continue; // no actual intersection
+
+            const bool selectionStartInLine = sl.from <= selection.start;
+            const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length;
+
+            if (sl.length && (selectionStartInLine || selectionEndInLine)) {
+                addSelectedRegionsToPath(d, line, position, &selection, &region, clipIfValid(lineRect, clip));
+            } else {
+                region.addRect(clipIfValid(lineRect, clip));
+            }
+
+            if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) {
+                QRectF fullLineRect(tl.rect());
+                fullLineRect.translate(position);
+                fullLineRect.setRight(QFIXED_MAX);
+                if (!selectionEndInLine)
+                    region.addRect(clipIfValid(QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip));
+                if (!selectionStartInLine)
+                    region.addRect(clipIfValid(QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), clip));
+            } else if (!selectionEndInLine
+                && isLastLineInBlock
+                &&!(d->option.flags() & QTextOption::ShowLineAndParagraphSeparators)) {
+                region.addRect(clipIfValid(QRectF(lineRect.right(), lineRect.top(),
+                                                  lineRect.height()/4, lineRect.height()), clip));
+            }
+
+        }
+        {
+            const QPen oldPen = p->pen();
+            const QBrush oldBrush = p->brush();
+
+            p->setPen(selection.format.penProperty(QTextFormat::OutlinePen));
+            p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush));
+            p->drawPath(region);
+
+            p->setPen(oldPen);
+            p->setBrush(oldBrush);
+        }
+
+
+
+        bool hasText = (selection.format.foreground().style() != Qt::NoBrush);
+        bool hasBackground= (selection.format.background().style() != Qt::NoBrush);
+        
+        if (hasBackground) {
+            selection.format.setProperty(ObjectSelectionBrush, selection.format.property(QTextFormat::BackgroundBrush));
+            // don't just clear the property, set an empty brush that overrides a potential
+            // background brush specified in the text
+            selection.format.setProperty(QTextFormat::BackgroundBrush, QBrush());
+            selection.format.clearProperty(QTextFormat::OutlinePen);
+        }
+
+        selection.format.setProperty(SuppressText, !hasText);
+
+        if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty())
+            continue;
+
+        p->save();
+        p->setClipPath(region, Qt::IntersectClip);
+
+        for (int line = firstLine; line < lastLine; ++line) {
+            QTextLine l(line, d);
+            l.draw(p, position, &selection);
+        }
+        p->restore();
+
+        if (hasText) {
+            textDoneRegion += region;
+        } else {
+            if (hasBackground)
+                textDoneRegion -= region;
+        }
+
+        excludedRegion += region;
+    }
+
+    QPainterPath needsTextButNoBackground = excludedRegion - textDoneRegion;
+    if (!needsTextButNoBackground.isEmpty()){
+        p->save();
+        p->setClipPath(needsTextButNoBackground, Qt::IntersectClip);
+        FormatRange selection;
+        selection.start = 0;
+        selection.length = INT_MAX;
+        selection.format.setProperty(SuppressBackground, true);
+        for (int line = firstLine; line < lastLine; ++line) {
+            QTextLine l(line, d);
+            l.draw(p, position, &selection);
+        }
+        p->restore();
+    }
+
+    if (!excludedRegion.isEmpty()) {
+        p->save();
+        QPainterPath path;
+        QRectF br = boundingRect().translated(position);
+        br.setRight(QFIXED_MAX);
+        if (!clip.isNull())
+            br = br.intersected(clip);
+        path.addRect(br);
+        path -= excludedRegion;
+        p->setClipPath(path, Qt::IntersectClip);
+    }
+
+    for (int i = firstLine; i < lastLine; ++i) {
+        QTextLine l(i, d);
+        l.draw(p, position);
+    }
+    if (!excludedRegion.isEmpty())
+        p->restore();
+
+
+    if (!d->cacheGlyphs)
+        d->freeMemory();
+}
+
+/*!
+  \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition) const
+  \overload
+
+  Draws a text cursor with the current pen at the given \a position using the
+  \a painter specified.
+  The corresponding position within the text is specified by \a cursorPosition.
+*/
+void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
+{
+    drawCursor(p, pos, cursorPosition, 1);
+}
+
+/*!
+  \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition, int width) const
+
+  Draws a text cursor with the current pen and the specified \a width at the given \a position using the
+  \a painter specified.
+  The corresponding position within the text is specified by \a cursorPosition.
+*/
+void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const
+{
+    if (d->lines.isEmpty())
+        return;
+
+    if (!d->layoutData)
+        d->itemize();
+
+    QPointF position = pos + d->position;
+    QFixed pos_x = QFixed::fromReal(position.x());
+    QFixed pos_y = QFixed::fromReal(position.y());
+
+    cursorPosition = qBound(0, cursorPosition, d->layoutData->string.length());
+    int line = 0;
+    if (cursorPosition == d->layoutData->string.length()) {
+        line = d->lines.size() - 1;
+    } else {
+        // ### binary search
+        for (line = 0; line < d->lines.size(); line++) {
+            const QScriptLine &sl = d->lines[line];
+            if (sl.from <= cursorPosition && sl.from + (int)sl.length > cursorPosition)
+                break;
+        }
+    }
+
+    if (line >= d->lines.size())
+        return;
+
+    QTextLine l(line, d);
+    const QScriptLine &sl = d->lines[line];
+
+    const qreal x = position.x() + l.cursorToX(cursorPosition);
+
+    int itm = d->findItem(cursorPosition - 1);
+    QFixed ascent = sl.ascent;
+    QFixed descent = sl.descent;
+    bool rightToLeft = (d->option.textDirection() == Qt::RightToLeft);
+    if (itm >= 0) {
+        const QScriptItem &si = d->layoutData->items.at(itm);
+        if (si.ascent > 0)
+            ascent = si.ascent;
+        if (si.descent > 0)
+            descent = si.descent;
+        rightToLeft = si.analysis.bidiLevel % 2;
+    }
+    qreal y = position.y() + (sl.y + sl.ascent - ascent).toReal();
+    bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
+                              && (p->transform().type() > QTransform::TxTranslate);
+    if (toggleAntialiasing)
+        p->setRenderHint(QPainter::Antialiasing);
+    p->fillRect(QRectF(x, y, qreal(width), (ascent + descent).toReal()), p->pen().brush());
+    if (toggleAntialiasing)
+        p->setRenderHint(QPainter::Antialiasing, false);
+    if (d->layoutData->hasBidi) {
+        const int arrow_extent = 4;
+        int sign = rightToLeft ? -1 : 1;
+        p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
+        p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
+    }
+    return;
+}
+
+/*!
+    \class QTextLine
+    \reentrant
+
+    \brief The QTextLine class represents a line of text inside a QTextLayout.
+
+    \ingroup richtext-processing
+
+    A text line is usually created by QTextLayout::createLine().
+
+    After being created, the line can be filled using the setLineWidth()
+    or setNumColumns() functions. A line has a number of attributes including the
+    rectangle it occupies, rect(), its coordinates, x() and y(), its
+    textLength(), width() and naturalTextWidth(), and its ascent() and decent()
+    relative to the text. The position of the cursor in terms of the
+    line is available from cursorToX() and its inverse from
+    xToCursor(). A line can be moved with setPosition().
+*/
+
+/*!
+    \enum QTextLine::Edge
+
+    \value Leading
+    \value Trailing
+*/
+
+/*!
+    \enum QTextLine::CursorPosition
+
+    \value CursorBetweenCharacters
+    \value CursorOnCharacter
+*/
+
+/*!
+    \fn QTextLine::QTextLine(int line, QTextEngine *e)
+    \internal
+
+    Constructs a new text line using the line at position \a line in
+    the text engine \a e.
+*/
+
+/*!
+    \fn QTextLine::QTextLine()
+
+    Creates an invalid line.
+*/
+
+/*!
+    \fn bool QTextLine::isValid() const
+
+    Returns true if this text line is valid; otherwise returns false.
+*/
+
+/*!
+    \fn int QTextLine::lineNumber() const
+
+    Returns the position of the line in the text engine.
+*/
+
+
+/*!
+    Returns the line's bounding rectangle.
+
+    \sa x() y() textLength() width()
+*/
+QRectF QTextLine::rect() const
+{
+    const QScriptLine& sl = eng->lines[i];
+    return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
+}
+
+/*!
+    Returns the rectangle covered by the line.
+*/
+QRectF QTextLine::naturalTextRect() const
+{
+    const QScriptLine& sl = eng->lines[i];
+    QFixed x = sl.x + alignLine(eng, sl);
+
+    QFixed width = sl.textWidth;
+    if (sl.justified)
+        width = sl.width;
+
+    return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
+}
+
+/*!
+    Returns the line's x position.
+
+    \sa rect() y() textLength() width()
+*/
+qreal QTextLine::x() const
+{
+    return eng->lines[i].x.toReal();
+}
+
+/*!
+    Returns the line's y position.
+
+    \sa x() rect() textLength() width()
+*/
+qreal QTextLine::y() const
+{
+    return eng->lines[i].y.toReal();
+}
+
+/*!
+    Returns the line's width as specified by the layout() function.
+
+    \sa naturalTextWidth() x() y() textLength() rect()
+*/
+qreal QTextLine::width() const
+{
+    return eng->lines[i].width.toReal();
+}
+
+
+/*!
+    Returns the line's ascent.
+
+    \sa descent() height()
+*/
+qreal QTextLine::ascent() const
+{
+    return eng->lines[i].ascent.toReal();
+}
+
+/*!
+    Returns the line's descent.
+
+    \sa ascent() height()
+*/
+qreal QTextLine::descent() const
+{
+    return eng->lines[i].descent.toReal();
+}
+
+/*!
+    Returns the line's height. This is equal to ascent() + descent() + 1.
+
+    \sa ascent() descent()
+*/
+qreal QTextLine::height() const
+{
+    return eng->lines[i].height().toReal();
+}
+
+/*!
+    Returns the width of the line that is occupied by text. This is
+    always \<= to width(), and is the minimum width that could be used
+    by layout() without changing the line break position.
+*/
+qreal QTextLine::naturalTextWidth() const
+{
+    return eng->lines[i].textWidth.toReal();
+}
+
+/*!
+    Lays out the line with the given \a width. The line is filled from
+    its starting position with as many characters as will fit into
+    the line. In case the text cannot be split at the end of the line,
+    it will be filled with additional characters to the next whitespace
+    or end of the text.
+*/
+void QTextLine::setLineWidth(qreal width)
+{
+    QScriptLine &line = eng->lines[i];
+    if (!eng->layoutData) {
+        qWarning("QTextLine: Can't set a line width while not layouting.");
+        return;
+    }
+
+    if (width > QFIXED_MAX)
+        width = QFIXED_MAX;
+
+    line.width = QFixed::fromReal(width);
+    if (line.length
+        && line.textWidth <= line.width
+        && line.from + line.length == eng->layoutData->string.length())
+        // no need to do anything if the line is already layouted and the last one. This optimisation helps
+        // when using things in a single line layout.
+        return;
+    line.length = 0;
+    line.textWidth = 0;
+
+    layout_helper(INT_MAX);
+}
+
+/*!
+    Lays out the line. The line is filled from its starting position
+    with as many characters as are specified by \a numColumns. In case
+    the text cannot be split until \a numColumns characters, the line
+    will be filled with as many characters to the next whitespace or
+    end of the text.
+*/
+void QTextLine::setNumColumns(int numColumns)
+{
+    QScriptLine &line = eng->lines[i];
+    line.width = QFIXED_MAX;
+    line.length = 0;
+    line.textWidth = 0;
+    layout_helper(numColumns);
+}
+
+/*!
+    Lays out the line. The line is filled from its starting position
+    with as many characters as are specified by \a numColumns. In case
+    the text cannot be split until \a numColumns characters, the line
+    will be filled with as many characters to the next whitespace or
+    end of the text. The provided \a alignmentWidth is used as reference
+    width for alignment.
+*/
+void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
+{
+    QScriptLine &line = eng->lines[i];
+    line.width = QFixed::fromReal(alignmentWidth);
+    line.length = 0;
+    line.textWidth = 0;
+    layout_helper(numColumns);
+}
+
+#if 0
+#define LB_DEBUG qDebug
+#else
+#define LB_DEBUG if (0) qDebug
+#endif
+
+namespace {
+
+    struct LineBreakHelper
+    {
+        LineBreakHelper() : glyphCount(0), maxGlyphs(0), manualWrap(false) {}
+
+        QScriptLine tmpData;
+        QScriptLine spaceData;
+
+        int glyphCount;
+        int maxGlyphs;
+
+        QFixed minw;
+        QFixed softHyphenWidth;
+        QFixed rightBearing;
+
+        bool manualWrap;
+
+        bool checkFullOtherwiseExtend(QScriptLine &line);
+    };
+
+inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line)
+{        
+    LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
+
+    QFixed newWidth = line.textWidth + tmpData.textWidth + spaceData.textWidth + softHyphenWidth + rightBearing;
+    if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs))
+        return true;
+
+    minw = qMax(minw, tmpData.textWidth);
+    line += tmpData;
+    line.textWidth += spaceData.textWidth;
+
+    line.length += spaceData.length;
+    tmpData.textWidth = 0;
+    tmpData.length = 0;
+    spaceData.textWidth = 0;
+    spaceData.length = 0;
+
+    return false;
+}
+
+} // anonymous namespace
+
+
+static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
+                                  const QScriptItem &current, const unsigned short *logClusters,
+                                  const QGlyphLayout &glyphs)
+{
+    int glyphPosition = logClusters[pos];
+    do { // got to the first next cluster
+        ++pos;
+        ++line.length;
+    } while (pos < end && logClusters[pos] == glyphPosition);
+    do { // calculate the textWidth for the rest of the current cluster.
+        line.textWidth += glyphs.advances_x[glyphPosition] * !glyphs.attributes[glyphPosition].dontPrint;
+        ++glyphPosition;
+    } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
+
+    Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
+
+    ++glyphCount;
+}
+
+
+// fill QScriptLine
+void QTextLine::layout_helper(int maxGlyphs)
+{
+    QScriptLine &line = eng->lines[i];
+    line.length = 0;
+    line.textWidth = 0;
+    line.hasTrailingSpaces = false;
+
+    if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.length()) {
+        line.setDefaultHeight(eng);
+        return;
+    }
+
+    Q_ASSERT(line.from < eng->layoutData->string.length());
+
+    LineBreakHelper lbh;
+
+    lbh.maxGlyphs = maxGlyphs;
+
+    QTextOption::WrapMode wrapMode = eng->option.wrapMode();
+    bool breakany = (wrapMode == QTextOption::WrapAnywhere);
+    lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
+
+    // #### binary search!
+    int item = -1;
+    int newItem;
+    for (newItem = eng->layoutData->items.size()-1; newItem > 0; --newItem) {
+        if (eng->layoutData->items[newItem].position <= line.from)
+            break;
+    }
+
+    LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, eng->layoutData->items.size(), line.width.toReal());
+
+    Qt::Alignment alignment = eng->option.alignment();
+
+    const HB_CharAttributes *attributes = eng->attributes();
+    int pos = line.from;
+    int end = 0;
+    QGlyphLayout glyphs;
+    const unsigned short *logClusters = eng->layoutData->logClustersPtr;
+
+    while (newItem < eng->layoutData->items.size()) {
+        lbh.rightBearing = 0;
+        lbh.softHyphenWidth = 0;
+        if (newItem != item) {
+            item = newItem;
+            const QScriptItem &current = eng->layoutData->items[item];
+            if (!current.num_glyphs) {
+                eng->shape(item);
+                attributes = eng->attributes();
+                logClusters = eng->layoutData->logClustersPtr;
+            }
+            pos = qMax(line.from, current.position);
+            end = current.position + eng->length(item);
+            glyphs = eng->shapedGlyphs(&current);
+        }
+        const QScriptItem &current = eng->layoutData->items[item];
+
+        lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent);
+        lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent);
+
+        if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
+            if (lbh.checkFullOtherwiseExtend(line))
+                goto found;
+
+            QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth;
+            QFixed tabWidth = eng->calculateTabWidth(item, x);
+
+            lbh.spaceData.textWidth += tabWidth;
+            lbh.spaceData.length++;
+            newItem = item + 1;
+
+            QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth();
+            lbh.glyphCount += qRound(tabWidth / averageCharWidth);
+
+            if (lbh.checkFullOtherwiseExtend(line))
+                goto found;
+        } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
+            // if the line consists only of the line separator make sure
+            // we have a sane height
+            if (!line.length && !lbh.tmpData.length)
+                line.setDefaultHeight(eng);
+            if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
+                addNextCluster(pos, end, lbh.tmpData, lbh.glyphCount,
+                               current, logClusters, glyphs);
+            } else {
+                lbh.tmpData.length++;
+            }
+            line += lbh.tmpData;
+            goto found;
+        } else if (current.analysis.flags == QScriptAnalysis::Object) {
+            lbh.tmpData.length++;
+
+            QTextFormat format = eng->formats()->format(eng->formatIndex(&eng->layoutData->items[item]));
+            if (eng->block.docHandle())
+                eng->docLayout()->positionInlineObject(QTextInlineObject(item, eng), eng->block.position() + current.position, format);
+
+            lbh.tmpData.textWidth += current.width;
+
+            newItem = item + 1;
+            ++lbh.glyphCount;
+            if (lbh.checkFullOtherwiseExtend(line))
+                goto found;
+        } else if (attributes[pos].whiteSpace) {
+            while (pos < end && attributes[pos].whiteSpace)
+                addNextCluster(pos, end, lbh.spaceData, lbh.glyphCount,
+                               current, logClusters, glyphs);
+
+            if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width) {
+                lbh.spaceData.textWidth = line.width; // ignore spaces that fall out of the line.
+                goto found;
+            }
+        } else {
+            bool sb_or_ws = false;
+            do {
+                addNextCluster(pos, end, lbh.tmpData, lbh.glyphCount,
+                               current, logClusters, glyphs);
+
+                if (attributes[pos].whiteSpace || attributes[pos-1].lineBreakType != HB_NoBreak) {
+                    sb_or_ws = true;
+                    break;
+                } else if (breakany && attributes[pos].charStop) {
+                    break;
+                }
+            } while (pos < end);
+            lbh.minw = qMax(lbh.tmpData.textWidth, lbh.minw);
+
+            if (pos && attributes[pos - 1].lineBreakType == HB_SoftHyphen) {
+                // if we are splitting up a word because of
+                // a soft hyphen then we ...
+                //
+                //  a) have to take the width of the soft hyphen into
+                //     account to see if the first syllable(s) /and/
+                //     the soft hyphen fit into the line
+                //
+                //  b) if we are so short of available width that the
+                //     soft hyphen is the first breakable position, then
+                //     we don't want to show it. However we initially
+                //     have to take the width for it into accoun so that
+                //     the text document layout sees the overflow and
+                //     switch to break-anywhere mode, in which we
+                //     want the soft-hyphen to slip into the next line
+                //     and thus become invisible again.
+                //
+                if (line.length)
+                    lbh.softHyphenWidth = glyphs.advances_x[logClusters[pos - 1]];
+                else if (breakany)
+                    lbh.tmpData.textWidth += glyphs.advances_x[logClusters[pos - 1]];
+            }
+
+            // The actual width of the text needs to take the right bearing into account. The
+            // right bearing is left-ward, which means that if the rightmost pixel is to the right
+            // of the advance of the glyph, the bearing will be negative. We flip the sign
+            // for the code to be more readable. Logic borrowed from qfontmetrics.cpp.
+            if (pos) {
+                QFontEngine *fontEngine = eng->fontEngine(current);
+                glyph_t glyph = glyphs.glyphs[logClusters[pos - 1]];
+                glyph_metrics_t gi = fontEngine->boundingBox(glyph);
+                if (gi.isValid())
+                    lbh.rightBearing = qMax(QFixed(), -(gi.xoff - gi.x - gi.width));
+            }
+
+            if ((sb_or_ws|breakany) && lbh.checkFullOtherwiseExtend(line)) {
+                if (!breakany) {
+                    line.textWidth += lbh.softHyphenWidth;
+                }
+
+                line.textWidth += lbh.rightBearing;
+
+                goto found;
+            }
+        }
+        if (pos == end)
+            newItem = item + 1;
+    }
+    LB_DEBUG("reached end of line");
+    lbh.checkFullOtherwiseExtend(line);
+    line.textWidth += lbh.rightBearing;
+
+found:       
+    if (line.length == 0) {
+        LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
+               lbh.tmpData.length, lbh.tmpData.textWidth.toReal(),
+               lbh.spaceData.length, lbh.spaceData.textWidth.toReal());
+        line += lbh.tmpData;
+    }
+
+    LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
+           line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal());
+    LB_DEBUG("        : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
+
+    if (lbh.manualWrap) {
+        eng->minWidth = qMax(eng->minWidth, line.textWidth);
+        eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
+    } else {
+        eng->minWidth = qMax(eng->minWidth, lbh.minw);
+        eng->maxWidth += line.textWidth;
+    }
+
+    if (line.textWidth > 0 && item < eng->layoutData->items.size())
+        eng->maxWidth += lbh.spaceData.textWidth;
+    if (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
+        line.textWidth += lbh.spaceData.textWidth;
+    line.length += lbh.spaceData.length;
+    if (lbh.spaceData.length)
+        line.hasTrailingSpaces = true;
+
+    line.justified = false;
+    line.gridfitted = false;
+
+    if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
+        if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs)
+            || (lbh.maxGlyphs == INT_MAX && line.textWidth > line.width)) {
+
+            eng->option.setWrapMode(QTextOption::WrapAnywhere);
+            line.length = 0;
+            line.textWidth = 0;
+            layout_helper(lbh.maxGlyphs);
+            eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
+        }
+    }
+}
+
+/*!
+    Moves the line to position \a pos.
+*/
+void QTextLine::setPosition(const QPointF &pos)
+{
+    eng->lines[i].x = QFixed::fromReal(pos.x());
+    eng->lines[i].y = QFixed::fromReal(pos.y());
+}
+
+/*!
+    Returns the line's position relative to the text layout's position.
+*/
+QPointF QTextLine::position() const
+{
+    return QPointF(eng->lines[i].x.toReal(), eng->lines[i].y.toReal());
+}
+
+// ### DOC: I have no idea what this means/does.
+// You create a text layout with a string of text. Once you laid
+// it out, it contains a number of QTextLines. from() returns the position
+// inside the text string where this line starts. If you e.g. has a
+// text of "This is a string", laid out into two lines (the second
+// starting at the word 'a'), layout.lineAt(0).from() == 0 and
+// layout.lineAt(1).from() == 8.
+/*!
+    Returns the start of the line from the beginning of the string
+    passed to the QTextLayout.
+*/
+int QTextLine::textStart() const
+{
+    return eng->lines[i].from;
+}
+
+/*!
+    Returns the length of the text in the line.
+
+    \sa naturalTextWidth()
+*/
+int QTextLine::textLength() const
+{
+    if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
+        && eng->block.isValid() && i == eng->lines.count()-1) {
+        return eng->lines[i].length - 1;
+    }
+    return eng->lines[i].length;
+}
+
+static void drawMenuText(QPainter *p, QFixed x, QFixed y, const QScriptItem &si, QTextItemInt &gf, QTextEngine *eng,
+                         int start, int glyph_start)
+{
+    int ge = glyph_start + gf.glyphs.numGlyphs;
+    int gs = glyph_start;
+    int end = start + gf.num_chars;
+    unsigned short *logClusters = eng->logClusters(&si);
+    QGlyphLayout glyphs = eng->shapedGlyphs(&si);
+    QFixed orig_width = gf.width;
+
+    int *ul = eng->underlinePositions;
+    if (ul)
+        while (*ul != -1 && *ul < start)
+            ++ul;
+    bool rtl = si.analysis.bidiLevel % 2;
+    if (rtl)
+        x += si.width;
+
+    do {
+        int gtmp = ge;
+        int stmp = end;
+        if (ul && *ul != -1 && *ul < end) {
+            stmp = *ul;
+            gtmp = logClusters[*ul-si.position];
+        }
+
+        gf.glyphs = glyphs.mid(gs, gtmp - gs);
+        gf.num_chars = stmp - start;
+        gf.chars = eng->layoutData->string.unicode() + start;
+        QFixed w = 0;
+        while (gs < gtmp) {
+            w += glyphs.effectiveAdvance(gs);
+            ++gs;
+        }
+        start = stmp;
+        gf.width = w;
+        if (rtl)
+            x -= w;
+        if (gf.num_chars)
+            p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
+        if (!rtl)
+            x += w;
+        if (ul && *ul != -1 && *ul < end) {
+            // draw underline
+            gtmp = (*ul == end-1) ? ge : logClusters[*ul+1-si.position];
+            ++stmp;
+            gf.glyphs = glyphs.mid(gs, gtmp - gs);
+            gf.num_chars = stmp - start;
+            gf.chars = eng->layoutData->string.unicode() + start;
+            gf.logClusters = logClusters + start - si.position;
+            w = 0;
+            while (gs < gtmp) {
+                w += glyphs.effectiveAdvance(gs);
+                ++gs;
+            }
+            ++start;
+            gf.width = w;
+            gf.underlineStyle = QTextCharFormat::SingleUnderline;
+            if (rtl)
+                x -= w;
+            p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
+            if (!rtl)
+                x += w;
+            gf.underlineStyle = QTextCharFormat::NoUnderline;
+            ++gf.chars;
+            ++ul;
+        }
+    } while (gs < ge);
+
+    gf.width = orig_width;
+}
+
+
+static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
+{
+    QBrush c = chf.foreground();
+    if (c.style() == Qt::NoBrush) {
+        p->setPen(defaultPen);
+    }
+
+    QBrush bg = chf.background();
+    if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
+        p->fillRect(r, bg);
+    if (c.style() != Qt::NoBrush) {
+        p->setPen(QPen(c, 0));
+    }
+
+}
+
+/*!
+    \fn void QTextLine::draw(QPainter *painter, const QPointF &position, const QTextLayout::FormatRange *selection) const
+
+    Draws a line on the given \a painter at the specified \a position.
+    The \a selection is reserved for internal use.
+*/
+void QTextLine::draw(QPainter *p, const QPointF &pos, const QTextLayout::FormatRange *selection) const
+{
+    const QScriptLine &line = eng->lines[i];
+    QPen pen = p->pen();
+
+    bool noText = (selection && selection->format.property(SuppressText).toBool());
+
+    if (!line.length) {
+        if (selection
+            && selection->start <= line.from
+            && selection->start + selection->length > line.from) {
+
+            const qreal lineHeight = line.height().toReal();
+            QRectF r(pos.x() + line.x.toReal(), pos.y() + line.y.toReal(),
+                     lineHeight / 2, QFontMetrics(eng->font()).width(QLatin1Char(' ')));
+            setPenAndDrawBackground(p, QPen(), selection->format, r);
+            p->setPen(pen);
+        }
+        return;
+    }
+
+
+    QTextLineItemIterator iterator(eng, i, pos, selection);
+    const QFixed y = QFixed::fromReal(pos.y()) + line.y + line.ascent;
+
+    bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
+    while (!iterator.atEnd()) {
+        QScriptItem &si = iterator.next();
+
+        if (selection && selection->start >= 0 && iterator.isOutsideSelection())
+            continue;
+
+        if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
+            && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
+            continue;
+
+        QFixed itemBaseLine = y;
+        QFont f = eng->font(si);
+        QTextCharFormat format;
+
+        if (eng->hasFormats() || selection) {
+            if (!suppressColors)
+                format = eng->format(&si);
+            if (selection)
+                format.merge(selection->format);
+
+            setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - line.ascent).toReal(),
+                                                           iterator.itemWidth.toReal(), line.height().toReal()));
+
+            QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
+            if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) {
+                QFontEngine *fe = f.d->engineForScript(si.analysis.script);
+                QFixed height = fe->ascent() + fe->descent();
+                if (valign == QTextCharFormat::AlignSubScript)
+                    itemBaseLine += height / 6;
+                else if (valign == QTextCharFormat::AlignSuperScript)
+                    itemBaseLine -= height / 2;
+            }
+        }
+
+        if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
+
+            if (eng->hasFormats()) {
+                p->save();
+                if (si.analysis.flags == QScriptAnalysis::Object && eng->block.docHandle()) {
+                    QFixed itemY = y - si.ascent;
+                    if (format.verticalAlignment() == QTextCharFormat::AlignTop) {
+                        itemY = y - line.ascent;
+                    }
+
+                    QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
+
+                    eng->docLayout()->drawInlineObject(p, itemRect,
+                                                       QTextInlineObject(iterator.item, eng),
+                                                       si.position + eng->block.position(),
+                                                       format);
+                    if (selection) {
+                        QBrush bg = format.brushProperty(ObjectSelectionBrush);
+                        if (bg.style() != Qt::NoBrush) {
+                            QColor c = bg.color();
+                            c.setAlpha(128);
+                            p->fillRect(itemRect, c);
+                        }
+                    }
+                } else { // si.isTab
+                    QFont f = eng->font(si);
+                    QTextItemInt gf(si, &f, format);
+                    gf.chars = 0;
+                    gf.num_chars = 0;
+                    gf.width = iterator.itemWidth;
+                    p->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf);
+                    if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
+                        QChar visualTab(0x2192);
+                        int w = QFontMetrics(f).width(visualTab);
+                        qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
+                        if (x < 0)
+                             p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
+                                                   iterator.itemWidth.toReal(), line.height().toReal()),
+                                            Qt::IntersectClip);
+                        else
+                             x /= 2; // Centered
+                        p->drawText(QPointF(iterator.x.toReal() + x,
+                                            y.toReal()), visualTab);
+                    }
+
+                }
+                p->restore();
+            }
+
+            continue;
+        }
+
+        unsigned short *logClusters = eng->logClusters(&si);
+        QGlyphLayout glyphs = eng->shapedGlyphs(&si);
+
+        QTextItemInt gf(si, &f, format);
+        gf.glyphs = glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart);
+        gf.chars = eng->layoutData->string.unicode() + iterator.itemStart;
+        gf.logClusters = logClusters + iterator.itemStart - si.position;
+        gf.num_chars = iterator.itemEnd - iterator.itemStart;
+        gf.width = iterator.itemWidth;
+        gf.justified = line.justified;
+
+        Q_ASSERT(gf.fontEngine);
+
+        if (eng->underlinePositions) {
+            // can't have selections in this case
+            drawMenuText(p, iterator.x, itemBaseLine, si, gf, eng, iterator.itemStart, iterator.glyphsStart);
+        } else {
+            QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
+            if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
+                QPainterPath path;
+                path.setFillRule(Qt::WindingFill);
+
+                if (gf.glyphs.numGlyphs)
+                    gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
+                if (gf.flags) {
+                    const QFontEngine *fe = gf.fontEngine;
+                    const qreal lw = fe->lineThickness().toReal();
+                    if (gf.flags & QTextItem::Underline) {
+                        qreal offs = fe->underlinePosition().toReal();
+                        path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
+                    }
+                    if (gf.flags & QTextItem::Overline) {
+                        qreal offs = fe->ascent().toReal() + 1;
+                        path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
+                    }
+                    if (gf.flags & QTextItem::StrikeOut) {
+                        qreal offs = fe->ascent().toReal() / 3;
+                        path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
+                    }
+                }
+
+                p->save();
+                p->setRenderHint(QPainter::Antialiasing);
+                //Currently QPen with a Qt::NoPen style still returns a default
+                //QBrush which != Qt::NoBrush so we need this specialcase to reset it
+                if (p->pen().style() == Qt::NoPen)
+                    p->setBrush(Qt::NoBrush);
+                else
+                    p->setBrush(p->pen().brush());
+
+                p->setPen(format.textOutline());
+                p->drawPath(path);
+                p->restore();
+            } else {
+                if (noText)
+                    gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
+                p->drawTextItem(pos, gf);
+            }
+        }
+        if (si.analysis.flags == QScriptAnalysis::Space
+            && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
+            QBrush c = format.foreground();
+            if (c.style() != Qt::NoBrush)
+                p->setPen(c.color());
+            QChar visualSpace((ushort)0xb7);
+            p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
+            p->setPen(pen);
+        }
+    }
+
+
+    if (eng->hasFormats())
+        p->setPen(pen);
+}
+
+/*!
+  \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
+
+  \overload
+*/
+
+
+/*!
+  Converts the cursor position \a cursorPos to the corresponding x position
+  inside the line, taking account of the \a edge.
+
+  If \a cursorPos is not a valid cursor position, the nearest valid
+  cursor position will be used instead, and cpos will be modified to
+  point to this valid cursor position.
+
+  \sa xToCursor()
+*/
+qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
+{
+    if (!eng->layoutData)
+        eng->itemize();
+
+    const QScriptLine &line = eng->lines[i];
+
+    QFixed x = line.x;
+    x += alignLine(eng, line);
+
+    if (!i && !eng->layoutData->items.size()) {
+        *cursorPos = 0;
+        return x.toReal();
+    }
+
+    int pos = *cursorPos;
+    int itm;
+    if (pos == line.from + (int)line.length) {
+        // end of line ensure we have the last item on the line
+        itm = eng->findItem(pos-1);
+    }
+    else
+        itm = eng->findItem(pos);
+    eng->shapeLine(line);
+
+    const QScriptItem *si = &eng->layoutData->items[itm];
+    if (!si->num_glyphs)
+        eng->shape(itm);
+    pos -= si->position;
+
+    QGlyphLayout glyphs = eng->shapedGlyphs(si);
+    unsigned short *logClusters = eng->logClusters(si);
+    Q_ASSERT(logClusters);
+
+    int l = eng->length(itm);
+    if (pos > l)
+        pos = l;
+    if (pos < 0)
+        pos = 0;
+
+    int glyph_pos = pos == l ? si->num_glyphs : logClusters[pos];
+    if (edge == Trailing) {
+        // trailing edge is leading edge of next cluster
+        while (glyph_pos < si->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
+            glyph_pos++;
+    }
+
+    bool reverse = eng->layoutData->items[itm].analysis.bidiLevel % 2;
+
+    int lineEnd = line.from + line.length;
+
+    // add the items left of the cursor
+
+    int firstItem = eng->findItem(line.from);
+    int lastItem = eng->findItem(lineEnd - 1);
+    int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
+
+    QVarLengthArray<int> visualOrder(nItems);
+    QVarLengthArray<uchar> levels(nItems);
+    for (int i = 0; i < nItems; ++i)
+        levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
+    QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
+
+    for (int i = 0; i < nItems; ++i) {
+        int item = visualOrder[i]+firstItem;
+        if (item == itm)
+            break;
+        QScriptItem &si = eng->layoutData->items[item];
+        if (!si.num_glyphs)
+            eng->shape(item);
+
+        if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
+            x += si.width;
+            continue;
+        }
+        int start = qMax(line.from, si.position);
+        int end = qMin(lineEnd, si.position + eng->length(item));
+
+        logClusters = eng->logClusters(&si);
+
+        int gs = logClusters[start-si.position];
+        int ge = (end == si.position + eng->length(item)) ? si.num_glyphs-1 : logClusters[end-si.position-1];
+
+        QGlyphLayout glyphs = eng->shapedGlyphs(&si);
+
+        while (gs <= ge) {
+            x += glyphs.effectiveAdvance(gs);
+            ++gs;
+        }
+    }
+
+    logClusters = eng->logClusters(si);
+    glyphs = eng->shapedGlyphs(si);
+    if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
+        if(pos == l)
+            x += si->width;
+    } else {
+        int offsetInCluster = 0;
+        for (int i=pos-1; i >= 0; i--) {
+            if (logClusters[i] == glyph_pos)
+                offsetInCluster++;
+            else
+                break;
+        }
+
+        if (reverse) {
+            int end = qMin(lineEnd, si->position + l) - si->position;
+            int glyph_end = end == l ? si->num_glyphs : logClusters[end];
+            for (int i = glyph_end - 1; i >= glyph_pos; i--)
+                x += glyphs.effectiveAdvance(i);
+        } else {
+            int start = qMax(line.from - si->position, 0);
+            int glyph_start = logClusters[start];
+            for (int i = glyph_start; i < glyph_pos; i++)
+                x += glyphs.effectiveAdvance(i);
+        }
+        if (offsetInCluster > 0) { // in the case that the offset is inside a (multi-character) glyph, interpolate the position.
+            int clusterLength = 0;
+            for (int i=pos - offsetInCluster; i < line.length; i++) {
+                if (logClusters[i] == glyph_pos)
+                    clusterLength++;
+                else
+                    break;
+            }
+            if (clusterLength)
+                x+= glyphs.advances_x[glyph_pos] * offsetInCluster / clusterLength;
+        }
+    }
+
+    *cursorPos = pos + si->position;
+    return x.toReal();
+}
+
+/*!
+  \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
+
+  Converts the x-coordinate \a x, to the nearest matching cursor
+  position, depending on the cursor position type, \a cpos.
+
+  \sa cursorToX()
+*/
+int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
+{
+    QFixed x = QFixed::fromReal(_x);
+    const QScriptLine &line = eng->lines[i];
+
+    if (!eng->layoutData)
+        eng->itemize();
+
+    int line_length = textLength();
+
+    if (!line_length)
+        return line.from;
+
+    int firstItem = eng->findItem(line.from);
+    int lastItem = eng->findItem(line.from + line_length - 1);
+    int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
+
+    if (!nItems)
+        return 0;
+
+    x -= line.x;
+    x -= alignLine(eng, line);
+//     qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
+
+    QVarLengthArray<int> visualOrder(nItems);
+    QVarLengthArray<unsigned char> levels(nItems);
+    for (int i = 0; i < nItems; ++i)
+        levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
+    QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
+
+    if (x <= 0) {
+        // left of first item
+        int item = visualOrder[0]+firstItem;
+        QScriptItem &si = eng->layoutData->items[item];
+        if (!si.num_glyphs)
+            eng->shape(item);
+        int pos = si.position;
+        if (si.analysis.bidiLevel % 2)
+            pos += eng->length(item);
+        pos = qMax(line.from, pos);
+        pos = qMin(line.from + line_length, pos);
+        return pos;
+    } else if (x < line.textWidth
+               || (line.justified && x < line.width)) {
+        // has to be in one of the runs
+        QFixed pos;
+
+        eng->shapeLine(line);
+        for (int i = 0; i < nItems; ++i) {
+            int item = visualOrder[i]+firstItem;
+            QScriptItem &si = eng->layoutData->items[item];
+            int item_length = eng->length(item);
+//             qDebug("    item %d, visual %d x_remain=%f", i, item, x.toReal());
+
+            int start = qMax(line.from - si.position, 0);
+            int end = qMin(line.from + line_length - si.position, item_length);
+
+            unsigned short *logClusters = eng->logClusters(&si);
+
+            int gs = logClusters[start];
+            int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
+            QGlyphLayout glyphs = eng->shapedGlyphs(&si);
+
+            QFixed item_width = 0;
+            if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
+                item_width = si.width;
+            } else {
+                int g = gs;
+                while (g <= ge) {
+                    item_width += glyphs.effectiveAdvance(g);
+                    ++g;
+                }
+            }
+//             qDebug("      start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
+
+            if (pos + item_width < x) {
+                pos += item_width;
+                continue;
+            }
+//             qDebug("      inside run");
+            if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
+                if (cpos == QTextLine::CursorOnCharacter)
+                    return si.position;
+                bool left_half = (x - pos) < item_width/2;
+
+                if (bool(si.analysis.bidiLevel % 2) != left_half)
+                    return si.position;
+                return si.position + 1;
+            }
+
+            int glyph_pos = -1;
+            // has to be inside run
+            if (cpos == QTextLine::CursorOnCharacter) {
+                if (si.analysis.bidiLevel % 2) {
+                    pos += item_width;
+                    glyph_pos = gs;
+                    while (gs <= ge) {
+                        if (glyphs.attributes[gs].clusterStart) {
+                            if (pos < x)
+                                break;
+                            glyph_pos = gs;
+                            break;
+                        }
+                        pos -= glyphs.effectiveAdvance(gs);
+                        ++gs;
+                    }
+                } else {
+                    glyph_pos = gs;
+                    while (gs <= ge) {
+                        if (glyphs.attributes[gs].clusterStart) {
+                            if (pos > x)
+                                break;
+                            glyph_pos = gs;
+                        }
+                        pos += glyphs.effectiveAdvance(gs);
+                        ++gs;
+                    }
+                }
+            } else {
+                QFixed dist = INT_MAX/256;
+                if (si.analysis.bidiLevel % 2) {
+                    pos += item_width;
+                    while (gs <= ge) {
+                        if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
+                            glyph_pos = gs;
+                            dist = qAbs(x-pos);
+                        }
+                        pos -= glyphs.effectiveAdvance(gs);
+                        ++gs;
+                    }
+                } else {
+                    while (gs <= ge) {
+                        if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
+                            glyph_pos = gs;
+                            dist = qAbs(x-pos);
+                        }
+                        pos += glyphs.effectiveAdvance(gs);
+                        ++gs;
+                    }
+                }
+                if (qAbs(x-pos) < dist)
+                    return si.position + end;
+            }
+            Q_ASSERT(glyph_pos != -1);
+            int j;
+            for (j = 0; j < eng->length(item); ++j)
+                if (logClusters[j] == glyph_pos)
+                    break;
+//             qDebug("at pos %d (in run: %d)", si.position + j, j);
+            return si.position + j;
+        }
+    }
+    // right of last item
+//     qDebug() << "right of last";
+    int item = visualOrder[nItems-1]+firstItem;
+    QScriptItem &si = eng->layoutData->items[item];
+    if (!si.num_glyphs)
+        eng->shape(item);
+    int pos = si.position;
+    if (!(si.analysis.bidiLevel % 2))
+        pos += eng->length(item);
+    pos = qMax(line.from, pos);
+
+    int maxPos = line.from + line_length;
+
+    // except for the last line we assume that the
+    // character between lines is a space and we want
+    // to position the cursor to the left of that
+    // character.
+    // ###### breaks with japanese for example
+    if (this->i < eng->lines.count() - 1)
+        --maxPos;
+
+    pos = qMin(pos, maxPos);
+    return pos;
+}
+
+QT_END_NAMESPACE