summaryrefslogtreecommitdiffstats
path: root/src/gui/text/qtextdocument.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/text/qtextdocument.cpp')
-rw-r--r--src/gui/text/qtextdocument.cpp2929
1 files changed, 2929 insertions, 0 deletions
diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp
new file mode 100644
index 0000000..e84b324
--- /dev/null
+++ b/src/gui/text/qtextdocument.cpp
@@ -0,0 +1,2929 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextdocument.h"
+#include <qtextformat.h>
+#include "qtextdocumentlayout_p.h"
+#include "qtextdocumentfragment.h"
+#include "qtextdocumentfragment_p.h"
+#include "qtexttable.h"
+#include "qtextlist.h"
+#include <qdebug.h>
+#include <qregexp.h>
+#include <qvarlengtharray.h>
+#include <qtextcodec.h>
+#include <qthread.h>
+#include "qtexthtmlparser_p.h"
+#include "qpainter.h"
+#include "qprinter.h"
+#include "qtextedit.h"
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qdir.h>
+#include <qapplication.h>
+#include "qtextcontrol_p.h"
+#include "private/qtextedit_p.h"
+
+#include "qtextdocument_p.h"
+#include <private/qprinter_p.h>
+
+#include <limits.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_CORE_EXPORT unsigned int qt_int_sqrt(unsigned int n);
+
+/*!
+ Returns true if the string \a text is likely to be rich text;
+ otherwise returns false.
+
+ This function uses a fast and therefore simple heuristic. It
+ mainly checks whether there is something that looks like a tag
+ before the first line break. Although the result may be correct
+ for common cases, there is no guarantee.
+
+ This function is defined in the \c <QTextDocument> header file.
+*/
+bool Qt::mightBeRichText(const QString& text)
+{
+ if (text.isEmpty())
+ return false;
+ int start = 0;
+
+ while (start < text.length() && text.at(start).isSpace())
+ ++start;
+
+ // skip a leading <?xml ... ?> as for example with xhtml
+ if (text.mid(start, 5) == QLatin1String("<?xml")) {
+ while (start < text.length()) {
+ if (text.at(start) == QLatin1Char('?')
+ && start + 2 < text.length()
+ && text.at(start + 1) == QLatin1Char('>')) {
+ start += 2;
+ break;
+ }
+ ++start;
+ }
+
+ while (start < text.length() && text.at(start).isSpace())
+ ++start;
+ }
+
+ if (text.mid(start, 5).toLower() == QLatin1String("<!doc"))
+ return true;
+ int open = start;
+ while (open < text.length() && text.at(open) != QLatin1Char('<')
+ && text.at(open) != QLatin1Char('\n')) {
+ if (text.at(open) == QLatin1Char('&') && text.mid(open+1,3) == QLatin1String("lt;"))
+ return true; // support desperate attempt of user to see <...>
+ ++open;
+ }
+ if (open < text.length() && text.at(open) == QLatin1Char('<')) {
+ const int close = text.indexOf(QLatin1Char('>'), open);
+ if (close > -1) {
+ QString tag;
+ for (int i = open+1; i < close; ++i) {
+ if (text[i].isDigit() || text[i].isLetter())
+ tag += text[i];
+ else if (!tag.isEmpty() && text[i].isSpace())
+ break;
+ else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != QLatin1Char('!')))
+ return false; // that's not a tag
+ }
+#ifndef QT_NO_TEXTHTMLPARSER
+ return QTextHtmlParser::lookupElement(tag.toLower()) != -1;
+#else
+ return false;
+#endif // QT_NO_TEXTHTMLPARSER
+ }
+ }
+ return false;
+}
+
+/*!
+ Converts the plain text string \a plain to a HTML string with
+ HTML metacharacters \c{<}, \c{>}, and \c{&} replaced by HTML
+ entities.
+
+ Example:
+
+ \snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 0
+
+ This function is defined in the \c <QTextDocument> header file.
+
+ \sa convertFromPlainText(), mightBeRichText()
+*/
+QString Qt::escape(const QString& plain)
+{
+ QString rich;
+ rich.reserve(int(plain.length() * 1.1));
+ for (int i = 0; i < plain.length(); ++i) {
+ if (plain.at(i) == QLatin1Char('<'))
+ rich += QLatin1String("&lt;");
+ else if (plain.at(i) == QLatin1Char('>'))
+ rich += QLatin1String("&gt;");
+ else if (plain.at(i) == QLatin1Char('&'))
+ rich += QLatin1String("&amp;");
+ else
+ rich += plain.at(i);
+ }
+ return rich;
+}
+
+/*!
+ \fn QString Qt::convertFromPlainText(const QString &plain, WhiteSpaceMode mode)
+
+ Converts the plain text string \a plain to an HTML-formatted
+ paragraph while preserving most of its look.
+
+ \a mode defines how whitespace is handled.
+
+ This function is defined in the \c <QTextDocument> header file.
+
+ \sa escape(), mightBeRichText()
+*/
+QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode)
+{
+ int col = 0;
+ QString rich;
+ rich += QLatin1String("<p>");
+ for (int i = 0; i < plain.length(); ++i) {
+ if (plain[i] == QLatin1Char('\n')){
+ int c = 1;
+ while (i+1 < plain.length() && plain[i+1] == QLatin1Char('\n')) {
+ i++;
+ c++;
+ }
+ if (c == 1)
+ rich += QLatin1String("<br>\n");
+ else {
+ rich += QLatin1String("</p>\n");
+ while (--c > 1)
+ rich += QLatin1String("<br>\n");
+ rich += QLatin1String("<p>");
+ }
+ col = 0;
+ } else {
+ if (mode == Qt::WhiteSpacePre && plain[i] == QLatin1Char('\t')){
+ rich += QChar(0x00a0U);
+ ++col;
+ while (col % 8) {
+ rich += QChar(0x00a0U);
+ ++col;
+ }
+ }
+ else if (mode == Qt::WhiteSpacePre && plain[i].isSpace())
+ rich += QChar(0x00a0U);
+ else if (plain[i] == QLatin1Char('<'))
+ rich += QLatin1String("&lt;");
+ else if (plain[i] == QLatin1Char('>'))
+ rich += QLatin1String("&gt;");
+ else if (plain[i] == QLatin1Char('&'))
+ rich += QLatin1String("&amp;");
+ else
+ rich += plain[i];
+ ++col;
+ }
+ }
+ if (col != 0)
+ rich += QLatin1String("</p>");
+ return rich;
+}
+
+#ifndef QT_NO_TEXTCODEC
+/*!
+ \internal
+
+ This function is defined in the \c <QTextDocument> header file.
+*/
+QTextCodec *Qt::codecForHtml(const QByteArray &ba)
+{
+ return QTextCodec::codecForHtml(ba);
+}
+#endif
+
+/*!
+ \class QTextDocument
+ \reentrant
+
+ \brief The QTextDocument class holds formatted text that can be
+ viewed and edited using a QTextEdit.
+
+ \ingroup text
+ \mainclass
+
+ QTextDocument is a container for structured rich text documents, providing
+ support for styled text and various types of document elements, such as
+ lists, tables, frames, and images.
+ They can be created for use in a QTextEdit, or used independently.
+
+ Each document element is described by an associated format object. Each
+ format object is treated as a unique object by QTextDocuments, and can be
+ passed to objectForFormat() to obtain the document element that it is
+ applied to.
+
+ A QTextDocument can be edited programmatically using a QTextCursor, and
+ its contents can be examined by traversing the document structure. The
+ entire document structure is stored as a hierarchy of document elements
+ beneath the root frame, found with the rootFrame() function. Alternatively,
+ if you just want to iterate over the textual contents of the document you
+ can use begin(), end(), and findBlock() to retrieve text blocks that you
+ can examine and iterate over.
+
+ The layout of a document is determined by the documentLayout();
+ you can create your own QAbstractTextDocumentLayout subclass and
+ set it using setDocumentLayout() if you want to use your own
+ layout logic. The document's title and other meta-information can be
+ obtained by calling the metaInformation() function. For documents that
+ are exposed to users through the QTextEdit class, the document title
+ is also available via the QTextEdit::documentTitle() function.
+
+ The toPlainText() and toHtml() convenience functions allow you to retrieve the
+ contents of the document as plain text and HTML.
+ The document's text can be searched using the find() functions.
+
+ Undo/redo of operations performed on the document can be controlled using
+ the setUndoRedoEnabled() function. The undo/redo system can be controlled
+ by an editor widget through the undo() and redo() slots; the document also
+ provides contentsChanged(), undoAvailable(), and redoAvailable() signals
+ that inform connected editor widgets about the state of the undo/redo
+ system.
+
+ \sa QTextCursor QTextEdit \link richtext.html Rich Text Processing\endlink
+*/
+
+/*!
+ \property QTextDocument::defaultFont
+ \brief the default font used to display the document's text
+*/
+
+/*!
+ \property QTextDocument::defaultTextOption
+ \brief the default text option will be set on all \l{QTextLayout}s in the document.
+
+ When \l{QTextBlock}s are created, the defaultTextOption is set on their
+ QTextLayout. This allows setting global properties for the document such as the
+ default word wrap mode.
+ */
+
+/*!
+ Constructs an empty QTextDocument with the given \a parent.
+*/
+QTextDocument::QTextDocument(QObject *parent)
+ : QObject(*new QTextDocumentPrivate, parent)
+{
+ Q_D(QTextDocument);
+ d->init();
+}
+
+/*!
+ Constructs a QTextDocument containing the plain (unformatted) \a text
+ specified, and with the given \a parent.
+*/
+QTextDocument::QTextDocument(const QString &text, QObject *parent)
+ : QObject(*new QTextDocumentPrivate, parent)
+{
+ Q_D(QTextDocument);
+ d->init();
+ QTextCursor(this).insertText(text);
+}
+
+/*!
+ \internal
+*/
+QTextDocument::QTextDocument(QTextDocumentPrivate &dd, QObject *parent)
+ : QObject(dd, parent)
+{
+ Q_D(QTextDocument);
+ d->init();
+}
+
+/*!
+ Destroys the document.
+*/
+QTextDocument::~QTextDocument()
+{
+}
+
+
+/*!
+ Creates a new QTextDocument that is a copy of this text document. \a
+ parent is the parent of the returned text document.
+*/
+QTextDocument *QTextDocument::clone(QObject *parent) const
+{
+ Q_D(const QTextDocument);
+ QTextDocument *doc = new QTextDocument(parent);
+ QTextCursor(doc).insertFragment(QTextDocumentFragment(this));
+ doc->rootFrame()->setFrameFormat(rootFrame()->frameFormat());
+ QTextDocumentPrivate *priv = doc->d_func();
+ priv->title = d->title;
+ priv->url = d->url;
+ priv->pageSize = d->pageSize;
+ priv->indentWidth = d->indentWidth;
+ priv->defaultTextOption = d->defaultTextOption;
+ priv->setDefaultFont(d->defaultFont());
+ priv->resources = d->resources;
+ priv->cachedResources.clear();
+#ifndef QT_NO_CSSPARSER
+ priv->defaultStyleSheet = d->defaultStyleSheet;
+ priv->parsedDefaultStyleSheet = d->parsedDefaultStyleSheet;
+#endif
+ return doc;
+}
+
+/*!
+ Returns true if the document is empty; otherwise returns false.
+*/
+bool QTextDocument::isEmpty() const
+{
+ Q_D(const QTextDocument);
+ /* because if we're empty we still have one single paragraph as
+ * one single fragment */
+ return d->length() <= 1;
+}
+
+/*!
+ Clears the document.
+*/
+void QTextDocument::clear()
+{
+ Q_D(QTextDocument);
+ d->clear();
+ d->resources.clear();
+}
+
+/*!
+ \since 4.2
+
+ Undoes the last editing operation on the document if undo is
+ available. The provided \a cursor is positioned at the end of the
+ location where the edition operation was undone.
+
+ See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework}
+ documentation for details.
+
+ \sa undoAvailable(), isUndoRedoEnabled()
+*/
+void QTextDocument::undo(QTextCursor *cursor)
+{
+ Q_D(QTextDocument);
+ const int pos = d->undoRedo(true);
+ if (cursor && pos >= 0) {
+ *cursor = QTextCursor(this);
+ cursor->setPosition(pos);
+ }
+}
+
+/*!
+ \since 4.2
+ Redoes the last editing operation on the document if \link
+ QTextDocument::isRedoAvailable() redo is available\endlink.
+
+ The provided \a cursor is positioned at the end of the location where
+ the edition operation was redone.
+*/
+void QTextDocument::redo(QTextCursor *cursor)
+{
+ Q_D(QTextDocument);
+ const int pos = d->undoRedo(false);
+ if (cursor && pos >= 0) {
+ *cursor = QTextCursor(this);
+ cursor->setPosition(pos);
+ }
+}
+
+/*!
+ \overload
+
+*/
+void QTextDocument::undo()
+{
+ Q_D(QTextDocument);
+ d->undoRedo(true);
+}
+
+/*!
+ \overload
+ Redoes the last editing operation on the document if \link
+ QTextDocument::isRedoAvailable() redo is available\endlink.
+*/
+void QTextDocument::redo()
+{
+ Q_D(QTextDocument);
+ d->undoRedo(false);
+}
+
+/*!
+ \internal
+
+ Appends a custom undo \a item to the undo stack.
+*/
+void QTextDocument::appendUndoItem(QAbstractUndoItem *item)
+{
+ Q_D(QTextDocument);
+ d->appendUndoItem(item);
+}
+
+/*!
+ \property QTextDocument::undoRedoEnabled
+ \brief whether undo/redo are enabled for this document
+
+ This defaults to true. If disabled, the undo stack is cleared and
+ no items will be added to it.
+*/
+void QTextDocument::setUndoRedoEnabled(bool enable)
+{
+ Q_D(QTextDocument);
+ d->enableUndoRedo(enable);
+}
+
+bool QTextDocument::isUndoRedoEnabled() const
+{
+ Q_D(const QTextDocument);
+ return d->isUndoRedoEnabled();
+}
+
+/*!
+ \property QTextDocument::maximumBlockCount
+ \since 4.2
+ \brief Specifies the limit for blocks in the document.
+
+ Specifies the maximum number of blocks the document may have. If there are
+ more blocks in the document that specified with this property blocks are removed
+ from the beginning of the document.
+
+ A negative or zero value specifies that the document may contain an unlimited
+ amount of blocks.
+
+ The default value is 0.
+
+ Note that setting this property will apply the limit immediately to the document
+ contents.
+
+ Setting this property also disables the undo redo history.
+
+ This property is undefined in documents with tables or frames.
+*/
+int QTextDocument::maximumBlockCount() const
+{
+ Q_D(const QTextDocument);
+ return d->maximumBlockCount;
+}
+
+void QTextDocument::setMaximumBlockCount(int maximum)
+{
+ Q_D(QTextDocument);
+ d->maximumBlockCount = maximum;
+ d->ensureMaximumBlockCount();
+ setUndoRedoEnabled(false);
+}
+
+/*!
+ \since 4.3
+
+ The default text option is used on all QTextLayout objects in the document.
+ This allows setting global properties for the document such as the default
+ word wrap mode.
+*/
+QTextOption QTextDocument::defaultTextOption() const
+{
+ Q_D(const QTextDocument);
+ return d->defaultTextOption;
+}
+
+/*!
+ \since 4.3
+
+ Sets the default text option.
+*/
+void QTextDocument::setDefaultTextOption(const QTextOption &option)
+{
+ Q_D(QTextDocument);
+ d->defaultTextOption = option;
+ if (d->lout)
+ d->lout->documentChanged(0, 0, d->length());
+}
+
+/*!
+ \fn void QTextDocument::markContentsDirty(int position, int length)
+
+ Marks the contents specified by the given \a position and \a length
+ as "dirty", informing the document that it needs to be laid out
+ again.
+*/
+void QTextDocument::markContentsDirty(int from, int length)
+{
+ Q_D(QTextDocument);
+ if (!d->inContentsChange)
+ d->beginEditBlock();
+ d->documentChange(from, length);
+ if (!d->inContentsChange)
+ d->endEditBlock();
+}
+
+/*!
+ \property QTextDocument::useDesignMetrics
+ \since 4.1
+ \brief whether the document uses design metrics of fonts to improve the accuracy of text layout
+
+ If this property is set to true, the layout will use design metrics.
+ Otherwise, the metrics of the paint device as set on
+ QAbstractTextDocumentLayout::setPaintDevice() will be used.
+
+ Using design metrics makes a layout have a width that is no longer dependent on hinting
+ and pixel-rounding. This means that WYSIWYG text layout becomes possible because the width
+ scales much more linearly based on paintdevice metrics than it would otherwise.
+
+ By default, this property is false.
+*/
+
+void QTextDocument::setUseDesignMetrics(bool b)
+{
+ Q_D(QTextDocument);
+ if (b == d->defaultTextOption.useDesignMetrics())
+ return;
+ d->defaultTextOption.setUseDesignMetrics(b);
+ if (d->lout)
+ d->lout->documentChanged(0, 0, d->length());
+}
+
+bool QTextDocument::useDesignMetrics() const
+{
+ Q_D(const QTextDocument);
+ return d->defaultTextOption.useDesignMetrics();
+}
+
+/*!
+ \since 4.2
+
+ Draws the content of the document with painter \a p, clipped to \a rect.
+ If \a rect is a null rectangle (default) then the document is painted unclipped.
+*/
+void QTextDocument::drawContents(QPainter *p, const QRectF &rect)
+{
+ p->save();
+ QAbstractTextDocumentLayout::PaintContext ctx;
+ if (rect.isValid()) {
+ p->setClipRect(rect);
+ ctx.clip = rect;
+ }
+ documentLayout()->draw(p, ctx);
+ p->restore();
+}
+
+/*!
+ \property QTextDocument::textWidth
+ \since 4.2
+
+ The text width specifies the preferred width for text in the document. If
+ the text (or content in general) is wider than the specified with it is broken
+ into multiple lines and grows vertically. If the text cannot be broken into multiple
+ lines to fit into the specified text width it will be larger and the size() and the
+ idealWidth() property will reflect that.
+
+ If the text width is set to -1 then the text will not be broken into multiple lines
+ unless it is enforced through an explicit line break or a new paragraph.
+
+ The default value is -1.
+
+ Setting the text width will also set the page height to -1, causing the document to
+ grow or shrink vertically in a continuous way. If you want the document layout to break
+ the text into multiple pages then you have to set the pageSize property instead.
+
+ \sa size(), idealWidth(), pageSize()
+*/
+void QTextDocument::setTextWidth(qreal width)
+{
+ Q_D(QTextDocument);
+ QSizeF sz = d->pageSize;
+ sz.setWidth(width);
+ sz.setHeight(-1);
+ setPageSize(sz);
+}
+
+qreal QTextDocument::textWidth() const
+{
+ Q_D(const QTextDocument);
+ return d->pageSize.width();
+}
+
+/*!
+ \since 4.2
+
+ Returns the ideal width of the text document. The ideal width is the actually used width
+ of the document without optional alignments taken into account. It is always <= size().width().
+
+ \sa adjustSize(), textWidth
+*/
+qreal QTextDocument::idealWidth() const
+{
+ if (QTextDocumentLayout *lout = qobject_cast<QTextDocumentLayout *>(documentLayout()))
+ return lout->idealWidth();
+ return textWidth();
+}
+
+/*!
+ \property QTextDocument::documentMargin
+ \since 4.5
+
+ The margin around the document. The default is 4.
+*/
+qreal QTextDocument::documentMargin() const
+{
+ Q_D(const QTextDocument);
+ return d->documentMargin;
+}
+
+void QTextDocument::setDocumentMargin(qreal margin)
+{
+ Q_D(QTextDocument);
+ if (d->documentMargin != margin) {
+ d->documentMargin = margin;
+
+ QTextFrame* root = rootFrame();
+ QTextFrameFormat format = root->frameFormat();
+ format.setMargin(margin);
+ root->setFrameFormat(format);
+
+ if (d->lout)
+ d->lout->documentChanged(0, 0, d->length());
+ }
+}
+
+
+/*!
+ \property QTextDocument::indentWidth
+ \since 4.4
+
+ Returns the width used for text list and text block indenting.
+
+ The indent properties of QTextListFormat and QTextBlockFormat specify
+ multiples of this value. The default indent width is 40.
+*/
+qreal QTextDocument::indentWidth() const
+{
+ Q_D(const QTextDocument);
+ return d->indentWidth;
+}
+
+
+/*!
+ \since 4.4
+
+ Sets the \a width used for text list and text block indenting.
+
+ The indent properties of QTextListFormat and QTextBlockFormat specify
+ multiples of this value. The default indent width is 40 .
+
+ \sa indentWidth()
+*/
+void QTextDocument::setIndentWidth(qreal width)
+{
+ Q_D(QTextDocument);
+ if (d->indentWidth != width) {
+ d->indentWidth = width;
+ if (d->lout)
+ d->lout->documentChanged(0, 0, d->length());
+ }
+}
+
+
+
+
+/*!
+ \since 4.2
+
+ Adjusts the document to a reasonable size.
+
+ \sa idealWidth(), textWidth, size
+*/
+void QTextDocument::adjustSize()
+{
+ // Pull this private function in from qglobal.cpp
+ QFont f = defaultFont();
+ QFontMetrics fm(f);
+ int mw = fm.width(QLatin1Char('x')) * 80;
+ int w = mw;
+ setTextWidth(w);
+ QSizeF size = documentLayout()->documentSize();
+ if (size.width() != 0) {
+ w = qt_int_sqrt((uint)(5 * size.height() * size.width() / 3));
+ setTextWidth(qMin(w, mw));
+
+ size = documentLayout()->documentSize();
+ if (w*3 < 5*size.height()) {
+ w = qt_int_sqrt((uint)(2 * size.height() * size.width()));
+ setTextWidth(qMin(w, mw));
+ }
+ }
+ setTextWidth(idealWidth());
+}
+
+/*!
+ \property QTextDocument::size
+ \since 4.2
+
+ Returns the actual size of the document.
+ This is equivalent to documentLayout()->documentSize();
+
+ The size of the document can be changed either by setting
+ a text width or setting an entire page size.
+
+ Note that the width is always >= pageSize().width().
+
+ By default, for a newly-created, empty document, this property contains
+ a configuration-dependent size.
+
+ \sa setTextWidth(), setPageSize(), idealWidth()
+*/
+QSizeF QTextDocument::size() const
+{
+ return documentLayout()->documentSize();
+}
+
+/*!
+ \property QTextDocument::blockCount
+ \since 4.2
+
+ Returns the number of text blocks in the document.
+
+ The value of this property is undefined in documents with tables or frames.
+
+ By default, if defined, this property contains a value of 1.
+ \sa lineCount(), characterCount()
+*/
+int QTextDocument::blockCount() const
+{
+ Q_D(const QTextDocument);
+ return d->blockMap().numNodes();
+}
+
+
+/*!
+ \since 4.5
+
+ Returns the number of lines of this document (if the layout supports
+ this). Otherwise, this is identical to the number of blocks.
+
+ \sa blockCount(), characterCount()
+ */
+int QTextDocument::lineCount() const
+{
+ Q_D(const QTextDocument);
+ return d->blockMap().length(2);
+}
+
+/*!
+ \since 4.5
+
+ Returns the number of characters of this document.
+
+ \sa blockCount(), characterAt()
+ */
+int QTextDocument::characterCount() const
+{
+ Q_D(const QTextDocument);
+ return d->length();
+}
+
+/*!
+ \since 4.5
+
+ Returns the character at position \a pos, or a null character if the
+ position is out of range.
+
+ \sa characterCount()
+ */
+QChar QTextDocument::characterAt(int pos) const
+{
+ Q_D(const QTextDocument);
+ if (pos < 0 || pos >= d->length())
+ return QChar();
+ QTextDocumentPrivate::FragmentIterator fragIt = d->find(pos);
+ const QTextFragmentData * const frag = fragIt.value();
+ const int offsetInFragment = qMax(0, pos - fragIt.position());
+ return d->text.at(frag->stringPosition + offsetInFragment);
+}
+
+
+/*!
+ \property QTextDocument::defaultStyleSheet
+ \since 4.2
+
+ The default style sheet is applied to all newly HTML formatted text that is
+ inserted into the document, for example using setHtml() or QTextCursor::insertHtml().
+
+ The style sheet needs to be compliant to CSS 2.1 syntax.
+
+ \bold{Note:} Changing the default style sheet does not have any effect to the existing content
+ of the document.
+
+ \sa {Supported HTML Subset}
+*/
+
+#ifndef QT_NO_CSSPARSER
+void QTextDocument::setDefaultStyleSheet(const QString &sheet)
+{
+ Q_D(QTextDocument);
+ d->defaultStyleSheet = sheet;
+ QCss::Parser parser(sheet);
+ d->parsedDefaultStyleSheet = QCss::StyleSheet();
+ d->parsedDefaultStyleSheet.origin = QCss::StyleSheetOrigin_UserAgent;
+ parser.parse(&d->parsedDefaultStyleSheet);
+}
+
+QString QTextDocument::defaultStyleSheet() const
+{
+ Q_D(const QTextDocument);
+ return d->defaultStyleSheet;
+}
+#endif // QT_NO_CSSPARSER
+
+/*!
+ \fn void QTextDocument::contentsChanged()
+
+ This signal is emitted whenever the document's content changes; for
+ example, when text is inserted or deleted, or when formatting is applied.
+
+ \sa contentsChange()
+*/
+
+/*!
+ \fn void QTextDocument::contentsChange(int position, int charsRemoved, int charsAdded)
+
+ This signal is emitted whenever the document's content changes; for
+ example, when text is inserted or deleted, or when formatting is applied.
+
+ Information is provided about the \a position of the character in the
+ document where the change occurred, the number of characters removed
+ (\a charsRemoved), and the number of characters added (\a charsAdded).
+
+ The signal is emitted before the document's layout manager is notified
+ about the change. This hook allows you to implement syntax highlighting
+ for the document.
+
+ \sa QAbstractTextDocumentLayout::documentChanged(), contentsChanged()
+*/
+
+
+/*!
+ \fn QTextDocument::undoAvailable(bool available);
+
+ This signal is emitted whenever undo operations become available
+ (\a available is true) or unavailable (\a available is false).
+
+ See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework}
+ documentation for details.
+
+ \sa undo(), isUndoRedoEnabled()
+*/
+
+/*!
+ \fn QTextDocument::redoAvailable(bool available);
+
+ This signal is emitted whenever redo operations become available
+ (\a available is true) or unavailable (\a available is false).
+*/
+
+/*!
+ \fn QTextDocument::cursorPositionChanged(const QTextCursor &cursor);
+
+ This signal is emitted whenever the position of a cursor changed
+ due to an editing operation. The cursor that changed is passed in
+ \a cursor. If you need a signal when the cursor is moved with the
+ arrow keys you can use the \l{QTextEdit::}{cursorPositionChanged()} signal in
+ QTextEdit.
+*/
+
+/*!
+ \fn QTextDocument::blockCountChanged(int newBlockCount);
+ \since 4.3
+
+ This signal is emitted when the total number of text blocks in the
+ document changes. The value passed in \a newBlockCount is the new
+ total.
+*/
+
+/*!
+ \fn QTextDocument::documentLayoutChanged();
+ \since 4.4
+
+ This signal is emitted when a new document layout is set.
+
+ \sa setDocumentLayout()
+
+*/
+
+
+/*!
+ Returns true if undo is available; otherwise returns false.
+*/
+bool QTextDocument::isUndoAvailable() const
+{
+ Q_D(const QTextDocument);
+ return d->isUndoAvailable();
+}
+
+/*!
+ Returns true if redo is available; otherwise returns false.
+*/
+bool QTextDocument::isRedoAvailable() const
+{
+ Q_D(const QTextDocument);
+ return d->isRedoAvailable();
+}
+
+
+/*! \since 4.4
+
+ Returns the document's revision (if undo is enabled).
+
+ The revision is guaranteed to increase when a document that is not
+ modified is edited.
+
+ \sa QTextBlock::revision(), isModified()
+ */
+int QTextDocument::revision() const
+{
+ Q_D(const QTextDocument);
+ return d->undoState;
+}
+
+
+
+/*!
+ Sets the document to use the given \a layout. The previous layout
+ is deleted.
+
+ \sa documentLayoutChanged()
+*/
+void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *layout)
+{
+ Q_D(QTextDocument);
+ d->setLayout(layout);
+}
+
+/*!
+ Returns the document layout for this document.
+*/
+QAbstractTextDocumentLayout *QTextDocument::documentLayout() const
+{
+ Q_D(const QTextDocument);
+ if (!d->lout) {
+ QTextDocument *that = const_cast<QTextDocument *>(this);
+ that->d_func()->setLayout(new QTextDocumentLayout(that));
+ }
+ return d->lout;
+}
+
+
+/*!
+ Returns meta information about the document of the type specified by
+ \a info.
+
+ \sa setMetaInformation()
+*/
+QString QTextDocument::metaInformation(MetaInformation info) const
+{
+ Q_D(const QTextDocument);
+ switch (info) {
+ case DocumentTitle:
+ return d->title;
+ case DocumentUrl:
+ return d->url;
+ }
+ return QString();
+}
+
+/*!
+ Sets the document's meta information of the type specified by \a info
+ to the given \a string.
+
+ \sa metaInformation()
+*/
+void QTextDocument::setMetaInformation(MetaInformation info, const QString &string)
+{
+ Q_D(QTextDocument);
+ switch (info) {
+ case DocumentTitle:
+ d->title = string;
+ break;
+ case DocumentUrl:
+ d->url = string;
+ break;
+ }
+}
+
+/*!
+ Returns the plain text contained in the document. If you want
+ formatting information use a QTextCursor instead.
+
+ \sa toHtml()
+*/
+QString QTextDocument::toPlainText() const
+{
+ Q_D(const QTextDocument);
+ QString txt = d->plainText();
+
+ QChar *uc = txt.data();
+ QChar *e = uc + txt.size();
+
+ for (; uc != e; ++uc) {
+ switch (uc->unicode()) {
+ case 0xfdd0: // QTextBeginningOfFrame
+ case 0xfdd1: // QTextEndOfFrame
+ case QChar::ParagraphSeparator:
+ case QChar::LineSeparator:
+ *uc = QLatin1Char('\n');
+ break;
+ case QChar::Nbsp:
+ *uc = QLatin1Char(' ');
+ break;
+ default:
+ ;
+ }
+ }
+ return txt;
+}
+
+/*!
+ Replaces the entire contents of the document with the given plain
+ \a text.
+
+ \sa setHtml()
+*/
+void QTextDocument::setPlainText(const QString &text)
+{
+ Q_D(QTextDocument);
+ bool previousState = d->isUndoRedoEnabled();
+ d->enableUndoRedo(false);
+ d->clear();
+ QTextCursor(this).insertText(text);
+ d->enableUndoRedo(previousState);
+}
+
+/*!
+ Replaces the entire contents of the document with the given
+ HTML-formatted text in the \a html string.
+
+ The HTML formatting is respected as much as possible; for example,
+ "<b>bold</b> text" will produce text where the first word has a font
+ weight that gives it a bold appearance: "\bold{bold} text".
+
+ \note It is the responsibility of the caller to make sure that the
+ text is correctly decoded when a QString containing HTML is created
+ and passed to setHtml().
+
+ \sa setPlainText(), {Supported HTML Subset}
+*/
+
+#ifndef QT_NO_TEXTHTMLPARSER
+
+void QTextDocument::setHtml(const QString &html)
+{
+ Q_D(QTextDocument);
+ bool previousState = d->isUndoRedoEnabled();
+ d->enableUndoRedo(false);
+ d->clear();
+ QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import();
+ d->enableUndoRedo(previousState);
+}
+
+#endif // QT_NO_TEXTHTMLPARSER
+
+/*!
+ \enum QTextDocument::FindFlag
+
+ This enum describes the options available to QTextDocument's find function. The options
+ can be OR-ed together from the following list:
+
+ \value FindBackward Search backwards instead of forwards.
+ \value FindCaseSensitively By default find works case insensitive. Specifying this option
+ changes the behaviour to a case sensitive find operation.
+ \value FindWholeWords Makes find match only complete words.
+*/
+
+/*!
+ \enum QTextDocument::MetaInformation
+
+ This enum describes the different types of meta information that can be
+ added to a document.
+
+ \value DocumentTitle The title of the document.
+ \value DocumentUrl The url of the document. The loadResource() function uses
+ this url as the base when loading relative resources.
+
+ \sa metaInformation(), setMetaInformation()
+*/
+
+/*!
+ \fn QTextCursor QTextDocument::find(const QString &subString, int position, FindFlags options) const
+
+ \overload
+
+ Finds the next occurrence of the string, \a subString, in the document.
+ The search starts at the given \a position, and proceeds forwards
+ through the document unless specified otherwise in the search options.
+ The \a options control the type of search performed.
+
+ Returns a cursor with the match selected if \a subString
+ was found; otherwise returns a null cursor.
+
+ If the \a position is 0 (the default) the search begins from the beginning
+ of the document; otherwise it begins at the specified position.
+*/
+QTextCursor QTextDocument::find(const QString &subString, int from, FindFlags options) const
+{
+ QRegExp expr(subString);
+ expr.setPatternSyntax(QRegExp::FixedString);
+ expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive);
+
+ return find(expr, from, options);
+}
+
+/*!
+ \fn QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &cursor, FindFlags options) const
+
+ Finds the next occurrence of the string, \a subString, in the document.
+ The search starts at the position of the given \a cursor, and proceeds
+ forwards through the document unless specified otherwise in the search
+ options. The \a options control the type of search performed.
+
+ Returns a cursor with the match selected if \a subString was found; otherwise
+ returns a null cursor.
+
+ If the given \a cursor has a selection, the search begins after the
+ selection; otherwise it begins at the cursor's position.
+
+ By default the search is case-sensitive, and can match text anywhere in the
+ document.
+*/
+QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &from, FindFlags options) const
+{
+ int pos = 0;
+ if (!from.isNull()) {
+ if (options & QTextDocument::FindBackward)
+ pos = from.selectionStart();
+ else
+ pos = from.selectionEnd();
+ }
+ QRegExp expr(subString);
+ expr.setPatternSyntax(QRegExp::FixedString);
+ expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive);
+
+ return find(expr, pos, options);
+}
+
+
+static bool findInBlock(const QTextBlock &block, const QRegExp &expression, int offset,
+ QTextDocument::FindFlags options, QTextCursor &cursor)
+{
+ const QRegExp expr(expression);
+ QString text = block.text();
+ text.replace(QChar::Nbsp, QLatin1Char(' '));
+
+ int idx = -1;
+ while (offset >=0 && offset <= text.length()) {
+ idx = (options & QTextDocument::FindBackward) ?
+ expr.lastIndexIn(text, offset) : expr.indexIn(text, offset);
+ if (idx == -1)
+ return false;
+
+ if (options & QTextDocument::FindWholeWords) {
+ const int start = idx;
+ const int end = start + expr.matchedLength();
+ if ((start != 0 && text.at(start - 1).isLetterOrNumber())
+ || (end != text.length() && text.at(end).isLetterOrNumber())) {
+ //if this is not a whole word, continue the search in the string
+ offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
+ idx = -1;
+ continue;
+ }
+ }
+ //we have a hit, return the cursor for that.
+ break;
+ }
+ if (idx == -1)
+ return false;
+ cursor = QTextCursor(block.docHandle(), block.position() + idx);
+ cursor.setPosition(cursor.position() + expr.matchedLength(), QTextCursor::KeepAnchor);
+ return true;
+}
+
+/*!
+ \fn QTextCursor QTextDocument::find(const QRegExp & expr, int position, FindFlags options) const
+
+ \overload
+
+ Finds the next occurrence, matching the regular expression, \a expr, in the document.
+ The search starts at the given \a position, and proceeds forwards
+ through the document unless specified otherwise in the search options.
+ The \a options control the type of search performed. The FindCaseSensitively
+ option is ignored for this overload, use QRegExp::caseSensitivity instead.
+
+ Returns a cursor with the match selected if a match was found; otherwise
+ returns a null cursor.
+
+ If the \a position is 0 (the default) the search begins from the beginning
+ of the document; otherwise it begins at the specified position.
+*/
+QTextCursor QTextDocument::find(const QRegExp & expr, int from, FindFlags options) const
+{
+ Q_D(const QTextDocument);
+
+ if (expr.isEmpty())
+ return QTextCursor();
+
+ int pos = from;
+ //the cursor is positioned between characters, so for a backward search
+ //do not include the character given in the position.
+ if (options & FindBackward) {
+ --pos ;
+ if(pos < 0)
+ return QTextCursor();
+ }
+
+ QTextCursor cursor;
+ QTextBlock block = d->blocksFind(pos);
+
+ if (!(options & FindBackward)) {
+ int blockOffset = qMax(0, pos - block.position());
+ while (block.isValid()) {
+ if (findInBlock(block, expr, blockOffset, options, cursor))
+ return cursor;
+ blockOffset = 0;
+ block = block.next();
+ }
+ } else {
+ int blockOffset = pos - block.position();
+ while (block.isValid()) {
+ if (findInBlock(block, expr, blockOffset, options, cursor))
+ return cursor;
+ block = block.previous();
+ blockOffset = block.length() - 1;
+ }
+ }
+
+ return QTextCursor();
+}
+
+/*!
+ \fn QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &cursor, FindFlags options) const
+
+ Finds the next occurrence, matching the regular expression, \a expr, in the document.
+ The search starts at the position of the given \a cursor, and proceeds
+ forwards through the document unless specified otherwise in the search
+ options. The \a options control the type of search performed. The FindCaseSensitively
+ option is ignored for this overload, use QRegExp::caseSensitivity instead.
+
+ Returns a cursor with the match selected if a match was found; otherwise
+ returns a null cursor.
+
+ If the given \a cursor has a selection, the search begins after the
+ selection; otherwise it begins at the cursor's position.
+
+ By default the search is case-sensitive, and can match text anywhere in the
+ document.
+*/
+QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &from, FindFlags options) const
+{
+ int pos = 0;
+ if (!from.isNull()) {
+ if (options & QTextDocument::FindBackward)
+ pos = from.selectionStart();
+ else
+ pos = from.selectionEnd();
+ }
+ return find(expr, pos, options);
+}
+
+
+/*!
+ \fn QTextObject *QTextDocument::createObject(const QTextFormat &format)
+
+ Creates and returns a new document object (a QTextObject), based
+ on the given \a format.
+
+ QTextObjects will always get created through this method, so you
+ must reimplement it if you use custom text objects inside your document.
+*/
+QTextObject *QTextDocument::createObject(const QTextFormat &f)
+{
+ QTextObject *obj = 0;
+ if (f.isListFormat())
+ obj = new QTextList(this);
+ else if (f.isTableFormat())
+ obj = new QTextTable(this);
+ else if (f.isFrameFormat())
+ obj = new QTextFrame(this);
+
+ return obj;
+}
+
+/*!
+ \internal
+
+ Returns the frame that contains the text cursor position \a pos.
+*/
+QTextFrame *QTextDocument::frameAt(int pos) const
+{
+ Q_D(const QTextDocument);
+ return d->frameAt(pos);
+}
+
+/*!
+ Returns the document's root frame.
+*/
+QTextFrame *QTextDocument::rootFrame() const
+{
+ Q_D(const QTextDocument);
+ return d->rootFrame();
+}
+
+/*!
+ Returns the text object associated with the given \a objectIndex.
+*/
+QTextObject *QTextDocument::object(int objectIndex) const
+{
+ Q_D(const QTextDocument);
+ return d->objectForIndex(objectIndex);
+}
+
+/*!
+ Returns the text object associated with the format \a f.
+*/
+QTextObject *QTextDocument::objectForFormat(const QTextFormat &f) const
+{
+ Q_D(const QTextDocument);
+ return d->objectForFormat(f);
+}
+
+
+/*!
+ Returns the text block that contains the \a{pos}-th character.
+*/
+QTextBlock QTextDocument::findBlock(int pos) const
+{
+ Q_D(const QTextDocument);
+ return QTextBlock(docHandle(), d->blockMap().findNode(pos));
+}
+
+/*!
+ \since 4.4
+ Returns the text block with the specified \a blockNumber.
+
+ \sa QTextBlock::blockNumber()
+*/
+QTextBlock QTextDocument::findBlockByNumber(int blockNumber) const
+{
+ Q_D(const QTextDocument);
+ return QTextBlock(docHandle(), d->blockMap().findNode(blockNumber, 1));
+}
+
+/*!
+ \since 4.5
+ Returns the text block that contains the specified \a lineNumber.
+
+ \sa QTextBlock::firstLineNumber()
+*/
+QTextBlock QTextDocument::findBlockByLineNumber(int lineNumber) const
+{
+ Q_D(const QTextDocument);
+ return QTextBlock(docHandle(), d->blockMap().findNode(lineNumber, 2));
+}
+
+/*!
+ Returns the document's first text block.
+
+ \sa firstBlock()
+*/
+QTextBlock QTextDocument::begin() const
+{
+ Q_D(const QTextDocument);
+ return QTextBlock(docHandle(), d->blockMap().begin().n);
+}
+
+/*!
+ This function returns a block to test for the end of the document
+ while iterating over it.
+
+ \snippet doc/src/snippets/textdocumentendsnippet.cpp 0
+
+ The block returned is invalid and represents the block after the
+ last block in the document. You can use lastBlock() to retrieve the
+ last valid block of the document.
+
+ \sa lastBlock()
+*/
+QTextBlock QTextDocument::end() const
+{
+ return QTextBlock(docHandle(), 0);
+}
+
+/*!
+ \since 4.4
+ Returns the document's first text block.
+*/
+QTextBlock QTextDocument::firstBlock() const
+{
+ Q_D(const QTextDocument);
+ return QTextBlock(docHandle(), d->blockMap().begin().n);
+}
+
+/*!
+ \since 4.4
+ Returns the document's last (valid) text block.
+*/
+QTextBlock QTextDocument::lastBlock() const
+{
+ Q_D(const QTextDocument);
+ return QTextBlock(docHandle(), d->blockMap().last().n);
+}
+
+/*!
+ \property QTextDocument::pageSize
+ \brief the page size that should be used for laying out the document
+
+ By default, for a newly-created, empty document, this property contains
+ an undefined size.
+
+ \sa modificationChanged()
+*/
+
+void QTextDocument::setPageSize(const QSizeF &size)
+{
+ Q_D(QTextDocument);
+ d->pageSize = size;
+ if (d->lout)
+ d->lout->documentChanged(0, 0, d->length());
+}
+
+QSizeF QTextDocument::pageSize() const
+{
+ Q_D(const QTextDocument);
+ return d->pageSize;
+}
+
+/*!
+ returns the number of pages in this document.
+*/
+int QTextDocument::pageCount() const
+{
+ return documentLayout()->pageCount();
+}
+
+/*!
+ Sets the default \a font to use in the document layout.
+*/
+void QTextDocument::setDefaultFont(const QFont &font)
+{
+ Q_D(QTextDocument);
+ d->setDefaultFont(font);
+ if (d->lout)
+ d->lout->documentChanged(0, 0, d->length());
+}
+
+/*!
+ Returns the default font to be used in the document layout.
+*/
+QFont QTextDocument::defaultFont() const
+{
+ Q_D(const QTextDocument);
+ return d->defaultFont();
+}
+
+/*!
+ \fn QTextDocument::modificationChanged(bool changed)
+
+ This signal is emitted whenever the content of the document
+ changes in a way that affects the modification state. If \a
+ changed is true, the document has been modified; otherwise it is
+ false.
+
+ For example, calling setModified(false) on a document and then
+ inserting text causes the signal to get emitted. If you undo that
+ operation, causing the document to return to its original
+ unmodified state, the signal will get emitted again.
+*/
+
+/*!
+ \property QTextDocument::modified
+ \brief whether the document has been modified by the user
+
+ By default, this property is false.
+
+ \sa modificationChanged()
+*/
+
+bool QTextDocument::isModified() const
+{
+ return docHandle()->isModified();
+}
+
+void QTextDocument::setModified(bool m)
+{
+ docHandle()->setModified(m);
+}
+
+#ifndef QT_NO_PRINTER
+static void printPage(int index, QPainter *painter, const QTextDocument *doc, const QRectF &body, const QPointF &pageNumberPos)
+{
+ painter->save();
+ painter->translate(body.left(), body.top() - (index - 1) * body.height());
+ QRectF view(0, (index - 1) * body.height(), body.width(), body.height());
+
+ QAbstractTextDocumentLayout *layout = doc->documentLayout();
+ QAbstractTextDocumentLayout::PaintContext ctx;
+
+ painter->setClipRect(view);
+ ctx.clip = view;
+
+ // don't use the system palette text as default text color, on HP/UX
+ // for example that's white, and white text on white paper doesn't
+ // look that nice
+ ctx.palette.setColor(QPalette::Text, Qt::black);
+
+ layout->draw(painter, ctx);
+
+ if (!pageNumberPos.isNull()) {
+ painter->setClipping(false);
+ painter->setFont(QFont(doc->defaultFont()));
+ const QString pageString = QString::number(index);
+
+ painter->drawText(qRound(pageNumberPos.x() - painter->fontMetrics().width(pageString)),
+ qRound(pageNumberPos.y() + view.top()),
+ pageString);
+ }
+
+ painter->restore();
+}
+
+extern int qt_defaultDpi();
+
+/*!
+ Prints the document to the given \a printer. The QPrinter must be
+ set up before being used with this function.
+
+ This is only a convenience method to print the whole document to the printer.
+
+ If the document is already paginated through a specified height in the pageSize()
+ property it is printed as-is.
+
+ If the document is not paginated, like for example a document used in a QTextEdit,
+ then a temporary copy of the document is created and the copy is broken into
+ multiple pages according to the size of the QPrinter's paperRect(). By default
+ a 2 cm margin is set around the document contents. In addition the current page
+ number is printed at the bottom of each page.
+
+ Note that QPrinter::Selection is not supported as print range with this function since
+ the selection is a property of QTextCursor. If you have a QTextEdit associated with
+ your QTextDocument then you can use QTextEdit's print() function because QTextEdit has
+ access to the user's selection.
+
+ \sa QTextEdit::print()
+*/
+
+void QTextDocument::print(QPrinter *printer) const
+{
+ Q_D(const QTextDocument);
+
+ if (!printer || !printer->isValid())
+ return;
+
+ if (!d->title.isEmpty())
+ printer->setDocName(d->title);
+
+ bool documentPaginated = d->pageSize.isValid() && !d->pageSize.isNull()
+ && d->pageSize.height() != INT_MAX;
+
+ if (!documentPaginated && !printer->fullPage() && !printer->d_func()->hasCustomPageMargins)
+ printer->setPageMargins(23.53, 23.53, 23.53, 23.53, QPrinter::Millimeter);
+
+ QPainter p(printer);
+
+ // Check that there is a valid device to print to.
+ if (!p.isActive())
+ return;
+
+ const QTextDocument *doc = this;
+ QTextDocument *clonedDoc = 0;
+ (void)doc->documentLayout(); // make sure that there is a layout
+
+ QRectF body = QRectF(QPointF(0, 0), d->pageSize);
+ QPointF pageNumberPos;
+
+ if (documentPaginated) {
+ qreal sourceDpiX = qt_defaultDpi();
+ qreal sourceDpiY = sourceDpiX;
+
+ QPaintDevice *dev = doc->documentLayout()->paintDevice();
+ if (dev) {
+ sourceDpiX = dev->logicalDpiX();
+ sourceDpiY = dev->logicalDpiY();
+ }
+
+ const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX;
+ const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY;
+
+ // scale to dpi
+ p.scale(dpiScaleX, dpiScaleY);
+
+ QSizeF scaledPageSize = d->pageSize;
+ scaledPageSize.rwidth() *= dpiScaleX;
+ scaledPageSize.rheight() *= dpiScaleY;
+
+ const QSizeF printerPageSize(printer->pageRect().size());
+
+ // scale to page
+ p.scale(printerPageSize.width() / scaledPageSize.width(),
+ printerPageSize.height() / scaledPageSize.height());
+ } else {
+ doc = clone(const_cast<QTextDocument *>(this));
+ clonedDoc = const_cast<QTextDocument *>(doc);
+
+ for (QTextBlock srcBlock = firstBlock(), dstBlock = clonedDoc->firstBlock();
+ srcBlock.isValid() && dstBlock.isValid();
+ srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) {
+ dstBlock.layout()->setAdditionalFormats(srcBlock.layout()->additionalFormats());
+ }
+
+ QAbstractTextDocumentLayout *layout = doc->documentLayout();
+ layout->setPaintDevice(p.device());
+
+ int dpiy = p.device()->logicalDpiY();
+ int margin = 0;
+ if (printer->fullPage() && !printer->d_func()->hasCustomPageMargins) {
+ // for compatibility
+ margin = (int) ((2/2.54)*dpiy); // 2 cm margins
+ QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
+ fmt.setMargin(margin);
+ doc->rootFrame()->setFrameFormat(fmt);
+ }
+
+ QRectF pageRect(printer->pageRect());
+ body = QRectF(0, 0, pageRect.width(), pageRect.height());
+ pageNumberPos = QPointF(body.width() - margin,
+ body.height() - margin
+ + QFontMetrics(doc->defaultFont(), p.device()).ascent()
+ + 5 * dpiy / 72.0);
+ clonedDoc->setPageSize(body.size());
+ }
+
+ int docCopies;
+ int pageCopies;
+ if (printer->collateCopies() == true){
+ docCopies = 1;
+ pageCopies = printer->numCopies();
+ } else {
+ docCopies = printer->numCopies();
+ pageCopies = 1;
+ }
+
+ int fromPage = printer->fromPage();
+ int toPage = printer->toPage();
+ bool ascending = true;
+
+ if (fromPage == 0 && toPage == 0) {
+ fromPage = 1;
+ toPage = doc->pageCount();
+ }
+ // paranoia check
+ fromPage = qMax(1, fromPage);
+ toPage = qMin(doc->pageCount(), toPage);
+
+ if (printer->pageOrder() == QPrinter::LastPageFirst) {
+ int tmp = fromPage;
+ fromPage = toPage;
+ toPage = tmp;
+ ascending = false;
+ }
+
+ for (int i = 0; i < docCopies; ++i) {
+
+ int page = fromPage;
+ while (true) {
+ for (int j = 0; j < pageCopies; ++j) {
+ if (printer->printerState() == QPrinter::Aborted
+ || printer->printerState() == QPrinter::Error)
+ goto UserCanceled;
+ printPage(page, &p, doc, body, pageNumberPos);
+ if (j < pageCopies - 1)
+ printer->newPage();
+ }
+
+ if (page == toPage)
+ break;
+
+ if (ascending)
+ ++page;
+ else
+ --page;
+
+ printer->newPage();
+ }
+
+ if ( i < docCopies - 1)
+ printer->newPage();
+ }
+
+UserCanceled:
+ delete clonedDoc;
+}
+#endif
+
+/*!
+ \enum QTextDocument::ResourceType
+
+ This enum describes the types of resources that can be loaded by
+ QTextDocument's loadResource() function.
+
+ \value HtmlResource The resource contains HTML.
+ \value ImageResource The resource contains image data.
+ Currently supported data types are QVariant::Pixmap and
+ QVariant::Image. If the corresponding variant is of type
+ QVariant::ByteArray then Qt attempts to load the image using
+ QImage::loadFromData. QVariant::Icon is currently not supported.
+ The icon needs to be converted to one of the supported types first,
+ for example using QIcon::pixmap.
+ \value StyleSheetResource The resource contains CSS.
+ \value UserResource The first available value for user defined
+ resource types.
+
+ \sa loadResource()
+*/
+
+/*!
+ Returns data of the specified \a type from the resource with the
+ given \a name.
+
+ This function is called by the rich text engine to request data that isn't
+ directly stored by QTextDocument, but still associated with it. For example,
+ images are referenced indirectly by the name attribute of a QTextImageFormat
+ object.
+
+ Resources are cached internally in the document. If a resource can
+ not be found in the cache, loadResource is called to try to load
+ the resource. loadResource should then use addResource to add the
+ resource to the cache.
+
+ \sa QTextDocument::ResourceType
+*/
+QVariant QTextDocument::resource(int type, const QUrl &name) const
+{
+ Q_D(const QTextDocument);
+ QVariant r = d->resources.value(name);
+ if (!r.isValid()) {
+ r = d->cachedResources.value(name);
+ if (!r.isValid())
+ r = const_cast<QTextDocument *>(this)->loadResource(type, name);
+ }
+ return r;
+}
+
+/*!
+ Adds the resource \a resource to the resource cache, using \a
+ type and \a name as identifiers. \a type should be a value from
+ QTextDocument::ResourceType.
+
+ For example, you can add an image as a resource in order to reference it
+ from within the document:
+
+ \snippet snippets/textdocument-resources/main.cpp Adding a resource
+
+ The image can be inserted into the document using the QTextCursor API:
+
+ \snippet snippets/textdocument-resources/main.cpp Inserting an image with a cursor
+
+ Alternatively, you can insert images using the HTML \c img tag:
+
+ \snippet snippets/textdocument-resources/main.cpp Inserting an image using HTML
+*/
+void QTextDocument::addResource(int type, const QUrl &name, const QVariant &resource)
+{
+ Q_UNUSED(type);
+ Q_D(QTextDocument);
+ d->resources.insert(name, resource);
+}
+
+/*!
+ Loads data of the specified \a type from the resource with the
+ given \a name.
+
+ This function is called by the rich text engine to request data that isn't
+ directly stored by QTextDocument, but still associated with it. For example,
+ images are referenced indirectly by the name attribute of a QTextImageFormat
+ object.
+
+ When called by Qt, \a type is one of the values of
+ QTextDocument::ResourceType.
+
+ If the QTextDocument is a child object of a QTextEdit, QTextBrowser,
+ or a QTextDocument itself then the default implementation tries
+ to retrieve the data from the parent.
+*/
+QVariant QTextDocument::loadResource(int type, const QUrl &name)
+{
+ Q_D(QTextDocument);
+ QVariant r;
+
+ QTextDocument *doc = qobject_cast<QTextDocument *>(parent());
+ if (doc) {
+ r = doc->loadResource(type, name);
+ }
+#ifndef QT_NO_TEXTEDIT
+ else if (QTextEdit *edit = qobject_cast<QTextEdit *>(parent())) {
+ QUrl resolvedName = edit->d_func()->resolveUrl(name);
+ r = edit->loadResource(type, resolvedName);
+ }
+#endif
+#ifndef QT_NO_TEXTCONTROL
+ else if (QTextControl *control = qobject_cast<QTextControl *>(parent())) {
+ r = control->loadResource(type, name);
+ }
+#endif
+
+ // if resource was not loaded try to load it here
+ if (!doc && r.isNull() && name.isRelative()) {
+ QUrl currentURL = d->url;
+ QUrl resourceUrl = name;
+
+ // For the second case QUrl can merge "#someanchor" with "foo.html"
+ // correctly to "foo.html#someanchor"
+ if (!(currentURL.isRelative()
+ || (currentURL.scheme() == QLatin1String("file")
+ && !QFileInfo(currentURL.toLocalFile()).isAbsolute()))
+ || (name.hasFragment() && name.path().isEmpty())) {
+ resourceUrl = currentURL.resolved(name);
+ } else {
+ // this is our last resort when current url and new url are both relative
+ // we try to resolve against the current working directory in the local
+ // file system.
+ QFileInfo fi(currentURL.toLocalFile());
+ if (fi.exists()) {
+ resourceUrl =
+ QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(name);
+ }
+ }
+
+ QString s = resourceUrl.toLocalFile();
+ QFile f(s);
+ if (!s.isEmpty() && f.open(QFile::ReadOnly)) {
+ r = f.readAll();
+ f.close();
+ }
+ }
+
+ if (!r.isNull()) {
+ if (type == ImageResource && r.type() == QVariant::ByteArray) {
+ if (qApp->thread() != QThread::currentThread()) {
+ // must use images in non-GUI threads
+ QImage image;
+ image.loadFromData(r.toByteArray());
+ if (!image.isNull())
+ r = image;
+ } else {
+ QPixmap pm;
+ pm.loadFromData(r.toByteArray());
+ if (!pm.isNull())
+ r = pm;
+ }
+ }
+ d->cachedResources.insert(name, r);
+ }
+ return r;
+}
+
+static QTextFormat formatDifference(const QTextFormat &from, const QTextFormat &to)
+{
+ QTextFormat diff = to;
+
+ const QMap<int, QVariant> props = to.properties();
+ for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end();
+ it != end; ++it)
+ if (it.value() == from.property(it.key()))
+ diff.clearProperty(it.key());
+
+ return diff;
+}
+
+QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc)
+ : doc(_doc), fragmentMarkers(false)
+{
+ const QFont defaultFont = doc->defaultFont();
+ defaultCharFormat.setFont(defaultFont);
+ // don't export those for the default font since we cannot turn them off with CSS
+ defaultCharFormat.clearProperty(QTextFormat::FontUnderline);
+ defaultCharFormat.clearProperty(QTextFormat::FontOverline);
+ defaultCharFormat.clearProperty(QTextFormat::FontStrikeOut);
+ defaultCharFormat.clearProperty(QTextFormat::TextUnderlineStyle);
+}
+
+/*!
+ Returns the document in HTML format. The conversion may not be
+ perfect, especially for complex documents, due to the limitations
+ of HTML.
+*/
+QString QTextHtmlExporter::toHtml(const QByteArray &encoding, ExportMode mode)
+{
+ html = QLatin1String("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
+ "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
+ "<html><head><meta name=\"qrichtext\" content=\"1\" />");
+ html.reserve(doc->docHandle()->length());
+
+ fragmentMarkers = (mode == ExportFragment);
+
+ if (!encoding.isEmpty())
+ html += QString::fromLatin1("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(QString::fromAscii(encoding));
+
+ QString title = doc->metaInformation(QTextDocument::DocumentTitle);
+ if (!title.isEmpty())
+ html += QString::fromLatin1("<title>") + title + QString::fromLatin1("</title>");
+ html += QLatin1String("<style type=\"text/css\">\n");
+ html += QLatin1String("p, li { white-space: pre-wrap; }\n");
+ html += QLatin1String("</style>");
+ html += QLatin1String("</head><body");
+
+ if (mode == ExportEntireDocument) {
+ html += QLatin1String(" style=\"");
+
+ emitFontFamily(defaultCharFormat.fontFamily());
+
+ if (defaultCharFormat.hasProperty(QTextFormat::FontPointSize)) {
+ html += QLatin1String(" font-size:");
+ html += QString::number(defaultCharFormat.fontPointSize());
+ html += QLatin1String("pt;");
+ }
+
+ html += QLatin1String(" font-weight:");
+ html += QString::number(defaultCharFormat.fontWeight() * 8);
+ html += QLatin1Char(';');
+
+ html += QLatin1String(" font-style:");
+ html += (defaultCharFormat.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
+ html += QLatin1Char(';');
+
+ // do not set text-decoration on the default font since those values are /always/ propagated
+ // and cannot be turned off with CSS
+
+ html += QLatin1Char('\"');
+
+ const QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
+ emitBackgroundAttribute(fmt);
+
+ } else {
+ defaultCharFormat = QTextCharFormat();
+ }
+ html += QLatin1Char('>');
+
+ QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat();
+ rootFmt.clearProperty(QTextFormat::BackgroundBrush);
+
+ QTextFrameFormat defaultFmt;
+ defaultFmt.setMargin(doc->documentMargin());
+
+ if (rootFmt == defaultFmt)
+ emitFrame(doc->rootFrame()->begin());
+ else
+ emitTextFrame(doc->rootFrame());
+
+ html += QLatin1String("</body></html>");
+ return html;
+}
+
+void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value)
+{
+ html += QLatin1Char(' ');
+ html += QLatin1String(attribute);
+ html += QLatin1String("=\"");
+ html += value;
+ html += QLatin1Char('"');
+}
+
+bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
+{
+ bool attributesEmitted = false;
+
+ {
+ const QString family = format.fontFamily();
+ if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) {
+ emitFontFamily(family);
+ attributesEmitted = true;
+ }
+ }
+
+ if (format.hasProperty(QTextFormat::FontPointSize)
+ && format.fontPointSize() != defaultCharFormat.fontPointSize()) {
+ html += QLatin1String(" font-size:");
+ html += QString::number(format.fontPointSize());
+ html += QLatin1String("pt;");
+ attributesEmitted = true;
+ } else if (format.hasProperty(QTextFormat::FontSizeAdjustment)) {
+ static const char * const sizeNames[] = {
+ "small", "medium", "large", "x-large", "xx-large"
+ };
+ const char *name = 0;
+ const int idx = format.intProperty(QTextFormat::FontSizeAdjustment) + 1;
+ if (idx >= 0 && idx <= 4) {
+ name = sizeNames[idx];
+ }
+ if (name) {
+ html += QLatin1String(" font-size:");
+ html += QLatin1String(name);
+ html += QLatin1Char(';');
+ attributesEmitted = true;
+ }
+ }
+
+ if (format.hasProperty(QTextFormat::FontWeight)
+ && format.fontWeight() != defaultCharFormat.fontWeight()) {
+ html += QLatin1String(" font-weight:");
+ html += QString::number(format.fontWeight() * 8);
+ html += QLatin1Char(';');
+ attributesEmitted = true;
+ }
+
+ if (format.hasProperty(QTextFormat::FontItalic)
+ && format.fontItalic() != defaultCharFormat.fontItalic()) {
+ html += QLatin1String(" font-style:");
+ html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
+ html += QLatin1Char(';');
+ attributesEmitted = true;
+ }
+
+ QLatin1String decorationTag(" text-decoration:");
+ html += decorationTag;
+ bool hasDecoration = false;
+ bool atLeastOneDecorationSet = false;
+
+ if ((format.hasProperty(QTextFormat::FontUnderline) || format.hasProperty(QTextFormat::TextUnderlineStyle))
+ && format.fontUnderline() != defaultCharFormat.fontUnderline()) {
+ hasDecoration = true;
+ if (format.fontUnderline()) {
+ html += QLatin1String(" underline");
+ atLeastOneDecorationSet = true;
+ }
+ }
+
+ if (format.hasProperty(QTextFormat::FontOverline)
+ && format.fontOverline() != defaultCharFormat.fontOverline()) {
+ hasDecoration = true;
+ if (format.fontOverline()) {
+ html += QLatin1String(" overline");
+ atLeastOneDecorationSet = true;
+ }
+ }
+
+ if (format.hasProperty(QTextFormat::FontStrikeOut)
+ && format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) {
+ hasDecoration = true;
+ if (format.fontStrikeOut()) {
+ html += QLatin1String(" line-through");
+ atLeastOneDecorationSet = true;
+ }
+ }
+
+ if (hasDecoration) {
+ if (!atLeastOneDecorationSet)
+ html += QLatin1String("none");
+ html += QLatin1Char(';');
+ attributesEmitted = true;
+ } else {
+ html.chop(qstrlen(decorationTag.latin1()));
+ }
+
+ if (format.foreground() != defaultCharFormat.foreground()
+ && format.foreground().style() != Qt::NoBrush) {
+ html += QLatin1String(" color:");
+ html += format.foreground().color().name();
+ html += QLatin1Char(';');
+ attributesEmitted = true;
+ }
+
+ if (format.background() != defaultCharFormat.background()
+ && format.background().style() == Qt::SolidPattern) {
+ html += QLatin1String(" background-color:");
+ html += format.background().color().name();
+ html += QLatin1Char(';');
+ attributesEmitted = true;
+ }
+
+ if (format.verticalAlignment() != defaultCharFormat.verticalAlignment()
+ && format.verticalAlignment() != QTextCharFormat::AlignNormal)
+ {
+ html += QLatin1String(" vertical-align:");
+
+ QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
+ if (valign == QTextCharFormat::AlignSubScript)
+ html += QLatin1String("sub");
+ else if (valign == QTextCharFormat::AlignSuperScript)
+ html += QLatin1String("super");
+ else if (valign == QTextCharFormat::AlignMiddle)
+ html += QLatin1String("middle");
+ else if (valign == QTextCharFormat::AlignTop)
+ html += QLatin1String("top");
+ else if (valign == QTextCharFormat::AlignBottom)
+ html += QLatin1String("bottom");
+
+ html += QLatin1Char(';');
+ attributesEmitted = true;
+ }
+
+ if (format.fontCapitalization() != QFont::MixedCase) {
+ const QFont::Capitalization caps = format.fontCapitalization();
+ if (caps == QFont::AllUppercase)
+ html += QLatin1String(" text-transform:uppercase;");
+ else if (caps == QFont::AllLowercase)
+ html += QLatin1String(" text-transform:lowercase;");
+ else if (caps == QFont::SmallCaps)
+ html += QLatin1String(" font-variant:small-caps;");
+ attributesEmitted = true;
+ }
+
+ if (format.fontWordSpacing() != 0.0) {
+ html += QLatin1String(" word-spacing:");
+ html += QString::number(format.fontWordSpacing());
+ html += QLatin1String("px;");
+ attributesEmitted = true;
+ }
+
+ return attributesEmitted;
+}
+
+void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength &length)
+{
+ if (length.type() == QTextLength::VariableLength) // default
+ return;
+
+ html += QLatin1Char(' ');
+ html += QLatin1String(attribute);
+ html += QLatin1String("=\"");
+ html += QString::number(length.rawValue());
+
+ if (length.type() == QTextLength::PercentageLength)
+ html += QLatin1String("%\"");
+ else
+ html += QLatin1String("\"");
+}
+
+void QTextHtmlExporter::emitAlignment(Qt::Alignment align)
+{
+ if (align & Qt::AlignLeft)
+ return;
+ else if (align & Qt::AlignRight)
+ html += QLatin1String(" align=\"right\"");
+ else if (align & Qt::AlignHCenter)
+ html += QLatin1String(" align=\"center\"");
+ else if (align & Qt::AlignJustify)
+ html += QLatin1String(" align=\"justify\"");
+}
+
+void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode)
+{
+ if (pos == QTextFrameFormat::InFlow)
+ return;
+
+ if (mode == EmitStyleTag)
+ html += QLatin1String(" style=\"float:");
+ else
+ html += QLatin1String(" float:");
+
+ if (pos == QTextFrameFormat::FloatLeft)
+ html += QLatin1String(" left;");
+ else if (pos == QTextFrameFormat::FloatRight)
+ html += QLatin1String(" right;");
+ else
+ Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type");
+
+ if (mode == EmitStyleTag)
+ html += QLatin1Char('\"');
+}
+
+void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style)
+{
+ Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset);
+
+ html += QLatin1String(" border-style:");
+
+ switch (style) {
+ case QTextFrameFormat::BorderStyle_None:
+ html += QLatin1String("none");
+ break;
+ case QTextFrameFormat::BorderStyle_Dotted:
+ html += QLatin1String("dotted");
+ break;
+ case QTextFrameFormat::BorderStyle_Dashed:
+ html += QLatin1String("dashed");
+ break;
+ case QTextFrameFormat::BorderStyle_Solid:
+ html += QLatin1String("solid");
+ break;
+ case QTextFrameFormat::BorderStyle_Double:
+ html += QLatin1String("double");
+ break;
+ case QTextFrameFormat::BorderStyle_DotDash:
+ html += QLatin1String("dot-dash");
+ break;
+ case QTextFrameFormat::BorderStyle_DotDotDash:
+ html += QLatin1String("dot-dot-dash");
+ break;
+ case QTextFrameFormat::BorderStyle_Groove:
+ html += QLatin1String("groove");
+ break;
+ case QTextFrameFormat::BorderStyle_Ridge:
+ html += QLatin1String("ridge");
+ break;
+ case QTextFrameFormat::BorderStyle_Inset:
+ html += QLatin1String("inset");
+ break;
+ case QTextFrameFormat::BorderStyle_Outset:
+ html += QLatin1String("outset");
+ break;
+ default:
+ Q_ASSERT(false);
+ break;
+ };
+
+ html += QLatin1Char(';');
+}
+
+void QTextHtmlExporter::emitPageBreakPolicy(QTextFormat::PageBreakFlags policy)
+{
+ if (policy & QTextFormat::PageBreak_AlwaysBefore)
+ html += QLatin1String(" page-break-before:always;");
+
+ if (policy & QTextFormat::PageBreak_AlwaysAfter)
+ html += QLatin1String(" page-break-after:always;");
+}
+
+void QTextHtmlExporter::emitFontFamily(const QString &family)
+{
+ html += QLatin1String(" font-family:");
+
+ QLatin1Char quote('\'');
+ if (family.contains(quote))
+ quote = QLatin1Char('\"');
+
+ html += quote;
+ html += family;
+ html += quote;
+ html += QLatin1Char(';');
+}
+
+void QTextHtmlExporter::emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right)
+{
+ html += QLatin1String(" margin-top:");
+ html += top;
+ html += QLatin1String("px;");
+
+ html += QLatin1String(" margin-bottom:");
+ html += bottom;
+ html += QLatin1String("px;");
+
+ html += QLatin1String(" margin-left:");
+ html += left;
+ html += QLatin1String("px;");
+
+ html += QLatin1String(" margin-right:");
+ html += right;
+ html += QLatin1String("px;");
+}
+
+void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
+{
+ const QTextCharFormat format = fragment.charFormat();
+
+ bool closeAnchor = false;
+
+ if (format.isAnchor()) {
+ const QString name = format.anchorName();
+ if (!name.isEmpty()) {
+ html += QLatin1String("<a name=\"");
+ html += name;
+ html += QLatin1String("\"></a>");
+ }
+ const QString href = format.anchorHref();
+ if (!href.isEmpty()) {
+ html += QLatin1String("<a href=\"");
+ html += href;
+ html += QLatin1String("\">");
+ closeAnchor = true;
+ }
+ }
+
+ QString txt = fragment.text();
+ const bool isObject = txt.contains(QChar::ObjectReplacementCharacter);
+ const bool isImage = isObject && format.isImageFormat();
+
+ QLatin1String styleTag("<span style=\"");
+ html += styleTag;
+
+ bool attributesEmitted = false;
+ if (!isImage)
+ attributesEmitted = emitCharFormatStyle(format);
+ if (attributesEmitted)
+ html += QLatin1String("\">");
+ else
+ html.chop(qstrlen(styleTag.latin1()));
+
+ if (isObject) {
+ for (int i = 0; isImage && i < txt.length(); ++i) {
+ QTextImageFormat imgFmt = format.toImageFormat();
+
+ html += QLatin1String("<img");
+
+ if (imgFmt.hasProperty(QTextFormat::ImageName))
+ emitAttribute("src", imgFmt.name());
+
+ if (imgFmt.hasProperty(QTextFormat::ImageWidth))
+ emitAttribute("width", QString::number(imgFmt.width()));
+
+ if (imgFmt.hasProperty(QTextFormat::ImageHeight))
+ emitAttribute("height", QString::number(imgFmt.height()));
+
+ if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle)
+ html += QLatin1String(" style=\"vertical-align: middle;\"");
+ else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop)
+ html += QLatin1String(" style=\"vertical-align: top;\"");
+
+ if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt)))
+ emitFloatStyle(imageFrame->frameFormat().position());
+
+ html += QLatin1String(" />");
+ }
+ } else {
+ Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter));
+
+ txt = Qt::escape(txt);
+
+ // split for [\n{LineSeparator}]
+ QString forcedLineBreakRegExp = QString::fromLatin1("[\\na]");
+ forcedLineBreakRegExp[3] = QChar::LineSeparator;
+
+ const QStringList lines = txt.split(QRegExp(forcedLineBreakRegExp));
+ for (int i = 0; i < lines.count(); ++i) {
+ if (i > 0)
+ html += QLatin1String("<br />"); // space on purpose for compatibility with Netscape, Lynx & Co.
+ html += lines.at(i);
+ }
+ }
+
+ if (attributesEmitted)
+ html += QLatin1String("</span>");
+
+ if (closeAnchor)
+ html += QLatin1String("</a>");
+}
+
+static bool isOrderedList(int style)
+{
+ return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
+ || style == QTextListFormat::ListUpperAlpha;
+}
+
+void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block)
+{
+ QTextBlockFormat format = block.blockFormat();
+ emitAlignment(format.alignment());
+
+ Qt::LayoutDirection dir = format.layoutDirection();
+ if (dir == Qt::LeftToRight) {
+ // assume default to not bloat the html too much
+ // html += QLatin1String(" dir='ltr'");
+ } else {
+ html += QLatin1String(" dir='rtl'");
+ }
+
+ QLatin1String style(" style=\"");
+ html += style;
+
+ const bool emptyBlock = block.begin().atEnd();
+ if (emptyBlock) {
+ html += QLatin1String("-qt-paragraph-type:empty;");
+ }
+
+ emitMargins(QString::number(format.topMargin()),
+ QString::number(format.bottomMargin()),
+ QString::number(format.leftMargin()),
+ QString::number(format.rightMargin()));
+
+ html += QLatin1String(" -qt-block-indent:");
+ html += QString::number(format.indent());
+ html += QLatin1Char(';');
+
+ html += QLatin1String(" text-indent:");
+ html += QString::number(format.textIndent());
+ html += QLatin1String("px;");
+
+ if (block.userState() != -1) {
+ html += QLatin1String(" -qt-user-state:");
+ html += QString::number(block.userState());
+ html += QLatin1Char(';');
+ }
+
+ emitPageBreakPolicy(format.pageBreakPolicy());
+
+ QTextCharFormat diff;
+ if (emptyBlock) { // only print character properties when we don't expect them to be repeated by actual text in the parag
+ const QTextCharFormat blockCharFmt = block.charFormat();
+ diff = formatDifference(defaultCharFormat, blockCharFmt).toCharFormat();
+ }
+
+ diff.clearProperty(QTextFormat::BackgroundBrush);
+ if (format.hasProperty(QTextFormat::BackgroundBrush)) {
+ QBrush bg = format.background();
+ if (bg.style() != Qt::NoBrush)
+ diff.setProperty(QTextFormat::BackgroundBrush, format.property(QTextFormat::BackgroundBrush));
+ }
+
+ if (!diff.properties().isEmpty())
+ emitCharFormatStyle(diff);
+
+ html += QLatin1Char('"');
+
+}
+
+void QTextHtmlExporter::emitBlock(const QTextBlock &block)
+{
+ if (block.begin().atEnd()) {
+ // ### HACK, remove once QTextFrame::Iterator is fixed
+ int p = block.position();
+ if (p > 0)
+ --p;
+ QTextDocumentPrivate::FragmentIterator frag = doc->docHandle()->find(p);
+ QChar ch = doc->docHandle()->buffer().at(frag->stringPosition);
+ if (ch == QTextBeginningOfFrame
+ || ch == QTextEndOfFrame)
+ return;
+ }
+
+ html += QLatin1Char('\n');
+
+ // save and later restore, in case we 'change' the default format by
+ // emitting block char format information
+ QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
+
+ QTextList *list = block.textList();
+ if (list) {
+ if (list->itemNumber(block) == 0) { // first item? emit <ul> or appropriate
+ const QTextListFormat format = list->format();
+ const int style = format.style();
+ switch (style) {
+ case QTextListFormat::ListDecimal: html += QLatin1String("<ol"); break;
+ case QTextListFormat::ListDisc: html += QLatin1String("<ul"); break;
+ case QTextListFormat::ListCircle: html += QLatin1String("<ul type=\"circle\""); break;
+ case QTextListFormat::ListSquare: html += QLatin1String("<ul type=\"square\""); break;
+ case QTextListFormat::ListLowerAlpha: html += QLatin1String("<ol type=\"a\""); break;
+ case QTextListFormat::ListUpperAlpha: html += QLatin1String("<ol type=\"A\""); break;
+ default: html += QLatin1String("<ul"); // ### should not happen
+ }
+
+ if (format.hasProperty(QTextFormat::ListIndent)) {
+ html += QLatin1String(" style=\"-qt-list-indent: ");
+ html += QString::number(format.indent());
+ html += QLatin1String(";\"");
+ }
+
+ html += QLatin1Char('>');
+ }
+
+ html += QLatin1String("<li");
+
+ const QTextCharFormat blockFmt = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat();
+ if (!blockFmt.properties().isEmpty()) {
+ html += QLatin1String(" style=\"");
+ emitCharFormatStyle(blockFmt);
+ html += QLatin1Char('\"');
+
+ defaultCharFormat.merge(block.charFormat());
+ }
+ }
+
+ const QTextBlockFormat blockFormat = block.blockFormat();
+ if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
+ html += QLatin1String("<hr");
+
+ QTextLength width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth);
+ if (width.type() != QTextLength::VariableLength)
+ emitTextLength("width", width);
+ else
+ html += QLatin1Char(' ');
+
+ html += QLatin1String("/>");
+ return;
+ }
+
+ const bool pre = blockFormat.nonBreakableLines();
+ if (pre) {
+ if (list)
+ html += QLatin1Char('>');
+ html += QLatin1String("<pre");
+ } else if (!list) {
+ html += QLatin1String("<p");
+ }
+
+ emitBlockAttributes(block);
+
+ html += QLatin1Char('>');
+
+ QTextBlock::Iterator it = block.begin();
+ if (fragmentMarkers && !it.atEnd() && block == doc->begin())
+ html += QLatin1String("<!--StartFragment-->");
+
+ for (; !it.atEnd(); ++it)
+ emitFragment(it.fragment());
+
+ if (fragmentMarkers && block.position() + block.length() == doc->docHandle()->length())
+ html += QLatin1String("<!--EndFragment-->");
+
+ if (pre)
+ html += QLatin1String("</pre>");
+ else if (list)
+ html += QLatin1String("</li>");
+ else
+ html += QLatin1String("</p>");
+
+ if (list) {
+ if (list->itemNumber(block) == list->count() - 1) { // last item? close list
+ if (isOrderedList(list->format().style()))
+ html += QLatin1String("</ol>");
+ else
+ html += QLatin1String("</ul>");
+ }
+ }
+
+ defaultCharFormat = oldDefaultCharFormat;
+}
+
+extern bool qHasPixmapTexture(const QBrush& brush);
+
+QString QTextHtmlExporter::findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap)
+{
+ QString url;
+ if (!doc)
+ return url;
+
+ if (QTextDocument *parent = qobject_cast<QTextDocument *>(doc->parent()))
+ return findUrlForImage(parent, cacheKey, isPixmap);
+
+ if (doc && doc->docHandle()) {
+ QTextDocumentPrivate *priv = doc->docHandle();
+ QMap<QUrl, QVariant>::const_iterator it = priv->cachedResources.constBegin();
+ for (; it != priv->cachedResources.constEnd(); ++it) {
+
+ const QVariant &v = it.value();
+ if (v.type() == QVariant::Image && !isPixmap) {
+ if (qvariant_cast<QImage>(v).cacheKey() == cacheKey)
+ break;
+ }
+
+ if (v.type() == QVariant::Pixmap && isPixmap) {
+ if (qvariant_cast<QPixmap>(v).cacheKey() == cacheKey)
+ break;
+ }
+ }
+
+ if (it != priv->cachedResources.constEnd())
+ url = it.key().toString();
+ }
+
+ return url;
+}
+
+void QTextDocumentPrivate::mergeCachedResources(const QTextDocumentPrivate *priv)
+{
+ if (!priv)
+ return;
+
+ cachedResources.unite(priv->cachedResources);
+}
+
+void QTextHtmlExporter::emitBackgroundAttribute(const QTextFormat &format)
+{
+ if (format.hasProperty(QTextFormat::BackgroundImageUrl)) {
+ QString url = format.property(QTextFormat::BackgroundImageUrl).toString();
+ emitAttribute("background", url);
+ } else {
+ const QBrush &brush = format.background();
+ if (brush.style() == Qt::SolidPattern) {
+ emitAttribute("bgcolor", brush.color().name());
+ } else if (brush.style() == Qt::TexturePattern) {
+ const bool isPixmap = qHasPixmapTexture(brush);
+ const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
+
+ const QString url = findUrlForImage(doc, cacheKey, isPixmap);
+
+ if (!url.isEmpty())
+ emitAttribute("background", url);
+ }
+ }
+}
+
+void QTextHtmlExporter::emitTable(const QTextTable *table)
+{
+ QTextTableFormat format = table->format();
+
+ html += QLatin1String("\n<table");
+
+ if (format.hasProperty(QTextFormat::FrameBorder))
+ emitAttribute("border", QString::number(format.border()));
+
+ emitFrameStyle(format, TableFrame);
+
+ emitAlignment(format.alignment());
+ emitTextLength("width", format.width());
+
+ if (format.hasProperty(QTextFormat::TableCellSpacing))
+ emitAttribute("cellspacing", QString::number(format.cellSpacing()));
+ if (format.hasProperty(QTextFormat::TableCellPadding))
+ emitAttribute("cellpadding", QString::number(format.cellPadding()));
+
+ emitBackgroundAttribute(format);
+
+ html += QLatin1Char('>');
+
+ const int rows = table->rows();
+ const int columns = table->columns();
+
+ QVector<QTextLength> columnWidths = format.columnWidthConstraints();
+ if (columnWidths.isEmpty()) {
+ columnWidths.resize(columns);
+ columnWidths.fill(QTextLength());
+ }
+ Q_ASSERT(columnWidths.count() == columns);
+
+ QVarLengthArray<bool> widthEmittedForColumn(columns);
+ for (int i = 0; i < columns; ++i)
+ widthEmittedForColumn[i] = false;
+
+ const int headerRowCount = qMin(format.headerRowCount(), rows);
+ if (headerRowCount > 0)
+ html += QLatin1String("<thead>");
+
+ for (int row = 0; row < rows; ++row) {
+ html += QLatin1String("\n<tr>");
+
+ for (int col = 0; col < columns; ++col) {
+ const QTextTableCell cell = table->cellAt(row, col);
+
+ // for col/rowspans
+ if (cell.row() != row)
+ continue;
+
+ if (cell.column() != col)
+ continue;
+
+ html += QLatin1String("\n<td");
+
+ if (!widthEmittedForColumn[col] && cell.columnSpan() == 1) {
+ emitTextLength("width", columnWidths.at(col));
+ widthEmittedForColumn[col] = true;
+ }
+
+ if (cell.columnSpan() > 1)
+ emitAttribute("colspan", QString::number(cell.columnSpan()));
+
+ if (cell.rowSpan() > 1)
+ emitAttribute("rowspan", QString::number(cell.rowSpan()));
+
+ const QTextTableCellFormat cellFormat = cell.format().toTableCellFormat();
+ emitBackgroundAttribute(cellFormat);
+
+ QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
+
+ QTextCharFormat::VerticalAlignment valign = cellFormat.verticalAlignment();
+
+ QString styleString;
+ if (valign >= QTextCharFormat::AlignMiddle && valign <= QTextCharFormat::AlignBottom) {
+ styleString += QLatin1String(" vertical-align:");
+ switch (valign) {
+ case QTextCharFormat::AlignMiddle:
+ styleString += QLatin1String("middle");
+ break;
+ case QTextCharFormat::AlignTop:
+ styleString += QLatin1String("top");
+ break;
+ case QTextCharFormat::AlignBottom:
+ styleString += QLatin1String("bottom");
+ break;
+ default:
+ break;
+ }
+ styleString += QLatin1Char(';');
+
+ QTextCharFormat temp;
+ temp.setVerticalAlignment(valign);
+ defaultCharFormat.merge(temp);
+ }
+
+ if (cellFormat.hasProperty(QTextFormat::TableCellLeftPadding))
+ styleString += QLatin1String(" padding-left:") + QString::number(cellFormat.leftPadding()) + QLatin1Char(';');
+ if (cellFormat.hasProperty(QTextFormat::TableCellRightPadding))
+ styleString += QLatin1String(" padding-right:") + QString::number(cellFormat.rightPadding()) + QLatin1Char(';');
+ if (cellFormat.hasProperty(QTextFormat::TableCellTopPadding))
+ styleString += QLatin1String(" padding-top:") + QString::number(cellFormat.topPadding()) + QLatin1Char(';');
+ if (cellFormat.hasProperty(QTextFormat::TableCellBottomPadding))
+ styleString += QLatin1String(" padding-bottom:") + QString::number(cellFormat.bottomPadding()) + QLatin1Char(';');
+
+ if (!styleString.isEmpty())
+ html += QLatin1String(" style=\"") + styleString + QLatin1Char('\"');
+
+ html += QLatin1Char('>');
+
+ emitFrame(cell.begin());
+
+ html += QLatin1String("</td>");
+
+ defaultCharFormat = oldDefaultCharFormat;
+ }
+
+ html += QLatin1String("</tr>");
+ if (headerRowCount > 0 && row == headerRowCount - 1)
+ html += QLatin1String("</thead>");
+ }
+
+ html += QLatin1String("</table>");
+}
+
+void QTextHtmlExporter::emitFrame(QTextFrame::Iterator frameIt)
+{
+ if (!frameIt.atEnd()) {
+ QTextFrame::Iterator next = frameIt;
+ ++next;
+ if (next.atEnd()
+ && frameIt.currentFrame() == 0
+ && frameIt.parentFrame() != doc->rootFrame()
+ && frameIt.currentBlock().begin().atEnd())
+ return;
+ }
+
+ for (QTextFrame::Iterator it = frameIt;
+ !it.atEnd(); ++it) {
+ if (QTextFrame *f = it.currentFrame()) {
+ if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
+ emitTable(table);
+ } else {
+ emitTextFrame(f);
+ }
+ } else if (it.currentBlock().isValid()) {
+ emitBlock(it.currentBlock());
+ }
+ }
+}
+
+void QTextHtmlExporter::emitTextFrame(const QTextFrame *f)
+{
+ FrameType frameType = f->parentFrame() ? TextFrame : RootFrame;
+
+ html += QLatin1String("\n<table");
+ QTextFrameFormat format = f->frameFormat();
+
+ if (format.hasProperty(QTextFormat::FrameBorder))
+ emitAttribute("border", QString::number(format.border()));
+
+ emitFrameStyle(format, frameType);
+
+ emitTextLength("width", format.width());
+ emitTextLength("height", format.height());
+
+ // root frame's bcolor goes in the <body> tag
+ if (frameType != RootFrame)
+ emitBackgroundAttribute(format);
+
+ html += QLatin1Char('>');
+ html += QLatin1String("\n<tr>\n<td style=\"border: none;\">");
+ emitFrame(f->begin());
+ html += QLatin1String("</td></tr></table>");
+}
+
+void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType frameType)
+{
+ QLatin1String styleAttribute(" style=\"");
+ html += styleAttribute;
+ const int originalHtmlLength = html.length();
+
+ if (frameType == TextFrame)
+ html += QLatin1String("-qt-table-type: frame;");
+ else if (frameType == RootFrame)
+ html += QLatin1String("-qt-table-type: root;");
+
+ const QTextFrameFormat defaultFormat;
+
+ emitFloatStyle(format.position(), OmitStyleTag);
+ emitPageBreakPolicy(format.pageBreakPolicy());
+
+ if (format.borderBrush() != defaultFormat.borderBrush()) {
+ html += QLatin1String(" border-color:");
+ html += format.borderBrush().color().name();
+ html += QLatin1Char(';');
+ }
+
+ if (format.borderStyle() != defaultFormat.borderStyle())
+ emitBorderStyle(format.borderStyle());
+
+ if (format.hasProperty(QTextFormat::FrameMargin)
+ || format.hasProperty(QTextFormat::FrameLeftMargin)
+ || format.hasProperty(QTextFormat::FrameRightMargin)
+ || format.hasProperty(QTextFormat::FrameTopMargin)
+ || format.hasProperty(QTextFormat::FrameBottomMargin))
+ emitMargins(QString::number(format.topMargin()),
+ QString::number(format.bottomMargin()),
+ QString::number(format.leftMargin()),
+ QString::number(format.rightMargin()));
+
+ if (html.length() == originalHtmlLength) // nothing emitted?
+ html.chop(qstrlen(styleAttribute.latin1()));
+ else
+ html += QLatin1Char('\"');
+}
+
+/*!
+ Returns a string containing an HTML representation of the document.
+
+ The \a encoding parameter specifies the value for the charset attribute
+ in the html header. For example if 'utf-8' is specified then the
+ beginning of the generated html will look like this:
+ \snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 1
+
+ If no encoding is specified then no such meta information is generated.
+
+ If you later on convert the returned html string into a byte array for
+ transmission over a network or when saving to disk you should specify
+ the encoding you're going to use for the conversion to a byte array here.
+
+ \sa {Supported HTML Subset}
+*/
+#ifndef QT_NO_TEXTHTMLPARSER
+QString QTextDocument::toHtml(const QByteArray &encoding) const
+{
+ return QTextHtmlExporter(this).toHtml(encoding);
+}
+#endif // QT_NO_TEXTHTMLPARSER
+
+/*!
+ Returns a vector of text formats for all the formats used in the document.
+*/
+QVector<QTextFormat> QTextDocument::allFormats() const
+{
+ Q_D(const QTextDocument);
+ return d->formatCollection()->formats;
+}
+
+
+/*!
+ \internal
+
+ So that not all classes have to be friends of each other...
+*/
+QTextDocumentPrivate *QTextDocument::docHandle() const
+{
+ Q_D(const QTextDocument);
+ return const_cast<QTextDocumentPrivate *>(d);
+}
+
+/*!
+ \since 4.4
+ \fn QTextDocument::undoCommandAdded()
+
+ This signal is emitted every time a new level of undo is added to the QTextDocument.
+*/
+
+QT_END_NAMESPACE