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