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

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