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 
82 QT_BEGIN_NAMESPACE
83 
84 Q_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 */
mightBeRichText(const QString & text)98 bool 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 */
convertFromPlainText(const QString & plain,Qt::WhiteSpaceMode mode)166 QString 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)
codecForHtml(const QByteArray & ba)221 QTextCodec *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 */
QTextDocument(QObject * parent)305 QTextDocument::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 */
QTextDocument(const QString & text,QObject * parent)316 QTextDocument::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 */
QTextDocument(QTextDocumentPrivate & dd,QObject * parent)327 QTextDocument::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 */
~QTextDocument()337 QTextDocument::~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 */
clone(QObject * parent) const346 QTextDocument *QTextDocument::clone(QObject *parent) const
347 {
348     Q_D(const QTextDocument);
349     QTextDocument *doc = new QTextDocument(parent);
350     if (isEmpty()) {
351         const QTextCursor thisCursor(const_cast<QTextDocument *>(this));
352 
353         const auto blockFormat = thisCursor.blockFormat();
354         if (blockFormat.isValid() && !blockFormat.isEmpty())
355             QTextCursor(doc).setBlockFormat(blockFormat);
356 
357         const auto blockCharFormat = thisCursor.blockCharFormat();
358         if (blockCharFormat.isValid() && !blockCharFormat.isEmpty())
359             QTextCursor(doc).setBlockCharFormat(blockCharFormat);
360     } else {
361         QTextCursor(doc).insertFragment(QTextDocumentFragment(this));
362     }
363     doc->rootFrame()->setFrameFormat(rootFrame()->frameFormat());
364     QTextDocumentPrivate *priv = doc->d_func();
365     priv->title = d->title;
366     priv->url = d->url;
367     priv->pageSize = d->pageSize;
368     priv->indentWidth = d->indentWidth;
369     priv->defaultTextOption = d->defaultTextOption;
370     priv->setDefaultFont(d->defaultFont());
371     priv->resources = d->resources;
372     priv->cachedResources.clear();
373 #ifndef QT_NO_CSSPARSER
374     priv->defaultStyleSheet = d->defaultStyleSheet;
375     priv->parsedDefaultStyleSheet = d->parsedDefaultStyleSheet;
376 #endif
377     return doc;
378 }
379 
380 /*!
381     Returns \c true if the document is empty; otherwise returns \c false.
382 */
isEmpty() const383 bool QTextDocument::isEmpty() const
384 {
385     Q_D(const QTextDocument);
386     /* because if we're empty we still have one single paragraph as
387      * one single fragment */
388     return d->length() <= 1;
389 }
390 
391 /*!
392   Clears the document.
393 */
clear()394 void QTextDocument::clear()
395 {
396     Q_D(QTextDocument);
397     d->clear();
398     d->resources.clear();
399 }
400 
401 /*!
402     \since 4.2
403 
404     Undoes the last editing operation on the document if undo is
405     available. The provided \a cursor is positioned at the end of the
406     location where the edition operation was undone.
407 
408     See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework}
409     documentation for details.
410 
411     \sa undoAvailable(), isUndoRedoEnabled()
412 */
undo(QTextCursor * cursor)413 void QTextDocument::undo(QTextCursor *cursor)
414 {
415     Q_D(QTextDocument);
416     const int pos = d->undoRedo(true);
417     if (cursor && pos >= 0) {
418         *cursor = QTextCursor(this);
419         cursor->setPosition(pos);
420     }
421 }
422 
423 /*!
424     \since 4.2
425     Redoes the last editing operation on the document if \l{QTextDocument::isRedoAvailable()}{redo is available}.
426 
427     The provided \a cursor is positioned at the end of the location where
428     the edition operation was redone.
429 */
redo(QTextCursor * cursor)430 void QTextDocument::redo(QTextCursor *cursor)
431 {
432     Q_D(QTextDocument);
433     const int pos = d->undoRedo(false);
434     if (cursor && pos >= 0) {
435         *cursor = QTextCursor(this);
436         cursor->setPosition(pos);
437     }
438 }
439 
440 /*! \enum QTextDocument::Stacks
441 
442   \value UndoStack              The undo stack.
443   \value RedoStack              The redo stack.
444   \value UndoAndRedoStacks      Both the undo and redo stacks.
445 */
446 
447 /*!
448     \since 4.7
449     Clears the stacks specified by \a stacksToClear.
450 
451     This method clears any commands on the undo stack, the redo stack,
452     or both (the default). If commands are cleared, the appropriate
453     signals are emitted, QTextDocument::undoAvailable() or
454     QTextDocument::redoAvailable().
455 
456     \sa QTextDocument::undoAvailable(), QTextDocument::redoAvailable()
457 */
clearUndoRedoStacks(Stacks stacksToClear)458 void QTextDocument::clearUndoRedoStacks(Stacks stacksToClear)
459 {
460     Q_D(QTextDocument);
461     d->clearUndoRedoStacks(stacksToClear, true);
462 }
463 
464 /*!
465     \overload
466 
467 */
undo()468 void QTextDocument::undo()
469 {
470     Q_D(QTextDocument);
471     d->undoRedo(true);
472 }
473 
474 /*!
475     \overload
476     Redoes the last editing operation on the document if \l{QTextDocument::isRedoAvailable()}{redo is available}.
477 */
redo()478 void QTextDocument::redo()
479 {
480     Q_D(QTextDocument);
481     d->undoRedo(false);
482 }
483 
484 /*!
485     \internal
486 
487     Appends a custom undo \a item to the undo stack.
488 */
appendUndoItem(QAbstractUndoItem * item)489 void QTextDocument::appendUndoItem(QAbstractUndoItem *item)
490 {
491     Q_D(QTextDocument);
492     d->appendUndoItem(item);
493 }
494 
495 /*!
496     \property QTextDocument::undoRedoEnabled
497     \brief whether undo/redo are enabled for this document
498 
499     This defaults to true. If disabled, the undo stack is cleared and
500     no items will be added to it.
501 */
setUndoRedoEnabled(bool enable)502 void QTextDocument::setUndoRedoEnabled(bool enable)
503 {
504     Q_D(QTextDocument);
505     d->enableUndoRedo(enable);
506 }
507 
isUndoRedoEnabled() const508 bool QTextDocument::isUndoRedoEnabled() const
509 {
510     Q_D(const QTextDocument);
511     return d->isUndoRedoEnabled();
512 }
513 
514 /*!
515     \property QTextDocument::maximumBlockCount
516     \since 4.2
517     \brief Specifies the limit for blocks in the document.
518 
519     Specifies the maximum number of blocks the document may have. If there are
520     more blocks in the document that specified with this property blocks are removed
521     from the beginning of the document.
522 
523     A negative or zero value specifies that the document may contain an unlimited
524     amount of blocks.
525 
526     The default value is 0.
527 
528     Note that setting this property will apply the limit immediately to the document
529     contents.
530 
531     Setting this property also disables the undo redo history.
532 
533     This property is undefined in documents with tables or frames.
534 */
maximumBlockCount() const535 int QTextDocument::maximumBlockCount() const
536 {
537     Q_D(const QTextDocument);
538     return d->maximumBlockCount;
539 }
540 
setMaximumBlockCount(int maximum)541 void QTextDocument::setMaximumBlockCount(int maximum)
542 {
543     Q_D(QTextDocument);
544     d->maximumBlockCount = maximum;
545     d->ensureMaximumBlockCount();
546     setUndoRedoEnabled(false);
547 }
548 
549 /*!
550     \since 4.3
551 
552     The default text option is used on all QTextLayout objects in the document.
553     This allows setting global properties for the document such as the default
554     word wrap mode.
555 */
defaultTextOption() const556 QTextOption QTextDocument::defaultTextOption() const
557 {
558     Q_D(const QTextDocument);
559     return d->defaultTextOption;
560 }
561 
562 /*!
563     \since 4.3
564 
565     Sets the default text option to \a option.
566 */
setDefaultTextOption(const QTextOption & option)567 void QTextDocument::setDefaultTextOption(const QTextOption &option)
568 {
569     Q_D(QTextDocument);
570     d->defaultTextOption = option;
571     if (d->lout)
572         d->lout->documentChanged(0, 0, d->length());
573 }
574 
575 /*!
576     \property QTextDocument::baseUrl
577     \since 5.3
578     \brief the base URL used to resolve relative resource URLs within the document.
579 
580     Resource URLs are resolved to be within the same directory as the target of the base
581     URL meaning any portion of the path after the last '/' will be ignored.
582 
583     \table
584     \header \li Base URL \li Relative URL \li Resolved URL
585     \row \li file:///path/to/content \li images/logo.png \li file:///path/to/images/logo.png
586     \row \li file:///path/to/content/ \li images/logo.png \li file:///path/to/content/images/logo.png
587     \row \li file:///path/to/content/index.html \li images/logo.png \li file:///path/to/content/images/logo.png
588     \row \li file:///path/to/content/images/ \li ../images/logo.png \li file:///path/to/content/images/logo.png
589     \endtable
590 */
baseUrl() const591 QUrl QTextDocument::baseUrl() const
592 {
593     Q_D(const QTextDocument);
594     return d->baseUrl;
595 }
596 
setBaseUrl(const QUrl & url)597 void QTextDocument::setBaseUrl(const QUrl &url)
598 {
599     Q_D(QTextDocument);
600     if (d->baseUrl != url) {
601         d->baseUrl = url;
602         if (d->lout)
603             d->lout->documentChanged(0, 0, d->length());
604         emit baseUrlChanged(url);
605     }
606 }
607 
608 /*!
609     \since 4.8
610 
611     The default cursor movement style is used by all QTextCursor objects
612     created from the document. The default is Qt::LogicalMoveStyle.
613 */
defaultCursorMoveStyle() const614 Qt::CursorMoveStyle QTextDocument::defaultCursorMoveStyle() const
615 {
616     Q_D(const QTextDocument);
617     return d->defaultCursorMoveStyle;
618 }
619 
620 /*!
621     \since 4.8
622 
623     Sets the default cursor movement style to the given \a style.
624 */
setDefaultCursorMoveStyle(Qt::CursorMoveStyle style)625 void QTextDocument::setDefaultCursorMoveStyle(Qt::CursorMoveStyle style)
626 {
627     Q_D(QTextDocument);
628     d->defaultCursorMoveStyle = style;
629 }
630 
631 /*!
632     \fn void QTextDocument::markContentsDirty(int position, int length)
633 
634     Marks the contents specified by the given \a position and \a length
635     as "dirty", informing the document that it needs to be laid out
636     again.
637 */
markContentsDirty(int from,int length)638 void QTextDocument::markContentsDirty(int from, int length)
639 {
640     Q_D(QTextDocument);
641     d->documentChange(from, length);
642     if (!d->inContentsChange) {
643         if (d->lout) {
644             d->lout->documentChanged(d->docChangeFrom, d->docChangeOldLength, d->docChangeLength);
645             d->docChangeFrom = -1;
646         }
647     }
648 }
649 
650 /*!
651     \property QTextDocument::useDesignMetrics
652     \since 4.1
653     \brief whether the document uses design metrics of fonts to improve the accuracy of text layout
654 
655     If this property is set to true, the layout will use design metrics.
656     Otherwise, the metrics of the paint device as set on
657     QAbstractTextDocumentLayout::setPaintDevice() will be used.
658 
659     Using design metrics makes a layout have a width that is no longer dependent on hinting
660     and pixel-rounding. This means that WYSIWYG text layout becomes possible because the width
661     scales much more linearly based on paintdevice metrics than it would otherwise.
662 
663     By default, this property is \c false.
664 */
665 
setUseDesignMetrics(bool b)666 void QTextDocument::setUseDesignMetrics(bool b)
667 {
668     Q_D(QTextDocument);
669     if (b == d->defaultTextOption.useDesignMetrics())
670         return;
671     d->defaultTextOption.setUseDesignMetrics(b);
672     if (d->lout)
673         d->lout->documentChanged(0, 0, d->length());
674 }
675 
useDesignMetrics() const676 bool QTextDocument::useDesignMetrics() const
677 {
678     Q_D(const QTextDocument);
679     return d->defaultTextOption.useDesignMetrics();
680 }
681 
682 /*!
683     \since 4.2
684 
685     Draws the content of the document with painter \a p, clipped to \a rect.
686     If \a rect is a null rectangle (default) then the document is painted unclipped.
687 */
drawContents(QPainter * p,const QRectF & rect)688 void QTextDocument::drawContents(QPainter *p, const QRectF &rect)
689 {
690     p->save();
691     QAbstractTextDocumentLayout::PaintContext ctx;
692     if (rect.isValid()) {
693         p->setClipRect(rect);
694         ctx.clip = rect;
695     }
696     documentLayout()->draw(p, ctx);
697     p->restore();
698 }
699 
700 /*!
701     \property QTextDocument::textWidth
702     \since 4.2
703 
704     The text width specifies the preferred width for text in the document. If
705     the text (or content in general) is wider than the specified with it is broken
706     into multiple lines and grows vertically. If the text cannot be broken into multiple
707     lines to fit into the specified text width it will be larger and the size() and the
708     idealWidth() property will reflect that.
709 
710     If the text width is set to -1 then the text will not be broken into multiple lines
711     unless it is enforced through an explicit line break or a new paragraph.
712 
713     The default value is -1.
714 
715     Setting the text width will also set the page height to -1, causing the document to
716     grow or shrink vertically in a continuous way. If you want the document layout to break
717     the text into multiple pages then you have to set the pageSize property instead.
718 
719     \sa size(), idealWidth(), pageSize()
720 */
setTextWidth(qreal width)721 void QTextDocument::setTextWidth(qreal width)
722 {
723     Q_D(QTextDocument);
724     QSizeF sz = d->pageSize;
725     sz.setWidth(width);
726     sz.setHeight(-1);
727     setPageSize(sz);
728 }
729 
textWidth() const730 qreal QTextDocument::textWidth() const
731 {
732     Q_D(const QTextDocument);
733     return d->pageSize.width();
734 }
735 
736 /*!
737     \since 4.2
738 
739     Returns the ideal width of the text document. The ideal width is the actually used width
740     of the document without optional alignments taken into account. It is always <= size().width().
741 
742     \sa adjustSize(), textWidth
743 */
idealWidth() const744 qreal QTextDocument::idealWidth() const
745 {
746     if (QTextDocumentLayout *lout = qobject_cast<QTextDocumentLayout *>(documentLayout()))
747         return lout->idealWidth();
748     return textWidth();
749 }
750 
751 /*!
752     \property QTextDocument::documentMargin
753     \since 4.5
754 
755      The margin around the document. The default is 4.
756 */
documentMargin() const757 qreal QTextDocument::documentMargin() const
758 {
759     Q_D(const QTextDocument);
760     return d->documentMargin;
761 }
762 
setDocumentMargin(qreal margin)763 void QTextDocument::setDocumentMargin(qreal margin)
764 {
765     Q_D(QTextDocument);
766     if (d->documentMargin != margin) {
767         d->documentMargin = margin;
768 
769         QTextFrame* root = rootFrame();
770         QTextFrameFormat format = root->frameFormat();
771         format.setMargin(margin);
772         root->setFrameFormat(format);
773 
774         if (d->lout)
775             d->lout->documentChanged(0, 0, d->length());
776     }
777 }
778 
779 
780 /*!
781     \property QTextDocument::indentWidth
782     \since 4.4
783 
784     Returns the width used for text list and text block indenting.
785 
786     The indent properties of QTextListFormat and QTextBlockFormat specify
787     multiples of this value. The default indent width is 40.
788 */
indentWidth() const789 qreal QTextDocument::indentWidth() const
790 {
791     Q_D(const QTextDocument);
792     return d->indentWidth;
793 }
794 
795 
796 /*!
797     \since 4.4
798 
799     Sets the \a width used for text list and text block indenting.
800 
801     The indent properties of QTextListFormat and QTextBlockFormat specify
802     multiples of this value. The default indent width is 40 .
803 
804     \sa indentWidth()
805 */
setIndentWidth(qreal width)806 void QTextDocument::setIndentWidth(qreal width)
807 {
808     Q_D(QTextDocument);
809     if (d->indentWidth != width) {
810         d->indentWidth = width;
811         if (d->lout)
812             d->lout->documentChanged(0, 0, d->length());
813     }
814 }
815 
816 
817 
818 
819 /*!
820     \since 4.2
821 
822     Adjusts the document to a reasonable size.
823 
824     \sa idealWidth(), textWidth, size
825 */
adjustSize()826 void QTextDocument::adjustSize()
827 {
828     // Pull this private function in from qglobal.cpp
829     QFont f = defaultFont();
830     QFontMetrics fm(f);
831     int mw =  fm.horizontalAdvance(QLatin1Char('x')) * 80;
832     int w = mw;
833     setTextWidth(w);
834     QSizeF size = documentLayout()->documentSize();
835     if (size.width() != 0) {
836         w = qt_int_sqrt((uint)(5 * size.height() * size.width() / 3));
837         setTextWidth(qMin(w, mw));
838 
839         size = documentLayout()->documentSize();
840         if (w*3 < 5*size.height()) {
841             w = qt_int_sqrt((uint)(2 * size.height() * size.width()));
842             setTextWidth(qMin(w, mw));
843         }
844     }
845     setTextWidth(idealWidth());
846 }
847 
848 /*!
849     \property QTextDocument::size
850     \since 4.2
851 
852     Returns the actual size of the document.
853     This is equivalent to documentLayout()->documentSize();
854 
855     The size of the document can be changed either by setting
856     a text width or setting an entire page size.
857 
858     Note that the width is always >= pageSize().width().
859 
860     By default, for a newly-created, empty document, this property contains
861     a configuration-dependent size.
862 
863     \sa setTextWidth(), setPageSize(), idealWidth()
864 */
size() const865 QSizeF QTextDocument::size() const
866 {
867     return documentLayout()->documentSize();
868 }
869 
870 /*!
871     \property QTextDocument::blockCount
872     \since 4.2
873 
874     Returns the number of text blocks in the document.
875 
876     The value of this property is undefined in documents with tables or frames.
877 
878     By default, if defined, this property contains a value of 1.
879     \sa lineCount(), characterCount()
880 */
blockCount() const881 int QTextDocument::blockCount() const
882 {
883     Q_D(const QTextDocument);
884     return d->blockMap().numNodes();
885 }
886 
887 
888 /*!
889   \since 4.5
890 
891   Returns the number of lines of this document (if the layout supports
892   this). Otherwise, this is identical to the number of blocks.
893 
894   \sa blockCount(), characterCount()
895  */
lineCount() const896 int QTextDocument::lineCount() const
897 {
898     Q_D(const QTextDocument);
899     return d->blockMap().length(2);
900 }
901 
902 /*!
903   \since 4.5
904 
905   Returns the number of characters of this document.
906 
907   \note As a QTextDocument always contains at least one
908   QChar::ParagraphSeparator, this method will return at least 1.
909 
910   \sa blockCount(), characterAt()
911  */
characterCount() const912 int QTextDocument::characterCount() const
913 {
914     Q_D(const QTextDocument);
915     return d->length();
916 }
917 
918 /*!
919   \since 4.5
920 
921   Returns the character at position \a pos, or a null character if the
922   position is out of range.
923 
924   \sa characterCount()
925  */
characterAt(int pos) const926 QChar QTextDocument::characterAt(int pos) const
927 {
928     Q_D(const QTextDocument);
929     if (pos < 0 || pos >= d->length())
930         return QChar();
931     QTextDocumentPrivate::FragmentIterator fragIt = d->find(pos);
932     const QTextFragmentData * const frag = fragIt.value();
933     const int offsetInFragment = qMax(0, pos - fragIt.position());
934     return d->text.at(frag->stringPosition + offsetInFragment);
935 }
936 
937 
938 /*!
939     \property QTextDocument::defaultStyleSheet
940     \since 4.2
941 
942     The default style sheet is applied to all newly HTML formatted text that is
943     inserted into the document, for example using setHtml() or QTextCursor::insertHtml().
944 
945     The style sheet needs to be compliant to CSS 2.1 syntax.
946 
947     \b{Note:} Changing the default style sheet does not have any effect to the existing content
948     of the document.
949 
950     \sa {Supported HTML Subset}
951 */
952 
953 #ifndef QT_NO_CSSPARSER
setDefaultStyleSheet(const QString & sheet)954 void QTextDocument::setDefaultStyleSheet(const QString &sheet)
955 {
956     Q_D(QTextDocument);
957     d->defaultStyleSheet = sheet;
958     QCss::Parser parser(sheet);
959     d->parsedDefaultStyleSheet = QCss::StyleSheet();
960     d->parsedDefaultStyleSheet.origin = QCss::StyleSheetOrigin_UserAgent;
961     parser.parse(&d->parsedDefaultStyleSheet);
962 }
963 
defaultStyleSheet() const964 QString QTextDocument::defaultStyleSheet() const
965 {
966     Q_D(const QTextDocument);
967     return d->defaultStyleSheet;
968 }
969 #endif // QT_NO_CSSPARSER
970 
971 /*!
972     \fn void QTextDocument::contentsChanged()
973 
974     This signal is emitted whenever the document's content changes; for
975     example, when text is inserted or deleted, or when formatting is applied.
976 
977     \sa contentsChange()
978 */
979 
980 /*!
981     \fn void QTextDocument::contentsChange(int position, int charsRemoved, int charsAdded)
982 
983     This signal is emitted whenever the document's content changes; for
984     example, when text is inserted or deleted, or when formatting is applied.
985 
986     Information is provided about the \a position of the character in the
987     document where the change occurred, the number of characters removed
988     (\a charsRemoved), and the number of characters added (\a charsAdded).
989 
990     The signal is emitted before the document's layout manager is notified
991     about the change. This hook allows you to implement syntax highlighting
992     for the document.
993 
994     \sa QAbstractTextDocumentLayout::documentChanged(), contentsChanged()
995 */
996 
997 
998 /*!
999     \fn void QTextDocument::undoAvailable(bool available);
1000 
1001     This signal is emitted whenever undo operations become available
1002     (\a available is true) or unavailable (\a available is false).
1003 
1004     See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework}
1005     documentation for details.
1006 
1007     \sa undo(), isUndoRedoEnabled()
1008 */
1009 
1010 /*!
1011     \fn void QTextDocument::redoAvailable(bool available);
1012 
1013     This signal is emitted whenever redo operations become available
1014     (\a available is true) or unavailable (\a available is false).
1015 */
1016 
1017 /*!
1018     \fn void QTextDocument::cursorPositionChanged(const QTextCursor &cursor);
1019 
1020     This signal is emitted whenever the position of a cursor changed
1021     due to an editing operation. The cursor that changed is passed in
1022     \a cursor.  If the document is used with the QTextEdit class and you need a signal when the
1023     cursor is moved with the arrow keys you can use the \l{QTextEdit::}{cursorPositionChanged()}
1024     signal in QTextEdit.
1025 */
1026 
1027 /*!
1028     \fn void QTextDocument::blockCountChanged(int newBlockCount);
1029     \since 4.3
1030 
1031     This signal is emitted when the total number of text blocks in the
1032     document changes. The value passed in \a newBlockCount is the new
1033     total.
1034 */
1035 
1036 /*!
1037     \fn void QTextDocument::documentLayoutChanged();
1038     \since 4.4
1039 
1040     This signal is emitted when a new document layout is set.
1041 
1042     \sa setDocumentLayout()
1043 
1044 */
1045 
1046 
1047 /*!
1048     Returns \c true if undo is available; otherwise returns \c false.
1049 
1050     \sa isRedoAvailable(), availableUndoSteps()
1051 */
isUndoAvailable() const1052 bool QTextDocument::isUndoAvailable() const
1053 {
1054     Q_D(const QTextDocument);
1055     return d->isUndoAvailable();
1056 }
1057 
1058 /*!
1059     Returns \c true if redo is available; otherwise returns \c false.
1060 
1061     \sa isUndoAvailable(), availableRedoSteps()
1062 */
isRedoAvailable() const1063 bool QTextDocument::isRedoAvailable() const
1064 {
1065     Q_D(const QTextDocument);
1066     return d->isRedoAvailable();
1067 }
1068 
1069 /*! \since 4.6
1070 
1071     Returns the number of available undo steps.
1072 
1073     \sa isUndoAvailable()
1074 */
availableUndoSteps() const1075 int QTextDocument::availableUndoSteps() const
1076 {
1077     Q_D(const QTextDocument);
1078     return d->availableUndoSteps();
1079 }
1080 
1081 /*! \since 4.6
1082 
1083     Returns the number of available redo steps.
1084 
1085     \sa isRedoAvailable()
1086 */
availableRedoSteps() const1087 int QTextDocument::availableRedoSteps() const
1088 {
1089     Q_D(const QTextDocument);
1090     return d->availableRedoSteps();
1091 }
1092 
1093 /*! \since 4.4
1094 
1095     Returns the document's revision (if undo is enabled).
1096 
1097     The revision is guaranteed to increase when a document that is not
1098     modified is edited.
1099 
1100     \sa QTextBlock::revision(), isModified()
1101  */
revision() const1102 int QTextDocument::revision() const
1103 {
1104     Q_D(const QTextDocument);
1105     return d->revision;
1106 }
1107 
1108 
1109 
1110 /*!
1111     Sets the document to use the given \a layout. The previous layout
1112     is deleted.
1113 
1114     \sa documentLayoutChanged()
1115 */
setDocumentLayout(QAbstractTextDocumentLayout * layout)1116 void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *layout)
1117 {
1118     Q_D(QTextDocument);
1119     d->setLayout(layout);
1120 }
1121 
1122 /*!
1123     Returns the document layout for this document.
1124 */
documentLayout() const1125 QAbstractTextDocumentLayout *QTextDocument::documentLayout() const
1126 {
1127     Q_D(const QTextDocument);
1128     if (!d->lout) {
1129         QTextDocument *that = const_cast<QTextDocument *>(this);
1130         that->d_func()->setLayout(new QTextDocumentLayout(that));
1131     }
1132     return d->lout;
1133 }
1134 
1135 
1136 /*!
1137     Returns meta information about the document of the type specified by
1138     \a info.
1139 
1140     \sa setMetaInformation()
1141 */
metaInformation(MetaInformation info) const1142 QString QTextDocument::metaInformation(MetaInformation info) const
1143 {
1144     Q_D(const QTextDocument);
1145     switch (info) {
1146     case DocumentTitle:
1147         return d->title;
1148     case DocumentUrl:
1149         return d->url;
1150     }
1151     return QString();
1152 }
1153 
1154 /*!
1155     Sets the document's meta information of the type specified by \a info
1156     to the given \a string.
1157 
1158     \sa metaInformation()
1159 */
setMetaInformation(MetaInformation info,const QString & string)1160 void QTextDocument::setMetaInformation(MetaInformation info, const QString &string)
1161 {
1162     Q_D(QTextDocument);
1163     switch (info) {
1164     case DocumentTitle:
1165         d->title = string;
1166         break;
1167     case DocumentUrl:
1168         d->url = string;
1169         break;
1170     }
1171 }
1172 
1173 /*!
1174     Returns the raw text contained in the document without any
1175     formatting information. If you want formatting information
1176     use a QTextCursor instead.
1177 
1178     \since 5.9
1179     \sa toPlainText()
1180 */
toRawText() const1181 QString QTextDocument::toRawText() const
1182 {
1183     Q_D(const QTextDocument);
1184     return d->plainText();
1185 }
1186 
1187 /*!
1188     Returns the plain text contained in the document. If you want
1189     formatting information use a QTextCursor instead.
1190 
1191     This function returns the same as toRawText(), but will replace
1192     some unicode characters with ASCII alternatives.
1193     In particular, no-break space (U+00A0) is replaced by a regular
1194     space (U+0020), and both paragraph (U+2029) and line (U+2028)
1195     separators are replaced by line feed (U+000A).
1196     If you need the precise contents of the document, use toRawText()
1197     instead.
1198 
1199     \note Embedded objects, such as images, are represented by a
1200     Unicode value U+FFFC (OBJECT REPLACEMENT CHARACTER).
1201 
1202     \sa toHtml()
1203 */
toPlainText() const1204 QString QTextDocument::toPlainText() const
1205 {
1206     Q_D(const QTextDocument);
1207     QString txt = d->plainText();
1208 
1209     QChar *uc = txt.data();
1210     QChar *e = uc + txt.size();
1211 
1212     for (; uc != e; ++uc) {
1213         switch (uc->unicode()) {
1214         case 0xfdd0: // QTextBeginningOfFrame
1215         case 0xfdd1: // QTextEndOfFrame
1216         case QChar::ParagraphSeparator:
1217         case QChar::LineSeparator:
1218             *uc = QLatin1Char('\n');
1219             break;
1220         case QChar::Nbsp:
1221             *uc = QLatin1Char(' ');
1222             break;
1223         default:
1224             ;
1225         }
1226     }
1227     return txt;
1228 }
1229 
1230 /*!
1231     Replaces the entire contents of the document with the given plain
1232     \a text. The undo/redo history is reset when this function is called.
1233 
1234     \sa setHtml()
1235 */
setPlainText(const QString & text)1236 void QTextDocument::setPlainText(const QString &text)
1237 {
1238     Q_D(QTextDocument);
1239     bool previousState = d->isUndoRedoEnabled();
1240     d->enableUndoRedo(false);
1241     d->beginEditBlock();
1242     d->clear();
1243     QTextCursor(this).insertText(text);
1244     d->endEditBlock();
1245     d->enableUndoRedo(previousState);
1246 }
1247 
1248 /*!
1249     Replaces the entire contents of the document with the given
1250     HTML-formatted text in the \a html string. The undo/redo history
1251     is reset when this function is called.
1252 
1253     The HTML formatting is respected as much as possible; for example,
1254     "<b>bold</b> text" will produce text where the first word has a font
1255     weight that gives it a bold appearance: "\b{bold} text".
1256 
1257     \note It is the responsibility of the caller to make sure that the
1258     text is correctly decoded when a QString containing HTML is created
1259     and passed to setHtml().
1260 
1261     \sa setPlainText(), {Supported HTML Subset}
1262 */
1263 
1264 #ifndef QT_NO_TEXTHTMLPARSER
1265 
setHtml(const QString & html)1266 void QTextDocument::setHtml(const QString &html)
1267 {
1268     Q_D(QTextDocument);
1269     bool previousState = d->isUndoRedoEnabled();
1270     d->enableUndoRedo(false);
1271     d->beginEditBlock();
1272     d->clear();
1273     QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import();
1274     d->endEditBlock();
1275     d->enableUndoRedo(previousState);
1276 }
1277 
1278 #endif // QT_NO_TEXTHTMLPARSER
1279 
1280 /*!
1281     \enum QTextDocument::FindFlag
1282 
1283     This enum describes the options available to QTextDocument's find function. The options
1284     can be OR-ed together from the following list:
1285 
1286     \value FindBackward Search backwards instead of forwards.
1287     \value FindCaseSensitively By default find works case insensitive. Specifying this option
1288     changes the behaviour to a case sensitive find operation.
1289     \value FindWholeWords Makes find match only complete words.
1290 */
1291 
1292 /*!
1293     \enum QTextDocument::MetaInformation
1294 
1295     This enum describes the different types of meta information that can be
1296     added to a document.
1297 
1298     \value DocumentTitle    The title of the document.
1299     \value DocumentUrl      The url of the document. The loadResource() function uses
1300                             this url as the base when loading relative resources.
1301 
1302     \sa metaInformation(), setMetaInformation()
1303 */
1304 
findInBlock(const QTextBlock & block,const QString & expression,int offset,QTextDocument::FindFlags options,QTextCursor * cursor)1305 static bool findInBlock(const QTextBlock &block, const QString &expression, int offset,
1306                         QTextDocument::FindFlags options, QTextCursor *cursor)
1307 {
1308     QString text = block.text();
1309     text.replace(QChar::Nbsp, QLatin1Char(' '));
1310     Qt::CaseSensitivity sensitivity = options & QTextDocument::FindCaseSensitively ? Qt::CaseSensitive : Qt::CaseInsensitive;
1311     int idx = -1;
1312 
1313     while (offset >= 0 && offset <= text.length()) {
1314         idx = (options & QTextDocument::FindBackward) ?
1315                text.lastIndexOf(expression, offset, sensitivity) : text.indexOf(expression, offset, sensitivity);
1316         if (idx == -1)
1317             return false;
1318 
1319         if (options & QTextDocument::FindWholeWords) {
1320             const int start = idx;
1321             const int end = start + expression.length();
1322             if ((start != 0 && text.at(start - 1).isLetterOrNumber())
1323                 || (end != text.length() && text.at(end).isLetterOrNumber())) {
1324                 //if this is not a whole word, continue the search in the string
1325                 offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
1326                 idx = -1;
1327                 continue;
1328             }
1329         }
1330         //we have a hit, return the cursor for that.
1331         *cursor = QTextCursorPrivate::fromPosition(block.docHandle(), block.position() + idx);
1332         cursor->setPosition(cursor->position() + expression.length(), QTextCursor::KeepAnchor);
1333         return true;
1334     }
1335     return false;
1336 }
1337 
1338 /*!
1339     \fn QTextCursor QTextDocument::find(const QString &subString, int position, FindFlags options) const
1340 
1341     \overload
1342 
1343     Finds the next occurrence of the string, \a subString, in the document.
1344     The search starts at the given \a position, and proceeds forwards
1345     through the document unless specified otherwise in the search options.
1346     The \a options control the type of search performed.
1347 
1348     Returns a cursor with the match selected if \a subString
1349     was found; otherwise returns a null cursor.
1350 
1351     If the \a position is 0 (the default) the search begins from the beginning
1352     of the document; otherwise it begins at the specified position.
1353 */
find(const QString & subString,int from,FindFlags options) const1354 QTextCursor QTextDocument::find(const QString &subString, int from, FindFlags options) const
1355 {
1356     Q_D(const QTextDocument);
1357 
1358     if (subString.isEmpty())
1359         return QTextCursor();
1360 
1361     int pos = from;
1362     //the cursor is positioned between characters, so for a backward search
1363     //do not include the character given in the position.
1364     if (options & FindBackward) {
1365         --pos ;
1366         if (pos < 0)
1367             return QTextCursor();
1368     }
1369 
1370     QTextCursor cursor;
1371     QTextBlock block = d->blocksFind(pos);
1372     int blockOffset = pos - block.position();
1373 
1374     if (!(options & FindBackward)) {
1375         while (block.isValid()) {
1376             if (findInBlock(block, subString, blockOffset, options, &cursor))
1377                 return cursor;
1378             block = block.next();
1379             blockOffset = 0;
1380         }
1381     } else {
1382         if (blockOffset == block.length() - 1)
1383             --blockOffset;  // make sure to skip end-of-paragraph character
1384         while (block.isValid()) {
1385             if (findInBlock(block, subString, blockOffset, options, &cursor))
1386                 return cursor;
1387             block = block.previous();
1388             blockOffset = block.length() - 2;
1389         }
1390     }
1391 
1392     return QTextCursor();
1393 }
1394 
1395 /*!
1396     Finds the next occurrence of the string, \a subString, in the document.
1397     The search starts at the position of the given \a cursor, and proceeds
1398     forwards through the document unless specified otherwise in the search
1399     options. The \a options control the type of search performed.
1400 
1401     Returns a cursor with the match selected if \a subString was found; otherwise
1402     returns a null cursor.
1403 
1404     If the given \a cursor has a selection, the search begins after the
1405     selection; otherwise it begins at the cursor's position.
1406 
1407     By default the search is case insensitive, and can match text anywhere in the
1408     document.
1409 */
find(const QString & subString,const QTextCursor & cursor,FindFlags options) const1410 QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &cursor, FindFlags options) const
1411 {
1412     int pos = 0;
1413     if (!cursor.isNull()) {
1414         if (options & QTextDocument::FindBackward)
1415             pos = cursor.selectionStart();
1416         else
1417             pos = cursor.selectionEnd();
1418     }
1419 
1420     return find(subString, pos, options);
1421 }
1422 
1423 
1424 #ifndef QT_NO_REGEXP
findInBlock(const QTextBlock & block,const QRegExp & expression,int offset,QTextDocument::FindFlags options,QTextCursor * cursor)1425 static bool findInBlock(const QTextBlock &block, const QRegExp &expression, int offset,
1426                         QTextDocument::FindFlags options, QTextCursor *cursor)
1427 {
1428     QRegExp expr(expression);
1429     QString text = block.text();
1430     text.replace(QChar::Nbsp, QLatin1Char(' '));
1431 
1432     int idx = -1;
1433     while (offset >=0 && offset <= text.length()) {
1434         idx = (options & QTextDocument::FindBackward) ?
1435                expr.lastIndexIn(text, offset) : expr.indexIn(text, offset);
1436         if (idx == -1)
1437             return false;
1438 
1439         if (options & QTextDocument::FindWholeWords) {
1440             const int start = idx;
1441             const int end = start + expr.matchedLength();
1442             if ((start != 0 && text.at(start - 1).isLetterOrNumber())
1443                 || (end != text.length() && text.at(end).isLetterOrNumber())) {
1444                 //if this is not a whole word, continue the search in the string
1445                 offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
1446                 idx = -1;
1447                 continue;
1448             }
1449         }
1450         //we have a hit, return the cursor for that.
1451         *cursor = QTextCursorPrivate::fromPosition(block.docHandle(), block.position() + idx);
1452         cursor->setPosition(cursor->position() + expr.matchedLength(), QTextCursor::KeepAnchor);
1453         return true;
1454     }
1455     return false;
1456 }
1457 
1458 /*!
1459     \overload
1460 
1461     Finds the next occurrence that matches the given regular expression,
1462     \a expr, within the same paragraph in the document.
1463 
1464     The search starts at the given \a from position, and proceeds forwards
1465     through the document unless specified otherwise in the search options.
1466     The \a options control the type of search performed. The FindCaseSensitively
1467     option is ignored for this overload, use QRegExp::caseSensitivity instead.
1468 
1469     Returns a cursor with the match selected if a match was found; otherwise
1470     returns a null cursor.
1471 
1472     If the \a from position is 0 (the default) the search begins from the beginning
1473     of the document; otherwise it begins at the specified position.
1474 */
find(const QRegExp & expr,int from,FindFlags options) const1475 QTextCursor QTextDocument::find(const QRegExp & expr, int from, FindFlags options) const
1476 {
1477     Q_D(const QTextDocument);
1478 
1479     if (expr.isEmpty())
1480         return QTextCursor();
1481 
1482     int pos = from;
1483     //the cursor is positioned between characters, so for a backward search
1484     //do not include the character given in the position.
1485     if (options & FindBackward) {
1486         --pos ;
1487         if(pos < 0)
1488             return QTextCursor();
1489     }
1490 
1491     QTextCursor cursor;
1492     QTextBlock block = d->blocksFind(pos);
1493     int blockOffset = pos - block.position();
1494     if (!(options & FindBackward)) {
1495         while (block.isValid()) {
1496             if (findInBlock(block, expr, blockOffset, options, &cursor))
1497                 return cursor;
1498             block = block.next();
1499             blockOffset = 0;
1500         }
1501     } else {
1502         while (block.isValid()) {
1503             if (findInBlock(block, expr, blockOffset, options, &cursor))
1504                 return cursor;
1505             block = block.previous();
1506             blockOffset = block.length() - 1;
1507         }
1508     }
1509 
1510     return QTextCursor();
1511 }
1512 
1513 /*!
1514     \overload
1515 
1516     Finds the next occurrence that matches the given regular expression,
1517     \a expr, within the same paragraph in the document.
1518 
1519     The search starts at the position of the given from \a cursor, and proceeds
1520     forwards through the document unless specified otherwise in the search
1521     options. The \a options control the type of search performed. The FindCaseSensitively
1522     option is ignored for this overload, use QRegExp::caseSensitivity instead.
1523 
1524     Returns a cursor with the match selected if a match was found; otherwise
1525     returns a null cursor.
1526 
1527     If the given \a cursor has a selection, the search begins after the
1528     selection; otherwise it begins at the cursor's position.
1529 
1530     By default the search is case insensitive, and can match text anywhere in the
1531     document.
1532 */
find(const QRegExp & expr,const QTextCursor & cursor,FindFlags options) const1533 QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &cursor, FindFlags options) const
1534 {
1535     int pos = 0;
1536     if (!cursor.isNull()) {
1537         if (options & QTextDocument::FindBackward)
1538             pos = cursor.selectionStart();
1539         else
1540             pos = cursor.selectionEnd();
1541     }
1542     return find(expr, pos, options);
1543 }
1544 #endif // QT_REGEXP
1545 
1546 #if QT_CONFIG(regularexpression)
findInBlock(const QTextBlock & block,const QRegularExpression & expr,int offset,QTextDocument::FindFlags options,QTextCursor * cursor)1547 static bool findInBlock(const QTextBlock &block, const QRegularExpression &expr, int offset,
1548                         QTextDocument::FindFlags options, QTextCursor *cursor)
1549 {
1550     QString text = block.text();
1551     text.replace(QChar::Nbsp, QLatin1Char(' '));
1552     QRegularExpressionMatch match;
1553     int idx = -1;
1554 
1555     while (offset >= 0 && offset <= text.length()) {
1556         idx = (options & QTextDocument::FindBackward) ?
1557                text.lastIndexOf(expr, offset, &match) : text.indexOf(expr, offset, &match);
1558         if (idx == -1)
1559             return false;
1560 
1561         if (options & QTextDocument::FindWholeWords) {
1562             const int start = idx;
1563             const int end = start + match.capturedLength();
1564             if ((start != 0 && text.at(start - 1).isLetterOrNumber())
1565                 || (end != text.length() && text.at(end).isLetterOrNumber())) {
1566                 //if this is not a whole word, continue the search in the string
1567                 offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
1568                 idx = -1;
1569                 continue;
1570             }
1571         }
1572         //we have a hit, return the cursor for that.
1573         *cursor = QTextCursorPrivate::fromPosition(block.docHandle(), block.position() + idx);
1574         cursor->setPosition(cursor->position() + match.capturedLength(), QTextCursor::KeepAnchor);
1575         return true;
1576     }
1577     return false;
1578 }
1579 
1580 /*!
1581     \since 5.5
1582 
1583     Finds the next occurrence that matches the given regular expression,
1584     \a expr, within the same paragraph in the document.
1585 
1586     The search starts at the given \a from position, and proceeds forwards
1587     through the document unless specified otherwise in the search options.
1588     The \a options control the type of search performed.
1589 
1590     Returns a cursor with the match selected if a match was found; otherwise
1591     returns a null cursor.
1592 
1593     If the \a from position is 0 (the default) the search begins from the beginning
1594     of the document; otherwise it begins at the specified position.
1595 */
find(const QRegularExpression & expr,int from,FindFlags options) const1596 QTextCursor QTextDocument::find(const QRegularExpression &expr, int from, FindFlags options) const
1597 {
1598     Q_D(const QTextDocument);
1599 
1600     if (!expr.isValid())
1601         return QTextCursor();
1602 
1603     int pos = from;
1604     //the cursor is positioned between characters, so for a backward search
1605     //do not include the character given in the position.
1606     if (options & FindBackward) {
1607         --pos ;
1608         if (pos < 0)
1609             return QTextCursor();
1610     }
1611 
1612     QTextCursor cursor;
1613     QTextBlock block = d->blocksFind(pos);
1614     int blockOffset = pos - block.position();
1615 
1616     QRegularExpression expression(expr);
1617     if (!(options & QTextDocument::FindCaseSensitively))
1618         expression.setPatternOptions(expr.patternOptions() | QRegularExpression::CaseInsensitiveOption);
1619     else
1620         expression.setPatternOptions(expr.patternOptions() & ~QRegularExpression::CaseInsensitiveOption);
1621 
1622     if (!(options & FindBackward)) {
1623         while (block.isValid()) {
1624             if (findInBlock(block, expression, blockOffset, options, &cursor))
1625                 return cursor;
1626             block = block.next();
1627             blockOffset = 0;
1628         }
1629     } else {
1630         while (block.isValid()) {
1631             if (findInBlock(block, expression, blockOffset, options, &cursor))
1632                 return cursor;
1633             block = block.previous();
1634             blockOffset = block.length() - 1;
1635         }
1636     }
1637 
1638     return QTextCursor();
1639 }
1640 
1641 /*!
1642     \since 5.5
1643 
1644     Finds the next occurrence that matches the given regular expression,
1645     \a expr, within the same paragraph in the document.
1646 
1647     The search starts at the position of the given \a cursor, and proceeds
1648     forwards through the document unless specified otherwise in the search
1649     options. The \a options control the type of search performed.
1650 
1651     Returns a cursor with the match selected if a match was found; otherwise
1652     returns a null cursor.
1653 
1654     If the given \a cursor has a selection, the search begins after the
1655     selection; otherwise it begins at the cursor's position.
1656 
1657     By default the search is case insensitive, and can match text anywhere in the
1658     document.
1659 */
find(const QRegularExpression & expr,const QTextCursor & cursor,FindFlags options) const1660 QTextCursor QTextDocument::find(const QRegularExpression &expr, const QTextCursor &cursor, FindFlags options) const
1661 {
1662     int pos = 0;
1663     if (!cursor.isNull()) {
1664         if (options & QTextDocument::FindBackward)
1665             pos = cursor.selectionStart();
1666         else
1667             pos = cursor.selectionEnd();
1668     }
1669     return find(expr, pos, options);
1670 }
1671 #endif // QT_CONFIG(regularexpression)
1672 
1673 /*!
1674     \fn QTextObject *QTextDocument::createObject(const QTextFormat &format)
1675 
1676     Creates and returns a new document object (a QTextObject), based
1677     on the given \a format.
1678 
1679     QTextObjects will always get created through this method, so you
1680     must reimplement it if you use custom text objects inside your document.
1681 */
createObject(const QTextFormat & f)1682 QTextObject *QTextDocument::createObject(const QTextFormat &f)
1683 {
1684     QTextObject *obj = nullptr;
1685     if (f.isListFormat())
1686         obj = new QTextList(this);
1687     else if (f.isTableFormat())
1688         obj = new QTextTable(this);
1689     else if (f.isFrameFormat())
1690         obj = new QTextFrame(this);
1691 
1692     return obj;
1693 }
1694 
1695 /*!
1696     \internal
1697 
1698     Returns the frame that contains the text cursor position \a pos.
1699 */
frameAt(int pos) const1700 QTextFrame *QTextDocument::frameAt(int pos) const
1701 {
1702     Q_D(const QTextDocument);
1703     return d->frameAt(pos);
1704 }
1705 
1706 /*!
1707     Returns the document's root frame.
1708 */
rootFrame() const1709 QTextFrame *QTextDocument::rootFrame() const
1710 {
1711     Q_D(const QTextDocument);
1712     return d->rootFrame();
1713 }
1714 
1715 /*!
1716     Returns the text object associated with the given \a objectIndex.
1717 */
object(int objectIndex) const1718 QTextObject *QTextDocument::object(int objectIndex) const
1719 {
1720     Q_D(const QTextDocument);
1721     return d->objectForIndex(objectIndex);
1722 }
1723 
1724 /*!
1725     Returns the text object associated with the format \a f.
1726 */
objectForFormat(const QTextFormat & f) const1727 QTextObject *QTextDocument::objectForFormat(const QTextFormat &f) const
1728 {
1729     Q_D(const QTextDocument);
1730     return d->objectForFormat(f);
1731 }
1732 
1733 
1734 /*!
1735     Returns the text block that contains the \a{pos}-th character.
1736 */
findBlock(int pos) const1737 QTextBlock QTextDocument::findBlock(int pos) const
1738 {
1739     Q_D(const QTextDocument);
1740     return QTextBlock(docHandle(), d->blockMap().findNode(pos));
1741 }
1742 
1743 /*!
1744     \since 4.4
1745     Returns the text block with the specified \a blockNumber.
1746 
1747     \sa QTextBlock::blockNumber()
1748 */
findBlockByNumber(int blockNumber) const1749 QTextBlock QTextDocument::findBlockByNumber(int blockNumber) const
1750 {
1751     Q_D(const QTextDocument);
1752     return QTextBlock(docHandle(), d->blockMap().findNode(blockNumber, 1));
1753 }
1754 
1755 /*!
1756     \since 4.5
1757     Returns the text block that contains the specified \a lineNumber.
1758 
1759     \sa QTextBlock::firstLineNumber()
1760 */
findBlockByLineNumber(int lineNumber) const1761 QTextBlock QTextDocument::findBlockByLineNumber(int lineNumber) const
1762 {
1763     Q_D(const QTextDocument);
1764     return QTextBlock(docHandle(), d->blockMap().findNode(lineNumber, 2));
1765 }
1766 
1767 /*!
1768     Returns the document's first text block.
1769 
1770     \sa firstBlock()
1771 */
begin() const1772 QTextBlock QTextDocument::begin() const
1773 {
1774     Q_D(const QTextDocument);
1775     return QTextBlock(docHandle(), d->blockMap().begin().n);
1776 }
1777 
1778 /*!
1779     This function returns a block to test for the end of the document
1780     while iterating over it.
1781 
1782     \snippet textdocumentendsnippet.cpp 0
1783 
1784     The block returned is invalid and represents the block after the
1785     last block in the document. You can use lastBlock() to retrieve the
1786     last valid block of the document.
1787 
1788     \sa lastBlock()
1789 */
end() const1790 QTextBlock QTextDocument::end() const
1791 {
1792     return QTextBlock(docHandle(), 0);
1793 }
1794 
1795 /*!
1796     \since 4.4
1797     Returns the document's first text block.
1798 */
firstBlock() const1799 QTextBlock QTextDocument::firstBlock() const
1800 {
1801     Q_D(const QTextDocument);
1802     return QTextBlock(docHandle(), d->blockMap().begin().n);
1803 }
1804 
1805 /*!
1806     \since 4.4
1807     Returns the document's last (valid) text block.
1808 */
lastBlock() const1809 QTextBlock QTextDocument::lastBlock() const
1810 {
1811     Q_D(const QTextDocument);
1812     return QTextBlock(docHandle(), d->blockMap().last().n);
1813 }
1814 
1815 /*!
1816     \property QTextDocument::pageSize
1817     \brief the page size that should be used for laying out the document
1818 
1819     The units are determined by the underlying paint device. The size is
1820     measured in logical pixels when painting to the screen, and in points
1821     (1/72 inch) when painting to a printer.
1822 
1823     By default, for a newly-created, empty document, this property contains
1824     an undefined size.
1825 
1826     \sa modificationChanged()
1827 */
1828 
setPageSize(const QSizeF & size)1829 void QTextDocument::setPageSize(const QSizeF &size)
1830 {
1831     Q_D(QTextDocument);
1832     d->pageSize = size;
1833     if (d->lout)
1834         d->lout->documentChanged(0, 0, d->length());
1835 }
1836 
pageSize() const1837 QSizeF QTextDocument::pageSize() const
1838 {
1839     Q_D(const QTextDocument);
1840     return d->pageSize;
1841 }
1842 
1843 /*!
1844   returns the number of pages in this document.
1845 */
pageCount() const1846 int QTextDocument::pageCount() const
1847 {
1848     return documentLayout()->pageCount();
1849 }
1850 
1851 /*!
1852     Sets the default \a font to use in the document layout.
1853 */
setDefaultFont(const QFont & font)1854 void QTextDocument::setDefaultFont(const QFont &font)
1855 {
1856     Q_D(QTextDocument);
1857     d->setDefaultFont(font);
1858     if (d->lout)
1859         d->lout->documentChanged(0, 0, d->length());
1860 }
1861 
1862 /*!
1863     Returns the default font to be used in the document layout.
1864 */
defaultFont() const1865 QFont QTextDocument::defaultFont() const
1866 {
1867     Q_D(const QTextDocument);
1868     return d->defaultFont();
1869 }
1870 
1871 /*!
1872     \fn void QTextDocument::modificationChanged(bool changed)
1873 
1874     This signal is emitted whenever the content of the document
1875     changes in a way that affects the modification state. If \a
1876     changed is true, the document has been modified; otherwise it is
1877     false.
1878 
1879     For example, calling setModified(false) on a document and then
1880     inserting text causes the signal to get emitted. If you undo that
1881     operation, causing the document to return to its original
1882     unmodified state, the signal will get emitted again.
1883 */
1884 
1885 /*!
1886     \property QTextDocument::modified
1887     \brief whether the document has been modified by the user
1888 
1889     By default, this property is \c false.
1890 
1891     \sa modificationChanged()
1892 */
1893 
isModified() const1894 bool QTextDocument::isModified() const
1895 {
1896     return docHandle()->isModified();
1897 }
1898 
setModified(bool m)1899 void QTextDocument::setModified(bool m)
1900 {
1901     docHandle()->setModified(m);
1902 }
1903 
1904 #ifndef QT_NO_PRINTER
printPage(int index,QPainter * painter,const QTextDocument * doc,const QRectF & body,const QPointF & pageNumberPos)1905 static void printPage(int index, QPainter *painter, const QTextDocument *doc, const QRectF &body, const QPointF &pageNumberPos)
1906 {
1907     painter->save();
1908     painter->translate(body.left(), body.top() - (index - 1) * body.height());
1909     QRectF view(0, (index - 1) * body.height(), body.width(), body.height());
1910 
1911     QAbstractTextDocumentLayout *layout = doc->documentLayout();
1912     QAbstractTextDocumentLayout::PaintContext ctx;
1913 
1914     painter->setClipRect(view);
1915     ctx.clip = view;
1916 
1917     // don't use the system palette text as default text color, on HP/UX
1918     // for example that's white, and white text on white paper doesn't
1919     // look that nice
1920     ctx.palette.setColor(QPalette::Text, Qt::black);
1921 
1922     layout->draw(painter, ctx);
1923 
1924     if (!pageNumberPos.isNull()) {
1925         painter->setClipping(false);
1926         painter->setFont(QFont(doc->defaultFont()));
1927         const QString pageString = QString::number(index);
1928 
1929         painter->drawText(qRound(pageNumberPos.x() - painter->fontMetrics().horizontalAdvance(pageString)),
1930                           qRound(pageNumberPos.y() + view.top()),
1931                           pageString);
1932     }
1933 
1934     painter->restore();
1935 }
1936 
1937 /*!
1938     Prints the document to the given \a printer. The QPagedPaintDevice must be
1939     set up before being used with this function.
1940 
1941     This is only a convenience method to print the whole document to the printer.
1942 
1943     If the document is already paginated through a specified height in the pageSize()
1944     property it is printed as-is.
1945 
1946     If the document is not paginated, like for example a document used in a QTextEdit,
1947     then a temporary copy of the document is created and the copy is broken into
1948     multiple pages according to the size of the paint device's paperRect(). By default
1949     a 2 cm margin is set around the document contents. In addition the current page
1950     number is printed at the bottom of each page.
1951 
1952     \sa QTextEdit::print()
1953 */
1954 
print(QPagedPaintDevice * printer) const1955 void QTextDocument::print(QPagedPaintDevice *printer) const
1956 {
1957     Q_D(const QTextDocument);
1958 
1959     if (!printer)
1960         return;
1961 
1962     bool documentPaginated = d->pageSize.isValid() && !d->pageSize.isNull()
1963                              && d->pageSize.height() != INT_MAX;
1964 
1965     QPagedPaintDevicePrivate *pd = QPagedPaintDevicePrivate::get(printer);
1966 
1967     // ### set page size to paginated size?
1968     QMarginsF m = printer->pageLayout().margins(QPageLayout::Millimeter);
1969     if (!documentPaginated && m.left() == 0 && m.right() == 0 && m.top() == 0 && m.bottom() == 0) {
1970         m.setLeft(2.);
1971         m.setRight(2.);
1972         m.setTop(2.);
1973         m.setBottom(2.);
1974         printer->setPageMargins(m, QPageLayout::Millimeter);
1975     }
1976     // ### use the margins correctly
1977 
1978     QPainter p(printer);
1979 
1980     // Check that there is a valid device to print to.
1981     if (!p.isActive())
1982         return;
1983 
1984     const QTextDocument *doc = this;
1985     QScopedPointer<QTextDocument> clonedDoc;
1986     (void)doc->documentLayout(); // make sure that there is a layout
1987 
1988     QRectF body = QRectF(QPointF(0, 0), d->pageSize);
1989     QPointF pageNumberPos;
1990 
1991     qreal sourceDpiX = qt_defaultDpiX();
1992     qreal sourceDpiY = qt_defaultDpiY();
1993     const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX;
1994     const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY;
1995 
1996     if (documentPaginated) {
1997 
1998         QPaintDevice *dev = doc->documentLayout()->paintDevice();
1999         if (dev) {
2000             sourceDpiX = dev->logicalDpiX();
2001             sourceDpiY = dev->logicalDpiY();
2002         }
2003 
2004         // scale to dpi
2005         p.scale(dpiScaleX, dpiScaleY);
2006 
2007         QSizeF scaledPageSize = d->pageSize;
2008         scaledPageSize.rwidth() *= dpiScaleX;
2009         scaledPageSize.rheight() *= dpiScaleY;
2010 
2011         const QSizeF printerPageSize(printer->width(), printer->height());
2012 
2013         // scale to page
2014         p.scale(printerPageSize.width() / scaledPageSize.width(),
2015                 printerPageSize.height() / scaledPageSize.height());
2016     } else {
2017         doc = clone(const_cast<QTextDocument *>(this));
2018         clonedDoc.reset(const_cast<QTextDocument *>(doc));
2019 
2020         for (QTextBlock srcBlock = firstBlock(), dstBlock = clonedDoc->firstBlock();
2021              srcBlock.isValid() && dstBlock.isValid();
2022              srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) {
2023             dstBlock.layout()->setFormats(srcBlock.layout()->formats());
2024         }
2025 
2026         QAbstractTextDocumentLayout *layout = doc->documentLayout();
2027         layout->setPaintDevice(p.device());
2028 
2029         // copy the custom object handlers
2030         layout->d_func()->handlers = documentLayout()->d_func()->handlers;
2031 
2032         // 2 cm margins, scaled to device in QTextDocumentLayoutPrivate::layoutFrame
2033         const int horizontalMargin = int((2/2.54)*sourceDpiX);
2034         const int verticalMargin = int((2/2.54)*sourceDpiY);
2035         QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
2036         fmt.setLeftMargin(horizontalMargin);
2037         fmt.setRightMargin(horizontalMargin);
2038         fmt.setTopMargin(verticalMargin);
2039         fmt.setBottomMargin(verticalMargin);
2040         doc->rootFrame()->setFrameFormat(fmt);
2041 
2042         // pageNumberPos must be in device coordinates, so scale to device here
2043         const int dpiy = p.device()->logicalDpiY();
2044         body = QRectF(0, 0, printer->width(), printer->height());
2045         pageNumberPos = QPointF(body.width() - horizontalMargin * dpiScaleX,
2046                                 body.height() - verticalMargin * dpiScaleY
2047                                 + QFontMetrics(doc->defaultFont(), p.device()).ascent()
2048                                 + 5 * dpiy / 72.0);
2049         clonedDoc->setPageSize(body.size());
2050     }
2051 
2052     int fromPage = pd->fromPage;
2053     int toPage = pd->toPage;
2054 
2055     if (fromPage == 0 && toPage == 0) {
2056         fromPage = 1;
2057         toPage = doc->pageCount();
2058     }
2059     // paranoia check
2060     fromPage = qMax(1, fromPage);
2061     toPage = qMin(doc->pageCount(), toPage);
2062 
2063     if (toPage < fromPage) {
2064         // if the user entered a page range outside the actual number
2065         // of printable pages, just return
2066         return;
2067     }
2068 
2069 //    bool ascending = true;
2070 //    if (printer->pageOrder() == QPrinter::LastPageFirst) {
2071 //        int tmp = fromPage;
2072 //        fromPage = toPage;
2073 //        toPage = tmp;
2074 //        ascending = false;
2075 //    }
2076 
2077     int page = fromPage;
2078     while (true) {
2079         printPage(page, &p, doc, body, pageNumberPos);
2080 
2081         if (page == toPage)
2082             break;
2083         ++page;
2084         if (!printer->newPage())
2085             return;
2086     }
2087 }
2088 #endif
2089 
2090 /*!
2091     \enum QTextDocument::ResourceType
2092 
2093     This enum describes the types of resources that can be loaded by
2094     QTextDocument's loadResource() function or by QTextBrowser::setSource().
2095 
2096     \value UnknownResource No resource is loaded, or the resource type is not known.
2097     \value HtmlResource  The resource contains HTML.
2098     \value ImageResource The resource contains image data.
2099                          Currently supported data types are QVariant::Pixmap and
2100                          QVariant::Image. If the corresponding variant is of type
2101                          QVariant::ByteArray then Qt attempts to load the image using
2102                          QImage::loadFromData. QVariant::Icon is currently not supported.
2103                          The icon needs to be converted to one of the supported types first,
2104                          for example using QIcon::pixmap.
2105     \value StyleSheetResource The resource contains CSS.
2106     \value MarkdownResource The resource contains Markdown.
2107     \value UserResource  The first available value for user defined
2108                          resource types.
2109 
2110     \sa loadResource(), QTextBrowser::sourceType()
2111 */
2112 
2113 /*!
2114     Returns data of the specified \a type from the resource with the
2115     given \a name.
2116 
2117     This function is called by the rich text engine to request data that isn't
2118     directly stored by QTextDocument, but still associated with it. For example,
2119     images are referenced indirectly by the name attribute of a QTextImageFormat
2120     object.
2121 
2122     Resources are cached internally in the document. If a resource can
2123     not be found in the cache, loadResource is called to try to load
2124     the resource. loadResource should then use addResource to add the
2125     resource to the cache.
2126 
2127     \sa QTextDocument::ResourceType
2128 */
resource(int type,const QUrl & name) const2129 QVariant QTextDocument::resource(int type, const QUrl &name) const
2130 {
2131     Q_D(const QTextDocument);
2132     const QUrl url = d->baseUrl.resolved(name);
2133     QVariant r = d->resources.value(url);
2134     if (!r.isValid()) {
2135         r = d->cachedResources.value(url);
2136         if (!r.isValid())
2137             r = const_cast<QTextDocument *>(this)->loadResource(type, url);
2138     }
2139     return r;
2140 }
2141 
2142 /*!
2143     Adds the resource \a resource to the resource cache, using \a
2144     type and \a name as identifiers. \a type should be a value from
2145     QTextDocument::ResourceType.
2146 
2147     For example, you can add an image as a resource in order to reference it
2148     from within the document:
2149 
2150     \snippet textdocument-resources/main.cpp Adding a resource
2151 
2152     The image can be inserted into the document using the QTextCursor API:
2153 
2154     \snippet textdocument-resources/main.cpp Inserting an image with a cursor
2155 
2156     Alternatively, you can insert images using the HTML \c img tag:
2157 
2158     \snippet textdocument-resources/main.cpp Inserting an image using HTML
2159 */
addResource(int type,const QUrl & name,const QVariant & resource)2160 void QTextDocument::addResource(int type, const QUrl &name, const QVariant &resource)
2161 {
2162     Q_UNUSED(type);
2163     Q_D(QTextDocument);
2164     d->resources.insert(name, resource);
2165 }
2166 
2167 /*!
2168     Loads data of the specified \a type from the resource with the
2169     given \a name.
2170 
2171     This function is called by the rich text engine to request data that isn't
2172     directly stored by QTextDocument, but still associated with it. For example,
2173     images are referenced indirectly by the name attribute of a QTextImageFormat
2174     object.
2175 
2176     When called by Qt, \a type is one of the values of
2177     QTextDocument::ResourceType.
2178 
2179     If the QTextDocument is a child object of a QObject that has an invokable
2180     loadResource method such as QTextEdit, QTextBrowser
2181     or a QTextDocument itself then the default implementation tries
2182     to retrieve the data from the parent.
2183 */
loadResource(int type,const QUrl & name)2184 QVariant QTextDocument::loadResource(int type, const QUrl &name)
2185 {
2186     Q_D(QTextDocument);
2187     QVariant r;
2188 
2189     QObject *p = parent();
2190     if (p) {
2191         const QMetaObject *me = p->metaObject();
2192         int index = me->indexOfMethod("loadResource(int,QUrl)");
2193         if (index >= 0) {
2194             QMetaMethod loader = me->method(index);
2195             loader.invoke(p, Q_RETURN_ARG(QVariant, r), Q_ARG(int, type), Q_ARG(QUrl, name));
2196         }
2197     }
2198 
2199     // handle data: URLs
2200     if (r.isNull() && name.scheme().compare(QLatin1String("data"), Qt::CaseInsensitive) == 0) {
2201         QString mimetype;
2202         QByteArray payload;
2203         if (qDecodeDataUrl(name, mimetype, payload))
2204             r = payload;
2205     }
2206 
2207     // if resource was not loaded try to load it here
2208     if (!qobject_cast<QTextDocument *>(p) && r.isNull()) {
2209         QUrl resourceUrl = name;
2210 
2211         if (name.isRelative()) {
2212             QUrl currentURL = d->url;
2213             // For the second case QUrl can merge "#someanchor" with "foo.html"
2214             // correctly to "foo.html#someanchor"
2215             if (!(currentURL.isRelative()
2216                   || (currentURL.scheme() == QLatin1String("file")
2217                       && !QFileInfo(currentURL.toLocalFile()).isAbsolute()))
2218                 || (name.hasFragment() && name.path().isEmpty())) {
2219                 resourceUrl =  currentURL.resolved(name);
2220             } else {
2221                 // this is our last resort when current url and new url are both relative
2222                 // we try to resolve against the current working directory in the local
2223                 // file system.
2224                 QFileInfo fi(currentURL.toLocalFile());
2225                 if (fi.exists()) {
2226                     resourceUrl =
2227                         QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(name);
2228                 } else if (currentURL.isEmpty()) {
2229                     resourceUrl.setScheme(QLatin1String("file"));
2230                 }
2231             }
2232         }
2233 
2234         QString s = resourceUrl.toLocalFile();
2235         QFile f(s);
2236         if (!s.isEmpty() && f.open(QFile::ReadOnly)) {
2237             r = f.readAll();
2238             f.close();
2239         }
2240     }
2241 
2242     if (!r.isNull()) {
2243         if (type == ImageResource && r.userType() == QMetaType::QByteArray) {
2244             if (qApp->thread() != QThread::currentThread()) {
2245                 // must use images in non-GUI threads
2246                 QImage image;
2247                 image.loadFromData(r.toByteArray());
2248                 if (!image.isNull())
2249                     r = image;
2250             } else {
2251                 QPixmap pm;
2252                 pm.loadFromData(r.toByteArray());
2253                 if (!pm.isNull())
2254                     r = pm;
2255             }
2256         }
2257         d->cachedResources.insert(name, r);
2258     }
2259     return r;
2260 }
2261 
formatDifference(const QTextFormat & from,const QTextFormat & to)2262 static QTextFormat formatDifference(const QTextFormat &from, const QTextFormat &to)
2263 {
2264     QTextFormat diff = to;
2265 
2266     const QMap<int, QVariant> props = to.properties();
2267     for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end();
2268          it != end; ++it)
2269         if (it.value() == from.property(it.key()))
2270             diff.clearProperty(it.key());
2271 
2272     return diff;
2273 }
2274 
colorValue(QColor color)2275 static QString colorValue(QColor color)
2276 {
2277     QString result;
2278 
2279     if (color.alpha() == 255) {
2280         result = color.name();
2281     } else if (color.alpha()) {
2282         QString alphaValue = QString::number(color.alphaF(), 'f', 6).remove(QRegExp(QLatin1String("\\.?0*$")));
2283         result = QString::fromLatin1("rgba(%1,%2,%3,%4)").arg(color.red())
2284                                                          .arg(color.green())
2285                                                          .arg(color.blue())
2286                                                          .arg(alphaValue);
2287     } else {
2288         result = QLatin1String("transparent");
2289     }
2290 
2291     return result;
2292 }
2293 
QTextHtmlExporter(const QTextDocument * _doc)2294 QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc)
2295     : doc(_doc), fragmentMarkers(false)
2296 {
2297     const QFont defaultFont = doc->defaultFont();
2298     defaultCharFormat.setFont(defaultFont);
2299     // don't export those for the default font since we cannot turn them off with CSS
2300     defaultCharFormat.clearProperty(QTextFormat::FontUnderline);
2301     defaultCharFormat.clearProperty(QTextFormat::FontOverline);
2302     defaultCharFormat.clearProperty(QTextFormat::FontStrikeOut);
2303     defaultCharFormat.clearProperty(QTextFormat::TextUnderlineStyle);
2304 }
2305 
resolvedFontFamilies(const QTextCharFormat & format)2306 static QStringList resolvedFontFamilies(const QTextCharFormat &format)
2307 {
2308     QStringList fontFamilies = format.fontFamilies().toStringList();
2309     const QString mainFontFamily = format.fontFamily();
2310     if (!mainFontFamily.isEmpty() && !fontFamilies.contains(mainFontFamily))
2311         fontFamilies.append(mainFontFamily);
2312     return fontFamilies;
2313 }
2314 
2315 /*!
2316     Returns the document in HTML format. The conversion may not be
2317     perfect, especially for complex documents, due to the limitations
2318     of HTML.
2319 */
toHtml(const QByteArray & encoding,ExportMode mode)2320 QString QTextHtmlExporter::toHtml(const QByteArray &encoding, ExportMode mode)
2321 {
2322     html = QLatin1String("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
2323             "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
2324             "<html><head><meta name=\"qrichtext\" content=\"1\" />");
2325     html.reserve(doc->docHandle()->length());
2326 
2327     fragmentMarkers = (mode == ExportFragment);
2328 
2329     if (!encoding.isEmpty())
2330         html += QString::fromLatin1("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(QString::fromLatin1(encoding));
2331 
2332     QString title  = doc->metaInformation(QTextDocument::DocumentTitle);
2333     if (!title.isEmpty())
2334         html += QString::fromLatin1("<title>") + title + QString::fromLatin1("</title>");
2335     html += QLatin1String("<style type=\"text/css\">\n");
2336     html += QLatin1String("p, li { white-space: pre-wrap; }\n");
2337     html += QLatin1String("</style>");
2338     html += QLatin1String("</head><body");
2339 
2340     if (mode == ExportEntireDocument) {
2341         html += QLatin1String(" style=\"");
2342 
2343         emitFontFamily(resolvedFontFamilies(defaultCharFormat));
2344 
2345         if (defaultCharFormat.hasProperty(QTextFormat::FontPointSize)) {
2346             html += QLatin1String(" font-size:");
2347             html += QString::number(defaultCharFormat.fontPointSize());
2348             html += QLatin1String("pt;");
2349         } else if (defaultCharFormat.hasProperty(QTextFormat::FontPixelSize)) {
2350             html += QLatin1String(" font-size:");
2351             html += QString::number(defaultCharFormat.intProperty(QTextFormat::FontPixelSize));
2352             html += QLatin1String("px;");
2353         }
2354 
2355         html += QLatin1String(" font-weight:");
2356         html += QString::number(defaultCharFormat.fontWeight() * 8);
2357         html += QLatin1Char(';');
2358 
2359         html += QLatin1String(" font-style:");
2360         html += (defaultCharFormat.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
2361         html += QLatin1Char(';');
2362 
2363         const bool percentSpacing = (defaultCharFormat.fontLetterSpacingType() == QFont::PercentageSpacing);
2364         if (defaultCharFormat.hasProperty(QTextFormat::FontLetterSpacing) &&
2365             (!percentSpacing || defaultCharFormat.fontLetterSpacing() != 0.0)) {
2366             html += QLatin1String(" letter-spacing:");
2367             qreal value = defaultCharFormat.fontLetterSpacing();
2368             if (percentSpacing) // Map to em (100% == 0em)
2369                 value = (value / 100) - 1;
2370             html += QString::number(value);
2371             html += percentSpacing ? QLatin1String("em;") : QLatin1String("px;");
2372         }
2373 
2374         if (defaultCharFormat.hasProperty(QTextFormat::FontWordSpacing) &&
2375             defaultCharFormat.fontWordSpacing() != 0.0) {
2376             html += QLatin1String(" word-spacing:");
2377             html += QString::number(defaultCharFormat.fontWordSpacing());
2378             html += QLatin1String("px;");
2379         }
2380 
2381         // do not set text-decoration on the default font since those values are /always/ propagated
2382         // and cannot be turned off with CSS
2383 
2384         html += QLatin1Char('\"');
2385 
2386         const QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
2387         emitBackgroundAttribute(fmt);
2388 
2389     } else {
2390         defaultCharFormat = QTextCharFormat();
2391     }
2392     html += QLatin1Char('>');
2393 
2394     QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat();
2395     rootFmt.clearProperty(QTextFormat::BackgroundBrush);
2396 
2397     QTextFrameFormat defaultFmt;
2398     defaultFmt.setMargin(doc->documentMargin());
2399 
2400     if (rootFmt == defaultFmt)
2401         emitFrame(doc->rootFrame()->begin());
2402     else
2403         emitTextFrame(doc->rootFrame());
2404 
2405     html += QLatin1String("</body></html>");
2406     return html;
2407 }
2408 
emitAttribute(const char * attribute,const QString & value)2409 void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value)
2410 {
2411     html += QLatin1Char(' ');
2412     html += QLatin1String(attribute);
2413     html += QLatin1String("=\"");
2414     html += value.toHtmlEscaped();
2415     html += QLatin1Char('"');
2416 }
2417 
emitCharFormatStyle(const QTextCharFormat & format)2418 bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
2419 {
2420     bool attributesEmitted = false;
2421 
2422     {
2423         const QStringList families = resolvedFontFamilies(format);
2424         if (!families.isEmpty() && families != resolvedFontFamilies(defaultCharFormat)) {
2425             emitFontFamily(families);
2426             attributesEmitted = true;
2427         }
2428     }
2429 
2430     if (format.hasProperty(QTextFormat::FontPointSize)
2431         && format.fontPointSize() != defaultCharFormat.fontPointSize()) {
2432         html += QLatin1String(" font-size:");
2433         html += QString::number(format.fontPointSize());
2434         html += QLatin1String("pt;");
2435         attributesEmitted = true;
2436     } else if (format.hasProperty(QTextFormat::FontSizeAdjustment)) {
2437         static const char sizeNameData[] =
2438             "small" "\0"
2439             "medium" "\0"
2440             "xx-large" ;
2441         static const quint8 sizeNameOffsets[] = {
2442             0,                                         // "small"
2443             sizeof("small"),                           // "medium"
2444             sizeof("small") + sizeof("medium") + 3,    // "large"    )
2445             sizeof("small") + sizeof("medium") + 1,    // "x-large"  )> compressed into "xx-large"
2446             sizeof("small") + sizeof("medium"),        // "xx-large" )
2447         };
2448         const char *name = nullptr;
2449         const int idx = format.intProperty(QTextFormat::FontSizeAdjustment) + 1;
2450         if (idx >= 0 && idx <= 4) {
2451             name = sizeNameData + sizeNameOffsets[idx];
2452         }
2453         if (name) {
2454             html += QLatin1String(" font-size:");
2455             html += QLatin1String(name);
2456             html += QLatin1Char(';');
2457             attributesEmitted = true;
2458         }
2459     } else if (format.hasProperty(QTextFormat::FontPixelSize)) {
2460         html += QLatin1String(" font-size:");
2461         html += QString::number(format.intProperty(QTextFormat::FontPixelSize));
2462         html += QLatin1String("px;");
2463         attributesEmitted = true;
2464     }
2465 
2466     if (format.hasProperty(QTextFormat::FontWeight)
2467         && format.fontWeight() != defaultCharFormat.fontWeight()) {
2468         html += QLatin1String(" font-weight:");
2469         html += QString::number(format.fontWeight() * 8);
2470         html += QLatin1Char(';');
2471         attributesEmitted = true;
2472     }
2473 
2474     if (format.hasProperty(QTextFormat::FontItalic)
2475         && format.fontItalic() != defaultCharFormat.fontItalic()) {
2476         html += QLatin1String(" font-style:");
2477         html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
2478         html += QLatin1Char(';');
2479         attributesEmitted = true;
2480     }
2481 
2482     QLatin1String decorationTag(" text-decoration:");
2483     html += decorationTag;
2484     bool hasDecoration = false;
2485     bool atLeastOneDecorationSet = false;
2486 
2487     if ((format.hasProperty(QTextFormat::FontUnderline) || format.hasProperty(QTextFormat::TextUnderlineStyle))
2488         && format.fontUnderline() != defaultCharFormat.fontUnderline()) {
2489         hasDecoration = true;
2490         if (format.fontUnderline()) {
2491             html += QLatin1String(" underline");
2492             atLeastOneDecorationSet = true;
2493         }
2494     }
2495 
2496     if (format.hasProperty(QTextFormat::FontOverline)
2497         && format.fontOverline() != defaultCharFormat.fontOverline()) {
2498         hasDecoration = true;
2499         if (format.fontOverline()) {
2500             html += QLatin1String(" overline");
2501             atLeastOneDecorationSet = true;
2502         }
2503     }
2504 
2505     if (format.hasProperty(QTextFormat::FontStrikeOut)
2506         && format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) {
2507         hasDecoration = true;
2508         if (format.fontStrikeOut()) {
2509             html += QLatin1String(" line-through");
2510             atLeastOneDecorationSet = true;
2511         }
2512     }
2513 
2514     if (hasDecoration) {
2515         if (!atLeastOneDecorationSet)
2516             html += QLatin1String("none");
2517         html += QLatin1Char(';');
2518         attributesEmitted = true;
2519     } else {
2520         html.chop(decorationTag.size());
2521     }
2522 
2523     if (format.foreground() != defaultCharFormat.foreground()
2524         && format.foreground().style() != Qt::NoBrush) {
2525         QBrush brush = format.foreground();
2526         if (brush.style() == Qt::TexturePattern) {
2527             const bool isPixmap = qHasPixmapTexture(brush);
2528             const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
2529 
2530             html += QLatin1String(" -qt-fg-texture-cachekey:");
2531             html += QString::number(cacheKey);
2532             html += QLatin1String(";");
2533         } else {
2534             html += QLatin1String(" color:");
2535             html += colorValue(brush.color());
2536             html += QLatin1Char(';');
2537         }
2538         attributesEmitted = true;
2539     }
2540 
2541     if (format.background() != defaultCharFormat.background()
2542         && format.background().style() == Qt::SolidPattern) {
2543         html += QLatin1String(" background-color:");
2544         html += colorValue(format.background().color());
2545         html += QLatin1Char(';');
2546         attributesEmitted = true;
2547     }
2548 
2549     if (format.verticalAlignment() != defaultCharFormat.verticalAlignment()
2550         && format.verticalAlignment() != QTextCharFormat::AlignNormal)
2551     {
2552         html += QLatin1String(" vertical-align:");
2553 
2554         QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2555         if (valign == QTextCharFormat::AlignSubScript)
2556             html += QLatin1String("sub");
2557         else if (valign == QTextCharFormat::AlignSuperScript)
2558             html += QLatin1String("super");
2559         else if (valign == QTextCharFormat::AlignMiddle)
2560             html += QLatin1String("middle");
2561         else if (valign == QTextCharFormat::AlignTop)
2562             html += QLatin1String("top");
2563         else if (valign == QTextCharFormat::AlignBottom)
2564             html += QLatin1String("bottom");
2565 
2566         html += QLatin1Char(';');
2567         attributesEmitted = true;
2568     }
2569 
2570     if (format.fontCapitalization() != QFont::MixedCase) {
2571         const QFont::Capitalization caps = format.fontCapitalization();
2572         if (caps == QFont::AllUppercase)
2573             html += QLatin1String(" text-transform:uppercase;");
2574         else if (caps == QFont::AllLowercase)
2575             html += QLatin1String(" text-transform:lowercase;");
2576         else if (caps == QFont::SmallCaps)
2577             html += QLatin1String(" font-variant:small-caps;");
2578         attributesEmitted = true;
2579     }
2580 
2581     if (format.fontWordSpacing() != 0.0) {
2582         html += QLatin1String(" word-spacing:");
2583         html += QString::number(format.fontWordSpacing());
2584         html += QLatin1String("px;");
2585         attributesEmitted = true;
2586     }
2587 
2588     return attributesEmitted;
2589 }
2590 
emitTextLength(const char * attribute,const QTextLength & length)2591 void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength &length)
2592 {
2593     if (length.type() == QTextLength::VariableLength) // default
2594         return;
2595 
2596     html += QLatin1Char(' ');
2597     html += QLatin1String(attribute);
2598     html += QLatin1String("=\"");
2599     html += QString::number(length.rawValue());
2600 
2601     if (length.type() == QTextLength::PercentageLength)
2602         html += QLatin1String("%\"");
2603     else
2604         html += QLatin1Char('\"');
2605 }
2606 
emitAlignment(Qt::Alignment align)2607 void QTextHtmlExporter::emitAlignment(Qt::Alignment align)
2608 {
2609     if (align & Qt::AlignLeft)
2610         return;
2611     else if (align & Qt::AlignRight)
2612         html += QLatin1String(" align=\"right\"");
2613     else if (align & Qt::AlignHCenter)
2614         html += QLatin1String(" align=\"center\"");
2615     else if (align & Qt::AlignJustify)
2616         html += QLatin1String(" align=\"justify\"");
2617 }
2618 
emitFloatStyle(QTextFrameFormat::Position pos,StyleMode mode)2619 void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode)
2620 {
2621     if (pos == QTextFrameFormat::InFlow)
2622         return;
2623 
2624     if (mode == EmitStyleTag)
2625         html += QLatin1String(" style=\"float:");
2626     else
2627         html += QLatin1String(" float:");
2628 
2629     if (pos == QTextFrameFormat::FloatLeft)
2630         html += QLatin1String(" left;");
2631     else if (pos == QTextFrameFormat::FloatRight)
2632         html += QLatin1String(" right;");
2633     else
2634         Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type");
2635 
2636     if (mode == EmitStyleTag)
2637         html += QLatin1Char('\"');
2638 }
2639 
richtextBorderStyleToHtmlBorderStyle(QTextFrameFormat::BorderStyle style)2640 static QLatin1String richtextBorderStyleToHtmlBorderStyle(QTextFrameFormat::BorderStyle style)
2641 {
2642     switch (style) {
2643     case QTextFrameFormat::BorderStyle_None:
2644         return QLatin1String("none");
2645     case QTextFrameFormat::BorderStyle_Dotted:
2646         return QLatin1String("dotted");
2647     case QTextFrameFormat::BorderStyle_Dashed:
2648         return QLatin1String("dashed");
2649     case QTextFrameFormat::BorderStyle_Solid:
2650         return QLatin1String("solid");
2651     case QTextFrameFormat::BorderStyle_Double:
2652         return QLatin1String("double");
2653     case QTextFrameFormat::BorderStyle_DotDash:
2654         return QLatin1String("dot-dash");
2655     case QTextFrameFormat::BorderStyle_DotDotDash:
2656         return QLatin1String("dot-dot-dash");
2657     case QTextFrameFormat::BorderStyle_Groove:
2658         return QLatin1String("groove");
2659     case QTextFrameFormat::BorderStyle_Ridge:
2660         return QLatin1String("ridge");
2661     case QTextFrameFormat::BorderStyle_Inset:
2662         return QLatin1String("inset");
2663     case QTextFrameFormat::BorderStyle_Outset:
2664         return QLatin1String("outset");
2665     default:
2666         Q_UNREACHABLE();
2667     };
2668     return QLatin1String("");
2669 }
2670 
emitBorderStyle(QTextFrameFormat::BorderStyle style)2671 void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style)
2672 {
2673     Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset);
2674 
2675     html += QLatin1String(" border-style:");
2676     html += richtextBorderStyleToHtmlBorderStyle(style);
2677     html += QLatin1Char(';');
2678 }
2679 
emitPageBreakPolicy(QTextFormat::PageBreakFlags policy)2680 void QTextHtmlExporter::emitPageBreakPolicy(QTextFormat::PageBreakFlags policy)
2681 {
2682     if (policy & QTextFormat::PageBreak_AlwaysBefore)
2683         html += QLatin1String(" page-break-before:always;");
2684 
2685     if (policy & QTextFormat::PageBreak_AlwaysAfter)
2686         html += QLatin1String(" page-break-after:always;");
2687 }
2688 
emitFontFamily(const QStringList & families)2689 void QTextHtmlExporter::emitFontFamily(const QStringList &families)
2690 {
2691     html += QLatin1String(" font-family:");
2692 
2693     bool first = true;
2694     for (const QString &family : families) {
2695         QLatin1String quote("\'");
2696         if (family.contains(QLatin1Char('\'')))
2697             quote = QLatin1String("&quot;");
2698 
2699         if (!first)
2700             html += QLatin1String(",");
2701         else
2702             first = false;
2703         html += quote;
2704         html += family.toHtmlEscaped();
2705         html += quote;
2706     }
2707     html += QLatin1Char(';');
2708 }
2709 
emitMargins(const QString & top,const QString & bottom,const QString & left,const QString & right)2710 void QTextHtmlExporter::emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right)
2711 {
2712     html += QLatin1String(" margin-top:");
2713     html += top;
2714     html += QLatin1String("px;");
2715 
2716     html += QLatin1String(" margin-bottom:");
2717     html += bottom;
2718     html += QLatin1String("px;");
2719 
2720     html += QLatin1String(" margin-left:");
2721     html += left;
2722     html += QLatin1String("px;");
2723 
2724     html += QLatin1String(" margin-right:");
2725     html += right;
2726     html += QLatin1String("px;");
2727 }
2728 
emitFragment(const QTextFragment & fragment)2729 void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
2730 {
2731     const QTextCharFormat format = fragment.charFormat();
2732 
2733     bool closeAnchor = false;
2734 
2735     if (format.isAnchor()) {
2736         const auto names = format.anchorNames();
2737         if (!names.isEmpty()) {
2738             html += QLatin1String("<a name=\"");
2739             html += names.constFirst().toHtmlEscaped();
2740             html += QLatin1String("\"></a>");
2741         }
2742         const QString href = format.anchorHref();
2743         if (!href.isEmpty()) {
2744             html += QLatin1String("<a href=\"");
2745             html += href.toHtmlEscaped();
2746             html += QLatin1String("\">");
2747             closeAnchor = true;
2748         }
2749     }
2750 
2751     QString txt = fragment.text();
2752     const bool isObject = txt.contains(QChar::ObjectReplacementCharacter);
2753     const bool isImage = isObject && format.isImageFormat();
2754 
2755     QLatin1String styleTag("<span style=\"");
2756     html += styleTag;
2757 
2758     bool attributesEmitted = false;
2759     if (!isImage)
2760         attributesEmitted = emitCharFormatStyle(format);
2761     if (attributesEmitted)
2762         html += QLatin1String("\">");
2763     else
2764         html.chop(styleTag.size());
2765 
2766     if (isObject) {
2767         for (int i = 0; isImage && i < txt.length(); ++i) {
2768             QTextImageFormat imgFmt = format.toImageFormat();
2769 
2770             html += QLatin1String("<img");
2771 
2772             if (imgFmt.hasProperty(QTextFormat::ImageName))
2773                 emitAttribute("src", imgFmt.name());
2774 
2775             if (imgFmt.hasProperty(QTextFormat::ImageAltText))
2776                 emitAttribute("alt", imgFmt.stringProperty(QTextFormat::ImageAltText));
2777 
2778             if (imgFmt.hasProperty(QTextFormat::ImageTitle))
2779                 emitAttribute("title", imgFmt.stringProperty(QTextFormat::ImageTitle));
2780 
2781             if (imgFmt.hasProperty(QTextFormat::ImageWidth))
2782                 emitAttribute("width", QString::number(imgFmt.width()));
2783 
2784             if (imgFmt.hasProperty(QTextFormat::ImageHeight))
2785                 emitAttribute("height", QString::number(imgFmt.height()));
2786 
2787             if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle)
2788                 html += QLatin1String(" style=\"vertical-align: middle;\"");
2789             else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop)
2790                 html += QLatin1String(" style=\"vertical-align: top;\"");
2791 
2792             if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt)))
2793                 emitFloatStyle(imageFrame->frameFormat().position());
2794 
2795             html += QLatin1String(" />");
2796         }
2797     } else {
2798         Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter));
2799 
2800         txt = txt.toHtmlEscaped();
2801 
2802         // split for [\n{LineSeparator}]
2803         QString forcedLineBreakRegExp = QString::fromLatin1("[\\na]");
2804         forcedLineBreakRegExp[3] = QChar::LineSeparator;
2805         // space in BR on purpose for compatibility with old-fashioned browsers
2806         html += txt.replace(QRegExp(forcedLineBreakRegExp), QLatin1String("<br />"));
2807     }
2808 
2809     if (attributesEmitted)
2810         html += QLatin1String("</span>");
2811 
2812     if (closeAnchor)
2813         html += QLatin1String("</a>");
2814 }
2815 
isOrderedList(int style)2816 static bool isOrderedList(int style)
2817 {
2818     return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
2819            || style == QTextListFormat::ListUpperAlpha
2820            || style == QTextListFormat::ListUpperRoman
2821            || style == QTextListFormat::ListLowerRoman
2822            ;
2823 }
2824 
emitBlockAttributes(const QTextBlock & block)2825 void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block)
2826 {
2827     QTextBlockFormat format = block.blockFormat();
2828     emitAlignment(format.alignment());
2829 
2830     // assume default to not bloat the html too much
2831     // html += QLatin1String(" dir='ltr'");
2832     if (block.textDirection() == Qt::RightToLeft)
2833         html += QLatin1String(" dir='rtl'");
2834 
2835     QLatin1String style(" style=\"");
2836     html += style;
2837 
2838     const bool emptyBlock = block.begin().atEnd();
2839     if (emptyBlock) {
2840         html += QLatin1String("-qt-paragraph-type:empty;");
2841     }
2842 
2843     emitMargins(QString::number(format.topMargin()),
2844                 QString::number(format.bottomMargin()),
2845                 QString::number(format.leftMargin()),
2846                 QString::number(format.rightMargin()));
2847 
2848     html += QLatin1String(" -qt-block-indent:");
2849     html += QString::number(format.indent());
2850     html += QLatin1Char(';');
2851 
2852     html += QLatin1String(" text-indent:");
2853     html += QString::number(format.textIndent());
2854     html += QLatin1String("px;");
2855 
2856     if (block.userState() != -1) {
2857         html += QLatin1String(" -qt-user-state:");
2858         html += QString::number(block.userState());
2859         html += QLatin1Char(';');
2860     }
2861 
2862     if (format.lineHeightType() != QTextBlockFormat::SingleHeight) {
2863         html += QLatin1String(" line-height:")
2864              + QString::number(format.lineHeight());
2865         switch (format.lineHeightType()) {
2866             case QTextBlockFormat::ProportionalHeight:
2867                 html += QLatin1String("%;");
2868                 break;
2869             case QTextBlockFormat::FixedHeight:
2870                 html += QLatin1String("; -qt-line-height-type: fixed;");
2871                 break;
2872             case QTextBlockFormat::MinimumHeight:
2873                 html += QLatin1String("px;");
2874                 break;
2875             case QTextBlockFormat::LineDistanceHeight:
2876                 html += QLatin1String("; -qt-line-height-type: line-distance;");
2877                 break;
2878             default:
2879                 html += QLatin1String(";");
2880                 break; // Should never reach here
2881         }
2882     }
2883 
2884     emitPageBreakPolicy(format.pageBreakPolicy());
2885 
2886     QTextCharFormat diff;
2887     if (emptyBlock) { // only print character properties when we don't expect them to be repeated by actual text in the parag
2888         const QTextCharFormat blockCharFmt = block.charFormat();
2889         diff = formatDifference(defaultCharFormat, blockCharFmt).toCharFormat();
2890     }
2891 
2892     diff.clearProperty(QTextFormat::BackgroundBrush);
2893     if (format.hasProperty(QTextFormat::BackgroundBrush)) {
2894         QBrush bg = format.background();
2895         if (bg.style() != Qt::NoBrush)
2896             diff.setProperty(QTextFormat::BackgroundBrush, format.property(QTextFormat::BackgroundBrush));
2897     }
2898 
2899     if (!diff.properties().isEmpty())
2900         emitCharFormatStyle(diff);
2901 
2902     html += QLatin1Char('"');
2903 
2904 }
2905 
emitBlock(const QTextBlock & block)2906 void QTextHtmlExporter::emitBlock(const QTextBlock &block)
2907 {
2908     if (block.begin().atEnd()) {
2909         // ### HACK, remove once QTextFrame::Iterator is fixed
2910         int p = block.position();
2911         if (p > 0)
2912             --p;
2913         QTextDocumentPrivate::FragmentIterator frag = doc->docHandle()->find(p);
2914         QChar ch = doc->docHandle()->buffer().at(frag->stringPosition);
2915         if (ch == QTextBeginningOfFrame
2916             || ch == QTextEndOfFrame)
2917             return;
2918     }
2919 
2920     html += QLatin1Char('\n');
2921 
2922     // save and later restore, in case we 'change' the default format by
2923     // emitting block char format information
2924     QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
2925 
2926     QTextList *list = block.textList();
2927     if (list) {
2928         if (list->itemNumber(block) == 0) { // first item? emit <ul> or appropriate
2929             const QTextListFormat format = list->format();
2930             const int style = format.style();
2931             switch (style) {
2932                 case QTextListFormat::ListDecimal: html += QLatin1String("<ol"); break;
2933                 case QTextListFormat::ListDisc: html += QLatin1String("<ul"); break;
2934                 case QTextListFormat::ListCircle: html += QLatin1String("<ul type=\"circle\""); break;
2935                 case QTextListFormat::ListSquare: html += QLatin1String("<ul type=\"square\""); break;
2936                 case QTextListFormat::ListLowerAlpha: html += QLatin1String("<ol type=\"a\""); break;
2937                 case QTextListFormat::ListUpperAlpha: html += QLatin1String("<ol type=\"A\""); break;
2938                 case QTextListFormat::ListLowerRoman: html += QLatin1String("<ol type=\"i\""); break;
2939                 case QTextListFormat::ListUpperRoman: html += QLatin1String("<ol type=\"I\""); break;
2940                 default: html += QLatin1String("<ul"); // ### should not happen
2941             }
2942 
2943             QString styleString = QString::fromLatin1("margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px;");
2944 
2945             if (format.hasProperty(QTextFormat::ListIndent)) {
2946                 styleString += QLatin1String(" -qt-list-indent: ");
2947                 styleString += QString::number(format.indent());
2948                 styleString += QLatin1Char(';');
2949             }
2950 
2951             if (format.hasProperty(QTextFormat::ListNumberPrefix)) {
2952                 QString numberPrefix = format.numberPrefix();
2953                 numberPrefix.replace(QLatin1Char('"'), QLatin1String("\\22"));
2954                 numberPrefix.replace(QLatin1Char('\''), QLatin1String("\\27")); // FIXME: There's a problem in the CSS parser the prevents this from being correctly restored
2955                 styleString += QLatin1String(" -qt-list-number-prefix: ");
2956                 styleString += QLatin1Char('\'');
2957                 styleString += numberPrefix;
2958                 styleString += QLatin1Char('\'');
2959                 styleString += QLatin1Char(';');
2960             }
2961 
2962             if (format.hasProperty(QTextFormat::ListNumberSuffix)) {
2963                 if (format.numberSuffix() != QLatin1String(".")) { // this is our default
2964                     QString numberSuffix = format.numberSuffix();
2965                     numberSuffix.replace(QLatin1Char('"'), QLatin1String("\\22"));
2966                     numberSuffix.replace(QLatin1Char('\''), QLatin1String("\\27")); // see above
2967                     styleString += QLatin1String(" -qt-list-number-suffix: ");
2968                     styleString += QLatin1Char('\'');
2969                     styleString += numberSuffix;
2970                     styleString += QLatin1Char('\'');
2971                     styleString += QLatin1Char(';');
2972                 }
2973             }
2974 
2975             html += QLatin1String(" style=\"");
2976             html += styleString;
2977             html += QLatin1String("\">");
2978         }
2979 
2980         html += QLatin1String("<li");
2981 
2982         const QTextCharFormat blockFmt = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat();
2983         if (!blockFmt.properties().isEmpty()) {
2984             html += QLatin1String(" style=\"");
2985             emitCharFormatStyle(blockFmt);
2986             html += QLatin1Char('\"');
2987 
2988             defaultCharFormat.merge(block.charFormat());
2989         }
2990     }
2991 
2992     const QTextBlockFormat blockFormat = block.blockFormat();
2993     if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
2994         html += QLatin1String("<hr");
2995 
2996         QTextLength width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth);
2997         if (width.type() != QTextLength::VariableLength)
2998             emitTextLength("width", width);
2999         else
3000             html += QLatin1Char(' ');
3001 
3002         html += QLatin1String("/>");
3003         return;
3004     }
3005 
3006     const bool pre = blockFormat.nonBreakableLines();
3007     if (pre) {
3008         if (list)
3009             html += QLatin1Char('>');
3010         html += QLatin1String("<pre");
3011     } else if (!list) {
3012         int headingLevel = blockFormat.headingLevel();
3013         if (headingLevel > 0 && headingLevel <= 6)
3014             html += QLatin1String("<h") + QString::number(headingLevel);
3015         else
3016             html += QLatin1String("<p");
3017     }
3018 
3019     emitBlockAttributes(block);
3020 
3021     html += QLatin1Char('>');
3022     if (block.begin().atEnd())
3023         html += QLatin1String("<br />");
3024 
3025     QTextBlock::Iterator it = block.begin();
3026     if (fragmentMarkers && !it.atEnd() && block == doc->begin())
3027         html += QLatin1String("<!--StartFragment-->");
3028 
3029     for (; !it.atEnd(); ++it)
3030         emitFragment(it.fragment());
3031 
3032     if (fragmentMarkers && block.position() + block.length() == doc->docHandle()->length())
3033         html += QLatin1String("<!--EndFragment-->");
3034 
3035     if (pre)
3036         html += QLatin1String("</pre>");
3037     else if (list)
3038         html += QLatin1String("</li>");
3039     else {
3040         int headingLevel = blockFormat.headingLevel();
3041         if (headingLevel > 0 && headingLevel <= 6)
3042             html += QLatin1String("</h") + QString::number(headingLevel) + QLatin1Char('>');
3043         else
3044             html += QLatin1String("</p>");
3045     }
3046 
3047     if (list) {
3048         if (list->itemNumber(block) == list->count() - 1) { // last item? close list
3049             if (isOrderedList(list->format().style()))
3050                 html += QLatin1String("</ol>");
3051             else
3052                 html += QLatin1String("</ul>");
3053         }
3054     }
3055 
3056     defaultCharFormat = oldDefaultCharFormat;
3057 }
3058 
3059 extern bool qHasPixmapTexture(const QBrush& brush);
3060 
findUrlForImage(const QTextDocument * doc,qint64 cacheKey,bool isPixmap)3061 QString QTextHtmlExporter::findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap)
3062 {
3063     QString url;
3064     if (!doc)
3065         return url;
3066 
3067     if (QTextDocument *parent = qobject_cast<QTextDocument *>(doc->parent()))
3068         return findUrlForImage(parent, cacheKey, isPixmap);
3069 
3070     if (doc && doc->docHandle()) {
3071         QTextDocumentPrivate *priv = doc->docHandle();
3072         QMap<QUrl, QVariant>::const_iterator it = priv->cachedResources.constBegin();
3073         for (; it != priv->cachedResources.constEnd(); ++it) {
3074 
3075             const QVariant &v = it.value();
3076             if (v.userType() == QMetaType::QImage && !isPixmap) {
3077                 if (qvariant_cast<QImage>(v).cacheKey() == cacheKey)
3078                     break;
3079             }
3080 
3081             if (v.userType() == QMetaType::QPixmap && isPixmap) {
3082                 if (qvariant_cast<QPixmap>(v).cacheKey() == cacheKey)
3083                     break;
3084             }
3085         }
3086 
3087         if (it != priv->cachedResources.constEnd())
3088             url = it.key().toString();
3089     }
3090 
3091     return url;
3092 }
3093 
mergeCachedResources(const QTextDocumentPrivate * priv)3094 void QTextDocumentPrivate::mergeCachedResources(const QTextDocumentPrivate *priv)
3095 {
3096     if (!priv)
3097         return;
3098 
3099     cachedResources.insert(priv->cachedResources);
3100 }
3101 
emitBackgroundAttribute(const QTextFormat & format)3102 void QTextHtmlExporter::emitBackgroundAttribute(const QTextFormat &format)
3103 {
3104     if (format.hasProperty(QTextFormat::BackgroundImageUrl)) {
3105         QString url = format.property(QTextFormat::BackgroundImageUrl).toString();
3106         emitAttribute("background", url);
3107     } else {
3108         const QBrush &brush = format.background();
3109         if (brush.style() == Qt::SolidPattern) {
3110             emitAttribute("bgcolor", colorValue(brush.color()));
3111         } else if (brush.style() == Qt::TexturePattern) {
3112             const bool isPixmap = qHasPixmapTexture(brush);
3113             const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
3114 
3115             const QString url = findUrlForImage(doc, cacheKey, isPixmap);
3116 
3117             if (!url.isEmpty())
3118                 emitAttribute("background", url);
3119         }
3120     }
3121 }
3122 
emitTable(const QTextTable * table)3123 void QTextHtmlExporter::emitTable(const QTextTable *table)
3124 {
3125     QTextTableFormat format = table->format();
3126 
3127     html += QLatin1String("\n<table");
3128 
3129     if (format.hasProperty(QTextFormat::FrameBorder))
3130         emitAttribute("border", QString::number(format.border()));
3131 
3132     emitFrameStyle(format, TableFrame);
3133 
3134     emitAlignment(format.alignment());
3135     emitTextLength("width", format.width());
3136 
3137     if (format.hasProperty(QTextFormat::TableCellSpacing))
3138         emitAttribute("cellspacing", QString::number(format.cellSpacing()));
3139     if (format.hasProperty(QTextFormat::TableCellPadding))
3140         emitAttribute("cellpadding", QString::number(format.cellPadding()));
3141 
3142     emitBackgroundAttribute(format);
3143 
3144     html += QLatin1Char('>');
3145 
3146     const int rows = table->rows();
3147     const int columns = table->columns();
3148 
3149     QVector<QTextLength> columnWidths = format.columnWidthConstraints();
3150     if (columnWidths.isEmpty()) {
3151         columnWidths.resize(columns);
3152         columnWidths.fill(QTextLength());
3153     }
3154     Q_ASSERT(columnWidths.count() == columns);
3155 
3156     QVarLengthArray<bool> widthEmittedForColumn(columns);
3157     for (int i = 0; i < columns; ++i)
3158         widthEmittedForColumn[i] = false;
3159 
3160     const int headerRowCount = qMin(format.headerRowCount(), rows);
3161     if (headerRowCount > 0)
3162         html += QLatin1String("<thead>");
3163 
3164     for (int row = 0; row < rows; ++row) {
3165         html += QLatin1String("\n<tr>");
3166 
3167         for (int col = 0; col < columns; ++col) {
3168             const QTextTableCell cell = table->cellAt(row, col);
3169 
3170             // for col/rowspans
3171             if (cell.row() != row)
3172                 continue;
3173 
3174             if (cell.column() != col)
3175                 continue;
3176 
3177             html += QLatin1String("\n<td");
3178 
3179             if (!widthEmittedForColumn[col] && cell.columnSpan() == 1) {
3180                 emitTextLength("width", columnWidths.at(col));
3181                 widthEmittedForColumn[col] = true;
3182             }
3183 
3184             if (cell.columnSpan() > 1)
3185                 emitAttribute("colspan", QString::number(cell.columnSpan()));
3186 
3187             if (cell.rowSpan() > 1)
3188                 emitAttribute("rowspan", QString::number(cell.rowSpan()));
3189 
3190             const QTextTableCellFormat cellFormat = cell.format().toTableCellFormat();
3191             emitBackgroundAttribute(cellFormat);
3192 
3193             QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
3194 
3195             QTextCharFormat::VerticalAlignment valign = cellFormat.verticalAlignment();
3196 
3197             QString styleString;
3198             if (valign >= QTextCharFormat::AlignMiddle && valign <= QTextCharFormat::AlignBottom) {
3199                 styleString += QLatin1String(" vertical-align:");
3200                 switch (valign) {
3201                 case QTextCharFormat::AlignMiddle:
3202                     styleString += QLatin1String("middle");
3203                     break;
3204                 case QTextCharFormat::AlignTop:
3205                     styleString += QLatin1String("top");
3206                     break;
3207                 case QTextCharFormat::AlignBottom:
3208                     styleString += QLatin1String("bottom");
3209                     break;
3210                 default:
3211                     break;
3212                 }
3213                 styleString += QLatin1Char(';');
3214 
3215                 QTextCharFormat temp;
3216                 temp.setVerticalAlignment(valign);
3217                 defaultCharFormat.merge(temp);
3218             }
3219 
3220             if (cellFormat.hasProperty(QTextFormat::TableCellLeftPadding))
3221                 styleString += QLatin1String(" padding-left:") + QString::number(cellFormat.leftPadding()) + QLatin1Char(';');
3222             if (cellFormat.hasProperty(QTextFormat::TableCellRightPadding))
3223                 styleString += QLatin1String(" padding-right:") + QString::number(cellFormat.rightPadding()) + QLatin1Char(';');
3224             if (cellFormat.hasProperty(QTextFormat::TableCellTopPadding))
3225                 styleString += QLatin1String(" padding-top:") + QString::number(cellFormat.topPadding()) + QLatin1Char(';');
3226             if (cellFormat.hasProperty(QTextFormat::TableCellBottomPadding))
3227                 styleString += QLatin1String(" padding-bottom:") + QString::number(cellFormat.bottomPadding()) + QLatin1Char(';');
3228 
3229             if (cellFormat.hasProperty(QTextFormat::TableCellTopBorder))
3230                 styleString += QLatin1String(" border-top:") + QString::number(cellFormat.topBorder()) + QLatin1String("px;");
3231             if (cellFormat.hasProperty(QTextFormat::TableCellRightBorder))
3232                 styleString += QLatin1String(" border-right:") + QString::number(cellFormat.rightBorder()) + QLatin1String("px;");
3233             if (cellFormat.hasProperty(QTextFormat::TableCellBottomBorder))
3234                 styleString += QLatin1String(" border-bottom:") + QString::number(cellFormat.bottomBorder()) + QLatin1String("px;");
3235             if (cellFormat.hasProperty(QTextFormat::TableCellLeftBorder))
3236                 styleString += QLatin1String(" border-left:") + QString::number(cellFormat.leftBorder()) + QLatin1String("px;");
3237 
3238             if (cellFormat.hasProperty(QTextFormat::TableCellTopBorderBrush))
3239                 styleString += QLatin1String(" border-top-color:") + cellFormat.topBorderBrush().color().name() + QLatin1Char(';');
3240             if (cellFormat.hasProperty(QTextFormat::TableCellRightBorderBrush))
3241                 styleString += QLatin1String(" border-right-color:") + cellFormat.rightBorderBrush().color().name() + QLatin1Char(';');
3242             if (cellFormat.hasProperty(QTextFormat::TableCellBottomBorderBrush))
3243                 styleString += QLatin1String(" border-bottom-color:") + cellFormat.bottomBorderBrush().color().name() + QLatin1Char(';');
3244             if (cellFormat.hasProperty(QTextFormat::TableCellLeftBorderBrush))
3245                 styleString += QLatin1String(" border-left-color:") + cellFormat.leftBorderBrush().color().name() + QLatin1Char(';');
3246 
3247             if (cellFormat.hasProperty(QTextFormat::TableCellTopBorderStyle))
3248                 styleString += QLatin1String(" border-top-style:") + richtextBorderStyleToHtmlBorderStyle(cellFormat.topBorderStyle()) + QLatin1Char(';');
3249             if (cellFormat.hasProperty(QTextFormat::TableCellRightBorderStyle))
3250                 styleString += QLatin1String(" border-right-style:") + richtextBorderStyleToHtmlBorderStyle(cellFormat.rightBorderStyle()) + QLatin1Char(';');
3251             if (cellFormat.hasProperty(QTextFormat::TableCellBottomBorderStyle))
3252                 styleString += QLatin1String(" border-bottom-style:") + richtextBorderStyleToHtmlBorderStyle(cellFormat.bottomBorderStyle()) + QLatin1Char(';');
3253             if (cellFormat.hasProperty(QTextFormat::TableCellLeftBorderStyle))
3254                 styleString += QLatin1String(" border-left-style:") + richtextBorderStyleToHtmlBorderStyle(cellFormat.leftBorderStyle()) + QLatin1Char(';');
3255 
3256             if (!styleString.isEmpty())
3257                 html += QLatin1String(" style=\"") + styleString + QLatin1Char('\"');
3258 
3259             html += QLatin1Char('>');
3260 
3261             emitFrame(cell.begin());
3262 
3263             html += QLatin1String("</td>");
3264 
3265             defaultCharFormat = oldDefaultCharFormat;
3266         }
3267 
3268         html += QLatin1String("</tr>");
3269         if (headerRowCount > 0 && row == headerRowCount - 1)
3270             html += QLatin1String("</thead>");
3271     }
3272 
3273     html += QLatin1String("</table>");
3274 }
3275 
emitFrame(const QTextFrame::Iterator & frameIt)3276 void QTextHtmlExporter::emitFrame(const QTextFrame::Iterator &frameIt)
3277 {
3278     if (!frameIt.atEnd()) {
3279         QTextFrame::Iterator next = frameIt;
3280         ++next;
3281         if (next.atEnd()
3282             && frameIt.currentFrame() == nullptr
3283             && frameIt.parentFrame() != doc->rootFrame()
3284             && frameIt.currentBlock().begin().atEnd())
3285             return;
3286     }
3287 
3288     for (QTextFrame::Iterator it = frameIt;
3289          !it.atEnd(); ++it) {
3290         if (QTextFrame *f = it.currentFrame()) {
3291             if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
3292                 emitTable(table);
3293             } else {
3294                 emitTextFrame(f);
3295             }
3296         } else if (it.currentBlock().isValid()) {
3297             emitBlock(it.currentBlock());
3298         }
3299     }
3300 }
3301 
emitTextFrame(const QTextFrame * f)3302 void QTextHtmlExporter::emitTextFrame(const QTextFrame *f)
3303 {
3304     FrameType frameType = f->parentFrame() ? TextFrame : RootFrame;
3305 
3306     html += QLatin1String("\n<table");
3307     QTextFrameFormat format = f->frameFormat();
3308 
3309     if (format.hasProperty(QTextFormat::FrameBorder))
3310         emitAttribute("border", QString::number(format.border()));
3311 
3312     emitFrameStyle(format, frameType);
3313 
3314     emitTextLength("width", format.width());
3315     emitTextLength("height", format.height());
3316 
3317     // root frame's bcolor goes in the <body> tag
3318     if (frameType != RootFrame)
3319         emitBackgroundAttribute(format);
3320 
3321     html += QLatin1Char('>');
3322     html += QLatin1String("\n<tr>\n<td style=\"border: none;\">");
3323     emitFrame(f->begin());
3324     html += QLatin1String("</td></tr></table>");
3325 }
3326 
emitFrameStyle(const QTextFrameFormat & format,FrameType frameType)3327 void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType frameType)
3328 {
3329     QLatin1String styleAttribute(" style=\"");
3330     html += styleAttribute;
3331     const int originalHtmlLength = html.length();
3332 
3333     if (frameType == TextFrame)
3334         html += QLatin1String("-qt-table-type: frame;");
3335     else if (frameType == RootFrame)
3336         html += QLatin1String("-qt-table-type: root;");
3337 
3338     const QTextFrameFormat defaultFormat;
3339 
3340     emitFloatStyle(format.position(), OmitStyleTag);
3341     emitPageBreakPolicy(format.pageBreakPolicy());
3342 
3343     if (format.borderBrush() != defaultFormat.borderBrush()) {
3344         html += QLatin1String(" border-color:");
3345         html += colorValue(format.borderBrush().color());
3346         html += QLatin1Char(';');
3347     }
3348 
3349     if (format.borderStyle() != defaultFormat.borderStyle())
3350         emitBorderStyle(format.borderStyle());
3351 
3352     if (format.hasProperty(QTextFormat::FrameMargin)
3353         || format.hasProperty(QTextFormat::FrameLeftMargin)
3354         || format.hasProperty(QTextFormat::FrameRightMargin)
3355         || format.hasProperty(QTextFormat::FrameTopMargin)
3356         || format.hasProperty(QTextFormat::FrameBottomMargin))
3357         emitMargins(QString::number(format.topMargin()),
3358                     QString::number(format.bottomMargin()),
3359                     QString::number(format.leftMargin()),
3360                     QString::number(format.rightMargin()));
3361 
3362     if (format.property(QTextFormat::TableBorderCollapse).toBool())
3363         html += QLatin1String(" border-collapse:collapse;");
3364 
3365     if (html.length() == originalHtmlLength) // nothing emitted?
3366         html.chop(styleAttribute.size());
3367     else
3368         html += QLatin1Char('\"');
3369 }
3370 
3371 /*!
3372     Returns a string containing an HTML representation of the document.
3373 
3374     The \a encoding parameter specifies the value for the charset attribute
3375     in the html header. For example if 'utf-8' is specified then the
3376     beginning of the generated html will look like this:
3377     \snippet code/src_gui_text_qtextdocument.cpp 0
3378 
3379     If no encoding is specified then no such meta information is generated.
3380 
3381     If you later on convert the returned html string into a byte array for
3382     transmission over a network or when saving to disk you should specify
3383     the encoding you're going to use for the conversion to a byte array here.
3384 
3385     \sa {Supported HTML Subset}
3386 */
3387 #ifndef QT_NO_TEXTHTMLPARSER
toHtml(const QByteArray & encoding) const3388 QString QTextDocument::toHtml(const QByteArray &encoding) const
3389 {
3390     return QTextHtmlExporter(this).toHtml(encoding);
3391 }
3392 #endif // QT_NO_TEXTHTMLPARSER
3393 
3394 /*!
3395     \since 5.14
3396     Returns a string containing a Markdown representation of the document with
3397     the given \a features, or an empty string if writing fails for any reason.
3398 
3399     \sa setMarkdown
3400 */
3401 #if QT_CONFIG(textmarkdownwriter)
toMarkdown(QTextDocument::MarkdownFeatures features) const3402 QString QTextDocument::toMarkdown(QTextDocument::MarkdownFeatures features) const
3403 {
3404     QString ret;
3405     QTextStream s(&ret);
3406     QTextMarkdownWriter w(s, features);
3407     if (w.writeAll(this))
3408         return ret;
3409     return QString();
3410 }
3411 #endif
3412 
3413 /*!
3414     \since 5.14
3415     Replaces the entire contents of the document with the given
3416     Markdown-formatted text in the \a markdown string, with the given
3417     \a features supported.  By default, all supported GitHub-style
3418     Markdown features are included; pass \c MarkdownDialectCommonMark
3419     for a more basic parse.
3420 
3421     The Markdown formatting is respected as much as possible; for example,
3422     "*bold* text" will produce text where the first word has a font weight that
3423     gives it an emphasized appearance.
3424 
3425     Parsing of HTML included in the \a markdown string is handled in the same
3426     way as in \l setHtml; however, Markdown formatting inside HTML blocks is
3427     not supported.
3428 
3429     Some features of the parser can be enabled or disabled via the \a features
3430     argument:
3431 
3432     \value MarkdownNoHTML
3433            Any HTML tags in the Markdown text will be discarded
3434     \value MarkdownDialectCommonMark
3435            The parser supports only the features standardized by CommonMark
3436     \value MarkdownDialectGitHub
3437            The parser supports the GitHub dialect
3438 
3439     The default is \c MarkdownDialectGitHub.
3440 
3441     The undo/redo history is reset when this function is called.
3442 */
3443 #if QT_CONFIG(textmarkdownreader)
setMarkdown(const QString & markdown,QTextDocument::MarkdownFeatures features)3444 void QTextDocument::setMarkdown(const QString &markdown, QTextDocument::MarkdownFeatures features)
3445 {
3446     QTextMarkdownImporter(features).import(this, markdown);
3447 }
3448 #endif
3449 
3450 /*!
3451     Returns a vector of text formats for all the formats used in the document.
3452 */
allFormats() const3453 QVector<QTextFormat> QTextDocument::allFormats() const
3454 {
3455     Q_D(const QTextDocument);
3456     return d->formatCollection()->formats;
3457 }
3458 
3459 
3460 /*!
3461   \internal
3462 
3463   So that not all classes have to be friends of each other...
3464 */
docHandle() const3465 QTextDocumentPrivate *QTextDocument::docHandle() const
3466 {
3467     Q_D(const QTextDocument);
3468     return const_cast<QTextDocumentPrivate *>(d);
3469 }
3470 
3471 /*!
3472     \since 4.4
3473     \fn QTextDocument::undoCommandAdded()
3474 
3475     This signal is emitted  every time a new level of undo is added to the QTextDocument.
3476 */
3477 
3478 QT_END_NAMESPACE
3479