1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qtextdocument.h"
43 #include <qtextformat.h>
44 #include "qtextdocumentlayout_p.h"
45 #include "qtextdocumentfragment.h"
46 #include "qtextdocumentfragment_p.h"
47 #include "qtexttable.h"
48 #include "qtextlist.h"
51 #include <qvarlengtharray.h>
52 #include <qtextcodec.h>
54 #include "qtexthtmlparser_p.h"
57 #include "qtextedit.h"
59 #include <qfileinfo.h>
61 #include <qapplication.h>
62 #include "qtextcontrol_p.h"
64 #include "private/qtextedit_p.h"
65 #include "private/qdataurl_p.h"
67 #include "qtextdocument_p.h"
68 #include <private/qprinter_p.h>
69 #include <private/qabstracttextdocumentlayout_p.h>
75 Q_CORE_EXPORT unsigned int qt_int_sqrt(unsigned int n);
78 Returns true if the string \a text is likely to be rich text;
79 otherwise returns false.
81 This function uses a fast and therefore simple heuristic. It
82 mainly checks whether there is something that looks like a tag
83 before the first line break. Although the result may be correct
84 for common cases, there is no guarantee.
86 This function is defined in the \c <QTextDocument> header file.
88 bool Qt::mightBeRichText(const QString& text)
94 while (start < text.length() && text.at(start).isSpace())
97 // skip a leading <?xml ... ?> as for example with xhtml
98 if (text.mid(start, 5) == QLatin1String("<?xml")) {
99 while (start < text.length()) {
100 if (text.at(start) == QLatin1Char('?')
101 && start + 2 < text.length()
102 && text.at(start + 1) == QLatin1Char('>')) {
109 while (start < text.length() && text.at(start).isSpace())
113 if (text.mid(start, 5).toLower() == QLatin1String("<!doc"))
116 while (open < text.length() && text.at(open) != QLatin1Char('<')
117 && text.at(open) != QLatin1Char('\n')) {
118 if (text.at(open) == QLatin1Char('&') && text.mid(open+1,3) == QLatin1String("lt;"))
119 return true; // support desperate attempt of user to see <...>
122 if (open < text.length() && text.at(open) == QLatin1Char('<')) {
123 const int close = text.indexOf(QLatin1Char('>'), open);
126 for (int i = open+1; i < close; ++i) {
127 if (text[i].isDigit() || text[i].isLetter())
129 else if (!tag.isEmpty() && text[i].isSpace())
131 else if (!tag.isEmpty() && text[i] == QLatin1Char('/') && i + 1 == close)
133 else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != QLatin1Char('!')))
134 return false; // that's not a tag
136 #ifndef QT_NO_TEXTHTMLPARSER
137 return QTextHtmlParser::lookupElement(tag.toLower()) != -1;
140 #endif // QT_NO_TEXTHTMLPARSER
147 Converts the plain text string \a plain to a HTML string with
148 HTML metacharacters \c{<}, \c{>}, \c{&}, and \c{"} replaced by HTML
153 \snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 0
155 This function is defined in the \c <QTextDocument> header file.
157 \sa convertFromPlainText(), mightBeRichText()
159 QString Qt::escape(const QString& plain)
162 rich.reserve(int(plain.length() * 1.1));
163 for (int i = 0; i < plain.length(); ++i) {
164 if (plain.at(i) == QLatin1Char('<'))
165 rich += QLatin1String("<");
166 else if (plain.at(i) == QLatin1Char('>'))
167 rich += QLatin1String(">");
168 else if (plain.at(i) == QLatin1Char('&'))
169 rich += QLatin1String("&");
170 else if (plain.at(i) == QLatin1Char('"'))
171 rich += QLatin1String(""");
179 \fn QString Qt::convertFromPlainText(const QString &plain, WhiteSpaceMode mode)
181 Converts the plain text string \a plain to an HTML-formatted
182 paragraph while preserving most of its look.
184 \a mode defines how whitespace is handled.
186 This function is defined in the \c <QTextDocument> header file.
188 \sa escape(), mightBeRichText()
190 QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode)
194 rich += QLatin1String("<p>");
195 for (int i = 0; i < plain.length(); ++i) {
196 if (plain[i] == QLatin1Char('\n')){
198 while (i+1 < plain.length() && plain[i+1] == QLatin1Char('\n')) {
203 rich += QLatin1String("<br>\n");
205 rich += QLatin1String("</p>\n");
207 rich += QLatin1String("<br>\n");
208 rich += QLatin1String("<p>");
212 if (mode == Qt::WhiteSpacePre && plain[i] == QLatin1Char('\t')){
213 rich += QChar(0x00a0U);
216 rich += QChar(0x00a0U);
220 else if (mode == Qt::WhiteSpacePre && plain[i].isSpace())
221 rich += QChar(0x00a0U);
222 else if (plain[i] == QLatin1Char('<'))
223 rich += QLatin1String("<");
224 else if (plain[i] == QLatin1Char('>'))
225 rich += QLatin1String(">");
226 else if (plain[i] == QLatin1Char('&'))
227 rich += QLatin1String("&");
234 rich += QLatin1String("</p>");
238 #ifndef QT_NO_TEXTCODEC
242 This function is defined in the \c <QTextDocument> header file.
244 QTextCodec *Qt::codecForHtml(const QByteArray &ba)
246 return QTextCodec::codecForHtml(ba);
254 \brief The QTextDocument class holds formatted text that can be
255 viewed and edited using a QTextEdit.
257 \ingroup richtext-processing
260 QTextDocument is a container for structured rich text documents, providing
261 support for styled text and various types of document elements, such as
262 lists, tables, frames, and images.
263 They can be created for use in a QTextEdit, or used independently.
265 Each document element is described by an associated format object. Each
266 format object is treated as a unique object by QTextDocuments, and can be
267 passed to objectForFormat() to obtain the document element that it is
270 A QTextDocument can be edited programmatically using a QTextCursor, and
271 its contents can be examined by traversing the document structure. The
272 entire document structure is stored as a hierarchy of document elements
273 beneath the root frame, found with the rootFrame() function. Alternatively,
274 if you just want to iterate over the textual contents of the document you
275 can use begin(), end(), and findBlock() to retrieve text blocks that you
276 can examine and iterate over.
278 The layout of a document is determined by the documentLayout();
279 you can create your own QAbstractTextDocumentLayout subclass and
280 set it using setDocumentLayout() if you want to use your own
281 layout logic. The document's title and other meta-information can be
282 obtained by calling the metaInformation() function. For documents that
283 are exposed to users through the QTextEdit class, the document title
284 is also available via the QTextEdit::documentTitle() function.
286 The toPlainText() and toHtml() convenience functions allow you to retrieve the
287 contents of the document as plain text and HTML.
288 The document's text can be searched using the find() functions.
290 Undo/redo of operations performed on the document can be controlled using
291 the setUndoRedoEnabled() function. The undo/redo system can be controlled
292 by an editor widget through the undo() and redo() slots; the document also
293 provides contentsChanged(), undoAvailable(), and redoAvailable() signals
294 that inform connected editor widgets about the state of the undo/redo
295 system. The following are the undo/redo operations of a QTextDocument:
298 \o Insertion or removal of characters. A sequence of insertions or removals
299 within the same text block are regarded as a single undo/redo operation.
300 \o Insertion or removal of text blocks. Sequences of insertion or removals
301 in a single operation (e.g., by selecting and then deleting text) are
302 regarded as a single undo/redo operation.
303 \o Text character format changes.
304 \o Text block format changes.
305 \o Text block group format changes.
308 \sa QTextCursor, QTextEdit, \link richtext.html Rich Text Processing\endlink , {Text Object Example}
312 \property QTextDocument::defaultFont
313 \brief the default font used to display the document's text
317 \property QTextDocument::defaultTextOption
318 \brief the default text option will be set on all \l{QTextLayout}s in the document.
320 When \l{QTextBlock}s are created, the defaultTextOption is set on their
321 QTextLayout. This allows setting global properties for the document such as the
322 default word wrap mode.
326 Constructs an empty QTextDocument with the given \a parent.
328 QTextDocument::QTextDocument(QObject *parent)
329 : QObject(*new QTextDocumentPrivate, parent)
336 Constructs a QTextDocument containing the plain (unformatted) \a text
337 specified, and with the given \a parent.
339 QTextDocument::QTextDocument(const QString &text, QObject *parent)
340 : QObject(*new QTextDocumentPrivate, parent)
344 QTextCursor(this).insertText(text);
350 QTextDocument::QTextDocument(QTextDocumentPrivate &dd, QObject *parent)
351 : QObject(dd, parent)
358 Destroys the document.
360 QTextDocument::~QTextDocument()
366 Creates a new QTextDocument that is a copy of this text document. \a
367 parent is the parent of the returned text document.
369 QTextDocument *QTextDocument::clone(QObject *parent) const
371 Q_D(const QTextDocument);
372 QTextDocument *doc = new QTextDocument(parent);
373 QTextCursor(doc).insertFragment(QTextDocumentFragment(this));
374 doc->rootFrame()->setFrameFormat(rootFrame()->frameFormat());
375 QTextDocumentPrivate *priv = doc->d_func();
376 priv->title = d->title;
378 priv->pageSize = d->pageSize;
379 priv->indentWidth = d->indentWidth;
380 priv->defaultTextOption = d->defaultTextOption;
381 priv->setDefaultFont(d->defaultFont());
382 priv->resources = d->resources;
383 priv->cachedResources.clear();
384 #ifndef QT_NO_CSSPARSER
385 priv->defaultStyleSheet = d->defaultStyleSheet;
386 priv->parsedDefaultStyleSheet = d->parsedDefaultStyleSheet;
392 Returns true if the document is empty; otherwise returns false.
394 bool QTextDocument::isEmpty() const
396 Q_D(const QTextDocument);
397 /* because if we're empty we still have one single paragraph as
398 * one single fragment */
399 return d->length() <= 1;
405 void QTextDocument::clear()
409 d->resources.clear();
415 Undoes the last editing operation on the document if undo is
416 available. The provided \a cursor is positioned at the end of the
417 location where the edition operation was undone.
419 See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework}
420 documentation for details.
422 \sa undoAvailable(), isUndoRedoEnabled()
424 void QTextDocument::undo(QTextCursor *cursor)
427 const int pos = d->undoRedo(true);
428 if (cursor && pos >= 0) {
429 *cursor = QTextCursor(this);
430 cursor->setPosition(pos);
436 Redoes the last editing operation on the document if \link
437 QTextDocument::isRedoAvailable() redo is available\endlink.
439 The provided \a cursor is positioned at the end of the location where
440 the edition operation was redone.
442 void QTextDocument::redo(QTextCursor *cursor)
445 const int pos = d->undoRedo(false);
446 if (cursor && pos >= 0) {
447 *cursor = QTextCursor(this);
448 cursor->setPosition(pos);
452 /*! \enum QTextDocument::Stacks
454 \value UndoStack The undo stack.
455 \value RedoStack The redo stack.
456 \value UndoAndRedoStacks Both the undo and redo stacks.
461 Clears the stacks specified by \a stacksToClear.
463 This method clears any commands on the undo stack, the redo stack,
464 or both (the default). If commands are cleared, the appropriate
465 signals are emitted, QTextDocument::undoAvailable() or
466 QTextDocument::redoAvailable().
468 \sa QTextDocument::undoAvailable() QTextDocument::redoAvailable()
470 void QTextDocument::clearUndoRedoStacks(Stacks stacksToClear)
473 d->clearUndoRedoStacks(stacksToClear, true);
480 void QTextDocument::undo()
488 Redoes the last editing operation on the document if \link
489 QTextDocument::isRedoAvailable() redo is available\endlink.
491 void QTextDocument::redo()
500 Appends a custom undo \a item to the undo stack.
502 void QTextDocument::appendUndoItem(QAbstractUndoItem *item)
505 d->appendUndoItem(item);
509 \property QTextDocument::undoRedoEnabled
510 \brief whether undo/redo are enabled for this document
512 This defaults to true. If disabled, the undo stack is cleared and
513 no items will be added to it.
515 void QTextDocument::setUndoRedoEnabled(bool enable)
518 d->enableUndoRedo(enable);
521 bool QTextDocument::isUndoRedoEnabled() const
523 Q_D(const QTextDocument);
524 return d->isUndoRedoEnabled();
528 \property QTextDocument::maximumBlockCount
530 \brief Specifies the limit for blocks in the document.
532 Specifies the maximum number of blocks the document may have. If there are
533 more blocks in the document that specified with this property blocks are removed
534 from the beginning of the document.
536 A negative or zero value specifies that the document may contain an unlimited
539 The default value is 0.
541 Note that setting this property will apply the limit immediately to the document
544 Setting this property also disables the undo redo history.
546 This property is undefined in documents with tables or frames.
548 int QTextDocument::maximumBlockCount() const
550 Q_D(const QTextDocument);
551 return d->maximumBlockCount;
554 void QTextDocument::setMaximumBlockCount(int maximum)
557 d->maximumBlockCount = maximum;
558 d->ensureMaximumBlockCount();
559 setUndoRedoEnabled(false);
565 The default text option is used on all QTextLayout objects in the document.
566 This allows setting global properties for the document such as the default
569 QTextOption QTextDocument::defaultTextOption() const
571 Q_D(const QTextDocument);
572 return d->defaultTextOption;
578 Sets the default text option.
580 void QTextDocument::setDefaultTextOption(const QTextOption &option)
583 d->defaultTextOption = option;
585 d->lout->documentChanged(0, 0, d->length());
591 The default cursor movement style is used by all QTextCursor objects
592 created from the document. The default is Qt::LogicalMoveStyle.
594 Qt::CursorMoveStyle QTextDocument::defaultCursorMoveStyle() const
596 Q_D(const QTextDocument);
597 return d->defaultCursorMoveStyle;
603 Sets the default cursor movement style to the given \a style.
605 void QTextDocument::setDefaultCursorMoveStyle(Qt::CursorMoveStyle style)
608 d->defaultCursorMoveStyle = style;
612 \fn void QTextDocument::markContentsDirty(int position, int length)
614 Marks the contents specified by the given \a position and \a length
615 as "dirty", informing the document that it needs to be laid out
618 void QTextDocument::markContentsDirty(int from, int length)
621 d->documentChange(from, length);
622 if (!d->inContentsChange) {
624 d->lout->documentChanged(d->docChangeFrom, d->docChangeOldLength, d->docChangeLength);
625 d->docChangeFrom = -1;
631 \property QTextDocument::useDesignMetrics
633 \brief whether the document uses design metrics of fonts to improve the accuracy of text layout
635 If this property is set to true, the layout will use design metrics.
636 Otherwise, the metrics of the paint device as set on
637 QAbstractTextDocumentLayout::setPaintDevice() will be used.
639 Using design metrics makes a layout have a width that is no longer dependent on hinting
640 and pixel-rounding. This means that WYSIWYG text layout becomes possible because the width
641 scales much more linearly based on paintdevice metrics than it would otherwise.
643 By default, this property is false.
646 void QTextDocument::setUseDesignMetrics(bool b)
649 if (b == d->defaultTextOption.useDesignMetrics())
651 d->defaultTextOption.setUseDesignMetrics(b);
653 d->lout->documentChanged(0, 0, d->length());
656 bool QTextDocument::useDesignMetrics() const
658 Q_D(const QTextDocument);
659 return d->defaultTextOption.useDesignMetrics();
665 Draws the content of the document with painter \a p, clipped to \a rect.
666 If \a rect is a null rectangle (default) then the document is painted unclipped.
668 void QTextDocument::drawContents(QPainter *p, const QRectF &rect)
671 QAbstractTextDocumentLayout::PaintContext ctx;
672 if (rect.isValid()) {
673 p->setClipRect(rect);
676 documentLayout()->draw(p, ctx);
681 \property QTextDocument::textWidth
684 The text width specifies the preferred width for text in the document. If
685 the text (or content in general) is wider than the specified with it is broken
686 into multiple lines and grows vertically. If the text cannot be broken into multiple
687 lines to fit into the specified text width it will be larger and the size() and the
688 idealWidth() property will reflect that.
690 If the text width is set to -1 then the text will not be broken into multiple lines
691 unless it is enforced through an explicit line break or a new paragraph.
693 The default value is -1.
695 Setting the text width will also set the page height to -1, causing the document to
696 grow or shrink vertically in a continuous way. If you want the document layout to break
697 the text into multiple pages then you have to set the pageSize property instead.
699 \sa size(), idealWidth(), pageSize()
701 void QTextDocument::setTextWidth(qreal width)
704 QSizeF sz = d->pageSize;
710 qreal QTextDocument::textWidth() const
712 Q_D(const QTextDocument);
713 return d->pageSize.width();
719 Returns the ideal width of the text document. The ideal width is the actually used width
720 of the document without optional alignments taken into account. It is always <= size().width().
722 \sa adjustSize(), textWidth
724 qreal QTextDocument::idealWidth() const
726 if (QTextDocumentLayout *lout = qobject_cast<QTextDocumentLayout *>(documentLayout()))
727 return lout->idealWidth();
732 \property QTextDocument::documentMargin
735 The margin around the document. The default is 4.
737 qreal QTextDocument::documentMargin() const
739 Q_D(const QTextDocument);
740 return d->documentMargin;
743 void QTextDocument::setDocumentMargin(qreal margin)
746 if (d->documentMargin != margin) {
747 d->documentMargin = margin;
749 QTextFrame* root = rootFrame();
750 QTextFrameFormat format = root->frameFormat();
751 format.setMargin(margin);
752 root->setFrameFormat(format);
755 d->lout->documentChanged(0, 0, d->length());
761 \property QTextDocument::indentWidth
764 Returns the width used for text list and text block indenting.
766 The indent properties of QTextListFormat and QTextBlockFormat specify
767 multiples of this value. The default indent width is 40.
769 qreal QTextDocument::indentWidth() const
771 Q_D(const QTextDocument);
772 return d->indentWidth;
779 Sets the \a width used for text list and text block indenting.
781 The indent properties of QTextListFormat and QTextBlockFormat specify
782 multiples of this value. The default indent width is 40 .
786 void QTextDocument::setIndentWidth(qreal width)
789 if (d->indentWidth != width) {
790 d->indentWidth = width;
792 d->lout->documentChanged(0, 0, d->length());
802 Adjusts the document to a reasonable size.
804 \sa idealWidth(), textWidth, size
806 void QTextDocument::adjustSize()
808 // Pull this private function in from qglobal.cpp
809 QFont f = defaultFont();
811 int mw = fm.width(QLatin1Char('x')) * 80;
814 QSizeF size = documentLayout()->documentSize();
815 if (size.width() != 0) {
816 w = qt_int_sqrt((uint)(5 * size.height() * size.width() / 3));
817 setTextWidth(qMin(w, mw));
819 size = documentLayout()->documentSize();
820 if (w*3 < 5*size.height()) {
821 w = qt_int_sqrt((uint)(2 * size.height() * size.width()));
822 setTextWidth(qMin(w, mw));
825 setTextWidth(idealWidth());
829 \property QTextDocument::size
832 Returns the actual size of the document.
833 This is equivalent to documentLayout()->documentSize();
835 The size of the document can be changed either by setting
836 a text width or setting an entire page size.
838 Note that the width is always >= pageSize().width().
840 By default, for a newly-created, empty document, this property contains
841 a configuration-dependent size.
843 \sa setTextWidth(), setPageSize(), idealWidth()
845 QSizeF QTextDocument::size() const
847 return documentLayout()->documentSize();
851 \property QTextDocument::blockCount
854 Returns the number of text blocks in the document.
856 The value of this property is undefined in documents with tables or frames.
858 By default, if defined, this property contains a value of 1.
859 \sa lineCount(), characterCount()
861 int QTextDocument::blockCount() const
863 Q_D(const QTextDocument);
864 return d->blockMap().numNodes();
871 Returns the number of lines of this document (if the layout supports
872 this). Otherwise, this is identical to the number of blocks.
874 \sa blockCount(), characterCount()
876 int QTextDocument::lineCount() const
878 Q_D(const QTextDocument);
879 return d->blockMap().length(2);
885 Returns the number of characters of this document.
887 \sa blockCount(), characterAt()
889 int QTextDocument::characterCount() const
891 Q_D(const QTextDocument);
898 Returns the character at position \a pos, or a null character if the
899 position is out of range.
903 QChar QTextDocument::characterAt(int pos) const
905 Q_D(const QTextDocument);
906 if (pos < 0 || pos >= d->length())
908 QTextDocumentPrivate::FragmentIterator fragIt = d->find(pos);
909 const QTextFragmentData * const frag = fragIt.value();
910 const int offsetInFragment = qMax(0, pos - fragIt.position());
911 return d->text.at(frag->stringPosition + offsetInFragment);
916 \property QTextDocument::defaultStyleSheet
919 The default style sheet is applied to all newly HTML formatted text that is
920 inserted into the document, for example using setHtml() or QTextCursor::insertHtml().
922 The style sheet needs to be compliant to CSS 2.1 syntax.
924 \bold{Note:} Changing the default style sheet does not have any effect to the existing content
927 \sa {Supported HTML Subset}
930 #ifndef QT_NO_CSSPARSER
931 void QTextDocument::setDefaultStyleSheet(const QString &sheet)
934 d->defaultStyleSheet = sheet;
935 QCss::Parser parser(sheet);
936 d->parsedDefaultStyleSheet = QCss::StyleSheet();
937 d->parsedDefaultStyleSheet.origin = QCss::StyleSheetOrigin_UserAgent;
938 parser.parse(&d->parsedDefaultStyleSheet);
941 QString QTextDocument::defaultStyleSheet() const
943 Q_D(const QTextDocument);
944 return d->defaultStyleSheet;
946 #endif // QT_NO_CSSPARSER
949 \fn void QTextDocument::contentsChanged()
951 This signal is emitted whenever the document's content changes; for
952 example, when text is inserted or deleted, or when formatting is applied.
958 \fn void QTextDocument::contentsChange(int position, int charsRemoved, int charsAdded)
960 This signal is emitted whenever the document's content changes; for
961 example, when text is inserted or deleted, or when formatting is applied.
963 Information is provided about the \a position of the character in the
964 document where the change occurred, the number of characters removed
965 (\a charsRemoved), and the number of characters added (\a charsAdded).
967 The signal is emitted before the document's layout manager is notified
968 about the change. This hook allows you to implement syntax highlighting
971 \sa QAbstractTextDocumentLayout::documentChanged(), contentsChanged()
976 \fn QTextDocument::undoAvailable(bool available);
978 This signal is emitted whenever undo operations become available
979 (\a available is true) or unavailable (\a available is false).
981 See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework}
982 documentation for details.
984 \sa undo(), isUndoRedoEnabled()
988 \fn QTextDocument::redoAvailable(bool available);
990 This signal is emitted whenever redo operations become available
991 (\a available is true) or unavailable (\a available is false).
995 \fn QTextDocument::cursorPositionChanged(const QTextCursor &cursor);
997 This signal is emitted whenever the position of a cursor changed
998 due to an editing operation. The cursor that changed is passed in
999 \a cursor. If you need a signal when the cursor is moved with the
1000 arrow keys you can use the \l{QTextEdit::}{cursorPositionChanged()} signal in
1005 \fn QTextDocument::blockCountChanged(int newBlockCount);
1008 This signal is emitted when the total number of text blocks in the
1009 document changes. The value passed in \a newBlockCount is the new
1014 \fn QTextDocument::documentLayoutChanged();
1017 This signal is emitted when a new document layout is set.
1019 \sa setDocumentLayout()
1025 Returns true if undo is available; otherwise returns false.
1027 \sa isRedoAvailable(), availableUndoSteps()
1029 bool QTextDocument::isUndoAvailable() const
1031 Q_D(const QTextDocument);
1032 return d->isUndoAvailable();
1036 Returns true if redo is available; otherwise returns false.
1038 \sa isUndoAvailable(), availableRedoSteps()
1040 bool QTextDocument::isRedoAvailable() const
1042 Q_D(const QTextDocument);
1043 return d->isRedoAvailable();
1048 Returns the number of available undo steps.
1050 \sa isUndoAvailable()
1052 int QTextDocument::availableUndoSteps() const
1054 Q_D(const QTextDocument);
1055 return d->availableUndoSteps();
1060 Returns the number of available redo steps.
1062 \sa isRedoAvailable()
1064 int QTextDocument::availableRedoSteps() const
1066 Q_D(const QTextDocument);
1067 return d->availableRedoSteps();
1072 Returns the document's revision (if undo is enabled).
1074 The revision is guaranteed to increase when a document that is not
1077 \sa QTextBlock::revision(), isModified()
1079 int QTextDocument::revision() const
1081 Q_D(const QTextDocument);
1088 Sets the document to use the given \a layout. The previous layout
1091 \sa documentLayoutChanged()
1093 void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *layout)
1096 d->setLayout(layout);
1100 Returns the document layout for this document.
1102 QAbstractTextDocumentLayout *QTextDocument::documentLayout() const
1104 Q_D(const QTextDocument);
1106 QTextDocument *that = const_cast<QTextDocument *>(this);
1107 that->d_func()->setLayout(new QTextDocumentLayout(that));
1114 Returns meta information about the document of the type specified by
1117 \sa setMetaInformation()
1119 QString QTextDocument::metaInformation(MetaInformation info) const
1121 Q_D(const QTextDocument);
1132 Sets the document's meta information of the type specified by \a info
1133 to the given \a string.
1135 \sa metaInformation()
1137 void QTextDocument::setMetaInformation(MetaInformation info, const QString &string)
1151 Returns the plain text contained in the document. If you want
1152 formatting information use a QTextCursor instead.
1156 QString QTextDocument::toPlainText() const
1158 Q_D(const QTextDocument);
1159 QString txt = d->plainText();
1161 QChar *uc = txt.data();
1162 QChar *e = uc + txt.size();
1164 for (; uc != e; ++uc) {
1165 switch (uc->unicode()) {
1166 case 0xfdd0: // QTextBeginningOfFrame
1167 case 0xfdd1: // QTextEndOfFrame
1168 case QChar::ParagraphSeparator:
1169 case QChar::LineSeparator:
1170 *uc = QLatin1Char('\n');
1173 *uc = QLatin1Char(' ');
1183 Replaces the entire contents of the document with the given plain
1188 void QTextDocument::setPlainText(const QString &text)
1191 bool previousState = d->isUndoRedoEnabled();
1192 d->enableUndoRedo(false);
1193 d->beginEditBlock();
1195 QTextCursor(this).insertText(text);
1197 d->enableUndoRedo(previousState);
1201 Replaces the entire contents of the document with the given
1202 HTML-formatted text in the \a html string.
1204 The HTML formatting is respected as much as possible; for example,
1205 "<b>bold</b> text" will produce text where the first word has a font
1206 weight that gives it a bold appearance: "\bold{bold} text".
1208 \note It is the responsibility of the caller to make sure that the
1209 text is correctly decoded when a QString containing HTML is created
1210 and passed to setHtml().
1212 \sa setPlainText(), {Supported HTML Subset}
1215 #ifndef QT_NO_TEXTHTMLPARSER
1217 void QTextDocument::setHtml(const QString &html)
1220 bool previousState = d->isUndoRedoEnabled();
1221 d->enableUndoRedo(false);
1222 d->beginEditBlock();
1224 QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import();
1226 d->enableUndoRedo(previousState);
1229 #endif // QT_NO_TEXTHTMLPARSER
1232 \enum QTextDocument::FindFlag
1234 This enum describes the options available to QTextDocument's find function. The options
1235 can be OR-ed together from the following list:
1237 \value FindBackward Search backwards instead of forwards.
1238 \value FindCaseSensitively By default find works case insensitive. Specifying this option
1239 changes the behaviour to a case sensitive find operation.
1240 \value FindWholeWords Makes find match only complete words.
1244 \enum QTextDocument::MetaInformation
1246 This enum describes the different types of meta information that can be
1247 added to a document.
1249 \value DocumentTitle The title of the document.
1250 \value DocumentUrl The url of the document. The loadResource() function uses
1251 this url as the base when loading relative resources.
1253 \sa metaInformation(), setMetaInformation()
1257 \fn QTextCursor QTextDocument::find(const QString &subString, int position, FindFlags options) const
1261 Finds the next occurrence of the string, \a subString, in the document.
1262 The search starts at the given \a position, and proceeds forwards
1263 through the document unless specified otherwise in the search options.
1264 The \a options control the type of search performed.
1266 Returns a cursor with the match selected if \a subString
1267 was found; otherwise returns a null cursor.
1269 If the \a position is 0 (the default) the search begins from the beginning
1270 of the document; otherwise it begins at the specified position.
1272 QTextCursor QTextDocument::find(const QString &subString, int from, FindFlags options) const
1274 QRegExp expr(subString);
1275 expr.setPatternSyntax(QRegExp::FixedString);
1276 expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive);
1278 return find(expr, from, options);
1282 \fn QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &cursor, FindFlags options) const
1284 Finds the next occurrence of the string, \a subString, in the document.
1285 The search starts at the position of the given \a cursor, and proceeds
1286 forwards through the document unless specified otherwise in the search
1287 options. The \a options control the type of search performed.
1289 Returns a cursor with the match selected if \a subString was found; otherwise
1290 returns a null cursor.
1292 If the given \a cursor has a selection, the search begins after the
1293 selection; otherwise it begins at the cursor's position.
1295 By default the search is case-sensitive, and can match text anywhere in the
1298 QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &from, FindFlags options) const
1301 if (!from.isNull()) {
1302 if (options & QTextDocument::FindBackward)
1303 pos = from.selectionStart();
1305 pos = from.selectionEnd();
1307 QRegExp expr(subString);
1308 expr.setPatternSyntax(QRegExp::FixedString);
1309 expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive);
1311 return find(expr, pos, options);
1315 static bool findInBlock(const QTextBlock &block, const QRegExp &expression, int offset,
1316 QTextDocument::FindFlags options, QTextCursor &cursor)
1318 const QRegExp expr(expression);
1319 QString text = block.text();
1320 text.replace(QChar::Nbsp, QLatin1Char(' '));
1323 while (offset >=0 && offset <= text.length()) {
1324 idx = (options & QTextDocument::FindBackward) ?
1325 expr.lastIndexIn(text, offset) : expr.indexIn(text, offset);
1329 if (options & QTextDocument::FindWholeWords) {
1330 const int start = idx;
1331 const int end = start + expr.matchedLength();
1332 if ((start != 0 && text.at(start - 1).isLetterOrNumber())
1333 || (end != text.length() && text.at(end).isLetterOrNumber())) {
1334 //if this is not a whole word, continue the search in the string
1335 offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
1340 //we have a hit, return the cursor for that.
1345 cursor = QTextCursor(block.docHandle(), block.position() + idx);
1346 cursor.setPosition(cursor.position() + expr.matchedLength(), QTextCursor::KeepAnchor);
1351 \fn QTextCursor QTextDocument::find(const QRegExp & expr, int position, FindFlags options) const
1355 Finds the next occurrence, matching the regular expression, \a expr, in the document.
1356 The search starts at the given \a position, and proceeds forwards
1357 through the document unless specified otherwise in the search options.
1358 The \a options control the type of search performed. The FindCaseSensitively
1359 option is ignored for this overload, use QRegExp::caseSensitivity instead.
1361 Returns a cursor with the match selected if a match was found; otherwise
1362 returns a null cursor.
1364 If the \a position is 0 (the default) the search begins from the beginning
1365 of the document; otherwise it begins at the specified position.
1367 QTextCursor QTextDocument::find(const QRegExp & expr, int from, FindFlags options) const
1369 Q_D(const QTextDocument);
1372 return QTextCursor();
1375 //the cursor is positioned between characters, so for a backward search
1376 //do not include the character given in the position.
1377 if (options & FindBackward) {
1380 return QTextCursor();
1384 QTextBlock block = d->blocksFind(pos);
1386 if (!(options & FindBackward)) {
1387 int blockOffset = qMax(0, pos - block.position());
1388 while (block.isValid()) {
1389 if (findInBlock(block, expr, blockOffset, options, cursor))
1392 block = block.next();
1395 int blockOffset = pos - block.position();
1396 while (block.isValid()) {
1397 if (findInBlock(block, expr, blockOffset, options, cursor))
1399 block = block.previous();
1400 blockOffset = block.length() - 1;
1404 return QTextCursor();
1408 \fn QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &cursor, FindFlags options) const
1410 Finds the next occurrence, matching the regular expression, \a expr, in the document.
1411 The search starts at the position of the given \a cursor, and proceeds
1412 forwards through the document unless specified otherwise in the search
1413 options. The \a options control the type of search performed. The FindCaseSensitively
1414 option is ignored for this overload, use QRegExp::caseSensitivity instead.
1416 Returns a cursor with the match selected if a match was found; otherwise
1417 returns a null cursor.
1419 If the given \a cursor has a selection, the search begins after the
1420 selection; otherwise it begins at the cursor's position.
1422 By default the search is case-sensitive, and can match text anywhere in the
1425 QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &from, FindFlags options) const
1428 if (!from.isNull()) {
1429 if (options & QTextDocument::FindBackward)
1430 pos = from.selectionStart();
1432 pos = from.selectionEnd();
1434 return find(expr, pos, options);
1439 \fn QTextObject *QTextDocument::createObject(const QTextFormat &format)
1441 Creates and returns a new document object (a QTextObject), based
1442 on the given \a format.
1444 QTextObjects will always get created through this method, so you
1445 must reimplement it if you use custom text objects inside your document.
1447 QTextObject *QTextDocument::createObject(const QTextFormat &f)
1449 QTextObject *obj = 0;
1450 if (f.isListFormat())
1451 obj = new QTextList(this);
1452 else if (f.isTableFormat())
1453 obj = new QTextTable(this);
1454 else if (f.isFrameFormat())
1455 obj = new QTextFrame(this);
1463 Returns the frame that contains the text cursor position \a pos.
1465 QTextFrame *QTextDocument::frameAt(int pos) const
1467 Q_D(const QTextDocument);
1468 return d->frameAt(pos);
1472 Returns the document's root frame.
1474 QTextFrame *QTextDocument::rootFrame() const
1476 Q_D(const QTextDocument);
1477 return d->rootFrame();
1481 Returns the text object associated with the given \a objectIndex.
1483 QTextObject *QTextDocument::object(int objectIndex) const
1485 Q_D(const QTextDocument);
1486 return d->objectForIndex(objectIndex);
1490 Returns the text object associated with the format \a f.
1492 QTextObject *QTextDocument::objectForFormat(const QTextFormat &f) const
1494 Q_D(const QTextDocument);
1495 return d->objectForFormat(f);
1500 Returns the text block that contains the \a{pos}-th character.
1502 QTextBlock QTextDocument::findBlock(int pos) const
1504 Q_D(const QTextDocument);
1505 return QTextBlock(docHandle(), d->blockMap().findNode(pos));
1510 Returns the text block with the specified \a blockNumber.
1512 \sa QTextBlock::blockNumber()
1514 QTextBlock QTextDocument::findBlockByNumber(int blockNumber) const
1516 Q_D(const QTextDocument);
1517 return QTextBlock(docHandle(), d->blockMap().findNode(blockNumber, 1));
1522 Returns the text block that contains the specified \a lineNumber.
1524 \sa QTextBlock::firstLineNumber()
1526 QTextBlock QTextDocument::findBlockByLineNumber(int lineNumber) const
1528 Q_D(const QTextDocument);
1529 return QTextBlock(docHandle(), d->blockMap().findNode(lineNumber, 2));
1533 Returns the document's first text block.
1537 QTextBlock QTextDocument::begin() const
1539 Q_D(const QTextDocument);
1540 return QTextBlock(docHandle(), d->blockMap().begin().n);
1544 This function returns a block to test for the end of the document
1545 while iterating over it.
1547 \snippet doc/src/snippets/textdocumentendsnippet.cpp 0
1549 The block returned is invalid and represents the block after the
1550 last block in the document. You can use lastBlock() to retrieve the
1551 last valid block of the document.
1555 QTextBlock QTextDocument::end() const
1557 return QTextBlock(docHandle(), 0);
1562 Returns the document's first text block.
1564 QTextBlock QTextDocument::firstBlock() const
1566 Q_D(const QTextDocument);
1567 return QTextBlock(docHandle(), d->blockMap().begin().n);
1572 Returns the document's last (valid) text block.
1574 QTextBlock QTextDocument::lastBlock() const
1576 Q_D(const QTextDocument);
1577 return QTextBlock(docHandle(), d->blockMap().last().n);
1581 \property QTextDocument::pageSize
1582 \brief the page size that should be used for laying out the document
1584 By default, for a newly-created, empty document, this property contains
1587 \sa modificationChanged()
1590 void QTextDocument::setPageSize(const QSizeF &size)
1595 d->lout->documentChanged(0, 0, d->length());
1598 QSizeF QTextDocument::pageSize() const
1600 Q_D(const QTextDocument);
1605 returns the number of pages in this document.
1607 int QTextDocument::pageCount() const
1609 return documentLayout()->pageCount();
1613 Sets the default \a font to use in the document layout.
1615 void QTextDocument::setDefaultFont(const QFont &font)
1618 d->setDefaultFont(font);
1620 d->lout->documentChanged(0, 0, d->length());
1624 Returns the default font to be used in the document layout.
1626 QFont QTextDocument::defaultFont() const
1628 Q_D(const QTextDocument);
1629 return d->defaultFont();
1633 \fn QTextDocument::modificationChanged(bool changed)
1635 This signal is emitted whenever the content of the document
1636 changes in a way that affects the modification state. If \a
1637 changed is true, the document has been modified; otherwise it is
1640 For example, calling setModified(false) on a document and then
1641 inserting text causes the signal to get emitted. If you undo that
1642 operation, causing the document to return to its original
1643 unmodified state, the signal will get emitted again.
1647 \property QTextDocument::modified
1648 \brief whether the document has been modified by the user
1650 By default, this property is false.
1652 \sa modificationChanged()
1655 bool QTextDocument::isModified() const
1657 return docHandle()->isModified();
1660 void QTextDocument::setModified(bool m)
1662 docHandle()->setModified(m);
1665 #ifndef QT_NO_PRINTER
1666 static void printPage(int index, QPainter *painter, const QTextDocument *doc, const QRectF &body, const QPointF &pageNumberPos)
1669 painter->translate(body.left(), body.top() - (index - 1) * body.height());
1670 QRectF view(0, (index - 1) * body.height(), body.width(), body.height());
1672 QAbstractTextDocumentLayout *layout = doc->documentLayout();
1673 QAbstractTextDocumentLayout::PaintContext ctx;
1675 painter->setClipRect(view);
1678 // don't use the system palette text as default text color, on HP/UX
1679 // for example that's white, and white text on white paper doesn't
1681 ctx.palette.setColor(QPalette::Text, Qt::black);
1683 layout->draw(painter, ctx);
1685 if (!pageNumberPos.isNull()) {
1686 painter->setClipping(false);
1687 painter->setFont(QFont(doc->defaultFont()));
1688 const QString pageString = QString::number(index);
1690 painter->drawText(qRound(pageNumberPos.x() - painter->fontMetrics().width(pageString)),
1691 qRound(pageNumberPos.y() + view.top()),
1699 Prints the document to the given \a printer. The QPrinter must be
1700 set up before being used with this function.
1702 This is only a convenience method to print the whole document to the printer.
1704 If the document is already paginated through a specified height in the pageSize()
1705 property it is printed as-is.
1707 If the document is not paginated, like for example a document used in a QTextEdit,
1708 then a temporary copy of the document is created and the copy is broken into
1709 multiple pages according to the size of the QPrinter's paperRect(). By default
1710 a 2 cm margin is set around the document contents. In addition the current page
1711 number is printed at the bottom of each page.
1713 Note that QPrinter::Selection is not supported as print range with this function since
1714 the selection is a property of QTextCursor. If you have a QTextEdit associated with
1715 your QTextDocument then you can use QTextEdit's print() function because QTextEdit has
1716 access to the user's selection.
1718 \sa QTextEdit::print()
1721 void QTextDocument::print(QPrinter *printer) const
1723 Q_D(const QTextDocument);
1725 if (!printer || !printer->isValid())
1728 if (!d->title.isEmpty())
1729 printer->setDocName(d->title);
1731 bool documentPaginated = d->pageSize.isValid() && !d->pageSize.isNull()
1732 && d->pageSize.height() != INT_MAX;
1734 if (!documentPaginated && !printer->fullPage() && !printer->d_func()->hasCustomPageMargins)
1735 printer->setPageMargins(23.53, 23.53, 23.53, 23.53, QPrinter::Millimeter);
1737 QPainter p(printer);
1739 // Check that there is a valid device to print to.
1743 const QTextDocument *doc = this;
1744 QScopedPointer<QTextDocument> clonedDoc;
1745 (void)doc->documentLayout(); // make sure that there is a layout
1747 QRectF body = QRectF(QPointF(0, 0), d->pageSize);
1748 QPointF pageNumberPos;
1750 if (documentPaginated) {
1751 qreal sourceDpiX = qt_defaultDpi();
1752 qreal sourceDpiY = sourceDpiX;
1754 QPaintDevice *dev = doc->documentLayout()->paintDevice();
1756 sourceDpiX = dev->logicalDpiX();
1757 sourceDpiY = dev->logicalDpiY();
1760 const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX;
1761 const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY;
1764 p.scale(dpiScaleX, dpiScaleY);
1766 QSizeF scaledPageSize = d->pageSize;
1767 scaledPageSize.rwidth() *= dpiScaleX;
1768 scaledPageSize.rheight() *= dpiScaleY;
1770 const QSizeF printerPageSize(printer->pageRect().size());
1773 p.scale(printerPageSize.width() / scaledPageSize.width(),
1774 printerPageSize.height() / scaledPageSize.height());
1776 doc = clone(const_cast<QTextDocument *>(this));
1777 clonedDoc.reset(const_cast<QTextDocument *>(doc));
1779 for (QTextBlock srcBlock = firstBlock(), dstBlock = clonedDoc->firstBlock();
1780 srcBlock.isValid() && dstBlock.isValid();
1781 srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) {
1782 dstBlock.layout()->setAdditionalFormats(srcBlock.layout()->additionalFormats());
1785 QAbstractTextDocumentLayout *layout = doc->documentLayout();
1786 layout->setPaintDevice(p.device());
1788 // copy the custom object handlers
1789 layout->d_func()->handlers = documentLayout()->d_func()->handlers;
1791 int dpiy = p.device()->logicalDpiY();
1793 if (printer->fullPage() && !printer->d_func()->hasCustomPageMargins) {
1794 // for compatibility
1795 margin = (int) ((2/2.54)*dpiy); // 2 cm margins
1796 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
1797 fmt.setMargin(margin);
1798 doc->rootFrame()->setFrameFormat(fmt);
1801 QRectF pageRect(printer->pageRect());
1802 body = QRectF(0, 0, pageRect.width(), pageRect.height());
1803 pageNumberPos = QPointF(body.width() - margin,
1804 body.height() - margin
1805 + QFontMetrics(doc->defaultFont(), p.device()).ascent()
1807 clonedDoc->setPageSize(body.size());
1812 if (printer->collateCopies() == true){
1814 pageCopies = printer->supportsMultipleCopies() ? 1 : printer->copyCount();
1816 docCopies = printer->supportsMultipleCopies() ? 1 : printer->copyCount();
1820 int fromPage = printer->fromPage();
1821 int toPage = printer->toPage();
1822 bool ascending = true;
1824 if (fromPage == 0 && toPage == 0) {
1826 toPage = doc->pageCount();
1829 fromPage = qMax(1, fromPage);
1830 toPage = qMin(doc->pageCount(), toPage);
1832 if (toPage < fromPage) {
1833 // if the user entered a page range outside the actual number
1834 // of printable pages, just return
1838 if (printer->pageOrder() == QPrinter::LastPageFirst) {
1845 for (int i = 0; i < docCopies; ++i) {
1847 int page = fromPage;
1849 for (int j = 0; j < pageCopies; ++j) {
1850 if (printer->printerState() == QPrinter::Aborted
1851 || printer->printerState() == QPrinter::Error)
1853 printPage(page, &p, doc, body, pageNumberPos);
1854 if (j < pageCopies - 1)
1869 if ( i < docCopies - 1)
1876 \enum QTextDocument::ResourceType
1878 This enum describes the types of resources that can be loaded by
1879 QTextDocument's loadResource() function.
1881 \value HtmlResource The resource contains HTML.
1882 \value ImageResource The resource contains image data.
1883 Currently supported data types are QVariant::Pixmap and
1884 QVariant::Image. If the corresponding variant is of type
1885 QVariant::ByteArray then Qt attempts to load the image using
1886 QImage::loadFromData. QVariant::Icon is currently not supported.
1887 The icon needs to be converted to one of the supported types first,
1888 for example using QIcon::pixmap.
1889 \value StyleSheetResource The resource contains CSS.
1890 \value UserResource The first available value for user defined
1897 Returns data of the specified \a type from the resource with the
1900 This function is called by the rich text engine to request data that isn't
1901 directly stored by QTextDocument, but still associated with it. For example,
1902 images are referenced indirectly by the name attribute of a QTextImageFormat
1905 Resources are cached internally in the document. If a resource can
1906 not be found in the cache, loadResource is called to try to load
1907 the resource. loadResource should then use addResource to add the
1908 resource to the cache.
1910 \sa QTextDocument::ResourceType
1912 QVariant QTextDocument::resource(int type, const QUrl &name) const
1914 Q_D(const QTextDocument);
1915 QVariant r = d->resources.value(name);
1917 r = d->cachedResources.value(name);
1919 r = const_cast<QTextDocument *>(this)->loadResource(type, name);
1925 Adds the resource \a resource to the resource cache, using \a
1926 type and \a name as identifiers. \a type should be a value from
1927 QTextDocument::ResourceType.
1929 For example, you can add an image as a resource in order to reference it
1930 from within the document:
1932 \snippet snippets/textdocument-resources/main.cpp Adding a resource
1934 The image can be inserted into the document using the QTextCursor API:
1936 \snippet snippets/textdocument-resources/main.cpp Inserting an image with a cursor
1938 Alternatively, you can insert images using the HTML \c img tag:
1940 \snippet snippets/textdocument-resources/main.cpp Inserting an image using HTML
1942 void QTextDocument::addResource(int type, const QUrl &name, const QVariant &resource)
1946 d->resources.insert(name, resource);
1950 Loads data of the specified \a type from the resource with the
1953 This function is called by the rich text engine to request data that isn't
1954 directly stored by QTextDocument, but still associated with it. For example,
1955 images are referenced indirectly by the name attribute of a QTextImageFormat
1958 When called by Qt, \a type is one of the values of
1959 QTextDocument::ResourceType.
1961 If the QTextDocument is a child object of a QTextEdit, QTextBrowser,
1962 or a QTextDocument itself then the default implementation tries
1963 to retrieve the data from the parent.
1965 QVariant QTextDocument::loadResource(int type, const QUrl &name)
1970 QTextDocument *doc = qobject_cast<QTextDocument *>(parent());
1972 r = doc->loadResource(type, name);
1974 #ifndef QT_NO_TEXTEDIT
1975 else if (QTextEdit *edit = qobject_cast<QTextEdit *>(parent())) {
1976 QUrl resolvedName = edit->d_func()->resolveUrl(name);
1977 r = edit->loadResource(type, resolvedName);
1980 #ifndef QT_NO_TEXTCONTROL
1981 else if (QTextControl *control = qobject_cast<QTextControl *>(parent())) {
1982 r = control->loadResource(type, name);
1986 // handle data: URLs
1987 if (r.isNull() && name.scheme().compare(QLatin1String("data"), Qt::CaseInsensitive) == 0)
1988 r = qDecodeDataUrl(name).second;
1990 // if resource was not loaded try to load it here
1991 if (!doc && r.isNull() && name.isRelative()) {
1992 QUrl currentURL = d->url;
1993 QUrl resourceUrl = name;
1995 // For the second case QUrl can merge "#someanchor" with "foo.html"
1996 // correctly to "foo.html#someanchor"
1997 if (!(currentURL.isRelative()
1998 || (currentURL.scheme() == QLatin1String("file")
1999 && !QFileInfo(currentURL.toLocalFile()).isAbsolute()))
2000 || (name.hasFragment() && name.path().isEmpty())) {
2001 resourceUrl = currentURL.resolved(name);
2003 // this is our last resort when current url and new url are both relative
2004 // we try to resolve against the current working directory in the local
2006 QFileInfo fi(currentURL.toLocalFile());
2009 QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(name);
2010 } else if (currentURL.isEmpty()) {
2011 resourceUrl.setScheme(QLatin1String("file"));
2015 QString s = resourceUrl.toLocalFile();
2017 if (!s.isEmpty() && f.open(QFile::ReadOnly)) {
2024 if (type == ImageResource && r.type() == QVariant::ByteArray) {
2025 if (qApp->thread() != QThread::currentThread()) {
2026 // must use images in non-GUI threads
2028 image.loadFromData(r.toByteArray());
2029 if (!image.isNull())
2033 pm.loadFromData(r.toByteArray());
2038 d->cachedResources.insert(name, r);
2043 static QTextFormat formatDifference(const QTextFormat &from, const QTextFormat &to)
2045 QTextFormat diff = to;
2047 const QMap<int, QVariant> props = to.properties();
2048 for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end();
2050 if (it.value() == from.property(it.key()))
2051 diff.clearProperty(it.key());
2056 QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc)
2057 : doc(_doc), fragmentMarkers(false)
2059 const QFont defaultFont = doc->defaultFont();
2060 defaultCharFormat.setFont(defaultFont);
2061 // don't export those for the default font since we cannot turn them off with CSS
2062 defaultCharFormat.clearProperty(QTextFormat::FontUnderline);
2063 defaultCharFormat.clearProperty(QTextFormat::FontOverline);
2064 defaultCharFormat.clearProperty(QTextFormat::FontStrikeOut);
2065 defaultCharFormat.clearProperty(QTextFormat::TextUnderlineStyle);
2069 Returns the document in HTML format. The conversion may not be
2070 perfect, especially for complex documents, due to the limitations
2073 QString QTextHtmlExporter::toHtml(const QByteArray &encoding, ExportMode mode)
2075 html = QLatin1String("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
2076 "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
2077 "<html><head><meta name=\"qrichtext\" content=\"1\" />");
2078 html.reserve(doc->docHandle()->length());
2080 fragmentMarkers = (mode == ExportFragment);
2082 if (!encoding.isEmpty())
2083 html += QString::fromLatin1("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(QString::fromAscii(encoding));
2085 QString title = doc->metaInformation(QTextDocument::DocumentTitle);
2086 if (!title.isEmpty())
2087 html += QString::fromLatin1("<title>") + title + QString::fromLatin1("</title>");
2088 html += QLatin1String("<style type=\"text/css\">\n");
2089 html += QLatin1String("p, li { white-space: pre-wrap; }\n");
2090 html += QLatin1String("</style>");
2091 html += QLatin1String("</head><body");
2093 if (mode == ExportEntireDocument) {
2094 html += QLatin1String(" style=\"");
2096 emitFontFamily(defaultCharFormat.fontFamily());
2098 if (defaultCharFormat.hasProperty(QTextFormat::FontPointSize)) {
2099 html += QLatin1String(" font-size:");
2100 html += QString::number(defaultCharFormat.fontPointSize());
2101 html += QLatin1String("pt;");
2102 } else if (defaultCharFormat.hasProperty(QTextFormat::FontPixelSize)) {
2103 html += QLatin1String(" font-size:");
2104 html += QString::number(defaultCharFormat.intProperty(QTextFormat::FontPixelSize));
2105 html += QLatin1String("px;");
2108 html += QLatin1String(" font-weight:");
2109 html += QString::number(defaultCharFormat.fontWeight() * 8);
2110 html += QLatin1Char(';');
2112 html += QLatin1String(" font-style:");
2113 html += (defaultCharFormat.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
2114 html += QLatin1Char(';');
2116 // do not set text-decoration on the default font since those values are /always/ propagated
2117 // and cannot be turned off with CSS
2119 html += QLatin1Char('\"');
2121 const QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
2122 emitBackgroundAttribute(fmt);
2125 defaultCharFormat = QTextCharFormat();
2127 html += QLatin1Char('>');
2129 QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat();
2130 rootFmt.clearProperty(QTextFormat::BackgroundBrush);
2132 QTextFrameFormat defaultFmt;
2133 defaultFmt.setMargin(doc->documentMargin());
2135 if (rootFmt == defaultFmt)
2136 emitFrame(doc->rootFrame()->begin());
2138 emitTextFrame(doc->rootFrame());
2140 html += QLatin1String("</body></html>");
2144 void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value)
2146 html += QLatin1Char(' ');
2147 html += QLatin1String(attribute);
2148 html += QLatin1String("=\"");
2149 html += Qt::escape(value);
2150 html += QLatin1Char('"');
2153 bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
2155 bool attributesEmitted = false;
2158 const QString family = format.fontFamily();
2159 if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) {
2160 emitFontFamily(family);
2161 attributesEmitted = true;
2165 if (format.hasProperty(QTextFormat::FontPointSize)
2166 && format.fontPointSize() != defaultCharFormat.fontPointSize()) {
2167 html += QLatin1String(" font-size:");
2168 html += QString::number(format.fontPointSize());
2169 html += QLatin1String("pt;");
2170 attributesEmitted = true;
2171 } else if (format.hasProperty(QTextFormat::FontSizeAdjustment)) {
2172 static const char * const sizeNames[] = {
2173 "small", "medium", "large", "x-large", "xx-large"
2175 const char *name = 0;
2176 const int idx = format.intProperty(QTextFormat::FontSizeAdjustment) + 1;
2177 if (idx >= 0 && idx <= 4) {
2178 name = sizeNames[idx];
2181 html += QLatin1String(" font-size:");
2182 html += QLatin1String(name);
2183 html += QLatin1Char(';');
2184 attributesEmitted = true;
2186 } else if (format.hasProperty(QTextFormat::FontPixelSize)) {
2187 html += QLatin1String(" font-size:");
2188 html += QString::number(format.intProperty(QTextFormat::FontPixelSize));
2189 html += QLatin1String("px;");
2192 if (format.hasProperty(QTextFormat::FontWeight)
2193 && format.fontWeight() != defaultCharFormat.fontWeight()) {
2194 html += QLatin1String(" font-weight:");
2195 html += QString::number(format.fontWeight() * 8);
2196 html += QLatin1Char(';');
2197 attributesEmitted = true;
2200 if (format.hasProperty(QTextFormat::FontItalic)
2201 && format.fontItalic() != defaultCharFormat.fontItalic()) {
2202 html += QLatin1String(" font-style:");
2203 html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
2204 html += QLatin1Char(';');
2205 attributesEmitted = true;
2208 QLatin1String decorationTag(" text-decoration:");
2209 html += decorationTag;
2210 bool hasDecoration = false;
2211 bool atLeastOneDecorationSet = false;
2213 if ((format.hasProperty(QTextFormat::FontUnderline) || format.hasProperty(QTextFormat::TextUnderlineStyle))
2214 && format.fontUnderline() != defaultCharFormat.fontUnderline()) {
2215 hasDecoration = true;
2216 if (format.fontUnderline()) {
2217 html += QLatin1String(" underline");
2218 atLeastOneDecorationSet = true;
2222 if (format.hasProperty(QTextFormat::FontOverline)
2223 && format.fontOverline() != defaultCharFormat.fontOverline()) {
2224 hasDecoration = true;
2225 if (format.fontOverline()) {
2226 html += QLatin1String(" overline");
2227 atLeastOneDecorationSet = true;
2231 if (format.hasProperty(QTextFormat::FontStrikeOut)
2232 && format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) {
2233 hasDecoration = true;
2234 if (format.fontStrikeOut()) {
2235 html += QLatin1String(" line-through");
2236 atLeastOneDecorationSet = true;
2240 if (hasDecoration) {
2241 if (!atLeastOneDecorationSet)
2242 html += QLatin1String("none");
2243 html += QLatin1Char(';');
2244 attributesEmitted = true;
2246 html.chop(qstrlen(decorationTag.latin1()));
2249 if (format.foreground() != defaultCharFormat.foreground()
2250 && format.foreground().style() != Qt::NoBrush) {
2251 html += QLatin1String(" color:");
2252 html += format.foreground().color().name();
2253 html += QLatin1Char(';');
2254 attributesEmitted = true;
2257 if (format.background() != defaultCharFormat.background()
2258 && format.background().style() == Qt::SolidPattern) {
2259 html += QLatin1String(" background-color:");
2260 html += format.background().color().name();
2261 html += QLatin1Char(';');
2262 attributesEmitted = true;
2265 if (format.verticalAlignment() != defaultCharFormat.verticalAlignment()
2266 && format.verticalAlignment() != QTextCharFormat::AlignNormal)
2268 html += QLatin1String(" vertical-align:");
2270 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2271 if (valign == QTextCharFormat::AlignSubScript)
2272 html += QLatin1String("sub");
2273 else if (valign == QTextCharFormat::AlignSuperScript)
2274 html += QLatin1String("super");
2275 else if (valign == QTextCharFormat::AlignMiddle)
2276 html += QLatin1String("middle");
2277 else if (valign == QTextCharFormat::AlignTop)
2278 html += QLatin1String("top");
2279 else if (valign == QTextCharFormat::AlignBottom)
2280 html += QLatin1String("bottom");
2282 html += QLatin1Char(';');
2283 attributesEmitted = true;
2286 if (format.fontCapitalization() != QFont::MixedCase) {
2287 const QFont::Capitalization caps = format.fontCapitalization();
2288 if (caps == QFont::AllUppercase)
2289 html += QLatin1String(" text-transform:uppercase;");
2290 else if (caps == QFont::AllLowercase)
2291 html += QLatin1String(" text-transform:lowercase;");
2292 else if (caps == QFont::SmallCaps)
2293 html += QLatin1String(" font-variant:small-caps;");
2294 attributesEmitted = true;
2297 if (format.fontWordSpacing() != 0.0) {
2298 html += QLatin1String(" word-spacing:");
2299 html += QString::number(format.fontWordSpacing());
2300 html += QLatin1String("px;");
2301 attributesEmitted = true;
2304 return attributesEmitted;
2307 void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength &length)
2309 if (length.type() == QTextLength::VariableLength) // default
2312 html += QLatin1Char(' ');
2313 html += QLatin1String(attribute);
2314 html += QLatin1String("=\"");
2315 html += QString::number(length.rawValue());
2317 if (length.type() == QTextLength::PercentageLength)
2318 html += QLatin1String("%\"");
2320 html += QLatin1Char('\"');
2323 void QTextHtmlExporter::emitAlignment(Qt::Alignment align)
2325 if (align & Qt::AlignLeft)
2327 else if (align & Qt::AlignRight)
2328 html += QLatin1String(" align=\"right\"");
2329 else if (align & Qt::AlignHCenter)
2330 html += QLatin1String(" align=\"center\"");
2331 else if (align & Qt::AlignJustify)
2332 html += QLatin1String(" align=\"justify\"");
2335 void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode)
2337 if (pos == QTextFrameFormat::InFlow)
2340 if (mode == EmitStyleTag)
2341 html += QLatin1String(" style=\"float:");
2343 html += QLatin1String(" float:");
2345 if (pos == QTextFrameFormat::FloatLeft)
2346 html += QLatin1String(" left;");
2347 else if (pos == QTextFrameFormat::FloatRight)
2348 html += QLatin1String(" right;");
2350 Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type");
2352 if (mode == EmitStyleTag)
2353 html += QLatin1Char('\"');
2356 void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style)
2358 Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset);
2360 html += QLatin1String(" border-style:");
2363 case QTextFrameFormat::BorderStyle_None:
2364 html += QLatin1String("none");
2366 case QTextFrameFormat::BorderStyle_Dotted:
2367 html += QLatin1String("dotted");
2369 case QTextFrameFormat::BorderStyle_Dashed:
2370 html += QLatin1String("dashed");
2372 case QTextFrameFormat::BorderStyle_Solid:
2373 html += QLatin1String("solid");
2375 case QTextFrameFormat::BorderStyle_Double:
2376 html += QLatin1String("double");
2378 case QTextFrameFormat::BorderStyle_DotDash:
2379 html += QLatin1String("dot-dash");
2381 case QTextFrameFormat::BorderStyle_DotDotDash:
2382 html += QLatin1String("dot-dot-dash");
2384 case QTextFrameFormat::BorderStyle_Groove:
2385 html += QLatin1String("groove");
2387 case QTextFrameFormat::BorderStyle_Ridge:
2388 html += QLatin1String("ridge");
2390 case QTextFrameFormat::BorderStyle_Inset:
2391 html += QLatin1String("inset");
2393 case QTextFrameFormat::BorderStyle_Outset:
2394 html += QLatin1String("outset");
2401 html += QLatin1Char(';');
2404 void QTextHtmlExporter::emitPageBreakPolicy(QTextFormat::PageBreakFlags policy)
2406 if (policy & QTextFormat::PageBreak_AlwaysBefore)
2407 html += QLatin1String(" page-break-before:always;");
2409 if (policy & QTextFormat::PageBreak_AlwaysAfter)
2410 html += QLatin1String(" page-break-after:always;");
2413 void QTextHtmlExporter::emitFontFamily(const QString &family)
2415 html += QLatin1String(" font-family:");
2417 QLatin1String quote("\'");
2418 if (family.contains(QLatin1Char('\'')))
2419 quote = QLatin1String(""");
2422 html += Qt::escape(family);
2424 html += QLatin1Char(';');
2427 void QTextHtmlExporter::emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right)
2429 html += QLatin1String(" margin-top:");
2431 html += QLatin1String("px;");
2433 html += QLatin1String(" margin-bottom:");
2435 html += QLatin1String("px;");
2437 html += QLatin1String(" margin-left:");
2439 html += QLatin1String("px;");
2441 html += QLatin1String(" margin-right:");
2443 html += QLatin1String("px;");
2446 void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
2448 const QTextCharFormat format = fragment.charFormat();
2450 bool closeAnchor = false;
2452 if (format.isAnchor()) {
2453 const QString name = format.anchorName();
2454 if (!name.isEmpty()) {
2455 html += QLatin1String("<a name=\"");
2456 html += Qt::escape(name);
2457 html += QLatin1String("\"></a>");
2459 const QString href = format.anchorHref();
2460 if (!href.isEmpty()) {
2461 html += QLatin1String("<a href=\"");
2462 html += Qt::escape(href);
2463 html += QLatin1String("\">");
2468 QString txt = fragment.text();
2469 const bool isObject = txt.contains(QChar::ObjectReplacementCharacter);
2470 const bool isImage = isObject && format.isImageFormat();
2472 QLatin1String styleTag("<span style=\"");
2475 bool attributesEmitted = false;
2477 attributesEmitted = emitCharFormatStyle(format);
2478 if (attributesEmitted)
2479 html += QLatin1String("\">");
2481 html.chop(qstrlen(styleTag.latin1()));
2484 for (int i = 0; isImage && i < txt.length(); ++i) {
2485 QTextImageFormat imgFmt = format.toImageFormat();
2487 html += QLatin1String("<img");
2489 if (imgFmt.hasProperty(QTextFormat::ImageName))
2490 emitAttribute("src", imgFmt.name());
2492 if (imgFmt.hasProperty(QTextFormat::ImageWidth))
2493 emitAttribute("width", QString::number(imgFmt.width()));
2495 if (imgFmt.hasProperty(QTextFormat::ImageHeight))
2496 emitAttribute("height", QString::number(imgFmt.height()));
2498 if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle)
2499 html += QLatin1String(" style=\"vertical-align: middle;\"");
2500 else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop)
2501 html += QLatin1String(" style=\"vertical-align: top;\"");
2503 if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt)))
2504 emitFloatStyle(imageFrame->frameFormat().position());
2506 html += QLatin1String(" />");
2509 Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter));
2511 txt = Qt::escape(txt);
2513 // split for [\n{LineSeparator}]
2514 QString forcedLineBreakRegExp = QString::fromLatin1("[\\na]");
2515 forcedLineBreakRegExp[3] = QChar::LineSeparator;
2517 const QStringList lines = txt.split(QRegExp(forcedLineBreakRegExp));
2518 for (int i = 0; i < lines.count(); ++i) {
2520 html += QLatin1String("<br />"); // space on purpose for compatibility with Netscape, Lynx & Co.
2521 html += lines.at(i);
2525 if (attributesEmitted)
2526 html += QLatin1String("</span>");
2529 html += QLatin1String("</a>");
2532 static bool isOrderedList(int style)
2534 return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
2535 || style == QTextListFormat::ListUpperAlpha
2536 || style == QTextListFormat::ListUpperRoman
2537 || style == QTextListFormat::ListLowerRoman
2541 void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block)
2543 QTextBlockFormat format = block.blockFormat();
2544 emitAlignment(format.alignment());
2546 // assume default to not bloat the html too much
2547 // html += QLatin1String(" dir='ltr'");
2548 if (block.textDirection() == Qt::RightToLeft)
2549 html += QLatin1String(" dir='rtl'");
2551 QLatin1String style(" style=\"");
2554 const bool emptyBlock = block.begin().atEnd();
2556 html += QLatin1String("-qt-paragraph-type:empty;");
2559 emitMargins(QString::number(format.topMargin()),
2560 QString::number(format.bottomMargin()),
2561 QString::number(format.leftMargin()),
2562 QString::number(format.rightMargin()));
2564 html += QLatin1String(" -qt-block-indent:");
2565 html += QString::number(format.indent());
2566 html += QLatin1Char(';');
2568 html += QLatin1String(" text-indent:");
2569 html += QString::number(format.textIndent());
2570 html += QLatin1String("px;");
2572 if (block.userState() != -1) {
2573 html += QLatin1String(" -qt-user-state:");
2574 html += QString::number(block.userState());
2575 html += QLatin1Char(';');
2578 emitPageBreakPolicy(format.pageBreakPolicy());
2580 QTextCharFormat diff;
2581 if (emptyBlock) { // only print character properties when we don't expect them to be repeated by actual text in the parag
2582 const QTextCharFormat blockCharFmt = block.charFormat();
2583 diff = formatDifference(defaultCharFormat, blockCharFmt).toCharFormat();
2586 diff.clearProperty(QTextFormat::BackgroundBrush);
2587 if (format.hasProperty(QTextFormat::BackgroundBrush)) {
2588 QBrush bg = format.background();
2589 if (bg.style() != Qt::NoBrush)
2590 diff.setProperty(QTextFormat::BackgroundBrush, format.property(QTextFormat::BackgroundBrush));
2593 if (!diff.properties().isEmpty())
2594 emitCharFormatStyle(diff);
2596 html += QLatin1Char('"');
2600 void QTextHtmlExporter::emitBlock(const QTextBlock &block)
2602 if (block.begin().atEnd()) {
2603 // ### HACK, remove once QTextFrame::Iterator is fixed
2604 int p = block.position();
2607 QTextDocumentPrivate::FragmentIterator frag = doc->docHandle()->find(p);
2608 QChar ch = doc->docHandle()->buffer().at(frag->stringPosition);
2609 if (ch == QTextBeginningOfFrame
2610 || ch == QTextEndOfFrame)
2614 html += QLatin1Char('\n');
2616 // save and later restore, in case we 'change' the default format by
2617 // emitting block char format information
2618 QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
2620 QTextList *list = block.textList();
2622 if (list->itemNumber(block) == 0) { // first item? emit <ul> or appropriate
2623 const QTextListFormat format = list->format();
2624 const int style = format.style();
2626 case QTextListFormat::ListDecimal: html += QLatin1String("<ol"); break;
2627 case QTextListFormat::ListDisc: html += QLatin1String("<ul"); break;
2628 case QTextListFormat::ListCircle: html += QLatin1String("<ul type=\"circle\""); break;
2629 case QTextListFormat::ListSquare: html += QLatin1String("<ul type=\"square\""); break;
2630 case QTextListFormat::ListLowerAlpha: html += QLatin1String("<ol type=\"a\""); break;
2631 case QTextListFormat::ListUpperAlpha: html += QLatin1String("<ol type=\"A\""); break;
2632 case QTextListFormat::ListLowerRoman: html += QLatin1String("<ol type=\"i\""); break;
2633 case QTextListFormat::ListUpperRoman: html += QLatin1String("<ol type=\"I\""); break;
2634 default: html += QLatin1String("<ul"); // ### should not happen
2637 QString styleString = QString::fromLatin1("margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px;");
2639 if (format.hasProperty(QTextFormat::ListIndent)) {
2640 styleString += QLatin1String(" -qt-list-indent: ");
2641 styleString += QString::number(format.indent());
2642 styleString += QLatin1Char(';');
2645 if (format.hasProperty(QTextFormat::ListNumberPrefix)) {
2646 QString numberPrefix = format.numberPrefix();
2647 numberPrefix.replace(QLatin1Char('"'), QLatin1String("\\22"));
2648 numberPrefix.replace(QLatin1Char('\''), QLatin1String("\\27")); // FIXME: There's a problem in the CSS parser the prevents this from being correctly restored
2649 styleString += QLatin1String(" -qt-list-number-prefix: ");
2650 styleString += QLatin1Char('\'');
2651 styleString += numberPrefix;
2652 styleString += QLatin1Char('\'');
2653 styleString += QLatin1Char(';');
2656 if (format.hasProperty(QTextFormat::ListNumberSuffix)) {
2657 if (format.numberSuffix() != QLatin1String(".")) { // this is our default
2658 QString numberSuffix = format.numberSuffix();
2659 numberSuffix.replace(QLatin1Char('"'), QLatin1String("\\22"));
2660 numberSuffix.replace(QLatin1Char('\''), QLatin1String("\\27")); // see above
2661 styleString += QLatin1String(" -qt-list-number-suffix: ");
2662 styleString += QLatin1Char('\'');
2663 styleString += numberSuffix;
2664 styleString += QLatin1Char('\'');
2665 styleString += QLatin1Char(';');
2669 html += QLatin1String(" style=\"");
2670 html += styleString;
2671 html += QLatin1String("\">");
2674 html += QLatin1String("<li");
2676 const QTextCharFormat blockFmt = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat();
2677 if (!blockFmt.properties().isEmpty()) {
2678 html += QLatin1String(" style=\"");
2679 emitCharFormatStyle(blockFmt);
2680 html += QLatin1Char('\"');
2682 defaultCharFormat.merge(block.charFormat());
2686 const QTextBlockFormat blockFormat = block.blockFormat();
2687 if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
2688 html += QLatin1String("<hr");
2690 QTextLength width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth);
2691 if (width.type() != QTextLength::VariableLength)
2692 emitTextLength("width", width);
2694 html += QLatin1Char(' ');
2696 html += QLatin1String("/>");
2700 const bool pre = blockFormat.nonBreakableLines();
2703 html += QLatin1Char('>');
2704 html += QLatin1String("<pre");
2706 html += QLatin1String("<p");
2709 emitBlockAttributes(block);
2711 html += QLatin1Char('>');
2712 if (block.begin().atEnd())
2713 html += QLatin1String("<br />");
2715 QTextBlock::Iterator it = block.begin();
2716 if (fragmentMarkers && !it.atEnd() && block == doc->begin())
2717 html += QLatin1String("<!--StartFragment-->");
2719 for (; !it.atEnd(); ++it)
2720 emitFragment(it.fragment());
2722 if (fragmentMarkers && block.position() + block.length() == doc->docHandle()->length())
2723 html += QLatin1String("<!--EndFragment-->");
2726 html += QLatin1String("</pre>");
2728 html += QLatin1String("</li>");
2730 html += QLatin1String("</p>");
2733 if (list->itemNumber(block) == list->count() - 1) { // last item? close list
2734 if (isOrderedList(list->format().style()))
2735 html += QLatin1String("</ol>");
2737 html += QLatin1String("</ul>");
2741 defaultCharFormat = oldDefaultCharFormat;
2744 extern bool qHasPixmapTexture(const QBrush& brush);
2746 QString QTextHtmlExporter::findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap)
2752 if (QTextDocument *parent = qobject_cast<QTextDocument *>(doc->parent()))
2753 return findUrlForImage(parent, cacheKey, isPixmap);
2755 if (doc && doc->docHandle()) {
2756 QTextDocumentPrivate *priv = doc->docHandle();
2757 QMap<QUrl, QVariant>::const_iterator it = priv->cachedResources.constBegin();
2758 for (; it != priv->cachedResources.constEnd(); ++it) {
2760 const QVariant &v = it.value();
2761 if (v.type() == QVariant::Image && !isPixmap) {
2762 if (qvariant_cast<QImage>(v).cacheKey() == cacheKey)
2766 if (v.type() == QVariant::Pixmap && isPixmap) {
2767 if (qvariant_cast<QPixmap>(v).cacheKey() == cacheKey)
2772 if (it != priv->cachedResources.constEnd())
2773 url = it.key().toString();
2779 void QTextDocumentPrivate::mergeCachedResources(const QTextDocumentPrivate *priv)
2784 cachedResources.unite(priv->cachedResources);
2787 void QTextHtmlExporter::emitBackgroundAttribute(const QTextFormat &format)
2789 if (format.hasProperty(QTextFormat::BackgroundImageUrl)) {
2790 QString url = format.property(QTextFormat::BackgroundImageUrl).toString();
2791 emitAttribute("background", url);
2793 const QBrush &brush = format.background();
2794 if (brush.style() == Qt::SolidPattern) {
2795 emitAttribute("bgcolor", brush.color().name());
2796 } else if (brush.style() == Qt::TexturePattern) {
2797 const bool isPixmap = qHasPixmapTexture(brush);
2798 const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
2800 const QString url = findUrlForImage(doc, cacheKey, isPixmap);
2803 emitAttribute("background", url);
2808 void QTextHtmlExporter::emitTable(const QTextTable *table)
2810 QTextTableFormat format = table->format();
2812 html += QLatin1String("\n<table");
2814 if (format.hasProperty(QTextFormat::FrameBorder))
2815 emitAttribute("border", QString::number(format.border()));
2817 emitFrameStyle(format, TableFrame);
2819 emitAlignment(format.alignment());
2820 emitTextLength("width", format.width());
2822 if (format.hasProperty(QTextFormat::TableCellSpacing))
2823 emitAttribute("cellspacing", QString::number(format.cellSpacing()));
2824 if (format.hasProperty(QTextFormat::TableCellPadding))
2825 emitAttribute("cellpadding", QString::number(format.cellPadding()));
2827 emitBackgroundAttribute(format);
2829 html += QLatin1Char('>');
2831 const int rows = table->rows();
2832 const int columns = table->columns();
2834 QVector<QTextLength> columnWidths = format.columnWidthConstraints();
2835 if (columnWidths.isEmpty()) {
2836 columnWidths.resize(columns);
2837 columnWidths.fill(QTextLength());
2839 Q_ASSERT(columnWidths.count() == columns);
2841 QVarLengthArray<bool> widthEmittedForColumn(columns);
2842 for (int i = 0; i < columns; ++i)
2843 widthEmittedForColumn[i] = false;
2845 const int headerRowCount = qMin(format.headerRowCount(), rows);
2846 if (headerRowCount > 0)
2847 html += QLatin1String("<thead>");
2849 for (int row = 0; row < rows; ++row) {
2850 html += QLatin1String("\n<tr>");
2852 for (int col = 0; col < columns; ++col) {
2853 const QTextTableCell cell = table->cellAt(row, col);
2856 if (cell.row() != row)
2859 if (cell.column() != col)
2862 html += QLatin1String("\n<td");
2864 if (!widthEmittedForColumn[col] && cell.columnSpan() == 1) {
2865 emitTextLength("width", columnWidths.at(col));
2866 widthEmittedForColumn[col] = true;
2869 if (cell.columnSpan() > 1)
2870 emitAttribute("colspan", QString::number(cell.columnSpan()));
2872 if (cell.rowSpan() > 1)
2873 emitAttribute("rowspan", QString::number(cell.rowSpan()));
2875 const QTextTableCellFormat cellFormat = cell.format().toTableCellFormat();
2876 emitBackgroundAttribute(cellFormat);
2878 QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
2880 QTextCharFormat::VerticalAlignment valign = cellFormat.verticalAlignment();
2882 QString styleString;
2883 if (valign >= QTextCharFormat::AlignMiddle && valign <= QTextCharFormat::AlignBottom) {
2884 styleString += QLatin1String(" vertical-align:");
2886 case QTextCharFormat::AlignMiddle:
2887 styleString += QLatin1String("middle");
2889 case QTextCharFormat::AlignTop:
2890 styleString += QLatin1String("top");
2892 case QTextCharFormat::AlignBottom:
2893 styleString += QLatin1String("bottom");
2898 styleString += QLatin1Char(';');
2900 QTextCharFormat temp;
2901 temp.setVerticalAlignment(valign);
2902 defaultCharFormat.merge(temp);
2905 if (cellFormat.hasProperty(QTextFormat::TableCellLeftPadding))
2906 styleString += QLatin1String(" padding-left:") + QString::number(cellFormat.leftPadding()) + QLatin1Char(';');
2907 if (cellFormat.hasProperty(QTextFormat::TableCellRightPadding))
2908 styleString += QLatin1String(" padding-right:") + QString::number(cellFormat.rightPadding()) + QLatin1Char(';');
2909 if (cellFormat.hasProperty(QTextFormat::TableCellTopPadding))
2910 styleString += QLatin1String(" padding-top:") + QString::number(cellFormat.topPadding()) + QLatin1Char(';');
2911 if (cellFormat.hasProperty(QTextFormat::TableCellBottomPadding))
2912 styleString += QLatin1String(" padding-bottom:") + QString::number(cellFormat.bottomPadding()) + QLatin1Char(';');
2914 if (!styleString.isEmpty())
2915 html += QLatin1String(" style=\"") + styleString + QLatin1Char('\"');
2917 html += QLatin1Char('>');
2919 emitFrame(cell.begin());
2921 html += QLatin1String("</td>");
2923 defaultCharFormat = oldDefaultCharFormat;
2926 html += QLatin1String("</tr>");
2927 if (headerRowCount > 0 && row == headerRowCount - 1)
2928 html += QLatin1String("</thead>");
2931 html += QLatin1String("</table>");
2934 void QTextHtmlExporter::emitFrame(QTextFrame::Iterator frameIt)
2936 if (!frameIt.atEnd()) {
2937 QTextFrame::Iterator next = frameIt;
2940 && frameIt.currentFrame() == 0
2941 && frameIt.parentFrame() != doc->rootFrame()
2942 && frameIt.currentBlock().begin().atEnd())
2946 for (QTextFrame::Iterator it = frameIt;
2947 !it.atEnd(); ++it) {
2948 if (QTextFrame *f = it.currentFrame()) {
2949 if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
2954 } else if (it.currentBlock().isValid()) {
2955 emitBlock(it.currentBlock());
2960 void QTextHtmlExporter::emitTextFrame(const QTextFrame *f)
2962 FrameType frameType = f->parentFrame() ? TextFrame : RootFrame;
2964 html += QLatin1String("\n<table");
2965 QTextFrameFormat format = f->frameFormat();
2967 if (format.hasProperty(QTextFormat::FrameBorder))
2968 emitAttribute("border", QString::number(format.border()));
2970 emitFrameStyle(format, frameType);
2972 emitTextLength("width", format.width());
2973 emitTextLength("height", format.height());
2975 // root frame's bcolor goes in the <body> tag
2976 if (frameType != RootFrame)
2977 emitBackgroundAttribute(format);
2979 html += QLatin1Char('>');
2980 html += QLatin1String("\n<tr>\n<td style=\"border: none;\">");
2981 emitFrame(f->begin());
2982 html += QLatin1String("</td></tr></table>");
2985 void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType frameType)
2987 QLatin1String styleAttribute(" style=\"");
2988 html += styleAttribute;
2989 const int originalHtmlLength = html.length();
2991 if (frameType == TextFrame)
2992 html += QLatin1String("-qt-table-type: frame;");
2993 else if (frameType == RootFrame)
2994 html += QLatin1String("-qt-table-type: root;");
2996 const QTextFrameFormat defaultFormat;
2998 emitFloatStyle(format.position(), OmitStyleTag);
2999 emitPageBreakPolicy(format.pageBreakPolicy());
3001 if (format.borderBrush() != defaultFormat.borderBrush()) {
3002 html += QLatin1String(" border-color:");
3003 html += format.borderBrush().color().name();
3004 html += QLatin1Char(';');
3007 if (format.borderStyle() != defaultFormat.borderStyle())
3008 emitBorderStyle(format.borderStyle());
3010 if (format.hasProperty(QTextFormat::FrameMargin)
3011 || format.hasProperty(QTextFormat::FrameLeftMargin)
3012 || format.hasProperty(QTextFormat::FrameRightMargin)
3013 || format.hasProperty(QTextFormat::FrameTopMargin)
3014 || format.hasProperty(QTextFormat::FrameBottomMargin))
3015 emitMargins(QString::number(format.topMargin()),
3016 QString::number(format.bottomMargin()),
3017 QString::number(format.leftMargin()),
3018 QString::number(format.rightMargin()));
3020 if (html.length() == originalHtmlLength) // nothing emitted?
3021 html.chop(qstrlen(styleAttribute.latin1()));
3023 html += QLatin1Char('\"');
3027 Returns a string containing an HTML representation of the document.
3029 The \a encoding parameter specifies the value for the charset attribute
3030 in the html header. For example if 'utf-8' is specified then the
3031 beginning of the generated html will look like this:
3032 \snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 1
3034 If no encoding is specified then no such meta information is generated.
3036 If you later on convert the returned html string into a byte array for
3037 transmission over a network or when saving to disk you should specify
3038 the encoding you're going to use for the conversion to a byte array here.
3040 \sa {Supported HTML Subset}
3042 #ifndef QT_NO_TEXTHTMLPARSER
3043 QString QTextDocument::toHtml(const QByteArray &encoding) const
3045 return QTextHtmlExporter(this).toHtml(encoding);
3047 #endif // QT_NO_TEXTHTMLPARSER
3050 Returns a vector of text formats for all the formats used in the document.
3052 QVector<QTextFormat> QTextDocument::allFormats() const
3054 Q_D(const QTextDocument);
3055 return d->formatCollection()->formats;
3062 So that not all classes have to be friends of each other...
3064 QTextDocumentPrivate *QTextDocument::docHandle() const
3066 Q_D(const QTextDocument);
3067 return const_cast<QTextDocumentPrivate *>(d);
3072 \fn QTextDocument::undoCommandAdded()
3074 This signal is emitted every time a new level of undo is added to the QTextDocument.