1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qtextdocument.h"
41#include <qtextformat.h>
42#include "qtextcursor_p.h"
43#include "qtextdocumentlayout_p.h"
44#include "qtextdocumentfragment.h"
45#include "qtextdocumentfragment_p.h"
46#include "qtexttable.h"
47#include "qtextlist.h"
48#include <qdebug.h>
49#include <qregexp.h>
50#if QT_CONFIG(regularexpression)
51#include <qregularexpression.h>
52#endif
53#include <qvarlengtharray.h>
54#if QT_CONFIG(textcodec)
55#include <qtextcodec.h>
56#endif
57#include <qthread.h>
58#include <qcoreapplication.h>
59#include <qmetaobject.h>
60
61#include "qtexthtmlparser_p.h"
62#include "qpainter.h"
63#include <qfile.h>
64#include <qfileinfo.h>
65#include <qdir.h>
66#include "qfont_p.h"
67#include "private/qdataurl_p.h"
68
69#include "qtextdocument_p.h"
70#include <private/qabstracttextdocumentlayout_p.h>
71#include "qpagedpaintdevice.h"
72#include "private/qpagedpaintdevice_p.h"
73#if QT_CONFIG(textmarkdownreader)
74#include <private/qtextmarkdownimporter_p.h>
75#endif
76#if QT_CONFIG(textmarkdownwriter)
77#include <private/qtextmarkdownwriter_p.h>
78#endif
79
80#include <limits.h>
81
82QT_BEGIN_NAMESPACE
83
84Q_CORE_EXPORT Q_DECL_CONST_FUNCTION unsigned int qt_int_sqrt(unsigned int n);
85
86
87/*!
88 Returns \c true if the string \a text is likely to be rich text;
89 otherwise returns \c false.
90
91 This function uses a fast and therefore simple heuristic. It
92 mainly checks whether there is something that looks like a tag
93 before the first line break. Although the result may be correct
94 for common cases, there is no guarantee.
95
96 This function is defined in the \c <QTextDocument> header file.
97*/
98bool Qt::mightBeRichText(const QString& text)
99{
100 if (text.isEmpty())
101 return false;
102 int start = 0;
103
104 while (start < text.length() && text.at(i: start).isSpace())
105 ++start;
106
107 // skip a leading <?xml ... ?> as for example with xhtml
108 if (text.midRef(position: start, n: 5).compare(s: QLatin1String("<?xml")) == 0) {
109 while (start < text.length()) {
110 if (text.at(i: start) == QLatin1Char('?')
111 && start + 2 < text.length()
112 && text.at(i: start + 1) == QLatin1Char('>')) {
113 start += 2;
114 break;
115 }
116 ++start;
117 }
118
119 while (start < text.length() && text.at(i: start).isSpace())
120 ++start;
121 }
122
123 if (text.midRef(position: start, n: 5).compare(s: QLatin1String("<!doc"), cs: Qt::CaseInsensitive) == 0)
124 return true;
125 int open = start;
126 while (open < text.length() && text.at(i: open) != QLatin1Char('<')
127 && text.at(i: open) != QLatin1Char('\n')) {
128 if (text.at(i: open) == QLatin1Char('&') && text.midRef(position: open + 1, n: 3) == QLatin1String("lt;"))
129 return true; // support desperate attempt of user to see <...>
130 ++open;
131 }
132 if (open < text.length() && text.at(i: open) == QLatin1Char('<')) {
133 const int close = text.indexOf(c: QLatin1Char('>'), from: 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(element: std::move(tag).toLower()) != -1;
148#else
149 return false;
150#endif // QT_NO_TEXTHTMLPARSER
151 }
152 }
153 return false;
154}
155
156/*!
157 Converts the plain text string \a plain to an HTML-formatted
158 paragraph while preserving most of its look.
159
160 \a mode defines how whitespace is handled.
161
162 This function is defined in the \c <QTextDocument> header file.
163
164 \sa QString::toHtmlEscaped(), mightBeRichText()
165*/
166QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode)
167{
168 int col = 0;
169 QString rich;
170 rich += QLatin1String("<p>");
171 for (int i = 0; i < plain.length(); ++i) {
172 if (plain[i] == QLatin1Char('\n')){
173 int c = 1;
174 while (i+1 < plain.length() && plain[i+1] == QLatin1Char('\n')) {
175 i++;
176 c++;
177 }
178 if (c == 1)
179 rich += QLatin1String("<br>\n");
180 else {
181 rich += QLatin1String("</p>\n");
182 while (--c > 1)
183 rich += QLatin1String("<br>\n");
184 rich += QLatin1String("<p>");
185 }
186 col = 0;
187 } else {
188 if (mode == Qt::WhiteSpacePre && plain[i] == QLatin1Char('\t')){
189 rich += QChar(0x00a0U);
190 ++col;
191 while (col % 8) {
192 rich += QChar(0x00a0U);
193 ++col;
194 }
195 }
196 else if (mode == Qt::WhiteSpacePre && plain[i].isSpace())
197 rich += QChar(0x00a0U);
198 else if (plain[i] == QLatin1Char('<'))
199 rich += QLatin1String("&lt;");
200 else if (plain[i] == QLatin1Char('>'))
201 rich += QLatin1String("&gt;");
202 else if (plain[i] == QLatin1Char('&'))
203 rich += QLatin1String("&amp;");
204 else
205 rich += plain[i];
206 ++col;
207 }
208 }
209 if (col != 0)
210 rich += QLatin1String("</p>");
211 return rich;
212}
213
214/*!
215 \fn QTextCodec *Qt::codecForHtml(const QByteArray &ba)
216 \internal
217
218 This function is defined in the \c <QTextDocument> header file.
219*/
220#if QT_CONFIG(textcodec)
221QTextCodec *Qt::codecForHtml(const QByteArray &ba)
222{
223 return QTextCodec::codecForHtml(ba);
224}
225#endif
226
227/*!
228 \class QTextDocument
229 \reentrant
230 \inmodule QtGui
231
232 \brief The QTextDocument class holds formatted text.
233
234 \ingroup richtext-processing
235
236
237 QTextDocument is a container for structured rich text documents, providing
238 support for styled text and various types of document elements, such as
239 lists, tables, frames, and images.
240 They can be created for use in a QTextEdit, or used independently.
241
242 Each document element is described by an associated format object. Each
243 format object is treated as a unique object by QTextDocuments, and can be
244 passed to objectForFormat() to obtain the document element that it is
245 applied to.
246
247 A QTextDocument can be edited programmatically using a QTextCursor, and
248 its contents can be examined by traversing the document structure. The
249 entire document structure is stored as a hierarchy of document elements
250 beneath the root frame, found with the rootFrame() function. Alternatively,
251 if you just want to iterate over the textual contents of the document you
252 can use begin(), end(), and findBlock() to retrieve text blocks that you
253 can examine and iterate over.
254
255 The layout of a document is determined by the documentLayout();
256 you can create your own QAbstractTextDocumentLayout subclass and
257 set it using setDocumentLayout() if you want to use your own
258 layout logic. The document's title and other meta-information can be
259 obtained by calling the metaInformation() function. For documents that
260 are exposed to users through the QTextEdit class, the document title
261 is also available via the QTextEdit::documentTitle() function.
262
263 The toPlainText() and toHtml() convenience functions allow you to retrieve the
264 contents of the document as plain text and HTML.
265 The document's text can be searched using the find() functions.
266
267 Undo/redo of operations performed on the document can be controlled using
268 the setUndoRedoEnabled() function. The undo/redo system can be controlled
269 by an editor widget through the undo() and redo() slots; the document also
270 provides contentsChanged(), undoAvailable(), and redoAvailable() signals
271 that inform connected editor widgets about the state of the undo/redo
272 system. The following are the undo/redo operations of a QTextDocument:
273
274 \list
275 \li Insertion or removal of characters. A sequence of insertions or removals
276 within the same text block are regarded as a single undo/redo operation.
277 \li Insertion or removal of text blocks. Sequences of insertion or removals
278 in a single operation (e.g., by selecting and then deleting text) are
279 regarded as a single undo/redo operation.
280 \li Text character format changes.
281 \li Text block format changes.
282 \li Text block group format changes.
283 \endlist
284
285 \sa QTextCursor, QTextEdit, {Rich Text Processing}, {Text Object Example}
286*/
287
288/*!
289 \property QTextDocument::defaultFont
290 \brief the default font used to display the document's text
291*/
292
293/*!
294 \property QTextDocument::defaultTextOption
295 \brief the default text option will be set on all \l{QTextLayout}s in the document.
296
297 When \l{QTextBlock}s are created, the defaultTextOption is set on their
298 QTextLayout. This allows setting global properties for the document such as the
299 default word wrap mode.
300 */
301
302/*!
303 Constructs an empty QTextDocument with the given \a parent.
304*/
305QTextDocument::QTextDocument(QObject *parent)
306 : QObject(*new QTextDocumentPrivate, parent)
307{
308 Q_D(QTextDocument);
309 d->init();
310}
311
312/*!
313 Constructs a QTextDocument containing the plain (unformatted) \a text
314 specified, and with the given \a parent.
315*/
316QTextDocument::QTextDocument(const QString &text, QObject *parent)
317 : QObject(*new QTextDocumentPrivate, parent)
318{
319 Q_D(QTextDocument);
320 d->init();
321 QTextCursor(this).insertText(text);
322}
323
324/*!
325 \internal
326*/
327QTextDocument::QTextDocument(QTextDocumentPrivate &dd, QObject *parent)
328 : QObject(dd, parent)
329{
330 Q_D(QTextDocument);
331 d->init();
332}
333
334/*!
335 Destroys the document.
336*/
337QTextDocument::~QTextDocument()
338{
339}
340
341
342/*!
343 Creates a new QTextDocument that is a copy of this text document. \a
344 parent is the parent of the returned text document.
345*/
346QTextDocument *QTextDocument::clone(QObject *parent) const
347{
348 Q_D(const QTextDocument);
349 QTextDocument *doc = new QTextDocument(parent);
350 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(fragment: 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*/
383bool 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*/
394void 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*/
413void QTextDocument::undo(QTextCursor *cursor)
414{
415 Q_D(QTextDocument);
416 const int pos = d->undoRedo(undo: 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*/
430void QTextDocument::redo(QTextCursor *cursor)
431{
432 Q_D(QTextDocument);
433 const int pos = d->undoRedo(undo: 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*/
458void QTextDocument::clearUndoRedoStacks(Stacks stacksToClear)
459{
460 Q_D(QTextDocument);
461 d->clearUndoRedoStacks(stacksToClear, emitSignals: true);
462}
463
464/*!
465 \overload
466
467*/
468void QTextDocument::undo()
469{
470 Q_D(QTextDocument);
471 d->undoRedo(undo: true);
472}
473
474/*!
475 \overload
476 Redoes the last editing operation on the document if \l{QTextDocument::isRedoAvailable()}{redo is available}.
477*/
478void QTextDocument::redo()
479{
480 Q_D(QTextDocument);
481 d->undoRedo(undo: false);
482}
483
484/*!
485 \internal
486
487 Appends a custom undo \a item to the undo stack.
488*/
489void 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*/
502void QTextDocument::setUndoRedoEnabled(bool enable)
503{
504 Q_D(QTextDocument);
505 d->enableUndoRedo(enable);
506}
507
508bool 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*/
535int QTextDocument::maximumBlockCount() const
536{
537 Q_D(const QTextDocument);
538 return d->maximumBlockCount;
539}
540
541void 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*/
556QTextOption 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*/
567void QTextDocument::setDefaultTextOption(const QTextOption &option)
568{
569 Q_D(QTextDocument);
570 d->defaultTextOption = option;
571 if (d->lout)
572 d->lout->documentChanged(from: 0, charsRemoved: 0, charsAdded: 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*/
591QUrl QTextDocument::baseUrl() const
592{
593 Q_D(const QTextDocument);
594 return d->baseUrl;
595}
596
597void 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(from: 0, charsRemoved: 0, charsAdded: 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*/
614Qt::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*/
625void 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*/
638void 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(from: d->docChangeFrom, charsRemoved: d->docChangeOldLength, charsAdded: 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
666void 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(from: 0, charsRemoved: 0, charsAdded: d->length());
674}
675
676bool 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*/
688void 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(painter: p, context: 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*/
721void 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
730qreal 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*/
744qreal QTextDocument::idealWidth() const
745{
746 if (QTextDocumentLayout *lout = qobject_cast<QTextDocumentLayout *>(object: 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*/
757qreal QTextDocument::documentMargin() const
758{
759 Q_D(const QTextDocument);
760 return d->documentMargin;
761}
762
763void 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(from: 0, charsRemoved: 0, charsAdded: 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*/
789qreal 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*/
806void 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(from: 0, charsRemoved: 0, charsAdded: 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*/
826void 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(n: (uint)(5 * size.height() * size.width() / 3));
837 setTextWidth(qMin(a: w, b: mw));
838
839 size = documentLayout()->documentSize();
840 if (w*3 < 5*size.height()) {
841 w = qt_int_sqrt(n: (uint)(2 * size.height() * size.width()));
842 setTextWidth(qMin(a: w, b: 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*/
865QSizeF 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*/
881int 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 */
896int QTextDocument::lineCount() const
897{
898 Q_D(const QTextDocument);
899 return d->blockMap().length(field: 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 */
912int 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 */
926QChar 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(a: 0, b: pos - fragIt.position());
934 return d->text.at(i: 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
954void 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(styleSheet: &d->parsedDefaultStyleSheet);
962}
963
964QString 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*/
1052bool 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*/
1063bool 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*/
1075int 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*/
1087int 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 */
1102int 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*/
1116void 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*/
1125QAbstractTextDocumentLayout *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*/
1142QString 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*/
1160void 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*/
1181QString 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*/
1204QString 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*/
1236void QTextDocument::setPlainText(const QString &text)
1237{
1238 Q_D(QTextDocument);
1239 bool previousState = d->isUndoRedoEnabled();
1240 d->enableUndoRedo(enable: false);
1241 d->beginEditBlock();
1242 d->clear();
1243 QTextCursor(this).insertText(text);
1244 d->endEditBlock();
1245 d->enableUndoRedo(enable: 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
1266void QTextDocument::setHtml(const QString &html)
1267{
1268 Q_D(QTextDocument);
1269 bool previousState = d->isUndoRedoEnabled();
1270 d->enableUndoRedo(enable: false);
1271 d->beginEditBlock();
1272 d->clear();
1273 QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import();
1274 d->endEditBlock();
1275 d->enableUndoRedo(enable: 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
1305static 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(before: QChar::Nbsp, after: 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(s: expression, from: offset, cs: sensitivity) : text.indexOf(s: expression, from: offset, cs: 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(i: start - 1).isLetterOrNumber())
1323 || (end != text.length() && text.at(i: 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(d: block.docHandle(), pos: block.position() + idx);
1332 cursor->setPosition(pos: cursor->position() + expression.length(), mode: 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*/
1354QTextCursor 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, expression: subString, offset: blockOffset, options, cursor: &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, expression: subString, offset: blockOffset, options, cursor: &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*/
1410QTextCursor 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, from: pos, options);
1421}
1422
1423
1424#ifndef QT_NO_REGEXP
1425static 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(before: QChar::Nbsp, after: QLatin1Char(' '));
1431
1432 int idx = -1;
1433 while (offset >=0 && offset <= text.length()) {
1434 idx = (options & QTextDocument::FindBackward) ?
1435 expr.lastIndexIn(str: text, offset) : expr.indexIn(str: 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(i: start - 1).isLetterOrNumber())
1443 || (end != text.length() && text.at(i: 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(d: block.docHandle(), pos: block.position() + idx);
1452 cursor->setPosition(pos: cursor->position() + expr.matchedLength(), mode: 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*/
1475QTextCursor 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, expression: expr, offset: blockOffset, options, cursor: &cursor))
1497 return cursor;
1498 block = block.next();
1499 blockOffset = 0;
1500 }
1501 } else {
1502 while (block.isValid()) {
1503 if (findInBlock(block, expression: expr, offset: blockOffset, options, cursor: &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*/
1533QTextCursor 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, from: pos, options);
1543}
1544#endif // QT_REGEXP
1545
1546#if QT_CONFIG(regularexpression)
1547static 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(before: QChar::Nbsp, after: QLatin1Char(' '));
1552 QRegularExpressionMatch match;
1553 int idx = -1;
1554
1555 while (offset >= 0 && offset <= text.length()) {
1556 idx = (options & QTextDocument::FindBackward) ?
1557 text.lastIndexOf(re: expr, from: offset, rmatch: &match) : text.indexOf(re: expr, from: offset, rmatch: &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(i: start - 1).isLetterOrNumber())
1565 || (end != text.length() && text.at(i: 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(d: block.docHandle(), pos: block.position() + idx);
1574 cursor->setPosition(pos: cursor->position() + match.capturedLength(), mode: 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*/
1596QTextCursor 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, expr: expression, offset: blockOffset, options, cursor: &cursor))
1625 return cursor;
1626 block = block.next();
1627 blockOffset = 0;
1628 }
1629 } else {
1630 while (block.isValid()) {
1631 if (findInBlock(block, expr: expression, offset: blockOffset, options, cursor: &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*/
1660QTextCursor 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, from: 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*/
1682QTextObject *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*/
1700QTextFrame *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*/
1709QTextFrame *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*/
1718QTextObject *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*/
1727QTextObject *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*/
1737QTextBlock QTextDocument::findBlock(int pos) const
1738{
1739 Q_D(const QTextDocument);
1740 return QTextBlock(docHandle(), d->blockMap().findNode(k: pos));
1741}
1742
1743/*!
1744 \since 4.4
1745 Returns the text block with the specified \a blockNumber.
1746
1747 \sa QTextBlock::blockNumber()
1748*/
1749QTextBlock QTextDocument::findBlockByNumber(int blockNumber) const
1750{
1751 Q_D(const QTextDocument);
1752 return QTextBlock(docHandle(), d->blockMap().findNode(k: blockNumber, field: 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*/
1761QTextBlock QTextDocument::findBlockByLineNumber(int lineNumber) const
1762{
1763 Q_D(const QTextDocument);
1764 return QTextBlock(docHandle(), d->blockMap().findNode(k: lineNumber, field: 2));
1765}
1766
1767/*!
1768 Returns the document's first text block.
1769
1770 \sa firstBlock()
1771*/
1772QTextBlock 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*/
1790QTextBlock 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*/
1799QTextBlock 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*/
1809QTextBlock 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
1829void QTextDocument::setPageSize(const QSizeF &size)
1830{
1831 Q_D(QTextDocument);
1832 d->pageSize = size;
1833 if (d->lout)
1834 d->lout->documentChanged(from: 0, charsRemoved: 0, charsAdded: d->length());
1835}
1836
1837QSizeF 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*/
1846int QTextDocument::pageCount() const
1847{
1848 return documentLayout()->pageCount();
1849}
1850
1851/*!
1852 Sets the default \a font to use in the document layout.
1853*/
1854void QTextDocument::setDefaultFont(const QFont &font)
1855{
1856 Q_D(QTextDocument);
1857 d->setDefaultFont(font);
1858 if (d->lout)
1859 d->lout->documentChanged(from: 0, charsRemoved: 0, charsAdded: d->length());
1860}
1861
1862/*!
1863 Returns the default font to be used in the document layout.
1864*/
1865QFont 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
1894bool QTextDocument::isModified() const
1895{
1896 return docHandle()->isModified();
1897}
1898
1899void QTextDocument::setModified(bool m)
1900{
1901 docHandle()->setModified(m);
1902}
1903
1904#ifndef QT_NO_PRINTER
1905static void printPage(int index, QPainter *painter, const QTextDocument *doc, const QRectF &body, const QPointF &pageNumberPos)
1906{
1907 painter->save();
1908 painter->translate(dx: body.left(), dy: 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(acr: QPalette::Text, acolor: Qt::black);
1921
1922 layout->draw(painter, context: 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(x: qRound(d: pageNumberPos.x() - painter->fontMetrics().horizontalAdvance(pageString)),
1930 y: qRound(d: pageNumberPos.y() + view.top()),
1931 s: 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
1955void 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(pd: printer);
1966
1967 // ### set page size to paginated size?
1968 QMarginsF m = printer->pageLayout().margins(units: 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(margins: m, units: 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(sx: dpiScaleX, sy: 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(sx: printerPageSize.width() / scaledPageSize.width(),
2015 sy: printerPageSize.height() / scaledPageSize.height());
2016 } else {
2017 doc = clone(parent: const_cast<QTextDocument *>(this));
2018 clonedDoc.reset(other: 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(a: 1, b: fromPage);
2061 toPage = qMin(a: doc->pageCount(), b: 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(index: page, painter: &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*/
2129QVariant QTextDocument::resource(int type, const QUrl &name) const
2130{
2131 Q_D(const QTextDocument);
2132 const QUrl url = d->baseUrl.resolved(relative: name);
2133 QVariant r = d->resources.value(akey: url);
2134 if (!r.isValid()) {
2135 r = d->cachedResources.value(akey: url);
2136 if (!r.isValid())
2137 r = const_cast<QTextDocument *>(this)->loadResource(type, name: 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*/
2160void QTextDocument::addResource(int type, const QUrl &name, const QVariant &resource)
2161{
2162 Q_UNUSED(type);
2163 Q_D(QTextDocument);
2164 d->resources.insert(akey: name, avalue: 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*/
2184QVariant 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(method: "loadResource(int,QUrl)");
2193 if (index >= 0) {
2194 QMetaMethod loader = me->method(index);
2195 loader.invoke(object: 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(other: QLatin1String("data"), cs: Qt::CaseInsensitive) == 0) {
2201 QString mimetype;
2202 QByteArray payload;
2203 if (qDecodeDataUrl(url: name, mimeType&: mimetype, payload))
2204 r = payload;
2205 }
2206
2207 // if resource was not loaded try to load it here
2208 if (!qobject_cast<QTextDocument *>(object: 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(relative: 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(localfile: fi.absolutePath() + QDir::separator()).resolved(relative: 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(flags: 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(data: r.toByteArray());
2248 if (!image.isNull())
2249 r = image;
2250 } else {
2251 QPixmap pm;
2252 pm.loadFromData(buf: r.toByteArray());
2253 if (!pm.isNull())
2254 r = pm;
2255 }
2256 }
2257 d->cachedResources.insert(akey: name, avalue: r);
2258 }
2259 return r;
2260}
2261
2262static 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(propertyId: it.key()))
2270 diff.clearProperty(propertyId: it.key());
2271
2272 return diff;
2273}
2274
2275static 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: 'f', prec: 6).remove(rx: QRegExp(QLatin1String("\\.?0*$")));
2283 result = QString::fromLatin1(str: "rgba(%1,%2,%3,%4)").arg(a: color.red())
2284 .arg(a: color.green())
2285 .arg(a: color.blue())
2286 .arg(a: alphaValue);
2287 } else {
2288 result = QLatin1String("transparent");
2289 }
2290
2291 return result;
2292}
2293
2294QTextHtmlExporter::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(propertyId: QTextFormat::FontUnderline);
2301 defaultCharFormat.clearProperty(propertyId: QTextFormat::FontOverline);
2302 defaultCharFormat.clearProperty(propertyId: QTextFormat::FontStrikeOut);
2303 defaultCharFormat.clearProperty(propertyId: QTextFormat::TextUnderlineStyle);
2304}
2305
2306static QStringList resolvedFontFamilies(const QTextCharFormat &format)
2307{
2308 QStringList fontFamilies = format.fontFamilies().toStringList();
2309 const QString mainFontFamily = format.fontFamily();
2310 if (!mainFontFamily.isEmpty() && !fontFamilies.contains(str: mainFontFamily))
2311 fontFamilies.append(t: 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*/
2320QString 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(asize: doc->docHandle()->length());
2326
2327 fragmentMarkers = (mode == ExportFragment);
2328
2329 if (!encoding.isEmpty())
2330 html += QString::fromLatin1(str: "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(a: QString::fromLatin1(str: encoding));
2331
2332 QString title = doc->metaInformation(info: QTextDocument::DocumentTitle);
2333 if (!title.isEmpty())
2334 html += QString::fromLatin1(str: "<title>") + title + QString::fromLatin1(str: "</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(families: resolvedFontFamilies(format: defaultCharFormat));
2344
2345 if (defaultCharFormat.hasProperty(propertyId: QTextFormat::FontPointSize)) {
2346 html += QLatin1String(" font-size:");
2347 html += QString::number(defaultCharFormat.fontPointSize());
2348 html += QLatin1String("pt;");
2349 } else if (defaultCharFormat.hasProperty(propertyId: QTextFormat::FontPixelSize)) {
2350 html += QLatin1String(" font-size:");
2351 html += QString::number(defaultCharFormat.intProperty(propertyId: 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(propertyId: 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(propertyId: 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(format: fmt);
2388
2389 } else {
2390 defaultCharFormat = QTextCharFormat();
2391 }
2392 html += QLatin1Char('>');
2393
2394 QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat();
2395 rootFmt.clearProperty(propertyId: QTextFormat::BackgroundBrush);
2396
2397 QTextFrameFormat defaultFmt;
2398 defaultFmt.setMargin(doc->documentMargin());
2399
2400 if (rootFmt == defaultFmt)
2401 emitFrame(frameIt: doc->rootFrame()->begin());
2402 else
2403 emitTextFrame(frame: doc->rootFrame());
2404
2405 html += QLatin1String("</body></html>");
2406 return html;
2407}
2408
2409void 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
2418bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
2419{
2420 bool attributesEmitted = false;
2421
2422 {
2423 const QStringList families = resolvedFontFamilies(format);
2424 if (!families.isEmpty() && families != resolvedFontFamilies(format: defaultCharFormat)) {
2425 emitFontFamily(families);
2426 attributesEmitted = true;
2427 }
2428 }
2429
2430 if (format.hasProperty(propertyId: 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(propertyId: 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(propertyId: 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(propertyId: QTextFormat::FontPixelSize)) {
2460 html += QLatin1String(" font-size:");
2461 html += QString::number(format.intProperty(propertyId: QTextFormat::FontPixelSize));
2462 html += QLatin1String("px;");
2463 attributesEmitted = true;
2464 }
2465
2466 if (format.hasProperty(propertyId: 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(propertyId: 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(propertyId: QTextFormat::FontUnderline) || format.hasProperty(propertyId: 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(propertyId: 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(propertyId: 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(n: 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(color: 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(color: 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
2591void 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
2607void 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
2619void 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
2640static 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
2671void 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
2680void 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
2689void 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(c: 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
2710void 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
2729void 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(c: 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(n: 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(propertyId: QTextFormat::ImageName))
2773 emitAttribute(attribute: "src", value: imgFmt.name());
2774
2775 if (imgFmt.hasProperty(propertyId: QTextFormat::ImageAltText))
2776 emitAttribute(attribute: "alt", value: imgFmt.stringProperty(propertyId: QTextFormat::ImageAltText));
2777
2778 if (imgFmt.hasProperty(propertyId: QTextFormat::ImageTitle))
2779 emitAttribute(attribute: "title", value: imgFmt.stringProperty(propertyId: QTextFormat::ImageTitle));
2780
2781 if (imgFmt.hasProperty(propertyId: QTextFormat::ImageWidth))
2782 emitAttribute(attribute: "width", value: QString::number(imgFmt.width()));
2783
2784 if (imgFmt.hasProperty(propertyId: QTextFormat::ImageHeight))
2785 emitAttribute(attribute: "height", value: 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 *>(object: doc->objectForFormat(f: imgFmt)))
2793 emitFloatStyle(pos: 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(str: "[\\na]");
2804 forcedLineBreakRegExp[3] = QChar::LineSeparator;
2805 // space in BR on purpose for compatibility with old-fashioned browsers
2806 html += txt.replace(rx: QRegExp(forcedLineBreakRegExp), after: QLatin1String("<br />"));
2807 }
2808
2809 if (attributesEmitted)
2810 html += QLatin1String("</span>");
2811
2812 if (closeAnchor)
2813 html += QLatin1String("</a>");
2814}
2815
2816static 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
2825void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block)
2826{
2827 QTextBlockFormat format = block.blockFormat();
2828 emitAlignment(align: 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(top: QString::number(format.topMargin()),
2844 bottom: QString::number(format.bottomMargin()),
2845 left: QString::number(format.leftMargin()),
2846 right: 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(policy: 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(from: defaultCharFormat, to: blockCharFmt).toCharFormat();
2890 }
2891
2892 diff.clearProperty(propertyId: QTextFormat::BackgroundBrush);
2893 if (format.hasProperty(propertyId: QTextFormat::BackgroundBrush)) {
2894 QBrush bg = format.background();
2895 if (bg.style() != Qt::NoBrush)
2896 diff.setProperty(propertyId: QTextFormat::BackgroundBrush, value: format.property(propertyId: QTextFormat::BackgroundBrush));
2897 }
2898
2899 if (!diff.properties().isEmpty())
2900 emitCharFormatStyle(format: diff);
2901
2902 html += QLatin1Char('"');
2903
2904}
2905
2906void 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(pos: p);
2914 QChar ch = doc->docHandle()->buffer().at(i: 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(str: "margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px;");
2944
2945 if (format.hasProperty(propertyId: QTextFormat::ListIndent)) {
2946 styleString += QLatin1String(" -qt-list-indent: ");
2947 styleString += QString::number(format.indent());
2948 styleString += QLatin1Char(';');
2949 }
2950
2951 if (format.hasProperty(propertyId: QTextFormat::ListNumberPrefix)) {
2952 QString numberPrefix = format.numberPrefix();
2953 numberPrefix.replace(c: QLatin1Char('"'), after: QLatin1String("\\22"));
2954 numberPrefix.replace(c: QLatin1Char('\''), after: 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(propertyId: QTextFormat::ListNumberSuffix)) {
2963 if (format.numberSuffix() != QLatin1String(".")) { // this is our default
2964 QString numberSuffix = format.numberSuffix();
2965 numberSuffix.replace(c: QLatin1Char('"'), after: QLatin1String("\\22"));
2966 numberSuffix.replace(c: QLatin1Char('\''), after: 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(from: defaultCharFormat, to: block.charFormat()).toCharFormat();
2983 if (!blockFmt.properties().isEmpty()) {
2984 html += QLatin1String(" style=\"");
2985 emitCharFormatStyle(format: blockFmt);
2986 html += QLatin1Char('\"');
2987
2988 defaultCharFormat.merge(other: block.charFormat());
2989 }
2990 }
2991
2992 const QTextBlockFormat blockFormat = block.blockFormat();
2993 if (blockFormat.hasProperty(propertyId: QTextFormat::BlockTrailingHorizontalRulerWidth)) {
2994 html += QLatin1String("<hr");
2995
2996 QTextLength width = blockFormat.lengthProperty(propertyId: QTextFormat::BlockTrailingHorizontalRulerWidth);
2997 if (width.type() != QTextLength::VariableLength)
2998 emitTextLength(attribute: "width", length: 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(fragment: it.fragment());
3031
3032 if (fragmentMarkers && block.position() + block.length() == doc->docHandle()->length())
3033 html += QLatin1String("<!--EndFragment-->");
3034
3035 QString closeTags;
3036
3037 if (pre)
3038 html += QLatin1String("</pre>");
3039 else if (list)
3040 closeTags += QLatin1String("</li>");
3041 else {
3042 int headingLevel = blockFormat.headingLevel();
3043 if (headingLevel > 0 && headingLevel <= 6)
3044 html += QLatin1String("</h") + QString::number(headingLevel) + QLatin1Char('>');
3045 else
3046 html += QLatin1String("</p>");
3047 }
3048
3049 if (list) {
3050 if (list->itemNumber(block) == list->count() - 1) { // last item? close list
3051 if (isOrderedList(style: list->format().style()))
3052 closeTags += QLatin1String("</ol>");
3053 else
3054 closeTags += QLatin1String("</ul>");
3055 }
3056 const QTextBlock nextBlock = block.next();
3057 // If the next block is the beginning of a new deeper nested list, then we don't
3058 // want to close the current list item just yet. This should be closed when this
3059 // item is fully finished
3060 if (nextBlock.isValid() && nextBlock.textList() &&
3061 nextBlock.textList()->itemNumber(nextBlock) == 0 &&
3062 nextBlock.textList()->format().indent() > list->format().indent()) {
3063 QString lastTag;
3064 if (!closingTags.isEmpty() && list->itemNumber(block) == list->count() - 1)
3065 lastTag = closingTags.takeLast();
3066 lastTag.prepend(s: closeTags);
3067 closingTags << lastTag;
3068 } else if (list->itemNumber(block) == list->count() - 1) {
3069 // If we are at the end of the list now then we can add in the closing tags for that
3070 // current block
3071 html += closeTags;
3072 if (!closingTags.isEmpty())
3073 html += closingTags.takeLast();
3074 } else {
3075 html += closeTags;
3076 }
3077 }
3078
3079 defaultCharFormat = oldDefaultCharFormat;
3080}
3081
3082extern bool qHasPixmapTexture(const QBrush& brush);
3083
3084QString QTextHtmlExporter::findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap)
3085{
3086 QString url;
3087 if (!doc)
3088 return url;
3089
3090 if (QTextDocument *parent = qobject_cast<QTextDocument *>(object: doc->parent()))
3091 return findUrlForImage(doc: parent, cacheKey, isPixmap);
3092
3093 if (doc && doc->docHandle()) {
3094 QTextDocumentPrivate *priv = doc->docHandle();
3095 QMap<QUrl, QVariant>::const_iterator it = priv->cachedResources.constBegin();
3096 for (; it != priv->cachedResources.constEnd(); ++it) {
3097
3098 const QVariant &v = it.value();
3099 if (v.userType() == QMetaType::QImage && !isPixmap) {
3100 if (qvariant_cast<QImage>(v).cacheKey() == cacheKey)
3101 break;
3102 }
3103
3104 if (v.userType() == QMetaType::QPixmap && isPixmap) {
3105 if (qvariant_cast<QPixmap>(v).cacheKey() == cacheKey)
3106 break;
3107 }
3108 }
3109
3110 if (it != priv->cachedResources.constEnd())
3111 url = it.key().toString();
3112 }
3113
3114 return url;
3115}
3116
3117void QTextDocumentPrivate::mergeCachedResources(const QTextDocumentPrivate *priv)
3118{
3119 if (!priv)
3120 return;
3121
3122 cachedResources.insert(map: priv->cachedResources);
3123}
3124
3125void QTextHtmlExporter::emitBackgroundAttribute(const QTextFormat &format)
3126{
3127 if (format.hasProperty(propertyId: QTextFormat::BackgroundImageUrl)) {
3128 QString url = format.property(propertyId: QTextFormat::BackgroundImageUrl).toString();
3129 emitAttribute(attribute: "background", value: url);
3130 } else {
3131 const QBrush &brush = format.background();
3132 if (brush.style() == Qt::SolidPattern) {
3133 emitAttribute(attribute: "bgcolor", value: colorValue(color: brush.color()));
3134 } else if (brush.style() == Qt::TexturePattern) {
3135 const bool isPixmap = qHasPixmapTexture(brush);
3136 const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
3137
3138 const QString url = findUrlForImage(doc, cacheKey, isPixmap);
3139
3140 if (!url.isEmpty())
3141 emitAttribute(attribute: "background", value: url);
3142 }
3143 }
3144}
3145
3146void QTextHtmlExporter::emitTable(const QTextTable *table)
3147{
3148 QTextTableFormat format = table->format();
3149
3150 html += QLatin1String("\n<table");
3151
3152 if (format.hasProperty(propertyId: QTextFormat::FrameBorder))
3153 emitAttribute(attribute: "border", value: QString::number(format.border()));
3154
3155 emitFrameStyle(format, frameType: TableFrame);
3156
3157 emitAlignment(align: format.alignment());
3158 emitTextLength(attribute: "width", length: format.width());
3159
3160 if (format.hasProperty(propertyId: QTextFormat::TableCellSpacing))
3161 emitAttribute(attribute: "cellspacing", value: QString::number(format.cellSpacing()));
3162 if (format.hasProperty(propertyId: QTextFormat::TableCellPadding))
3163 emitAttribute(attribute: "cellpadding", value: QString::number(format.cellPadding()));
3164
3165 emitBackgroundAttribute(format);
3166
3167 html += QLatin1Char('>');
3168
3169 const int rows = table->rows();
3170 const int columns = table->columns();
3171
3172 QVector<QTextLength> columnWidths = format.columnWidthConstraints();
3173 if (columnWidths.isEmpty()) {
3174 columnWidths.resize(asize: columns);
3175 columnWidths.fill(from: QTextLength());
3176 }
3177 Q_ASSERT(columnWidths.count() == columns);
3178
3179 QVarLengthArray<bool> widthEmittedForColumn(columns);
3180 for (int i = 0; i < columns; ++i)
3181 widthEmittedForColumn[i] = false;
3182
3183 const int headerRowCount = qMin(a: format.headerRowCount(), b: rows);
3184 if (headerRowCount > 0)
3185 html += QLatin1String("<thead>");
3186
3187 for (int row = 0; row < rows; ++row) {
3188 html += QLatin1String("\n<tr>");
3189
3190 for (int col = 0; col < columns; ++col) {
3191 const QTextTableCell cell = table->cellAt(row, col);
3192
3193 // for col/rowspans
3194 if (cell.row() != row)
3195 continue;
3196
3197 if (cell.column() != col)
3198 continue;
3199
3200 html += QLatin1String("\n<td");
3201
3202 if (!widthEmittedForColumn[col] && cell.columnSpan() == 1) {
3203 emitTextLength(attribute: "width", length: columnWidths.at(i: col));
3204 widthEmittedForColumn[col] = true;
3205 }
3206
3207 if (cell.columnSpan() > 1)
3208 emitAttribute(attribute: "colspan", value: QString::number(cell.columnSpan()));
3209
3210 if (cell.rowSpan() > 1)
3211 emitAttribute(attribute: "rowspan", value: QString::number(cell.rowSpan()));
3212
3213 const QTextTableCellFormat cellFormat = cell.format().toTableCellFormat();
3214 emitBackgroundAttribute(format: cellFormat);
3215
3216 QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
3217
3218 QTextCharFormat::VerticalAlignment valign = cellFormat.verticalAlignment();
3219
3220 QString styleString;
3221 if (valign >= QTextCharFormat::AlignMiddle && valign <= QTextCharFormat::AlignBottom) {
3222 styleString += QLatin1String(" vertical-align:");
3223 switch (valign) {
3224 case QTextCharFormat::AlignMiddle:
3225 styleString += QLatin1String("middle");
3226 break;
3227 case QTextCharFormat::AlignTop:
3228 styleString += QLatin1String("top");
3229 break;
3230 case QTextCharFormat::AlignBottom:
3231 styleString += QLatin1String("bottom");
3232 break;
3233 default:
3234 break;
3235 }
3236 styleString += QLatin1Char(';');
3237
3238 QTextCharFormat temp;
3239 temp.setVerticalAlignment(valign);
3240 defaultCharFormat.merge(other: temp);
3241 }
3242
3243 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellLeftPadding))
3244 styleString += QLatin1String(" padding-left:") + QString::number(cellFormat.leftPadding()) + QLatin1Char(';');
3245 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellRightPadding))
3246 styleString += QLatin1String(" padding-right:") + QString::number(cellFormat.rightPadding()) + QLatin1Char(';');
3247 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellTopPadding))
3248 styleString += QLatin1String(" padding-top:") + QString::number(cellFormat.topPadding()) + QLatin1Char(';');
3249 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellBottomPadding))
3250 styleString += QLatin1String(" padding-bottom:") + QString::number(cellFormat.bottomPadding()) + QLatin1Char(';');
3251
3252 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellTopBorder))
3253 styleString += QLatin1String(" border-top:") + QString::number(cellFormat.topBorder()) + QLatin1String("px;");
3254 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellRightBorder))
3255 styleString += QLatin1String(" border-right:") + QString::number(cellFormat.rightBorder()) + QLatin1String("px;");
3256 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellBottomBorder))
3257 styleString += QLatin1String(" border-bottom:") + QString::number(cellFormat.bottomBorder()) + QLatin1String("px;");
3258 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellLeftBorder))
3259 styleString += QLatin1String(" border-left:") + QString::number(cellFormat.leftBorder()) + QLatin1String("px;");
3260
3261 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellTopBorderBrush))
3262 styleString += QLatin1String(" border-top-color:") + cellFormat.topBorderBrush().color().name() + QLatin1Char(';');
3263 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellRightBorderBrush))
3264 styleString += QLatin1String(" border-right-color:") + cellFormat.rightBorderBrush().color().name() + QLatin1Char(';');
3265 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellBottomBorderBrush))
3266 styleString += QLatin1String(" border-bottom-color:") + cellFormat.bottomBorderBrush().color().name() + QLatin1Char(';');
3267 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellLeftBorderBrush))
3268 styleString += QLatin1String(" border-left-color:") + cellFormat.leftBorderBrush().color().name() + QLatin1Char(';');
3269
3270 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellTopBorderStyle))
3271 styleString += QLatin1String(" border-top-style:") + richtextBorderStyleToHtmlBorderStyle(style: cellFormat.topBorderStyle()) + QLatin1Char(';');
3272 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellRightBorderStyle))
3273 styleString += QLatin1String(" border-right-style:") + richtextBorderStyleToHtmlBorderStyle(style: cellFormat.rightBorderStyle()) + QLatin1Char(';');
3274 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellBottomBorderStyle))
3275 styleString += QLatin1String(" border-bottom-style:") + richtextBorderStyleToHtmlBorderStyle(style: cellFormat.bottomBorderStyle()) + QLatin1Char(';');
3276 if (cellFormat.hasProperty(propertyId: QTextFormat::TableCellLeftBorderStyle))
3277 styleString += QLatin1String(" border-left-style:") + richtextBorderStyleToHtmlBorderStyle(style: cellFormat.leftBorderStyle()) + QLatin1Char(';');
3278
3279 if (!styleString.isEmpty())
3280 html += QLatin1String(" style=\"") + styleString + QLatin1Char('\"');
3281
3282 html += QLatin1Char('>');
3283
3284 emitFrame(frameIt: cell.begin());
3285
3286 html += QLatin1String("</td>");
3287
3288 defaultCharFormat = oldDefaultCharFormat;
3289 }
3290
3291 html += QLatin1String("</tr>");
3292 if (headerRowCount > 0 && row == headerRowCount - 1)
3293 html += QLatin1String("</thead>");
3294 }
3295
3296 html += QLatin1String("</table>");
3297}
3298
3299void QTextHtmlExporter::emitFrame(const QTextFrame::Iterator &frameIt)
3300{
3301 if (!frameIt.atEnd()) {
3302 QTextFrame::Iterator next = frameIt;
3303 ++next;
3304 if (next.atEnd()
3305 && frameIt.currentFrame() == nullptr
3306 && frameIt.parentFrame() != doc->rootFrame()
3307 && frameIt.currentBlock().begin().atEnd())
3308 return;
3309 }
3310
3311 for (QTextFrame::Iterator it = frameIt;
3312 !it.atEnd(); ++it) {
3313 if (QTextFrame *f = it.currentFrame()) {
3314 if (QTextTable *table = qobject_cast<QTextTable *>(object: f)) {
3315 emitTable(table);
3316 } else {
3317 emitTextFrame(frame: f);
3318 }
3319 } else if (it.currentBlock().isValid()) {
3320 emitBlock(block: it.currentBlock());
3321 }
3322 }
3323}
3324
3325void QTextHtmlExporter::emitTextFrame(const QTextFrame *f)
3326{
3327 FrameType frameType = f->parentFrame() ? TextFrame : RootFrame;
3328
3329 html += QLatin1String("\n<table");
3330 QTextFrameFormat format = f->frameFormat();
3331
3332 if (format.hasProperty(propertyId: QTextFormat::FrameBorder))
3333 emitAttribute(attribute: "border", value: QString::number(format.border()));
3334
3335 emitFrameStyle(format, frameType);
3336
3337 emitTextLength(attribute: "width", length: format.width());
3338 emitTextLength(attribute: "height", length: format.height());
3339
3340 // root frame's bcolor goes in the <body> tag
3341 if (frameType != RootFrame)
3342 emitBackgroundAttribute(format);
3343
3344 html += QLatin1Char('>');
3345 html += QLatin1String("\n<tr>\n<td style=\"border: none;\">");
3346 emitFrame(frameIt: f->begin());
3347 html += QLatin1String("</td></tr></table>");
3348}
3349
3350void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType frameType)
3351{
3352 QLatin1String styleAttribute(" style=\"");
3353 html += styleAttribute;
3354 const int originalHtmlLength = html.length();
3355
3356 if (frameType == TextFrame)
3357 html += QLatin1String("-qt-table-type: frame;");
3358 else if (frameType == RootFrame)
3359 html += QLatin1String("-qt-table-type: root;");
3360
3361 const QTextFrameFormat defaultFormat;
3362
3363 emitFloatStyle(pos: format.position(), mode: OmitStyleTag);
3364 emitPageBreakPolicy(policy: format.pageBreakPolicy());
3365
3366 if (format.borderBrush() != defaultFormat.borderBrush()) {
3367 html += QLatin1String(" border-color:");
3368 html += colorValue(color: format.borderBrush().color());
3369 html += QLatin1Char(';');
3370 }
3371
3372 if (format.borderStyle() != defaultFormat.borderStyle())
3373 emitBorderStyle(style: format.borderStyle());
3374
3375 if (format.hasProperty(propertyId: QTextFormat::FrameMargin)
3376 || format.hasProperty(propertyId: QTextFormat::FrameLeftMargin)
3377 || format.hasProperty(propertyId: QTextFormat::FrameRightMargin)
3378 || format.hasProperty(propertyId: QTextFormat::FrameTopMargin)
3379 || format.hasProperty(propertyId: QTextFormat::FrameBottomMargin))
3380 emitMargins(top: QString::number(format.topMargin()),
3381 bottom: QString::number(format.bottomMargin()),
3382 left: QString::number(format.leftMargin()),
3383 right: QString::number(format.rightMargin()));
3384
3385 if (format.property(propertyId: QTextFormat::TableBorderCollapse).toBool())
3386 html += QLatin1String(" border-collapse:collapse;");
3387
3388 if (html.length() == originalHtmlLength) // nothing emitted?
3389 html.chop(n: styleAttribute.size());
3390 else
3391 html += QLatin1Char('\"');
3392}
3393
3394/*!
3395 Returns a string containing an HTML representation of the document.
3396
3397 The \a encoding parameter specifies the value for the charset attribute
3398 in the html header. For example if 'utf-8' is specified then the
3399 beginning of the generated html will look like this:
3400 \snippet code/src_gui_text_qtextdocument.cpp 0
3401
3402 If no encoding is specified then no such meta information is generated.
3403
3404 If you later on convert the returned html string into a byte array for
3405 transmission over a network or when saving to disk you should specify
3406 the encoding you're going to use for the conversion to a byte array here.
3407
3408 \sa {Supported HTML Subset}
3409*/
3410#ifndef QT_NO_TEXTHTMLPARSER
3411QString QTextDocument::toHtml(const QByteArray &encoding) const
3412{
3413 return QTextHtmlExporter(this).toHtml(encoding);
3414}
3415#endif // QT_NO_TEXTHTMLPARSER
3416
3417/*!
3418 \since 5.14
3419 Returns a string containing a Markdown representation of the document with
3420 the given \a features, or an empty string if writing fails for any reason.
3421
3422 \sa setMarkdown
3423*/
3424#if QT_CONFIG(textmarkdownwriter)
3425QString QTextDocument::toMarkdown(QTextDocument::MarkdownFeatures features) const
3426{
3427 QString ret;
3428 QTextStream s(&ret);
3429 QTextMarkdownWriter w(s, features);
3430 if (w.writeAll(document: this))
3431 return ret;
3432 return QString();
3433}
3434#endif
3435
3436/*!
3437 \since 5.14
3438 Replaces the entire contents of the document with the given
3439 Markdown-formatted text in the \a markdown string, with the given
3440 \a features supported. By default, all supported GitHub-style
3441 Markdown features are included; pass \c MarkdownDialectCommonMark
3442 for a more basic parse.
3443
3444 The Markdown formatting is respected as much as possible; for example,
3445 "*bold* text" will produce text where the first word has a font weight that
3446 gives it an emphasized appearance.
3447
3448 Parsing of HTML included in the \a markdown string is handled in the same
3449 way as in \l setHtml; however, Markdown formatting inside HTML blocks is
3450 not supported.
3451
3452 Some features of the parser can be enabled or disabled via the \a features
3453 argument:
3454
3455 \value MarkdownNoHTML
3456 Any HTML tags in the Markdown text will be discarded
3457 \value MarkdownDialectCommonMark
3458 The parser supports only the features standardized by CommonMark
3459 \value MarkdownDialectGitHub
3460 The parser supports the GitHub dialect
3461
3462 The default is \c MarkdownDialectGitHub.
3463
3464 The undo/redo history is reset when this function is called.
3465*/
3466#if QT_CONFIG(textmarkdownreader)
3467void QTextDocument::setMarkdown(const QString &markdown, QTextDocument::MarkdownFeatures features)
3468{
3469 QTextMarkdownImporter(features).import(doc: this, markdown);
3470}
3471#endif
3472
3473/*!
3474 Returns a vector of text formats for all the formats used in the document.
3475*/
3476QVector<QTextFormat> QTextDocument::allFormats() const
3477{
3478 Q_D(const QTextDocument);
3479 return d->formatCollection()->formats;
3480}
3481
3482
3483/*!
3484 \internal
3485
3486 So that not all classes have to be friends of each other...
3487*/
3488QTextDocumentPrivate *QTextDocument::docHandle() const
3489{
3490 Q_D(const QTextDocument);
3491 return const_cast<QTextDocumentPrivate *>(d);
3492}
3493
3494/*!
3495 \since 4.4
3496 \fn QTextDocument::undoCommandAdded()
3497
3498 This signal is emitted every time a new level of undo is added to the QTextDocument.
3499*/
3500
3501QT_END_NAMESPACE
3502

source code of qtbase/src/gui/text/qtextdocument.cpp