1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qtextdocument.h"
41#include <qtextformat.h>
42#include "qtextcursor_p.h"
43#include "qtextdocumentlayout_p.h"
44#include "qtextdocumentfragment.h"
45#include "qtextdocumentfragment_p.h"
46#include "qtexttable.h"
47#include "qtextlist.h"
48#include <qdebug.h>
49#include <qregexp.h>
50#if QT_CONFIG(regularexpression)
51#include <qregularexpression.h>
52#endif
53#include <qvarlengtharray.h>
54#if QT_CONFIG(textcodec)
55#include <qtextcodec.h>
56#endif
57#include <qthread.h>
58#include <qcoreapplication.h>
59#include <qmetaobject.h>
60
61#include "qtexthtmlparser_p.h"
62#include "qpainter.h"
63#include <qfile.h>
64#include <qfileinfo.h>
65#include <qdir.h>
66#include "qfont_p.h"
67#include "private/qdataurl_p.h"
68
69#include "qtextdocument_p.h"
70#include <private/qabstracttextdocumentlayout_p.h>
71#include "qpagedpaintdevice.h"
72#include "private/qpagedpaintdevice_p.h"
73#if QT_CONFIG(textmarkdownreader)
74#include <private/qtextmarkdownimporter_p.h>
75#endif
76#if QT_CONFIG(textmarkdownwriter)
77#include <private/qtextmarkdownwriter_p.h>
78#endif
79
80#include <limits.h>
81
82QT_BEGIN_NAMESPACE
83
84Q_CORE_EXPORT Q_DECL_CONST_FUNCTION unsigned int qt_int_sqrt(unsigned int n);
85
86
87/*!
88 Returns \c true if the string \a text is likely to be rich text;
89 otherwise returns \c false.
90
91 This function uses a fast and therefore simple heuristic. It
92 mainly checks whether there is something that looks like a tag
93 before the first line break. Although the result may be correct
94 for common cases, there is no guarantee.
95
96 This function is defined in the \c <QTextDocument> header file.
97*/
98bool Qt::mightBeRichText(const QString& text)
99{
100 if (text.isEmpty())
101 return false;
102 int start = 0;
103
104 while (start < text.length() && text.at(start).isSpace())
105 ++start;
106
107 // skip a leading <?xml ... ?> as for example with xhtml
108 if (text.midRef(start, 5).compare(QLatin1String("<?xml")) == 0) {
109 while (start < text.length()) {
110 if (text.at(start) == QLatin1Char('?')
111 && start + 2 < text.length()
112 && text.at(start + 1) == QLatin1Char('>')) {
113 start += 2;
114 break;
115 }
116 ++start;
117 }
118
119 while (start < text.length() && text.at(start).isSpace())
120 ++start;
121 }
122
123 if (text.midRef(start, 5).compare(QLatin1String("<!doc"), Qt::CaseInsensitive) == 0)
124 return true;
125 int open = start;
126 while (open < text.length() && text.at(open) != QLatin1Char('<')
127 && text.at(open) != QLatin1Char('\n')) {
128 if (text.at(open) == QLatin1Char('&') && text.midRef(open + 1, 3) == QLatin1String("lt;"))
129 return true; // support desperate attempt of user to see <...>
130 ++open;
131 }
132 if (open < text.length() && text.at(open) == QLatin1Char('<')) {
133 const int close = text.indexOf(QLatin1Char('>'), open);
134 if (close > -1) {
135 QString tag;
136 for (int i = open+1; i < close; ++i) {
137 if (text[i].isDigit() || text[i].isLetter())
138 tag += text[i];
139 else if (!tag.isEmpty() && text[i].isSpace())
140 break;
141 else if (!tag.isEmpty() && text[i] == QLatin1Char('/') && i + 1 == close)
142 break;
143 else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != QLatin1Char('!')))
144 return false; // that's not a tag
145 }
146#ifndef QT_NO_TEXTHTMLPARSER
147 return QTextHtmlParser::lookupElement(std::move(tag).toLower()) != -1;
148#else
149 return false;
150#endif // QT_NO_TEXTHTMLPARSER
151 }
152 }
153 return false;
154}
155
156/*!
157 Converts the plain text string \a plain to an HTML-formatted
158 paragraph while preserving most of its look.
159
160 \a mode defines how whitespace is handled.
161
162 This function is defined in the \c <QTextDocument> header file.
163
164 \sa QString::toHtmlEscaped(), mightBeRichText()
165*/
166QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode)
167{
168 int col = 0;
169 QString rich;
170 rich += QLatin1String("<p>");
171 for (int i = 0; i < plain.length(); ++i) {
172 if (plain[i] == QLatin1Char('\n')){
173 int c = 1;
174 while (i+1 < plain.length() && plain[i+1] == QLatin1Char('\n')) {
175 i++;
176 c++;
177 }
178 if (c == 1)
179 rich += QLatin1String("<br>\n");
180 else {
181 rich += QLatin1String("</p>\n");
182 while (--c > 1)
183 rich += QLatin1String("<br>\n");
184 rich += QLatin1String("<p>");
185 }
186 col = 0;
187 } else {
188 if (mode == Qt::WhiteSpacePre && plain[i] == QLatin1Char('\t')){
189 rich += QChar(0x00a0U);
190 ++col;
191 while (col % 8) {
192 rich += QChar(0x00a0U);
193 ++col;
194 }
195 }
196 else if (mode == Qt::WhiteSpacePre && plain[i].isSpace())
197 rich += QChar(0x00a0U);
198 else if (plain[i] == QLatin1Char('<'))
199 rich += QLatin1String("&lt;");
200 else if (plain[i] == QLatin1Char('>'))
201 rich += QLatin1String("&gt;");
202 else if (plain[i] == QLatin1Char('&'))
203 rich += QLatin1String("&amp;");
204 else
205 rich += plain[i];
206 ++col;
207 }
208 }
209 if (col != 0)
210 rich += QLatin1String("</p>");
211 return rich;
212}
213
214/*!
215 \fn QTextCodec *Qt::codecForHtml(const QByteArray &ba)
216 \internal
217
218 This function is defined in the \c <QTextDocument> header file.
219*/
220#if QT_CONFIG(textcodec)
221QTextCodec *Qt::codecForHtml(const QByteArray &ba)
222{
223 return QTextCodec::codecForHtml(ba);
224}
225#endif
226
227/*!
228 \class QTextDocument
229 \reentrant
230 \inmodule QtGui
231
232 \brief The QTextDocument class holds formatted text.
233
234 \ingroup richtext-processing
235
236
237 QTextDocument is a container for structured rich text documents, providing
238 support for styled text and various types of document elements, such as
239 lists, tables, frames, and images.
240 They can be created for use in a QTextEdit, or used independently.
241
242 Each document element is described by an associated format object. Each
243 format object is treated as a unique object by QTextDocuments, and can be
244 passed to objectForFormat() to obtain the document element that it is
245 applied to.
246
247 A QTextDocument can be edited programmatically using a QTextCursor, and
248 its contents can be examined by traversing the document structure. The
249 entire document structure is stored as a hierarchy of document elements
250 beneath the root frame, found with the rootFrame() function. Alternatively,
251 if you just want to iterate over the textual contents of the document you
252 can use begin(), end(), and findBlock() to retrieve text blocks that you
253 can examine and iterate over.
254
255 The layout of a document is determined by the documentLayout();
256 you can create your own QAbstractTextDocumentLayout subclass and
257 set it using setDocumentLayout() if you want to use your own
258 layout logic. The document's title and other meta-information can be
259 obtained by calling the metaInformation() function. For documents that
260 are exposed to users through the QTextEdit class, the document title
261 is also available via the QTextEdit::documentTitle() function.
262
263 The toPlainText() and toHtml() convenience functions allow you to retrieve the
264 contents of the document as plain text and HTML.
265 The document's text can be searched using the find() functions.
266
267 Undo/redo of operations performed on the document can be controlled using
268 the setUndoRedoEnabled() function. The undo/redo system can be controlled
269 by an editor widget through the undo() and redo() slots; the document also
270 provides contentsChanged(), undoAvailable(), and redoAvailable() signals
271 that inform connected editor widgets about the state of the undo/redo
272 system. The following are the undo/redo operations of a QTextDocument:
273
274 \list
275 \li Insertion or removal of characters. A sequence of insertions or removals
276 within the same text block are regarded as a single undo/redo operation.
277 \li Insertion or removal of text blocks. Sequences of insertion or removals
278 in a single operation (e.g., by selecting and then deleting text) are
279 regarded as a single undo/redo operation.
280 \li Text character format changes.
281 \li Text block format changes.
282 \li Text block group format changes.
283 \endlist
284
285 \sa QTextCursor, QTextEdit, {Rich Text Processing}, {Text Object Example}
286*/
287
288/*!
289 \property QTextDocument::defaultFont
290 \brief the default font used to display the document's text
291*/
292
293/*!
294 \property QTextDocument::defaultTextOption
295 \brief the default text option will be set on all \l{QTextLayout}s in the document.
296
297 When \l{QTextBlock}s are created, the defaultTextOption is set on their
298 QTextLayout. This allows setting global properties for the document such as the
299 default word wrap mode.
300 */
301
302/*!
303 Constructs an empty QTextDocument with the given \a parent.
304*/
305QTextDocument::QTextDocument(QObject *parent)
306 : QObject(*new QTextDocumentPrivate, parent)
307{
308 Q_D(QTextDocument);
309 d->init();
310}
311
312/*!
313 Constructs a QTextDocument containing the plain (unformatted) \a text
314 specified, and with the given \a parent.
315*/
316QTextDocument::QTextDocument(const QString &text, QObject *parent)
317 : QObject(*new QTextDocumentPrivate, parent)
318{
319 Q_D(QTextDocument);
320 d->init();
321 QTextCursor(this).insertText(text);
322}
323
324/*!
325 \internal
326*/
327QTextDocument::QTextDocument(QTextDocumentPrivate &dd, QObject *parent)
328 : QObject(dd, parent)
329{
330 Q_D(QTextDocument);
331 d->init();
332}
333
334/*!
335 Destroys the document.
336*/
337QTextDocument::~QTextDocument()
338{
339}
340
341
342/*!
343 Creates a new QTextDocument that is a copy of this text document. \a
344 parent is the parent of the returned text document.
345*/
346QTextDocument *QTextDocument::clone(QObject *parent) const
347{
348 Q_D(const QTextDocument);
349 QTextDocument *doc = new QTextDocument(parent);
350 QTextCursor(doc).insertFragment(QTextDocumentFragment(this));
351 doc->rootFrame()->setFrameFormat(rootFrame()->frameFormat());
352 QTextDocumentPrivate *priv = doc->d_func();
353 priv->title = d->title;
354 priv->url = d->url;
355 priv->pageSize = d->pageSize;
356 priv->indentWidth = d->indentWidth;
357 priv->defaultTextOption = d->defaultTextOption;
358 priv->setDefaultFont(d->defaultFont());
359 priv->resources = d->resources;
360 priv->cachedResources.clear();
361#ifndef QT_NO_CSSPARSER
362 priv->defaultStyleSheet = d->defaultStyleSheet;
363 priv->parsedDefaultStyleSheet = d->parsedDefaultStyleSheet;
364#endif
365 return doc;
366}
367
368/*!
369 Returns \c true if the document is empty; otherwise returns \c false.
370*/
371bool QTextDocument::isEmpty() const
372{
373 Q_D(const QTextDocument);
374 /* because if we're empty we still have one single paragraph as
375 * one single fragment */
376 return d->length() <= 1;
377}
378
379/*!
380 Clears the document.
381*/
382void QTextDocument::clear()
383{
384 Q_D(QTextDocument);
385 d->clear();
386 d->resources.clear();
387}
388
389/*!
390 \since 4.2
391
392 Undoes the last editing operation on the document if undo is
393 available. The provided \a cursor is positioned at the end of the
394 location where the edition operation was undone.
395
396 See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework}
397 documentation for details.
398
399 \sa undoAvailable(), isUndoRedoEnabled()
400*/
401void QTextDocument::undo(QTextCursor *cursor)
402{
403 Q_D(QTextDocument);
404 const int pos = d->undoRedo(true);
405 if (cursor && pos >= 0) {
406 *cursor = QTextCursor(this);
407 cursor->setPosition(pos);
408 }
409}
410
411/*!
412 \since 4.2
413 Redoes the last editing operation on the document if \l{QTextDocument::isRedoAvailable()}{redo is available}.
414
415 The provided \a cursor is positioned at the end of the location where
416 the edition operation was redone.
417*/
418void QTextDocument::redo(QTextCursor *cursor)
419{
420 Q_D(QTextDocument);
421 const int pos = d->undoRedo(false);
422 if (cursor && pos >= 0) {
423 *cursor = QTextCursor(this);
424 cursor->setPosition(pos);
425 }
426}
427
428/*! \enum QTextDocument::Stacks
429
430 \value UndoStack The undo stack.
431 \value RedoStack The redo stack.
432 \value UndoAndRedoStacks Both the undo and redo stacks.
433*/
434
435/*!
436 \since 4.7
437 Clears the stacks specified by \a stacksToClear.
438
439 This method clears any commands on the undo stack, the redo stack,
440 or both (the default). If commands are cleared, the appropriate
441 signals are emitted, QTextDocument::undoAvailable() or
442 QTextDocument::redoAvailable().
443
444 \sa QTextDocument::undoAvailable(), QTextDocument::redoAvailable()
445*/
446void QTextDocument::clearUndoRedoStacks(Stacks stacksToClear)
447{
448 Q_D(QTextDocument);
449 d->clearUndoRedoStacks(stacksToClear, true);
450}
451
452/*!
453 \overload
454
455*/
456void QTextDocument::undo()
457{
458 Q_D(QTextDocument);
459 d->undoRedo(true);
460}
461
462/*!
463 \overload
464 Redoes the last editing operation on the document if \l{QTextDocument::isRedoAvailable()}{redo is available}.
465*/
466void QTextDocument::redo()
467{
468 Q_D(QTextDocument);
469 d->undoRedo(false);
470}
471
472/*!
473 \internal
474
475 Appends a custom undo \a item to the undo stack.
476*/
477void QTextDocument::appendUndoItem(QAbstractUndoItem *item)
478{
479 Q_D(QTextDocument);
480 d->appendUndoItem(item);
481}
482
483/*!
484 \property QTextDocument::undoRedoEnabled
485 \brief whether undo/redo are enabled for this document
486
487 This defaults to true. If disabled, the undo stack is cleared and
488 no items will be added to it.
489*/
490void QTextDocument::setUndoRedoEnabled(bool enable)
491{
492 Q_D(QTextDocument);
493 d->enableUndoRedo(enable);
494}
495
496bool QTextDocument::isUndoRedoEnabled() const
497{
498 Q_D(const QTextDocument);
499 return d->isUndoRedoEnabled();
500}
501
502/*!
503 \property QTextDocument::maximumBlockCount
504 \since 4.2
505 \brief Specifies the limit for blocks in the document.
506
507 Specifies the maximum number of blocks the document may have. If there are
508 more blocks in the document that specified with this property blocks are removed
509 from the beginning of the document.
510
511 A negative or zero value specifies that the document may contain an unlimited
512 amount of blocks.
513
514 The default value is 0.
515
516 Note that setting this property will apply the limit immediately to the document
517 contents.
518
519 Setting this property also disables the undo redo history.
520
521 This property is undefined in documents with tables or frames.
522*/
523int QTextDocument::maximumBlockCount() const
524{
525 Q_D(const QTextDocument);
526 return d->maximumBlockCount;
527}
528
529void QTextDocument::setMaximumBlockCount(int maximum)
530{
531 Q_D(QTextDocument);
532 d->maximumBlockCount = maximum;
533 d->ensureMaximumBlockCount();
534 setUndoRedoEnabled(false);
535}
536
537/*!
538 \since 4.3
539
540 The default text option is used on all QTextLayout objects in the document.
541 This allows setting global properties for the document such as the default
542 word wrap mode.
543*/
544QTextOption QTextDocument::defaultTextOption() const
545{
546 Q_D(const QTextDocument);
547 return d->defaultTextOption;
548}
549
550/*!
551 \since 4.3
552
553 Sets the default text option to \a option.
554*/
555void QTextDocument::setDefaultTextOption(const QTextOption &option)
556{
557 Q_D(QTextDocument);
558 d->defaultTextOption = option;
559 if (d->lout)
560 d->lout->documentChanged(0, 0, d->length());
561}
562
563/*!
564 \property QTextDocument::baseUrl
565 \since 5.3
566 \brief the base URL used to resolve relative resource URLs within the document.
567
568 Resource URLs are resolved to be within the same directory as the target of the base
569 URL meaning any portion of the path after the last '/' will be ignored.
570
571 \table
572 \header \li Base URL \li Relative URL \li Resolved URL
573 \row \li file:///path/to/content \li images/logo.png \li file:///path/to/images/logo.png
574 \row \li file:///path/to/content/ \li images/logo.png \li file:///path/to/content/images/logo.png
575 \row \li file:///path/to/content/index.html \li images/logo.png \li file:///path/to/content/images/logo.png
576 \row \li file:///path/to/content/images/ \li ../images/logo.png \li file:///path/to/content/images/logo.png
577 \endtable
578*/
579QUrl QTextDocument::baseUrl() const
580{
581 Q_D(const QTextDocument);
582 return d->baseUrl;
583}
584
585void QTextDocument::setBaseUrl(const QUrl &url)
586{
587 Q_D(QTextDocument);
588 if (d->baseUrl != url) {
589 d->baseUrl = url;
590 if (d->lout)
591 d->lout->documentChanged(0, 0, d->length());
592 emit baseUrlChanged(url);
593 }
594}
595
596/*!
597 \since 4.8
598
599 The default cursor movement style is used by all QTextCursor objects
600 created from the document. The default is Qt::LogicalMoveStyle.
601*/
602Qt::CursorMoveStyle QTextDocument::defaultCursorMoveStyle() const
603{
604 Q_D(const QTextDocument);
605 return d->defaultCursorMoveStyle;
606}
607
608/*!
609 \since 4.8
610
611 Sets the default cursor movement style to the given \a style.
612*/
613void QTextDocument::setDefaultCursorMoveStyle(Qt::CursorMoveStyle style)
614{
615 Q_D(QTextDocument);
616 d->defaultCursorMoveStyle = style;
617}
618
619/*!
620 \fn void QTextDocument::markContentsDirty(int position, int length)
621
622 Marks the contents specified by the given \a position and \a length
623 as "dirty", informing the document that it needs to be laid out
624 again.
625*/
626void QTextDocument::markContentsDirty(int from, int length)
627{
628 Q_D(QTextDocument);
629 d->documentChange(from, length);
630 if (!d->inContentsChange) {
631 if (d->lout) {
632 d->lout->documentChanged(d->docChangeFrom, d->docChangeOldLength, d->docChangeLength);
633 d->docChangeFrom = -1;
634 }
635 }
636}
637
638/*!
639 \property QTextDocument::useDesignMetrics
640 \since 4.1
641 \brief whether the document uses design metrics of fonts to improve the accuracy of text layout
642
643 If this property is set to true, the layout will use design metrics.
644 Otherwise, the metrics of the paint device as set on
645 QAbstractTextDocumentLayout::setPaintDevice() will be used.
646
647 Using design metrics makes a layout have a width that is no longer dependent on hinting
648 and pixel-rounding. This means that WYSIWYG text layout becomes possible because the width
649 scales much more linearly based on paintdevice metrics than it would otherwise.
650
651 By default, this property is \c false.
652*/
653
654void QTextDocument::setUseDesignMetrics(bool b)
655{
656 Q_D(QTextDocument);
657 if (b == d->defaultTextOption.useDesignMetrics())
658 return;
659 d->defaultTextOption.setUseDesignMetrics(b);
660 if (d->lout)
661 d->lout->documentChanged(0, 0, d->length());
662}
663
664bool QTextDocument::useDesignMetrics() const
665{
666 Q_D(const QTextDocument);
667 return d->defaultTextOption.useDesignMetrics();
668}
669
670/*!
671 \since 4.2
672
673 Draws the content of the document with painter \a p, clipped to \a rect.
674 If \a rect is a null rectangle (default) then the document is painted unclipped.
675*/
676void QTextDocument::drawContents(QPainter *p, const QRectF &rect)
677{
678 p->save();
679 QAbstractTextDocumentLayout::PaintContext ctx;
680 if (rect.isValid()) {
681 p->setClipRect(rect);
682 ctx.clip = rect;
683 }
684 documentLayout()->draw(p, ctx);
685 p->restore();
686}
687
688/*!
689 \property QTextDocument::textWidth
690 \since 4.2
691
692 The text width specifies the preferred width for text in the document. If
693 the text (or content in general) is wider than the specified with it is broken
694 into multiple lines and grows vertically. If the text cannot be broken into multiple
695 lines to fit into the specified text width it will be larger and the size() and the
696 idealWidth() property will reflect that.
697
698 If the text width is set to -1 then the text will not be broken into multiple lines
699 unless it is enforced through an explicit line break or a new paragraph.
700
701 The default value is -1.
702
703 Setting the text width will also set the page height to -1, causing the document to
704 grow or shrink vertically in a continuous way. If you want the document layout to break
705 the text into multiple pages then you have to set the pageSize property instead.
706
707 \sa size(), idealWidth(), pageSize()
708*/
709void QTextDocument::setTextWidth(qreal width)
710{
711 Q_D(QTextDocument);
712 QSizeF sz = d->pageSize;
713 sz.setWidth(width);
714 sz.setHeight(-1);
715 setPageSize(sz);
716}
717
718qreal QTextDocument::textWidth() const
719{
720 Q_D(const QTextDocument);
721 return d->pageSize.width();
722}
723
724/*!
725 \since 4.2
726
727 Returns the ideal width of the text document. The ideal width is the actually used width
728 of the document without optional alignments taken into account. It is always <= size().width().
729
730 \sa adjustSize(), textWidth
731*/
732qreal QTextDocument::idealWidth() const
733{
734 if (QTextDocumentLayout *lout = qobject_cast<QTextDocumentLayout *>(documentLayout()))
735 return lout->idealWidth();
736 return textWidth();
737}
738
739/*!
740 \property QTextDocument::documentMargin
741 \since 4.5
742
743 The margin around the document. The default is 4.
744*/
745qreal QTextDocument::documentMargin() const
746{
747 Q_D(const QTextDocument);
748 return d->documentMargin;
749}
750
751void QTextDocument::setDocumentMargin(qreal margin)
752{
753 Q_D(QTextDocument);
754 if (d->documentMargin != margin) {
755 d->documentMargin = margin;
756
757 QTextFrame* root = rootFrame();
758 QTextFrameFormat format = root->frameFormat();
759 format.setMargin(margin);
760 root->setFrameFormat(format);
761
762 if (d->lout)
763 d->lout->documentChanged(0, 0, d->length());
764 }
765}
766
767
768/*!
769 \property QTextDocument::indentWidth
770 \since 4.4
771
772 Returns the width used for text list and text block indenting.
773
774 The indent properties of QTextListFormat and QTextBlockFormat specify
775 multiples of this value. The default indent width is 40.
776*/
777qreal QTextDocument::indentWidth() const
778{
779 Q_D(const QTextDocument);
780 return d->indentWidth;
781}
782
783
784/*!
785 \since 4.4
786
787 Sets the \a width used for text list and text block indenting.
788
789 The indent properties of QTextListFormat and QTextBlockFormat specify
790 multiples of this value. The default indent width is 40 .
791
792 \sa indentWidth()
793*/
794void QTextDocument::setIndentWidth(qreal width)
795{
796 Q_D(QTextDocument);
797 if (d->indentWidth != width) {
798 d->indentWidth = width;
799 if (d->lout)
800 d->lout->documentChanged(0, 0, d->length());
801 }
802}
803
804
805
806
807/*!
808 \since 4.2
809
810 Adjusts the document to a reasonable size.
811
812 \sa idealWidth(), textWidth, size
813*/
814void QTextDocument::adjustSize()
815{
816 // Pull this private function in from qglobal.cpp
817 QFont f = defaultFont();
818 QFontMetrics fm(f);
819 int mw = fm.horizontalAdvance(QLatin1Char('x')) * 80;
820 int w = mw;
821 setTextWidth(w);
822 QSizeF size = documentLayout()->documentSize();
823 if (size.width() != 0) {
824 w = qt_int_sqrt((uint)(5 * size.height() * size.width() / 3));
825 setTextWidth(qMin(w, mw));
826
827 size = documentLayout()->documentSize();
828 if (w*3 < 5*size.height()) {
829 w = qt_int_sqrt((uint)(2 * size.height() * size.width()));
830 setTextWidth(qMin(w, mw));
831 }
832 }
833 setTextWidth(idealWidth());
834}
835
836/*!
837 \property QTextDocument::size
838 \since 4.2
839
840 Returns the actual size of the document.
841 This is equivalent to documentLayout()->documentSize();
842
843 The size of the document can be changed either by setting
844 a text width or setting an entire page size.
845
846 Note that the width is always >= pageSize().width().
847
848 By default, for a newly-created, empty document, this property contains
849 a configuration-dependent size.
850
851 \sa setTextWidth(), setPageSize(), idealWidth()
852*/
853QSizeF QTextDocument::size() const
854{
855 return documentLayout()->documentSize();
856}
857
858/*!
859 \property QTextDocument::blockCount
860 \since 4.2
861
862 Returns the number of text blocks in the document.
863
864 The value of this property is undefined in documents with tables or frames.
865
866 By default, if defined, this property contains a value of 1.
867 \sa lineCount(), characterCount()
868*/
869int QTextDocument::blockCount() const
870{
871 Q_D(const QTextDocument);
872 return d->blockMap().numNodes();
873}
874
875
876/*!
877 \since 4.5
878
879 Returns the number of lines of this document (if the layout supports
880 this). Otherwise, this is identical to the number of blocks.
881
882 \sa blockCount(), characterCount()
883 */
884int QTextDocument::lineCount() const
885{
886 Q_D(const QTextDocument);
887 return d->blockMap().length(2);
888}
889
890/*!
891 \since 4.5
892
893 Returns the number of characters of this document.
894
895 \sa blockCount(), characterAt()
896 */
897int QTextDocument::characterCount() const
898{
899 Q_D(const QTextDocument);
900 return d->length();
901}
902
903/*!
904 \since 4.5
905
906 Returns the character at position \a pos, or a null character if the
907 position is out of range.
908
909 \sa characterCount()
910 */
911QChar QTextDocument::characterAt(int pos) const
912{
913 Q_D(const QTextDocument);
914 if (pos < 0 || pos >= d->length())
915 return QChar();
916 QTextDocumentPrivate::FragmentIterator fragIt = d->find(pos);
917 const QTextFragmentData * const frag = fragIt.value();
918 const int offsetInFragment = qMax(0, pos - fragIt.position());
919 return d->text.at(frag->stringPosition + offsetInFragment);
920}
921
922
923/*!
924 \property QTextDocument::defaultStyleSheet
925 \since 4.2
926
927 The default style sheet is applied to all newly HTML formatted text that is
928 inserted into the document, for example using setHtml() or QTextCursor::insertHtml().
929
930 The style sheet needs to be compliant to CSS 2.1 syntax.
931
932 \b{Note:} Changing the default style sheet does not have any effect to the existing content
933 of the document.
934
935 \sa {Supported HTML Subset}
936*/
937
938#ifndef QT_NO_CSSPARSER
939void QTextDocument::setDefaultStyleSheet(const QString &sheet)
940{
941 Q_D(QTextDocument);
942 d->defaultStyleSheet = sheet;
943 QCss::Parser parser(sheet);
944 d->parsedDefaultStyleSheet = QCss::StyleSheet();
945 d->parsedDefaultStyleSheet.origin = QCss::StyleSheetOrigin_UserAgent;
946 parser.parse(&d->parsedDefaultStyleSheet);
947}
948
949QString QTextDocument::defaultStyleSheet() const
950{
951 Q_D(const QTextDocument);
952 return d->defaultStyleSheet;
953}
954#endif // QT_NO_CSSPARSER
955
956/*!
957 \fn void QTextDocument::contentsChanged()
958
959 This signal is emitted whenever the document's content changes; for
960 example, when text is inserted or deleted, or when formatting is applied.
961
962 \sa contentsChange()
963*/
964
965/*!
966 \fn void QTextDocument::contentsChange(int position, int charsRemoved, int charsAdded)
967
968 This signal is emitted whenever the document's content changes; for
969 example, when text is inserted or deleted, or when formatting is applied.
970
971 Information is provided about the \a position of the character in the
972 document where the change occurred, the number of characters removed
973 (\a charsRemoved), and the number of characters added (\a charsAdded).
974
975 The signal is emitted before the document's layout manager is notified
976 about the change. This hook allows you to implement syntax highlighting
977 for the document.
978
979 \sa QAbstractTextDocumentLayout::documentChanged(), contentsChanged()
980*/
981
982
983/*!
984 \fn void QTextDocument::undoAvailable(bool available);
985
986 This signal is emitted whenever undo operations become available
987 (\a available is true) or unavailable (\a available is false).
988
989 See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework}
990 documentation for details.
991
992 \sa undo(), isUndoRedoEnabled()
993*/
994
995/*!
996 \fn void QTextDocument::redoAvailable(bool available);
997
998 This signal is emitted whenever redo operations become available
999 (\a available is true) or unavailable (\a available is false).
1000*/
1001
1002/*!
1003 \fn void QTextDocument::cursorPositionChanged(const QTextCursor &cursor);
1004
1005 This signal is emitted whenever the position of a cursor changed
1006 due to an editing operation. The cursor that changed is passed in
1007 \a cursor. If the document is used with the QTextEdit class and you need a signal when the
1008 cursor is moved with the arrow keys you can use the \l{QTextEdit::}{cursorPositionChanged()}
1009 signal in QTextEdit.
1010*/
1011
1012/*!
1013 \fn void QTextDocument::blockCountChanged(int newBlockCount);
1014 \since 4.3
1015
1016 This signal is emitted when the total number of text blocks in the
1017 document changes. The value passed in \a newBlockCount is the new
1018 total.
1019*/
1020
1021/*!
1022 \fn void QTextDocument::documentLayoutChanged();
1023 \since 4.4
1024
1025 This signal is emitted when a new document layout is set.
1026
1027 \sa setDocumentLayout()
1028
1029*/
1030
1031
1032/*!
1033 Returns \c true if undo is available; otherwise returns \c false.
1034
1035 \sa isRedoAvailable(), availableUndoSteps()
1036*/
1037bool QTextDocument::isUndoAvailable() const
1038{
1039 Q_D(const QTextDocument);
1040 return d->isUndoAvailable();
1041}
1042
1043/*!
1044 Returns \c true if redo is available; otherwise returns \c false.
1045
1046 \sa isUndoAvailable(), availableRedoSteps()
1047*/
1048bool QTextDocument::isRedoAvailable() const
1049{
1050 Q_D(const QTextDocument);
1051 return d->isRedoAvailable();
1052}
1053
1054/*! \since 4.6
1055
1056 Returns the number of available undo steps.
1057
1058 \sa isUndoAvailable()
1059*/
1060int QTextDocument::availableUndoSteps() const
1061{
1062 Q_D(const QTextDocument);
1063 return d->availableUndoSteps();
1064}
1065
1066/*! \since 4.6
1067
1068 Returns the number of available redo steps.
1069
1070 \sa isRedoAvailable()
1071*/
1072int QTextDocument::availableRedoSteps() const
1073{
1074 Q_D(const QTextDocument);
1075 return d->availableRedoSteps();
1076}
1077
1078/*! \since 4.4
1079
1080 Returns the document's revision (if undo is enabled).
1081
1082 The revision is guaranteed to increase when a document that is not
1083 modified is edited.
1084
1085 \sa QTextBlock::revision(), isModified()
1086 */
1087int QTextDocument::revision() const
1088{
1089 Q_D(const QTextDocument);
1090 return d->revision;
1091}
1092
1093
1094
1095/*!
1096 Sets the document to use the given \a layout. The previous layout
1097 is deleted.
1098
1099 \sa documentLayoutChanged()
1100*/
1101void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *layout)
1102{
1103 Q_D(QTextDocument);
1104 d->setLayout(layout);
1105}
1106
1107/*!
1108 Returns the document layout for this document.
1109*/
1110QAbstractTextDocumentLayout *QTextDocument::documentLayout() const
1111{
1112 Q_D(const QTextDocument);
1113 if (!d->lout) {
1114 QTextDocument *that = const_cast<QTextDocument *>(this);
1115 that->d_func()->setLayout(new QTextDocumentLayout(that));
1116 }
1117 return d->lout;
1118}
1119
1120
1121/*!
1122 Returns meta information about the document of the type specified by
1123 \a info.
1124
1125 \sa setMetaInformation()
1126*/
1127QString QTextDocument::metaInformation(MetaInformation info) const
1128{
1129 Q_D(const QTextDocument);
1130 switch (info) {
1131 case DocumentTitle:
1132 return d->title;
1133 case DocumentUrl:
1134 return d->url;
1135 }
1136 return QString();
1137}
1138
1139/*!
1140 Sets the document's meta information of the type specified by \a info
1141 to the given \a string.
1142
1143 \sa metaInformation()
1144*/
1145void QTextDocument::setMetaInformation(MetaInformation info, const QString &string)
1146{
1147 Q_D(QTextDocument);
1148 switch (info) {
1149 case DocumentTitle:
1150 d->title = string;
1151 break;
1152 case DocumentUrl:
1153 d->url = string;
1154 break;
1155 }
1156}
1157
1158/*!
1159 Returns the raw text contained in the document without any
1160 formatting information. If you want formatting information
1161 use a QTextCursor instead.
1162
1163 \since 5.9
1164 \sa toPlainText()
1165*/
1166QString QTextDocument::toRawText() const
1167{
1168 Q_D(const QTextDocument);
1169 return d->plainText();
1170}
1171
1172/*!
1173 Returns the plain text contained in the document. If you want
1174 formatting information use a QTextCursor instead.
1175
1176 This function returns the same as toRawText(), but will replace
1177 some unicode characters with ASCII alternatives.
1178 In particular, no-break space (U+00A0) is replaced by a regular
1179 space (U+0020), and both paragraph (U+2029) and line (U+2028)
1180 separators are replaced by line feed (U+000A).
1181 If you need the precise contents of the document, use toRawText()
1182 instead.
1183
1184 \note Embedded objects, such as images, are represented by a
1185 Unicode value U+FFFC (OBJECT REPLACEMENT CHARACTER).
1186
1187 \sa toHtml()
1188*/
1189QString QTextDocument::toPlainText() const
1190{
1191 Q_D(const QTextDocument);
1192 QString txt = d->plainText();
1193
1194 QChar *uc = txt.data();
1195 QChar *e = uc + txt.size();
1196
1197 for (; uc != e; ++uc) {
1198 switch (uc->unicode()) {
1199 case 0xfdd0: // QTextBeginningOfFrame
1200 case 0xfdd1: // QTextEndOfFrame
1201 case QChar::ParagraphSeparator:
1202 case QChar::LineSeparator:
1203 *uc = QLatin1Char('\n');
1204 break;
1205 case QChar::Nbsp:
1206 *uc = QLatin1Char(' ');
1207 break;
1208 default:
1209 ;
1210 }
1211 }
1212 return txt;
1213}
1214
1215/*!
1216 Replaces the entire contents of the document with the given plain
1217 \a text. The undo/redo history is reset when this function is called.
1218
1219 \sa setHtml()
1220*/
1221void QTextDocument::setPlainText(const QString &text)
1222{
1223 Q_D(QTextDocument);
1224 bool previousState = d->isUndoRedoEnabled();
1225 d->enableUndoRedo(false);
1226 d->beginEditBlock();
1227 d->clear();
1228 QTextCursor(this).insertText(text);
1229 d->endEditBlock();
1230 d->enableUndoRedo(previousState);
1231}
1232
1233/*!
1234 Replaces the entire contents of the document with the given
1235 HTML-formatted text in the \a html string. The undo/redo history
1236 is reset when this function is called.
1237
1238 The HTML formatting is respected as much as possible; for example,
1239 "<b>bold</b> text" will produce text where the first word has a font
1240 weight that gives it a bold appearance: "\b{bold} text".
1241
1242 \note It is the responsibility of the caller to make sure that the
1243 text is correctly decoded when a QString containing HTML is created
1244 and passed to setHtml().
1245
1246 \sa setPlainText(), {Supported HTML Subset}
1247*/
1248
1249#ifndef QT_NO_TEXTHTMLPARSER
1250
1251void QTextDocument::setHtml(const QString &html)
1252{
1253 Q_D(QTextDocument);
1254 bool previousState = d->isUndoRedoEnabled();
1255 d->enableUndoRedo(false);
1256 d->beginEditBlock();
1257 d->clear();
1258 QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import();
1259 d->endEditBlock();
1260 d->enableUndoRedo(previousState);
1261}
1262
1263#endif // QT_NO_TEXTHTMLPARSER
1264
1265/*!
1266 \enum QTextDocument::FindFlag
1267
1268 This enum describes the options available to QTextDocument's find function. The options
1269 can be OR-ed together from the following list:
1270
1271 \value FindBackward Search backwards instead of forwards.
1272 \value FindCaseSensitively By default find works case insensitive. Specifying this option
1273 changes the behaviour to a case sensitive find operation.
1274 \value FindWholeWords Makes find match only complete words.
1275*/
1276
1277/*!
1278 \enum QTextDocument::MetaInformation
1279
1280 This enum describes the different types of meta information that can be
1281 added to a document.
1282
1283 \value DocumentTitle The title of the document.
1284 \value DocumentUrl The url of the document. The loadResource() function uses
1285 this url as the base when loading relative resources.
1286
1287 \sa metaInformation(), setMetaInformation()
1288*/
1289
1290static bool findInBlock(const QTextBlock &block, const QString &expression, int offset,
1291 QTextDocument::FindFlags options, QTextCursor *cursor)
1292{
1293 QString text = block.text();
1294 text.replace(QChar::Nbsp, QLatin1Char(' '));
1295 Qt::CaseSensitivity sensitivity = options & QTextDocument::FindCaseSensitively ? Qt::CaseSensitive : Qt::CaseInsensitive;
1296 int idx = -1;
1297
1298 while (offset >= 0 && offset <= text.length()) {
1299 idx = (options & QTextDocument::FindBackward) ?
1300 text.lastIndexOf(expression, offset, sensitivity) : text.indexOf(expression, offset, sensitivity);
1301 if (idx == -1)
1302 return false;
1303
1304 if (options & QTextDocument::FindWholeWords) {
1305 const int start = idx;
1306 const int end = start + expression.length();
1307 if ((start != 0 && text.at(start - 1).isLetterOrNumber())
1308 || (end != text.length() && text.at(end).isLetterOrNumber())) {
1309 //if this is not a whole word, continue the search in the string
1310 offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
1311 idx = -1;
1312 continue;
1313 }
1314 }
1315 //we have a hit, return the cursor for that.
1316 *cursor = QTextCursorPrivate::fromPosition(block.docHandle(), block.position() + idx);
1317 cursor->setPosition(cursor->position() + expression.length(), QTextCursor::KeepAnchor);
1318 return true;
1319 }
1320 return false;
1321}
1322
1323/*!
1324 \fn QTextCursor QTextDocument::find(const QString &subString, int position, FindFlags options) const
1325
1326 \overload
1327
1328 Finds the next occurrence of the string, \a subString, in the document.
1329 The search starts at the given \a position, and proceeds forwards
1330 through the document unless specified otherwise in the search options.
1331 The \a options control the type of search performed.
1332
1333 Returns a cursor with the match selected if \a subString
1334 was found; otherwise returns a null cursor.
1335
1336 If the \a position is 0 (the default) the search begins from the beginning
1337 of the document; otherwise it begins at the specified position.
1338*/
1339QTextCursor QTextDocument::find(const QString &subString, int from, FindFlags options) const
1340{
1341 Q_D(const QTextDocument);
1342
1343 if (subString.isEmpty())
1344 return QTextCursor();
1345
1346 int pos = from;
1347 //the cursor is positioned between characters, so for a backward search
1348 //do not include the character given in the position.
1349 if (options & FindBackward) {
1350 --pos ;
1351 if (pos < 0)
1352 return QTextCursor();
1353 }
1354
1355 QTextCursor cursor;
1356 QTextBlock block = d->blocksFind(pos);
1357 int blockOffset = pos - block.position();
1358
1359 if (!(options & FindBackward)) {
1360 while (block.isValid()) {
1361 if (findInBlock(block, subString, blockOffset, options, &cursor))
1362 return cursor;
1363 block = block.next();
1364 blockOffset = 0;
1365 }
1366 } else {
1367 if (blockOffset == block.length() - 1)
1368 --blockOffset; // make sure to skip end-of-paragraph character
1369 while (block.isValid()) {
1370 if (findInBlock(block, subString, blockOffset, options, &cursor))
1371 return cursor;
1372 block = block.previous();
1373 blockOffset = block.length() - 2;
1374 }
1375 }
1376
1377 return QTextCursor();
1378}
1379
1380/*!
1381 Finds the next occurrence of the string, \a subString, in the document.
1382 The search starts at the position of the given \a cursor, and proceeds
1383 forwards through the document unless specified otherwise in the search
1384 options. The \a options control the type of search performed.
1385
1386 Returns a cursor with the match selected if \a subString was found; otherwise
1387 returns a null cursor.
1388
1389 If the given \a cursor has a selection, the search begins after the
1390 selection; otherwise it begins at the cursor's position.
1391
1392 By default the search is case insensitive, and can match text anywhere in the
1393 document.
1394*/
1395QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &cursor, FindFlags options) const
1396{
1397 int pos = 0;
1398 if (!cursor.isNull()) {
1399 if (options & QTextDocument::FindBackward)
1400 pos = cursor.selectionStart();
1401 else
1402 pos = cursor.selectionEnd();
1403 }
1404
1405 return find(subString, pos, options);
1406}
1407
1408
1409#ifndef QT_NO_REGEXP
1410static bool findInBlock(const QTextBlock &block, const QRegExp &expression, int offset,
1411 QTextDocument::FindFlags options, QTextCursor *cursor)
1412{
1413 QRegExp expr(expression);
1414 QString text = block.text();
1415 text.replace(QChar::Nbsp, QLatin1Char(' '));
1416
1417 int idx = -1;
1418 while (offset >=0 && offset <= text.length()) {
1419 idx = (options & QTextDocument::FindBackward) ?
1420 expr.lastIndexIn(text, offset) : expr.indexIn(text, offset);
1421 if (idx == -1)
1422 return false;
1423
1424 if (options & QTextDocument::FindWholeWords) {
1425 const int start = idx;
1426 const int end = start + expr.matchedLength();
1427 if ((start != 0 && text.at(start - 1).isLetterOrNumber())
1428 || (end != text.length() && text.at(end).isLetterOrNumber())) {
1429 //if this is not a whole word, continue the search in the string
1430 offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
1431 idx = -1;
1432 continue;
1433 }
1434 }
1435 //we have a hit, return the cursor for that.
1436 *cursor = QTextCursorPrivate::fromPosition(block.docHandle(), block.position() + idx);
1437 cursor->setPosition(cursor->position() + expr.matchedLength(), QTextCursor::KeepAnchor);
1438 return true;
1439 }
1440 return false;
1441}
1442
1443/*!
1444 \overload
1445
1446 Finds the next occurrence that matches the given regular expression,
1447 \a expr, within the same paragraph in the document.
1448
1449 The search starts at the given \a from position, and proceeds forwards
1450 through the document unless specified otherwise in the search options.
1451 The \a options control the type of search performed. The FindCaseSensitively
1452 option is ignored for this overload, use QRegExp::caseSensitivity instead.
1453
1454 Returns a cursor with the match selected if a match was found; otherwise
1455 returns a null cursor.
1456
1457 If the \a from position is 0 (the default) the search begins from the beginning
1458 of the document; otherwise it begins at the specified position.
1459*/
1460QTextCursor QTextDocument::find(const QRegExp & expr, int from, FindFlags options) const
1461{
1462 Q_D(const QTextDocument);
1463
1464 if (expr.isEmpty())
1465 return QTextCursor();
1466
1467 int pos = from;
1468 //the cursor is positioned between characters, so for a backward search
1469 //do not include the character given in the position.
1470 if (options & FindBackward) {
1471 --pos ;
1472 if(pos < 0)
1473 return QTextCursor();
1474 }
1475
1476 QTextCursor cursor;
1477 QTextBlock block = d->blocksFind(pos);
1478 int blockOffset = pos - block.position();
1479 if (!(options & FindBackward)) {
1480 while (block.isValid()) {
1481 if (findInBlock(block, expr, blockOffset, options, &cursor))
1482 return cursor;
1483 block = block.next();
1484 blockOffset = 0;
1485 }
1486 } else {
1487 while (block.isValid()) {
1488 if (findInBlock(block, expr, blockOffset, options, &cursor))
1489 return cursor;
1490 block = block.previous();
1491 blockOffset = block.length() - 1;
1492 }
1493 }
1494
1495 return QTextCursor();
1496}
1497
1498/*!
1499 \overload
1500
1501 Finds the next occurrence that matches the given regular expression,
1502 \a expr, within the same paragraph in the document.
1503
1504 The search starts at the position of the given from \a cursor, and proceeds
1505 forwards through the document unless specified otherwise in the search
1506 options. The \a options control the type of search performed. The FindCaseSensitively
1507 option is ignored for this overload, use QRegExp::caseSensitivity instead.
1508
1509 Returns a cursor with the match selected if a match was found; otherwise
1510 returns a null cursor.
1511
1512 If the given \a cursor has a selection, the search begins after the
1513 selection; otherwise it begins at the cursor's position.
1514
1515 By default the search is case insensitive, and can match text anywhere in the
1516 document.
1517*/
1518QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &cursor, FindFlags options) const
1519{
1520 int pos = 0;
1521 if (!cursor.isNull()) {
1522 if (options & QTextDocument::FindBackward)
1523 pos = cursor.selectionStart();
1524 else
1525 pos = cursor.selectionEnd();
1526 }
1527 return find(expr, pos, options);
1528}
1529#endif // QT_REGEXP
1530
1531#if QT_CONFIG(regularexpression)
1532static bool findInBlock(const QTextBlock &block, const QRegularExpression &expression, int offset,
1533 QTextDocument::FindFlags options, QTextCursor *cursor)
1534{
1535 QRegularExpression expr(expression);
1536 if (!(options & QTextDocument::FindCaseSensitively))
1537 expr.setPatternOptions(expr.patternOptions() | QRegularExpression::CaseInsensitiveOption);
1538 else
1539 expr.setPatternOptions(expr.patternOptions() & ~QRegularExpression::CaseInsensitiveOption);
1540
1541 QString text = block.text();
1542 text.replace(QChar::Nbsp, QLatin1Char(' '));
1543 QRegularExpressionMatch match;
1544 int idx = -1;
1545
1546 while (offset >= 0 && offset <= text.length()) {
1547 idx = (options & QTextDocument::FindBackward) ?
1548 text.lastIndexOf(expr, offset, &match) : text.indexOf(expr, offset, &match);
1549 if (idx == -1)
1550 return false;
1551
1552 if (options & QTextDocument::FindWholeWords) {
1553 const int start = idx;
1554 const int end = start + match.capturedLength();
1555 if ((start != 0 && text.at(start - 1).isLetterOrNumber())
1556 || (end != text.length() && text.at(end).isLetterOrNumber())) {
1557 //if this is not a whole word, continue the search in the string
1558 offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
1559 idx = -1;
1560 continue;
1561 }
1562 }
1563 //we have a hit, return the cursor for that.
1564 *cursor = QTextCursorPrivate::fromPosition(block.docHandle(), block.position() + idx);
1565 cursor->setPosition(cursor->position() + match.capturedLength(), QTextCursor::KeepAnchor);
1566 return true;
1567 }
1568 return false;
1569}
1570
1571/*!
1572 \since 5.5
1573
1574 Finds the next occurrence that matches the given regular expression,
1575 \a expr, within the same paragraph in the document.
1576
1577 The search starts at the given \a from position, and proceeds forwards
1578 through the document unless specified otherwise in the search options.
1579 The \a options control the type of search performed.
1580
1581 Returns a cursor with the match selected if a match was found; otherwise
1582 returns a null cursor.
1583
1584 If the \a from position is 0 (the default) the search begins from the beginning
1585 of the document; otherwise it begins at the specified position.
1586*/
1587QTextCursor QTextDocument::find(const QRegularExpression &expr, int from, FindFlags options) const
1588{
1589 Q_D(const QTextDocument);
1590
1591 if (!expr.isValid())
1592 return QTextCursor();
1593
1594 int pos = from;
1595 //the cursor is positioned between characters, so for a backward search
1596 //do not include the character given in the position.
1597 if (options & FindBackward) {
1598 --pos ;
1599 if (pos < 0)
1600 return QTextCursor();
1601 }
1602
1603 QTextCursor cursor;
1604 QTextBlock block = d->blocksFind(pos);
1605 int blockOffset = pos - block.position();
1606
1607 if (!(options & FindBackward)) {
1608 while (block.isValid()) {
1609 if (findInBlock(block, expr, blockOffset, options, &cursor))
1610 return cursor;
1611 block = block.next();
1612 blockOffset = 0;
1613 }
1614 } else {
1615 while (block.isValid()) {
1616 if (findInBlock(block, expr, blockOffset, options, &cursor))
1617 return cursor;
1618 block = block.previous();
1619 blockOffset = block.length() - 1;
1620 }
1621 }
1622
1623 return QTextCursor();
1624}
1625
1626/*!
1627 \since 5.5
1628
1629 Finds the next occurrence that matches the given regular expression,
1630 \a expr, within the same paragraph in the document.
1631
1632 The search starts at the position of the given \a cursor, and proceeds
1633 forwards through the document unless specified otherwise in the search
1634 options. The \a options control the type of search performed.
1635
1636 Returns a cursor with the match selected if a match was found; otherwise
1637 returns a null cursor.
1638
1639 If the given \a cursor has a selection, the search begins after the
1640 selection; otherwise it begins at the cursor's position.
1641
1642 By default the search is case insensitive, and can match text anywhere in the
1643 document.
1644*/
1645QTextCursor QTextDocument::find(const QRegularExpression &expr, const QTextCursor &cursor, FindFlags options) const
1646{
1647 int pos = 0;
1648 if (!cursor.isNull()) {
1649 if (options & QTextDocument::FindBackward)
1650 pos = cursor.selectionStart();
1651 else
1652 pos = cursor.selectionEnd();
1653 }
1654 return find(expr, pos, options);
1655}
1656#endif // QT_CONFIG(regularexpression)
1657
1658/*!
1659 \fn QTextObject *QTextDocument::createObject(const QTextFormat &format)
1660
1661 Creates and returns a new document object (a QTextObject), based
1662 on the given \a format.
1663
1664 QTextObjects will always get created through this method, so you
1665 must reimplement it if you use custom text objects inside your document.
1666*/
1667QTextObject *QTextDocument::createObject(const QTextFormat &f)
1668{
1669 QTextObject *obj = 0;
1670 if (f.isListFormat())
1671 obj = new QTextList(this);
1672 else if (f.isTableFormat())
1673 obj = new QTextTable(this);
1674 else if (f.isFrameFormat())
1675 obj = new QTextFrame(this);
1676
1677 return obj;
1678}
1679
1680/*!
1681 \internal
1682
1683 Returns the frame that contains the text cursor position \a pos.
1684*/
1685QTextFrame *QTextDocument::frameAt(int pos) const
1686{
1687 Q_D(const QTextDocument);
1688 return d->frameAt(pos);
1689}
1690
1691/*!
1692 Returns the document's root frame.
1693*/
1694QTextFrame *QTextDocument::rootFrame() const
1695{
1696 Q_D(const QTextDocument);
1697 return d->rootFrame();
1698}
1699
1700/*!
1701 Returns the text object associated with the given \a objectIndex.
1702*/
1703QTextObject *QTextDocument::object(int objectIndex) const
1704{
1705 Q_D(const QTextDocument);
1706 return d->objectForIndex(objectIndex);
1707}
1708
1709/*!
1710 Returns the text object associated with the format \a f.
1711*/
1712QTextObject *QTextDocument::objectForFormat(const QTextFormat &f) const
1713{
1714 Q_D(const QTextDocument);
1715 return d->objectForFormat(f);
1716}
1717
1718
1719/*!
1720 Returns the text block that contains the \a{pos}-th character.
1721*/
1722QTextBlock QTextDocument::findBlock(int pos) const
1723{
1724 Q_D(const QTextDocument);
1725 return QTextBlock(docHandle(), d->blockMap().findNode(pos));
1726}
1727
1728/*!
1729 \since 4.4
1730 Returns the text block with the specified \a blockNumber.
1731
1732 \sa QTextBlock::blockNumber()
1733*/
1734QTextBlock QTextDocument::findBlockByNumber(int blockNumber) const
1735{
1736 Q_D(const QTextDocument);
1737 return QTextBlock(docHandle(), d->blockMap().findNode(blockNumber, 1));
1738}
1739
1740/*!
1741 \since 4.5
1742 Returns the text block that contains the specified \a lineNumber.
1743
1744 \sa QTextBlock::firstLineNumber()
1745*/
1746QTextBlock QTextDocument::findBlockByLineNumber(int lineNumber) const
1747{
1748 Q_D(const QTextDocument);
1749 return QTextBlock(docHandle(), d->blockMap().findNode(lineNumber, 2));
1750}
1751
1752/*!
1753 Returns the document's first text block.
1754
1755 \sa firstBlock()
1756*/
1757QTextBlock QTextDocument::begin() const
1758{
1759 Q_D(const QTextDocument);
1760 return QTextBlock(docHandle(), d->blockMap().begin().n);
1761}
1762
1763/*!
1764 This function returns a block to test for the end of the document
1765 while iterating over it.
1766
1767 \snippet textdocumentendsnippet.cpp 0
1768
1769 The block returned is invalid and represents the block after the
1770 last block in the document. You can use lastBlock() to retrieve the
1771 last valid block of the document.
1772
1773 \sa lastBlock()
1774*/
1775QTextBlock QTextDocument::end() const
1776{
1777 return QTextBlock(docHandle(), 0);
1778}
1779
1780/*!
1781 \since 4.4
1782 Returns the document's first text block.
1783*/
1784QTextBlock QTextDocument::firstBlock() const
1785{
1786 Q_D(const QTextDocument);
1787 return QTextBlock(docHandle(), d->blockMap().begin().n);
1788}
1789
1790/*!
1791 \since 4.4
1792 Returns the document's last (valid) text block.
1793*/
1794QTextBlock QTextDocument::lastBlock() const
1795{
1796 Q_D(const QTextDocument);
1797 return QTextBlock(docHandle(), d->blockMap().last().n);
1798}
1799
1800/*!
1801 \property QTextDocument::pageSize
1802 \brief the page size that should be used for laying out the document
1803
1804 The units are determined by the underlying paint device. The size is
1805 measured in logical pixels when painting to the screen, and in points
1806 (1/72 inch) when painting to a printer.
1807
1808 By default, for a newly-created, empty document, this property contains
1809 an undefined size.
1810
1811 \sa modificationChanged()
1812*/
1813
1814void QTextDocument::setPageSize(const QSizeF &size)
1815{
1816 Q_D(QTextDocument);
1817 d->pageSize = size;
1818 if (d->lout)
1819 d->lout->documentChanged(0, 0, d->length());
1820}
1821
1822QSizeF QTextDocument::pageSize() const
1823{
1824 Q_D(const QTextDocument);
1825 return d->pageSize;
1826}
1827
1828/*!
1829 returns the number of pages in this document.
1830*/
1831int QTextDocument::pageCount() const
1832{
1833 return documentLayout()->pageCount();
1834}
1835
1836/*!
1837 Sets the default \a font to use in the document layout.
1838*/
1839void QTextDocument::setDefaultFont(const QFont &font)
1840{
1841 Q_D(QTextDocument);
1842 d->setDefaultFont(font);
1843 if (d->lout)
1844 d->lout->documentChanged(0, 0, d->length());
1845}
1846
1847/*!
1848 Returns the default font to be used in the document layout.
1849*/
1850QFont QTextDocument::defaultFont() const
1851{
1852 Q_D(const QTextDocument);
1853 return d->defaultFont();
1854}
1855
1856/*!
1857 \fn void QTextDocument::modificationChanged(bool changed)
1858
1859 This signal is emitted whenever the content of the document
1860 changes in a way that affects the modification state. If \a
1861 changed is true, the document has been modified; otherwise it is
1862 false.
1863
1864 For example, calling setModified(false) on a document and then
1865 inserting text causes the signal to get emitted. If you undo that
1866 operation, causing the document to return to its original
1867 unmodified state, the signal will get emitted again.
1868*/
1869
1870/*!
1871 \property QTextDocument::modified
1872 \brief whether the document has been modified by the user
1873
1874 By default, this property is \c false.
1875
1876 \sa modificationChanged()
1877*/
1878
1879bool QTextDocument::isModified() const
1880{
1881 return docHandle()->isModified();
1882}
1883
1884void QTextDocument::setModified(bool m)
1885{
1886 docHandle()->setModified(m);
1887}
1888
1889#ifndef QT_NO_PRINTER
1890static void printPage(int index, QPainter *painter, const QTextDocument *doc, const QRectF &body, const QPointF &pageNumberPos)
1891{
1892 painter->save();
1893 painter->translate(body.left(), body.top() - (index - 1) * body.height());
1894 QRectF view(0, (index - 1) * body.height(), body.width(), body.height());
1895
1896 QAbstractTextDocumentLayout *layout = doc->documentLayout();
1897 QAbstractTextDocumentLayout::PaintContext ctx;
1898
1899 painter->setClipRect(view);
1900 ctx.clip = view;
1901
1902 // don't use the system palette text as default text color, on HP/UX
1903 // for example that's white, and white text on white paper doesn't
1904 // look that nice
1905 ctx.palette.setColor(QPalette::Text, Qt::black);
1906
1907 layout->draw(painter, ctx);
1908
1909 if (!pageNumberPos.isNull()) {
1910 painter->setClipping(false);
1911 painter->setFont(QFont(doc->defaultFont()));
1912 const QString pageString = QString::number(index);
1913
1914 painter->drawText(qRound(pageNumberPos.x() - painter->fontMetrics().horizontalAdvance(pageString)),
1915 qRound(pageNumberPos.y() + view.top()),
1916 pageString);
1917 }
1918
1919 painter->restore();
1920}
1921
1922/*!
1923 Prints the document to the given \a printer. The QPagedPaintDevice must be
1924 set up before being used with this function.
1925
1926 This is only a convenience method to print the whole document to the printer.
1927
1928 If the document is already paginated through a specified height in the pageSize()
1929 property it is printed as-is.
1930
1931 If the document is not paginated, like for example a document used in a QTextEdit,
1932 then a temporary copy of the document is created and the copy is broken into
1933 multiple pages according to the size of the paint device's paperRect(). By default
1934 a 2 cm margin is set around the document contents. In addition the current page
1935 number is printed at the bottom of each page.
1936
1937 \sa QTextEdit::print()
1938*/
1939
1940void QTextDocument::print(QPagedPaintDevice *printer) const
1941{
1942 Q_D(const QTextDocument);
1943
1944 if (!printer)
1945 return;
1946
1947 bool documentPaginated = d->pageSize.isValid() && !d->pageSize.isNull()
1948 && d->pageSize.height() != INT_MAX;
1949
1950 QPagedPaintDevicePrivate *pd = QPagedPaintDevicePrivate::get(printer);
1951
1952 // ### set page size to paginated size?
1953 QPagedPaintDevice::Margins m = printer->margins();
1954 if (!documentPaginated && m.left == 0. && m.right == 0. && m.top == 0. && m.bottom == 0.) {
1955 m.left = m.right = m.top = m.bottom = 2.;
1956 printer->setMargins(m);
1957 }
1958 // ### use the margins correctly
1959
1960 QPainter p(printer);
1961
1962 // Check that there is a valid device to print to.
1963 if (!p.isActive())
1964 return;
1965
1966 const QTextDocument *doc = this;
1967 QScopedPointer<QTextDocument> clonedDoc;
1968 (void)doc->documentLayout(); // make sure that there is a layout
1969
1970 QRectF body = QRectF(QPointF(0, 0), d->pageSize);
1971 QPointF pageNumberPos;
1972
1973 if (documentPaginated) {
1974 qreal sourceDpiX = qt_defaultDpi();
1975 qreal sourceDpiY = sourceDpiX;
1976
1977 QPaintDevice *dev = doc->documentLayout()->paintDevice();
1978 if (dev) {
1979 sourceDpiX = dev->logicalDpiX();
1980 sourceDpiY = dev->logicalDpiY();
1981 }
1982
1983 const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX;
1984 const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY;
1985
1986 // scale to dpi
1987 p.scale(dpiScaleX, dpiScaleY);
1988
1989 QSizeF scaledPageSize = d->pageSize;
1990 scaledPageSize.rwidth() *= dpiScaleX;
1991 scaledPageSize.rheight() *= dpiScaleY;
1992
1993 const QSizeF printerPageSize(printer->width(), printer->height());
1994
1995 // scale to page
1996 p.scale(printerPageSize.width() / scaledPageSize.width(),
1997 printerPageSize.height() / scaledPageSize.height());
1998 } else {
1999 doc = clone(const_cast<QTextDocument *>(this));
2000 clonedDoc.reset(const_cast<QTextDocument *>(doc));
2001
2002 for (QTextBlock srcBlock = firstBlock(), dstBlock = clonedDoc->firstBlock();
2003 srcBlock.isValid() && dstBlock.isValid();
2004 srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) {
2005 dstBlock.layout()->setFormats(srcBlock.layout()->formats());
2006 }
2007
2008 QAbstractTextDocumentLayout *layout = doc->documentLayout();
2009 layout->setPaintDevice(p.device());
2010
2011 // copy the custom object handlers
2012 layout->d_func()->handlers = documentLayout()->d_func()->handlers;
2013
2014 int dpiy = p.device()->logicalDpiY();
2015 int margin = (int) ((2/2.54)*dpiy); // 2 cm margins
2016 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
2017 fmt.setMargin(margin);
2018 doc->rootFrame()->setFrameFormat(fmt);
2019
2020 body = QRectF(0, 0, printer->width(), printer->height());
2021 pageNumberPos = QPointF(body.width() - margin,
2022 body.height() - margin
2023 + QFontMetrics(doc->defaultFont(), p.device()).ascent()
2024 + 5 * dpiy / 72.0);
2025 clonedDoc->setPageSize(body.size());
2026 }
2027
2028 int fromPage = pd->fromPage;
2029 int toPage = pd->toPage;
2030
2031 if (fromPage == 0 && toPage == 0) {
2032 fromPage = 1;
2033 toPage = doc->pageCount();
2034 }
2035 // paranoia check
2036 fromPage = qMax(1, fromPage);
2037 toPage = qMin(doc->pageCount(), toPage);
2038
2039 if (toPage < fromPage) {
2040 // if the user entered a page range outside the actual number
2041 // of printable pages, just return
2042 return;
2043 }
2044
2045// bool ascending = true;
2046// if (printer->pageOrder() == QPrinter::LastPageFirst) {
2047// int tmp = fromPage;
2048// fromPage = toPage;
2049// toPage = tmp;
2050// ascending = false;
2051// }
2052
2053 int page = fromPage;
2054 while (true) {
2055 printPage(page, &p, doc, body, pageNumberPos);
2056
2057 if (page == toPage)
2058 break;
2059 ++page;
2060 if (!printer->newPage())
2061 return;
2062 }
2063}
2064#endif
2065
2066/*!
2067 \enum QTextDocument::ResourceType
2068
2069 This enum describes the types of resources that can be loaded by
2070 QTextDocument's loadResource() function.
2071
2072 \value HtmlResource The resource contains HTML.
2073 \value ImageResource The resource contains image data.
2074 Currently supported data types are QVariant::Pixmap and
2075 QVariant::Image. If the corresponding variant is of type
2076 QVariant::ByteArray then Qt attempts to load the image using
2077 QImage::loadFromData. QVariant::Icon is currently not supported.
2078 The icon needs to be converted to one of the supported types first,
2079 for example using QIcon::pixmap.
2080 \value StyleSheetResource The resource contains CSS.
2081 \value MarkdownResource The resource contains Markdown.
2082 \value UserResource The first available value for user defined
2083 resource types.
2084
2085 \sa loadResource()
2086*/
2087
2088/*!
2089 Returns data of the specified \a type from the resource with the
2090 given \a name.
2091
2092 This function is called by the rich text engine to request data that isn't
2093 directly stored by QTextDocument, but still associated with it. For example,
2094 images are referenced indirectly by the name attribute of a QTextImageFormat
2095 object.
2096
2097 Resources are cached internally in the document. If a resource can
2098 not be found in the cache, loadResource is called to try to load
2099 the resource. loadResource should then use addResource to add the
2100 resource to the cache.
2101
2102 \sa QTextDocument::ResourceType
2103*/
2104QVariant QTextDocument::resource(int type, const QUrl &name) const
2105{
2106 Q_D(const QTextDocument);
2107 const QUrl url = d->baseUrl.resolved(name);
2108 QVariant r = d->resources.value(url);
2109 if (!r.isValid()) {
2110 r = d->cachedResources.value(url);
2111 if (!r.isValid())
2112 r = const_cast<QTextDocument *>(this)->loadResource(type, url);
2113 }
2114 return r;
2115}
2116
2117/*!
2118 Adds the resource \a resource to the resource cache, using \a
2119 type and \a name as identifiers. \a type should be a value from
2120 QTextDocument::ResourceType.
2121
2122 For example, you can add an image as a resource in order to reference it
2123 from within the document:
2124
2125 \snippet textdocument-resources/main.cpp Adding a resource
2126
2127 The image can be inserted into the document using the QTextCursor API:
2128
2129 \snippet textdocument-resources/main.cpp Inserting an image with a cursor
2130
2131 Alternatively, you can insert images using the HTML \c img tag:
2132
2133 \snippet textdocument-resources/main.cpp Inserting an image using HTML
2134*/
2135void QTextDocument::addResource(int type, const QUrl &name, const QVariant &resource)
2136{
2137 Q_UNUSED(type);
2138 Q_D(QTextDocument);
2139 d->resources.insert(name, resource);
2140}
2141
2142/*!
2143 Loads data of the specified \a type from the resource with the
2144 given \a name.
2145
2146 This function is called by the rich text engine to request data that isn't
2147 directly stored by QTextDocument, but still associated with it. For example,
2148 images are referenced indirectly by the name attribute of a QTextImageFormat
2149 object.
2150
2151 When called by Qt, \a type is one of the values of
2152 QTextDocument::ResourceType.
2153
2154 If the QTextDocument is a child object of a QObject that has an invokable
2155 loadResource method such as QTextEdit, QTextBrowser
2156 or a QTextDocument itself then the default implementation tries
2157 to retrieve the data from the parent.
2158*/
2159QVariant QTextDocument::loadResource(int type, const QUrl &name)
2160{
2161 Q_D(QTextDocument);
2162 QVariant r;
2163
2164 QObject *p = parent();
2165 if (p) {
2166 const QMetaObject *me = p->metaObject();
2167 int index = me->indexOfMethod("loadResource(int,QUrl)");
2168 if (index >= 0) {
2169 QMetaMethod loader = me->method(index);
2170 loader.invoke(p, Q_RETURN_ARG(QVariant, r), Q_ARG(int, type), Q_ARG(QUrl, name));
2171 }
2172 }
2173
2174 // handle data: URLs
2175 if (r.isNull() && name.scheme().compare(QLatin1String("data"), Qt::CaseInsensitive) == 0) {
2176 QString mimetype;
2177 QByteArray payload;
2178 if (qDecodeDataUrl(name, mimetype, payload))
2179 r = payload;
2180 }
2181
2182 // if resource was not loaded try to load it here
2183 if (!qobject_cast<QTextDocument *>(p) && r.isNull()) {
2184 QUrl resourceUrl = name;
2185
2186 if (name.isRelative()) {
2187 QUrl currentURL = d->url;
2188 // For the second case QUrl can merge "#someanchor" with "foo.html"
2189 // correctly to "foo.html#someanchor"
2190 if (!(currentURL.isRelative()
2191 || (currentURL.scheme() == QLatin1String("file")
2192 && !QFileInfo(currentURL.toLocalFile()).isAbsolute()))
2193 || (name.hasFragment() && name.path().isEmpty())) {
2194 resourceUrl = currentURL.resolved(name);
2195 } else {
2196 // this is our last resort when current url and new url are both relative
2197 // we try to resolve against the current working directory in the local
2198 // file system.
2199 QFileInfo fi(currentURL.toLocalFile());
2200 if (fi.exists()) {
2201 resourceUrl =
2202 QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(name);
2203 } else if (currentURL.isEmpty()) {
2204 resourceUrl.setScheme(QLatin1String("file"));
2205 }
2206 }
2207 }
2208
2209 QString s = resourceUrl.toLocalFile();
2210 QFile f(s);
2211 if (!s.isEmpty() && f.open(QFile::ReadOnly)) {
2212 r = f.readAll();
2213 f.close();
2214 }
2215 }
2216
2217 if (!r.isNull()) {
2218 if (type == ImageResource && r.type() == QVariant::ByteArray) {
2219 if (qApp->thread() != QThread::currentThread()) {
2220 // must use images in non-GUI threads
2221 QImage image;
2222 image.loadFromData(r.toByteArray());
2223 if (!image.isNull())
2224 r = image;
2225 } else {
2226 QPixmap pm;
2227 pm.loadFromData(r.toByteArray());
2228 if (!pm.isNull())
2229 r = pm;
2230 }
2231 }
2232 d->cachedResources.insert(name, r);
2233 }
2234 return r;
2235}
2236
2237static QTextFormat formatDifference(const QTextFormat &from, const QTextFormat &to)
2238{
2239 QTextFormat diff = to;
2240
2241 const QMap<int, QVariant> props = to.properties();
2242 for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end();
2243 it != end; ++it)
2244 if (it.value() == from.property(it.key()))
2245 diff.clearProperty(it.key());
2246
2247 return diff;
2248}
2249
2250static QString colorValue(QColor color)
2251{
2252 QString result;
2253
2254 if (color.alpha() == 255) {
2255 result = color.name();
2256 } else if (color.alpha()) {
2257 QString alphaValue = QString::number(color.alphaF(), 'f', 6).remove(QRegExp(QLatin1String("\\.?0*$")));
2258 result = QString::fromLatin1("rgba(%1,%2,%3,%4)").arg(color.red())
2259 .arg(color.green())
2260 .arg(color.blue())
2261 .arg(alphaValue);
2262 } else {
2263 result = QLatin1String("transparent");
2264 }
2265
2266 return result;
2267}
2268
2269QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc)
2270 : doc(_doc), fragmentMarkers(false)
2271{
2272 const QFont defaultFont = doc->defaultFont();
2273 defaultCharFormat.setFont(defaultFont);
2274 // don't export those for the default font since we cannot turn them off with CSS
2275 defaultCharFormat.clearProperty(QTextFormat::FontUnderline);
2276 defaultCharFormat.clearProperty(QTextFormat::FontOverline);
2277 defaultCharFormat.clearProperty(QTextFormat::FontStrikeOut);
2278 defaultCharFormat.clearProperty(QTextFormat::TextUnderlineStyle);
2279}
2280
2281/*!
2282 Returns the document in HTML format. The conversion may not be
2283 perfect, especially for complex documents, due to the limitations
2284 of HTML.
2285*/
2286QString QTextHtmlExporter::toHtml(const QByteArray &encoding, ExportMode mode)
2287{
2288 html = QLatin1String("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
2289 "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
2290 "<html><head><meta name=\"qrichtext\" content=\"1\" />");
2291 html.reserve(doc->docHandle()->length());
2292
2293 fragmentMarkers = (mode == ExportFragment);
2294
2295 if (!encoding.isEmpty())
2296 html += QString::fromLatin1("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(QString::fromLatin1(encoding));
2297
2298 QString title = doc->metaInformation(QTextDocument::DocumentTitle);
2299 if (!title.isEmpty())
2300 html += QString::fromLatin1("<title>") + title + QString::fromLatin1("</title>");
2301 html += QLatin1String("<style type=\"text/css\">\n");
2302 html += QLatin1String("p, li { white-space: pre-wrap; }\n");
2303 html += QLatin1String("</style>");
2304 html += QLatin1String("</head><body");
2305
2306 if (mode == ExportEntireDocument) {
2307 html += QLatin1String(" style=\"");
2308
2309 QStringList fontFamilies = defaultCharFormat.fontFamilies().toStringList();
2310 if (!fontFamilies.isEmpty())
2311 emitFontFamily(fontFamilies);
2312 else
2313 emitFontFamily(defaultCharFormat.fontFamily());
2314
2315 if (defaultCharFormat.hasProperty(QTextFormat::FontPointSize)) {
2316 html += QLatin1String(" font-size:");
2317 html += QString::number(defaultCharFormat.fontPointSize());
2318 html += QLatin1String("pt;");
2319 } else if (defaultCharFormat.hasProperty(QTextFormat::FontPixelSize)) {
2320 html += QLatin1String(" font-size:");
2321 html += QString::number(defaultCharFormat.intProperty(QTextFormat::FontPixelSize));
2322 html += QLatin1String("px;");
2323 }
2324
2325 html += QLatin1String(" font-weight:");
2326 html += QString::number(defaultCharFormat.fontWeight() * 8);
2327 html += QLatin1Char(';');
2328
2329 html += QLatin1String(" font-style:");
2330 html += (defaultCharFormat.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
2331 html += QLatin1Char(';');
2332
2333 // do not set text-decoration on the default font since those values are /always/ propagated
2334 // and cannot be turned off with CSS
2335
2336 html += QLatin1Char('\"');
2337
2338 const QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
2339 emitBackgroundAttribute(fmt);
2340
2341 } else {
2342 defaultCharFormat = QTextCharFormat();
2343 }
2344 html += QLatin1Char('>');
2345
2346 QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat();
2347 rootFmt.clearProperty(QTextFormat::BackgroundBrush);
2348
2349 QTextFrameFormat defaultFmt;
2350 defaultFmt.setMargin(doc->documentMargin());
2351
2352 if (rootFmt == defaultFmt)
2353 emitFrame(doc->rootFrame()->begin());
2354 else
2355 emitTextFrame(doc->rootFrame());
2356
2357 html += QLatin1String("</body></html>");
2358 return html;
2359}
2360
2361void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value)
2362{
2363 html += QLatin1Char(' ');
2364 html += QLatin1String(attribute);
2365 html += QLatin1String("=\"");
2366 html += value.toHtmlEscaped();
2367 html += QLatin1Char('"');
2368}
2369
2370bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
2371{
2372 bool attributesEmitted = false;
2373
2374 {
2375 const QStringList families = format.fontFamilies().toStringList();
2376 const QString family = format.fontFamily();
2377 if (!families.isEmpty() && families != defaultCharFormat.fontFamilies().toStringList()) {
2378 emitFontFamily(families);
2379 attributesEmitted = true;
2380 } else if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) {
2381 emitFontFamily(family);
2382 attributesEmitted = true;
2383 }
2384 }
2385
2386 if (format.hasProperty(QTextFormat::FontPointSize)
2387 && format.fontPointSize() != defaultCharFormat.fontPointSize()) {
2388 html += QLatin1String(" font-size:");
2389 html += QString::number(format.fontPointSize());
2390 html += QLatin1String("pt;");
2391 attributesEmitted = true;
2392 } else if (format.hasProperty(QTextFormat::FontSizeAdjustment)) {
2393 static const char sizeNameData[] =
2394 "small" "\0"
2395 "medium" "\0"
2396 "xx-large" ;
2397 static const quint8 sizeNameOffsets[] = {
2398 0, // "small"
2399 sizeof("small"), // "medium"
2400 sizeof("small") + sizeof("medium") + 3, // "large" )
2401 sizeof("small") + sizeof("medium") + 1, // "x-large" )> compressed into "xx-large"
2402 sizeof("small") + sizeof("medium"), // "xx-large" )
2403 };
2404 const char *name = 0;
2405 const int idx = format.intProperty(QTextFormat::FontSizeAdjustment) + 1;
2406 if (idx >= 0 && idx <= 4) {
2407 name = sizeNameData + sizeNameOffsets[idx];
2408 }
2409 if (name) {
2410 html += QLatin1String(" font-size:");
2411 html += QLatin1String(name);
2412 html += QLatin1Char(';');
2413 attributesEmitted = true;
2414 }
2415 } else if (format.hasProperty(QTextFormat::FontPixelSize)) {
2416 html += QLatin1String(" font-size:");
2417 html += QString::number(format.intProperty(QTextFormat::FontPixelSize));
2418 html += QLatin1String("px;");
2419 attributesEmitted = true;
2420 }
2421
2422 if (format.hasProperty(QTextFormat::FontWeight)
2423 && format.fontWeight() != defaultCharFormat.fontWeight()) {
2424 html += QLatin1String(" font-weight:");
2425 html += QString::number(format.fontWeight() * 8);
2426 html += QLatin1Char(';');
2427 attributesEmitted = true;
2428 }
2429
2430 if (format.hasProperty(QTextFormat::FontItalic)
2431 && format.fontItalic() != defaultCharFormat.fontItalic()) {
2432 html += QLatin1String(" font-style:");
2433 html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
2434 html += QLatin1Char(';');
2435 attributesEmitted = true;
2436 }
2437
2438 QLatin1String decorationTag(" text-decoration:");
2439 html += decorationTag;
2440 bool hasDecoration = false;
2441 bool atLeastOneDecorationSet = false;
2442
2443 if ((format.hasProperty(QTextFormat::FontUnderline) || format.hasProperty(QTextFormat::TextUnderlineStyle))
2444 && format.fontUnderline() != defaultCharFormat.fontUnderline()) {
2445 hasDecoration = true;
2446 if (format.fontUnderline()) {
2447 html += QLatin1String(" underline");
2448 atLeastOneDecorationSet = true;
2449 }
2450 }
2451
2452 if (format.hasProperty(QTextFormat::FontOverline)
2453 && format.fontOverline() != defaultCharFormat.fontOverline()) {
2454 hasDecoration = true;
2455 if (format.fontOverline()) {
2456 html += QLatin1String(" overline");
2457 atLeastOneDecorationSet = true;
2458 }
2459 }
2460
2461 if (format.hasProperty(QTextFormat::FontStrikeOut)
2462 && format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) {
2463 hasDecoration = true;
2464 if (format.fontStrikeOut()) {
2465 html += QLatin1String(" line-through");
2466 atLeastOneDecorationSet = true;
2467 }
2468 }
2469
2470 if (hasDecoration) {
2471 if (!atLeastOneDecorationSet)
2472 html += QLatin1String("none");
2473 html += QLatin1Char(';');
2474 attributesEmitted = true;
2475 } else {
2476 html.chop(decorationTag.size());
2477 }
2478
2479 if (format.foreground() != defaultCharFormat.foreground()
2480 && format.foreground().style() != Qt::NoBrush) {
2481 QBrush brush = format.foreground();
2482 if (brush.style() == Qt::TexturePattern) {
2483 const bool isPixmap = qHasPixmapTexture(brush);
2484 const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
2485
2486 html += QLatin1String(" -qt-fg-texture-cachekey:");
2487 html += QString::number(cacheKey);
2488 html += QLatin1String(";");
2489 } else {
2490 html += QLatin1String(" color:");
2491 html += colorValue(brush.color());
2492 html += QLatin1Char(';');
2493 }
2494 attributesEmitted = true;
2495 }
2496
2497 if (format.background() != defaultCharFormat.background()
2498 && format.background().style() == Qt::SolidPattern) {
2499 html += QLatin1String(" background-color:");
2500 html += colorValue(format.background().color());
2501 html += QLatin1Char(';');
2502 attributesEmitted = true;
2503 }
2504
2505 if (format.verticalAlignment() != defaultCharFormat.verticalAlignment()
2506 && format.verticalAlignment() != QTextCharFormat::AlignNormal)
2507 {
2508 html += QLatin1String(" vertical-align:");
2509
2510 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2511 if (valign == QTextCharFormat::AlignSubScript)
2512 html += QLatin1String("sub");
2513 else if (valign == QTextCharFormat::AlignSuperScript)
2514 html += QLatin1String("super");
2515 else if (valign == QTextCharFormat::AlignMiddle)
2516 html += QLatin1String("middle");
2517 else if (valign == QTextCharFormat::AlignTop)
2518 html += QLatin1String("top");
2519 else if (valign == QTextCharFormat::AlignBottom)
2520 html += QLatin1String("bottom");
2521
2522 html += QLatin1Char(';');
2523 attributesEmitted = true;
2524 }
2525
2526 if (format.fontCapitalization() != QFont::MixedCase) {
2527 const QFont::Capitalization caps = format.fontCapitalization();
2528 if (caps == QFont::AllUppercase)
2529 html += QLatin1String(" text-transform:uppercase;");
2530 else if (caps == QFont::AllLowercase)
2531 html += QLatin1String(" text-transform:lowercase;");
2532 else if (caps == QFont::SmallCaps)
2533 html += QLatin1String(" font-variant:small-caps;");
2534 attributesEmitted = true;
2535 }
2536
2537 if (format.fontWordSpacing() != 0.0) {
2538 html += QLatin1String(" word-spacing:");
2539 html += QString::number(format.fontWordSpacing());
2540 html += QLatin1String("px;");
2541 attributesEmitted = true;
2542 }
2543
2544 return attributesEmitted;
2545}
2546
2547void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength &length)
2548{
2549 if (length.type() == QTextLength::VariableLength) // default
2550 return;
2551
2552 html += QLatin1Char(' ');
2553 html += QLatin1String(attribute);
2554 html += QLatin1String("=\"");
2555 html += QString::number(length.rawValue());
2556
2557 if (length.type() == QTextLength::PercentageLength)
2558 html += QLatin1String("%\"");
2559 else
2560 html += QLatin1Char('\"');
2561}
2562
2563void QTextHtmlExporter::emitAlignment(Qt::Alignment align)
2564{
2565 if (align & Qt::AlignLeft)
2566 return;
2567 else if (align & Qt::AlignRight)
2568 html += QLatin1String(" align=\"right\"");
2569 else if (align & Qt::AlignHCenter)
2570 html += QLatin1String(" align=\"center\"");
2571 else if (align & Qt::AlignJustify)
2572 html += QLatin1String(" align=\"justify\"");
2573}
2574
2575void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode)
2576{
2577 if (pos == QTextFrameFormat::InFlow)
2578 return;
2579
2580 if (mode == EmitStyleTag)
2581 html += QLatin1String(" style=\"float:");
2582 else
2583 html += QLatin1String(" float:");
2584
2585 if (pos == QTextFrameFormat::FloatLeft)
2586 html += QLatin1String(" left;");
2587 else if (pos == QTextFrameFormat::FloatRight)
2588 html += QLatin1String(" right;");
2589 else
2590 Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type");
2591
2592 if (mode == EmitStyleTag)
2593 html += QLatin1Char('\"');
2594}
2595
2596void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style)
2597{
2598 Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset);
2599
2600 html += QLatin1String(" border-style:");
2601
2602 switch (style) {
2603 case QTextFrameFormat::BorderStyle_None:
2604 html += QLatin1String("none");
2605 break;
2606 case QTextFrameFormat::BorderStyle_Dotted:
2607 html += QLatin1String("dotted");
2608 break;
2609 case QTextFrameFormat::BorderStyle_Dashed:
2610 html += QLatin1String("dashed");
2611 break;
2612 case QTextFrameFormat::BorderStyle_Solid:
2613 html += QLatin1String("solid");
2614 break;
2615 case QTextFrameFormat::BorderStyle_Double:
2616 html += QLatin1String("double");
2617 break;
2618 case QTextFrameFormat::BorderStyle_DotDash:
2619 html += QLatin1String("dot-dash");
2620 break;
2621 case QTextFrameFormat::BorderStyle_DotDotDash:
2622 html += QLatin1String("dot-dot-dash");
2623 break;
2624 case QTextFrameFormat::BorderStyle_Groove:
2625 html += QLatin1String("groove");
2626 break;
2627 case QTextFrameFormat::BorderStyle_Ridge:
2628 html += QLatin1String("ridge");
2629 break;
2630 case QTextFrameFormat::BorderStyle_Inset:
2631 html += QLatin1String("inset");
2632 break;
2633 case QTextFrameFormat::BorderStyle_Outset:
2634 html += QLatin1String("outset");
2635 break;
2636 default:
2637 Q_ASSERT(false);
2638 break;
2639 };
2640
2641 html += QLatin1Char(';');
2642}
2643
2644void QTextHtmlExporter::emitPageBreakPolicy(QTextFormat::PageBreakFlags policy)
2645{
2646 if (policy & QTextFormat::PageBreak_AlwaysBefore)
2647 html += QLatin1String(" page-break-before:always;");
2648
2649 if (policy & QTextFormat::PageBreak_AlwaysAfter)
2650 html += QLatin1String(" page-break-after:always;");
2651}
2652
2653void QTextHtmlExporter::emitFontFamily(const QString &family)
2654{
2655 html += QLatin1String(" font-family:");
2656
2657 QLatin1String quote("\'");
2658 if (family.contains(QLatin1Char('\'')))
2659 quote = QLatin1String("&quot;");
2660
2661 html += quote;
2662 html += family.toHtmlEscaped();
2663 html += quote;
2664 html += QLatin1Char(';');
2665}
2666
2667void QTextHtmlExporter::emitFontFamily(const QStringList &families)
2668{
2669 html += QLatin1String(" font-family:");
2670
2671 bool first = true;
2672 for (const QString &family : families) {
2673 QLatin1String quote("\'");
2674 if (family.contains(QLatin1Char('\'')))
2675 quote = QLatin1String("&quot;");
2676
2677 if (!first)
2678 html += QLatin1String(",");
2679 else
2680 first = false;
2681 html += quote;
2682 html += family.toHtmlEscaped();
2683 html += quote;
2684 }
2685 html += QLatin1Char(';');
2686}
2687
2688void QTextHtmlExporter::emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right)
2689{
2690 html += QLatin1String(" margin-top:");
2691 html += top;
2692 html += QLatin1String("px;");
2693
2694 html += QLatin1String(" margin-bottom:");
2695 html += bottom;
2696 html += QLatin1String("px;");
2697
2698 html += QLatin1String(" margin-left:");
2699 html += left;
2700 html += QLatin1String("px;");
2701
2702 html += QLatin1String(" margin-right:");
2703 html += right;
2704 html += QLatin1String("px;");
2705}
2706
2707void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
2708{
2709 const QTextCharFormat format = fragment.charFormat();
2710
2711 bool closeAnchor = false;
2712
2713 if (format.isAnchor()) {
2714 const auto names = format.anchorNames();
2715 if (!names.isEmpty()) {
2716 html += QLatin1String("<a name=\"");
2717 html += names.constFirst().toHtmlEscaped();
2718 html += QLatin1String("\"></a>");
2719 }
2720 const QString href = format.anchorHref();
2721 if (!href.isEmpty()) {
2722 html += QLatin1String("<a href=\"");
2723 html += href.toHtmlEscaped();
2724 html += QLatin1String("\">");
2725 closeAnchor = true;
2726 }
2727 }
2728
2729 QString txt = fragment.text();
2730 const bool isObject = txt.contains(QChar::ObjectReplacementCharacter);
2731 const bool isImage = isObject && format.isImageFormat();
2732
2733 QLatin1String styleTag("<span style=\"");
2734 html += styleTag;
2735
2736 bool attributesEmitted = false;
2737 if (!isImage)
2738 attributesEmitted = emitCharFormatStyle(format);
2739 if (attributesEmitted)
2740 html += QLatin1String("\">");
2741 else
2742 html.chop(styleTag.size());
2743
2744 if (isObject) {
2745 for (int i = 0; isImage && i < txt.length(); ++i) {
2746 QTextImageFormat imgFmt = format.toImageFormat();
2747
2748 html += QLatin1String("<img");
2749
2750 if (imgFmt.hasProperty(QTextFormat::ImageName))
2751 emitAttribute("src", imgFmt.name());
2752
2753 if (imgFmt.hasProperty(QTextFormat::ImageAltText))
2754 emitAttribute("alt", imgFmt.stringProperty(QTextFormat::ImageAltText));
2755
2756 if (imgFmt.hasProperty(QTextFormat::ImageTitle))
2757 emitAttribute("title", imgFmt.stringProperty(QTextFormat::ImageTitle));
2758
2759 if (imgFmt.hasProperty(QTextFormat::ImageWidth))
2760 emitAttribute("width", QString::number(imgFmt.width()));
2761
2762 if (imgFmt.hasProperty(QTextFormat::ImageHeight))
2763 emitAttribute("height", QString::number(imgFmt.height()));
2764
2765 if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle)
2766 html += QLatin1String(" style=\"vertical-align: middle;\"");
2767 else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop)
2768 html += QLatin1String(" style=\"vertical-align: top;\"");
2769
2770 if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt)))
2771 emitFloatStyle(imageFrame->frameFormat().position());
2772
2773 html += QLatin1String(" />");
2774 }
2775 } else {
2776 Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter));
2777
2778 txt = txt.toHtmlEscaped();
2779
2780 // split for [\n{LineSeparator}]
2781 QString forcedLineBreakRegExp = QString::fromLatin1("[\\na]");
2782 forcedLineBreakRegExp[3] = QChar::LineSeparator;
2783 // space in BR on purpose for compatibility with old-fashioned browsers
2784 html += txt.replace(QRegExp(forcedLineBreakRegExp), QLatin1String("<br />"));
2785 }
2786
2787 if (attributesEmitted)
2788 html += QLatin1String("</span>");
2789
2790 if (closeAnchor)
2791 html += QLatin1String("</a>");
2792}
2793
2794static bool isOrderedList(int style)
2795{
2796 return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
2797 || style == QTextListFormat::ListUpperAlpha
2798 || style == QTextListFormat::ListUpperRoman
2799 || style == QTextListFormat::ListLowerRoman
2800 ;
2801}
2802
2803void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block)
2804{
2805 QTextBlockFormat format = block.blockFormat();
2806 emitAlignment(format.alignment());
2807
2808 // assume default to not bloat the html too much
2809 // html += QLatin1String(" dir='ltr'");
2810 if (block.textDirection() == Qt::RightToLeft)
2811 html += QLatin1String(" dir='rtl'");
2812
2813 QLatin1String style(" style=\"");
2814 html += style;
2815
2816 const bool emptyBlock = block.begin().atEnd();
2817 if (emptyBlock) {
2818 html += QLatin1String("-qt-paragraph-type:empty;");
2819 }
2820
2821 emitMargins(QString::number(format.topMargin()),
2822 QString::number(format.bottomMargin()),
2823 QString::number(format.leftMargin()),
2824 QString::number(format.rightMargin()));
2825
2826 html += QLatin1String(" -qt-block-indent:");
2827 html += QString::number(format.indent());
2828 html += QLatin1Char(';');
2829
2830 html += QLatin1String(" text-indent:");
2831 html += QString::number(format.textIndent());
2832 html += QLatin1String("px;");
2833
2834 if (block.userState() != -1) {
2835 html += QLatin1String(" -qt-user-state:");
2836 html += QString::number(block.userState());
2837 html += QLatin1Char(';');
2838 }
2839
2840 if (format.lineHeightType() != QTextBlockFormat::SingleHeight) {
2841 html += QLatin1String(" line-height:")
2842 + QString::number(format.lineHeight());
2843 switch (format.lineHeightType()) {
2844 case QTextBlockFormat::ProportionalHeight:
2845 html += QLatin1String("%;");
2846 break;
2847 case QTextBlockFormat::FixedHeight:
2848 html += QLatin1String("; -qt-line-height-type: fixed;");
2849 break;
2850 case QTextBlockFormat::MinimumHeight:
2851 html += QLatin1String("px;");
2852 break;
2853 case QTextBlockFormat::LineDistanceHeight:
2854 html += QLatin1String("; -qt-line-height-type: line-distance;");
2855 break;
2856 default:
2857 html += QLatin1String(";");
2858 break; // Should never reach here
2859 }
2860 }
2861
2862 emitPageBreakPolicy(format.pageBreakPolicy());
2863
2864 QTextCharFormat diff;
2865 if (emptyBlock) { // only print character properties when we don't expect them to be repeated by actual text in the parag
2866 const QTextCharFormat blockCharFmt = block.charFormat();
2867 diff = formatDifference(defaultCharFormat, blockCharFmt).toCharFormat();
2868 }
2869
2870 diff.clearProperty(QTextFormat::BackgroundBrush);
2871 if (format.hasProperty(QTextFormat::BackgroundBrush)) {
2872 QBrush bg = format.background();
2873 if (bg.style() != Qt::NoBrush)
2874 diff.setProperty(QTextFormat::BackgroundBrush, format.property(QTextFormat::BackgroundBrush));
2875 }
2876
2877 if (!diff.properties().isEmpty())
2878 emitCharFormatStyle(diff);
2879
2880 html += QLatin1Char('"');
2881
2882}
2883
2884void QTextHtmlExporter::emitBlock(const QTextBlock &block)
2885{
2886 if (block.begin().atEnd()) {
2887 // ### HACK, remove once QTextFrame::Iterator is fixed
2888 int p = block.position();
2889 if (p > 0)
2890 --p;
2891 QTextDocumentPrivate::FragmentIterator frag = doc->docHandle()->find(p);
2892 QChar ch = doc->docHandle()->buffer().at(frag->stringPosition);
2893 if (ch == QTextBeginningOfFrame
2894 || ch == QTextEndOfFrame)
2895 return;
2896 }
2897
2898 html += QLatin1Char('\n');
2899
2900 // save and later restore, in case we 'change' the default format by
2901 // emitting block char format information
2902 QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
2903
2904 QTextList *list = block.textList();
2905 if (list) {
2906 if (list->itemNumber(block) == 0) { // first item? emit <ul> or appropriate
2907 const QTextListFormat format = list->format();
2908 const int style = format.style();
2909 switch (style) {
2910 case QTextListFormat::ListDecimal: html += QLatin1String("<ol"); break;
2911 case QTextListFormat::ListDisc: html += QLatin1String("<ul"); break;
2912 case QTextListFormat::ListCircle: html += QLatin1String("<ul type=\"circle\""); break;
2913 case QTextListFormat::ListSquare: html += QLatin1String("<ul type=\"square\""); break;
2914 case QTextListFormat::ListLowerAlpha: html += QLatin1String("<ol type=\"a\""); break;
2915 case QTextListFormat::ListUpperAlpha: html += QLatin1String("<ol type=\"A\""); break;
2916 case QTextListFormat::ListLowerRoman: html += QLatin1String("<ol type=\"i\""); break;
2917 case QTextListFormat::ListUpperRoman: html += QLatin1String("<ol type=\"I\""); break;
2918 default: html += QLatin1String("<ul"); // ### should not happen
2919 }
2920
2921 QString styleString = QString::fromLatin1("margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px;");
2922
2923 if (format.hasProperty(QTextFormat::ListIndent)) {
2924 styleString += QLatin1String(" -qt-list-indent: ");
2925 styleString += QString::number(format.indent());
2926 styleString += QLatin1Char(';');
2927 }
2928
2929 if (format.hasProperty(QTextFormat::ListNumberPrefix)) {
2930 QString numberPrefix = format.numberPrefix();
2931 numberPrefix.replace(QLatin1Char('"'), QLatin1String("\\22"));
2932 numberPrefix.replace(QLatin1Char('\''), QLatin1String("\\27")); // FIXME: There's a problem in the CSS parser the prevents this from being correctly restored
2933 styleString += QLatin1String(" -qt-list-number-prefix: ");
2934 styleString += QLatin1Char('\'');
2935 styleString += numberPrefix;
2936 styleString += QLatin1Char('\'');
2937 styleString += QLatin1Char(';');
2938 }
2939
2940 if (format.hasProperty(QTextFormat::ListNumberSuffix)) {
2941 if (format.numberSuffix() != QLatin1String(".")) { // this is our default
2942 QString numberSuffix = format.numberSuffix();
2943 numberSuffix.replace(QLatin1Char('"'), QLatin1String("\\22"));
2944 numberSuffix.replace(QLatin1Char('\''), QLatin1String("\\27")); // see above
2945 styleString += QLatin1String(" -qt-list-number-suffix: ");
2946 styleString += QLatin1Char('\'');
2947 styleString += numberSuffix;
2948 styleString += QLatin1Char('\'');
2949 styleString += QLatin1Char(';');
2950 }
2951 }
2952
2953 html += QLatin1String(" style=\"");
2954 html += styleString;
2955 html += QLatin1String("\">");
2956 }
2957
2958 html += QLatin1String("<li");
2959
2960 const QTextCharFormat blockFmt = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat();
2961 if (!blockFmt.properties().isEmpty()) {
2962 html += QLatin1String(" style=\"");
2963 emitCharFormatStyle(blockFmt);
2964 html += QLatin1Char('\"');
2965
2966 defaultCharFormat.merge(block.charFormat());
2967 }
2968 }
2969
2970 const QTextBlockFormat blockFormat = block.blockFormat();
2971 if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
2972 html += QLatin1String("<hr");
2973
2974 QTextLength width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth);
2975 if (width.type() != QTextLength::VariableLength)
2976 emitTextLength("width", width);
2977 else
2978 html += QLatin1Char(' ');
2979
2980 html += QLatin1String("/>");
2981 return;
2982 }
2983
2984 const bool pre = blockFormat.nonBreakableLines();
2985 if (pre) {
2986 if (list)
2987 html += QLatin1Char('>');
2988 html += QLatin1String("<pre");
2989 } else if (!list) {
2990 int headingLevel = blockFormat.headingLevel();
2991 if (headingLevel > 0 && headingLevel <= 6)
2992 html += QLatin1String("<h") + QString::number(headingLevel);
2993 else
2994 html += QLatin1String("<p");
2995 }
2996
2997 emitBlockAttributes(block);
2998
2999 html += QLatin1Char('>');
3000 if (block.begin().atEnd())
3001 html += QLatin1String("<br />");
3002
3003 QTextBlock::Iterator it = block.begin();
3004 if (fragmentMarkers && !it.atEnd() && block == doc->begin())
3005 html += QLatin1String("<!--StartFragment-->");
3006
3007 for (; !it.atEnd(); ++it)
3008 emitFragment(it.fragment());
3009
3010 if (fragmentMarkers && block.position() + block.length() == doc->docHandle()->length())
3011 html += QLatin1String("<!--EndFragment-->");
3012
3013 if (pre)
3014 html += QLatin1String("</pre>");
3015 else if (list)
3016 html += QLatin1String("</li>");
3017 else {
3018 int headingLevel = blockFormat.headingLevel();
3019 if (headingLevel > 0 && headingLevel <= 6)
3020 html += QLatin1String("</h") + QString::number(headingLevel) + QLatin1Char('>');
3021 else
3022 html += QLatin1String("</p>");
3023 }
3024
3025 if (list) {
3026 if (list->itemNumber(block) == list->count() - 1) { // last item? close list
3027 if (isOrderedList(list->format().style()))
3028 html += QLatin1String("</ol>");
3029 else
3030 html += QLatin1String("</ul>");
3031 }
3032 }
3033
3034 defaultCharFormat = oldDefaultCharFormat;
3035}
3036
3037extern bool qHasPixmapTexture(const QBrush& brush);
3038
3039QString QTextHtmlExporter::findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap)
3040{
3041 QString url;
3042 if (!doc)
3043 return url;
3044
3045 if (QTextDocument *parent = qobject_cast<QTextDocument *>(doc->parent()))
3046 return findUrlForImage(parent, cacheKey, isPixmap);
3047
3048 if (doc && doc->docHandle()) {
3049 QTextDocumentPrivate *priv = doc->docHandle();
3050 QMap<QUrl, QVariant>::const_iterator it = priv->cachedResources.constBegin();
3051 for (; it != priv->cachedResources.constEnd(); ++it) {
3052
3053 const QVariant &v = it.value();
3054 if (v.type() == QVariant::Image && !isPixmap) {
3055 if (qvariant_cast<QImage>(v).cacheKey() == cacheKey)
3056 break;
3057 }
3058
3059 if (v.type() == QVariant::Pixmap && isPixmap) {
3060 if (qvariant_cast<QPixmap>(v).cacheKey() == cacheKey)
3061 break;
3062 }
3063 }
3064
3065 if (it != priv->cachedResources.constEnd())
3066 url = it.key().toString();
3067 }
3068
3069 return url;
3070}
3071
3072void QTextDocumentPrivate::mergeCachedResources(const QTextDocumentPrivate *priv)
3073{
3074 if (!priv)
3075 return;
3076
3077 cachedResources.unite(priv->cachedResources);
3078}
3079
3080void QTextHtmlExporter::emitBackgroundAttribute(const QTextFormat &format)
3081{
3082 if (format.hasProperty(QTextFormat::BackgroundImageUrl)) {
3083 QString url = format.property(QTextFormat::BackgroundImageUrl).toString();
3084 emitAttribute("background", url);
3085 } else {
3086 const QBrush &brush = format.background();
3087 if (brush.style() == Qt::SolidPattern) {
3088 emitAttribute("bgcolor", colorValue(brush.color()));
3089 } else if (brush.