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() != 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 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
2388QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc)
2389 : doc(_doc), fragmentMarkers(false)
2390{
2391 const QFont defaultFont = doc->defaultFont();
2392 defaultCharFormat.setFont(defaultFont);
2393}
2394
2395static QStringList resolvedFontFamilies(const QTextCharFormat &format)
2396{
2397 return format.fontFamilies().toStringList();
2398}
2399
2400/*!
2401 Returns the document in HTML format. The conversion may not be
2402 perfect, especially for complex documents, due to the limitations
2403 of HTML.
2404*/
2406{
2407 html = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
2408 "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
2409 "<html><head><meta name=\"qrichtext\" content=\"1\" />"_L1;
2410 html.reserve(QTextDocumentPrivate::get(doc)->length());
2411
2412 fragmentMarkers = (mode == ExportFragment);
2413
2414 html += "<meta charset=\"utf-8\" />"_L1;
2415
2416 QString title = doc->metaInformation(QTextDocument::DocumentTitle);
2417 if (!title.isEmpty()) {
2418 html += "<title>"_L1;
2419 html += title;
2420 html += "</title>"_L1;
2421 }
2422 html += "<style type=\"text/css\">\n"_L1;
2423 html += "p, li { white-space: pre-wrap; }\n"_L1;
2424 html += "hr { height: 1px; border-width: 0; }\n"_L1;
2425 html += "li.unchecked::marker { content: \"\\2610\"; }\n"_L1;
2426 html += "li.checked::marker { content: \"\\2612\"; }\n"_L1;
2427 html += "</style>"_L1;
2428 html += "</head><body"_L1;
2429
2430 if (mode == ExportEntireDocument) {
2431 html += " style=\""_L1;
2432
2433 emitFontFamily(resolvedFontFamilies(defaultCharFormat));
2434
2435 if (defaultCharFormat.hasProperty(QTextFormat::FontPointSize)) {
2436 html += " font-size:"_L1;
2437 html += QString::number(defaultCharFormat.fontPointSize());
2438 html += "pt;"_L1;
2439 } else if (defaultCharFormat.hasProperty(QTextFormat::FontPixelSize)) {
2440 html += " font-size:"_L1;
2441 html += QString::number(defaultCharFormat.intProperty(QTextFormat::FontPixelSize));
2442 html += "px;"_L1;
2443 }
2444
2445 html += " font-weight:"_L1;
2446 html += QString::number(defaultCharFormat.fontWeight());
2447 html += u';';
2448
2449 html += " font-style:"_L1;
2450 html += (defaultCharFormat.fontItalic() ? "italic"_L1 : "normal"_L1);
2451 html += u';';
2452
2453 const bool percentSpacing = (defaultCharFormat.fontLetterSpacingType() == QFont::PercentageSpacing);
2454 if (defaultCharFormat.hasProperty(QTextFormat::FontLetterSpacing) &&
2455 (!percentSpacing || defaultCharFormat.fontLetterSpacing() != 0.0)) {
2456 html += " letter-spacing:"_L1;
2457 qreal value = defaultCharFormat.fontLetterSpacing();
2458 if (percentSpacing) // Map to em (100% == 0em)
2459 value = (value / 100) - 1;
2460 html += QString::number(value);
2461 html += percentSpacing ? "em;"_L1 : "px;"_L1;
2462 }
2463
2464 if (defaultCharFormat.hasProperty(QTextFormat::FontWordSpacing) &&
2465 defaultCharFormat.fontWordSpacing() != 0.0) {
2466 html += " word-spacing:"_L1;
2467 html += QString::number(defaultCharFormat.fontWordSpacing());
2468 html += "px;"_L1;
2469 }
2470
2471 QString decorationTag(" text-decoration:"_L1);
2472 bool atLeastOneDecorationSet = false;
2473 if (defaultCharFormat.hasProperty(QTextFormat::FontUnderline) || defaultCharFormat.hasProperty(QTextFormat::TextUnderlineStyle)) {
2474 if (defaultCharFormat.fontUnderline()) {
2475 decorationTag += " underline"_L1;
2476 atLeastOneDecorationSet = true;
2477 }
2478 }
2479 if (defaultCharFormat.hasProperty(QTextFormat::FontOverline)) {
2480 if (defaultCharFormat.fontOverline()) {
2481 decorationTag += " overline"_L1;
2482 atLeastOneDecorationSet = true;
2483 }
2484 }
2485 if (defaultCharFormat.hasProperty(QTextFormat::FontStrikeOut)) {
2486 if (defaultCharFormat.fontStrikeOut()) {
2487 decorationTag += " line-through"_L1;
2488 atLeastOneDecorationSet = true;
2489 }
2490 }
2491 if (atLeastOneDecorationSet)
2492 html += decorationTag + u';';
2493
2494 html += u'\"';
2495
2496 const QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
2497 emitBackgroundAttribute(fmt);
2498
2499 } else {
2500 defaultCharFormat = QTextCharFormat();
2501 }
2502 html += u'>';
2503
2504 QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat();
2505 rootFmt.clearProperty(QTextFormat::BackgroundBrush);
2506
2507 QTextFrameFormat defaultFmt;
2508 defaultFmt.setMargin(doc->documentMargin());
2509
2510 if (rootFmt == defaultFmt)
2511 emitFrame(doc->rootFrame()->begin());
2512 else
2513 emitTextFrame(doc->rootFrame());
2514
2515 html += "</body></html>"_L1;
2516 return html;
2517}
2518
2519void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value)
2520{
2521 html += u' ';
2522 html += QLatin1StringView(attribute);
2523 html += "=\""_L1;
2524 html += value.toHtmlEscaped();
2525 html += u'"';
2526}
2527
2528bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
2529{
2530 bool attributesEmitted = false;
2531
2532 {
2533 const QStringList families = resolvedFontFamilies(format);
2534 if (!families.isEmpty() && families != resolvedFontFamilies(defaultCharFormat)) {
2535 emitFontFamily(families);
2536 attributesEmitted = true;
2537 }
2538 }
2539
2540 if (format.hasProperty(QTextFormat::FontPointSize)
2541 && format.fontPointSize() != defaultCharFormat.fontPointSize()) {
2542 html += " font-size:"_L1;
2543 html += QString::number(format.fontPointSize());
2544 html += "pt;"_L1;
2545 attributesEmitted = true;
2546 } else if (format.hasProperty(QTextFormat::FontSizeAdjustment)) {
2547 static const char sizeNameData[] =
2548 "small" "\0"
2549 "medium" "\0"
2550 "xx-large" ;
2551 static const quint8 sizeNameOffsets[] = {
2552 0, // "small"
2553 sizeof("small"), // "medium"
2554 sizeof("small") + sizeof("medium") + 3, // "large" )
2555 sizeof("small") + sizeof("medium") + 1, // "x-large" )> compressed into "xx-large"
2556 sizeof("small") + sizeof("medium"), // "xx-large" )
2557 };
2558 const char *name = nullptr;
2559 const int idx = format.intProperty(QTextFormat::FontSizeAdjustment) + 1;
2560 if (idx >= 0 && idx <= 4) {
2561 name = sizeNameData + sizeNameOffsets[idx];
2562 }
2563 if (name) {
2564 html += " font-size:"_L1;
2565 html += QLatin1StringView(name);
2566 html += u';';
2567 attributesEmitted = true;
2568 }
2569 } else if (format.hasProperty(QTextFormat::FontPixelSize)
2570 && format.property(QTextFormat::FontPixelSize)
2571 != defaultCharFormat.property(QTextFormat::FontPixelSize)) {
2572 html += " font-size:"_L1;
2573 html += QString::number(format.intProperty(QTextFormat::FontPixelSize));
2574 html += "px;"_L1;
2575 attributesEmitted = true;
2576 }
2577
2578 if (format.hasProperty(QTextFormat::FontWeight)
2579 && format.fontWeight() != defaultCharFormat.fontWeight()) {
2580 html += " font-weight:"_L1;
2581 html += QString::number(format.fontWeight());
2582 html += u';';
2583 attributesEmitted = true;
2584 }
2585
2586 if (format.hasProperty(QTextFormat::FontItalic)
2587 && format.fontItalic() != defaultCharFormat.fontItalic()) {
2588 html += " font-style:"_L1;
2589 html += (format.fontItalic() ? "italic"_L1 : "normal"_L1);
2590 html += u';';
2591 attributesEmitted = true;
2592 }
2593
2594 const auto decorationTag = " text-decoration:"_L1;
2595 html += decorationTag;
2596 bool hasDecoration = false;
2597 bool atLeastOneDecorationSet = false;
2598
2599 if ((format.hasProperty(QTextFormat::FontUnderline) || format.hasProperty(QTextFormat::TextUnderlineStyle))
2600 && format.fontUnderline() != defaultCharFormat.fontUnderline()) {
2601 hasDecoration = true;
2602 if (format.fontUnderline()) {
2603 html += " underline"_L1;
2604 atLeastOneDecorationSet = true;
2605 }
2606 }
2607
2608 if (format.hasProperty(QTextFormat::FontOverline)
2609 && format.fontOverline() != defaultCharFormat.fontOverline()) {
2610 hasDecoration = true;
2611 if (format.fontOverline()) {
2612 html += " overline"_L1;
2613 atLeastOneDecorationSet = true;
2614 }
2615 }
2616
2617 if (format.hasProperty(QTextFormat::FontStrikeOut)
2618 && format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) {
2619 hasDecoration = true;
2620 if (format.fontStrikeOut()) {
2621 html += " line-through"_L1;
2622 atLeastOneDecorationSet = true;
2623 }
2624 }
2625
2626 if (hasDecoration) {
2627 if (!atLeastOneDecorationSet)
2628 html += "none"_L1;
2629 html += u';';
2630 if (format.hasProperty(QTextFormat::TextUnderlineColor)) {
2631 html += " text-decoration-color:"_L1;
2632 html += colorValue(format.underlineColor());
2633 html += u';';
2634 }
2635 attributesEmitted = true;
2636 } else {
2637 html.chop(decorationTag.size());
2638 }
2639
2640 if (format.foreground() != defaultCharFormat.foreground()
2641 && format.foreground().style() != Qt::NoBrush) {
2642 QBrush brush = format.foreground();
2643 if (brush.style() == Qt::TexturePattern) {
2644 const bool isPixmap = qHasPixmapTexture(brush);
2645 const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
2646
2647 html += " -qt-fg-texture-cachekey:"_L1;
2648 html += QString::number(cacheKey);
2649 html += ";"_L1;
2650 } else if (brush.style() == Qt::LinearGradientPattern
2651 || brush.style() == Qt::RadialGradientPattern
2652 || brush.style() == Qt::ConicalGradientPattern) {
2653 const QGradient *gradient = brush.gradient();
2654 if (gradient->type() == QGradient::LinearGradient) {
2655 const QLinearGradient *linearGradient = static_cast<const QLinearGradient *>(brush.gradient());
2656
2657 html += " -qt-foreground: qlineargradient("_L1;
2658 html += "x1:"_L1 + QString::number(linearGradient->start().x()) + u',';
2659 html += "y1:"_L1 + QString::number(linearGradient->start().y()) + u',';
2660 html += "x2:"_L1 + QString::number(linearGradient->finalStop().x()) + u',';
2661 html += "y2:"_L1 + QString::number(linearGradient->finalStop().y()) + u',';
2662 } else if (gradient->type() == QGradient::RadialGradient) {
2663 const QRadialGradient *radialGradient = static_cast<const QRadialGradient *>(brush.gradient());
2664
2665 html += " -qt-foreground: qradialgradient("_L1;
2666 html += "cx:"_L1 + QString::number(radialGradient->center().x()) + u',';
2667 html += "cy:"_L1 + QString::number(radialGradient->center().y()) + u',';
2668 html += "fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + u',';
2669 html += "fy:"_L1 + QString::number(radialGradient->focalPoint().y()) + u',';
2670 html += "radius:"_L1 + QString::number(radialGradient->radius()) + u',';
2671 } else {
2672 const QConicalGradient *conicalGradient = static_cast<const QConicalGradient *>(brush.gradient());
2673
2674 html += " -qt-foreground: qconicalgradient("_L1;
2675 html += "cx:"_L1 + QString::number(conicalGradient->center().x()) + u',';
2676 html += "cy:"_L1 + QString::number(conicalGradient->center().y()) + u',';
2677 html += "angle:"_L1 + QString::number(conicalGradient->angle()) + u',';
2678 }
2679
2680 const QStringList coordinateModes = { "logical"_L1, "stretchtodevice"_L1, "objectbounding"_L1, "object"_L1 };
2681 html += "coordinatemode:"_L1;
2682 html += coordinateModes.at(int(gradient->coordinateMode()));
2683 html += u',';
2684
2685 const QStringList spreads = { "pad"_L1, "reflect"_L1, "repeat"_L1 };
2686 html += "spread:"_L1;
2687 html += spreads.at(int(gradient->spread()));
2688
2689 for (const QGradientStop &stop : gradient->stops()) {
2690 html += ",stop:"_L1;
2691 html += QString::number(stop.first);
2692 html += u' ';
2693 html += colorValue(stop.second);
2694 }
2695
2696 html += ");"_L1;
2697 } else {
2698 html += " color:"_L1;
2699 html += colorValue(brush.color());
2700 html += u';';
2701 }
2702 attributesEmitted = true;
2703 }
2704
2705 if (format.background() != defaultCharFormat.background()
2706 && format.background().style() == Qt::SolidPattern) {
2707 html += " background-color:"_L1;
2708 html += colorValue(format.background().color());
2709 html += u';';
2710 attributesEmitted = true;
2711 }
2712
2713 if (format.verticalAlignment() != defaultCharFormat.verticalAlignment()
2714 && format.verticalAlignment() != QTextCharFormat::AlignNormal)
2715 {
2716 html += " vertical-align:"_L1;
2717
2718 QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
2719 if (valign == QTextCharFormat::AlignSubScript)
2720 html += "sub"_L1;
2721 else if (valign == QTextCharFormat::AlignSuperScript)
2722 html += "super"_L1;
2723 else if (valign == QTextCharFormat::AlignMiddle)
2724 html += "middle"_L1;
2725 else if (valign == QTextCharFormat::AlignTop)
2726 html += "top"_L1;
2727 else if (valign == QTextCharFormat::AlignBottom)
2728 html += "bottom"_L1;
2729
2730 html += u';';
2731 attributesEmitted = true;
2732 }
2733
2734 if (format.fontCapitalization() != QFont::MixedCase) {
2735 const QFont::Capitalization caps = format.fontCapitalization();
2736 if (caps == QFont::AllUppercase)
2737 html += " text-transform:uppercase;"_L1;
2738 else if (caps == QFont::AllLowercase)
2739 html += " text-transform:lowercase;"_L1;
2740 else if (caps == QFont::SmallCaps)
2741 html += " font-variant:small-caps;"_L1;
2742 attributesEmitted = true;
2743 }
2744
2745 if (format.fontWordSpacing() != 0.0) {
2746 html += " word-spacing:"_L1;
2747 html += QString::number(format.fontWordSpacing());
2748 html += "px;"_L1;
2749 attributesEmitted = true;
2750 }
2751
2752 if (format.hasProperty(QTextFormat::TextOutline)) {
2753 QPen outlinePen = format.textOutline();
2754 html += " -qt-stroke-color:"_L1;
2755 html += colorValue(outlinePen.color());
2756 html += u';';
2757
2758 html += " -qt-stroke-width:"_L1;
2759 html += QString::number(outlinePen.widthF());
2760 html += "px;"_L1;
2761
2762 html += " -qt-stroke-linecap:"_L1;
2763 if (outlinePen.capStyle() == Qt::SquareCap)
2764 html += "squarecap;"_L1;
2765 else if (outlinePen.capStyle() == Qt::FlatCap)
2766 html += "flatcap;"_L1;
2767 else if (outlinePen.capStyle() == Qt::RoundCap)
2768 html += "roundcap;"_L1;
2769
2770 html += " -qt-stroke-linejoin:"_L1;
2771 if (outlinePen.joinStyle() == Qt::MiterJoin)
2772 html += "miterjoin;"_L1;
2773 else if (outlinePen.joinStyle() == Qt::SvgMiterJoin)
2774 html += "svgmiterjoin;"_L1;
2775 else if (outlinePen.joinStyle() == Qt::BevelJoin)
2776 html += "beveljoin;"_L1;
2777 else if (outlinePen.joinStyle() == Qt::RoundJoin)
2778 html += "roundjoin;"_L1;
2779
2780 if (outlinePen.joinStyle() == Qt::MiterJoin ||
2781 outlinePen.joinStyle() == Qt::SvgMiterJoin) {
2782 html += " -qt-stroke-miterlimit:"_L1;
2783 html += QString::number(outlinePen.miterLimit());
2784 html += u';';
2785 }
2786
2787 if (outlinePen.style() == Qt::CustomDashLine && !outlinePen.dashPattern().empty()) {
2788 html += " -qt-stroke-dasharray:"_L1;
2789 QString dashArrayString;
2790 QList<qreal> dashes = outlinePen.dashPattern();
2791
2792 for (int i = 0; i < dashes.length() - 1; i++) {
2793 qreal dash = dashes[i];
2794 dashArrayString += QString::number(dash) + u',';
2795 }
2796
2797 dashArrayString += QString::number(dashes.last());
2798 html += dashArrayString;
2799 html += u';';
2800
2801 html += " -qt-stroke-dashoffset:"_L1;
2802 html += QString::number(outlinePen.dashOffset());
2803 html += u';';
2804 }
2805
2806 attributesEmitted = true;
2807 }
2808
2809 return attributesEmitted;
2810}
2811
2812void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength &length)
2813{
2814 if (length.type() == QTextLength::VariableLength) // default
2815 return;
2816
2817 html += u' ';
2818 html += QLatin1StringView(attribute);
2819 html += "=\""_L1;
2820 html += QString::number(length.rawValue());
2821
2822 if (length.type() == QTextLength::PercentageLength)
2823 html += "%\""_L1;
2824 else
2825 html += u'\"';
2826}
2827
2828void QTextHtmlExporter::emitAlignment(Qt::Alignment align)
2829{
2830 if (align & Qt::AlignLeft)
2831 return;
2832 else if (align & Qt::AlignRight)
2833 html += " align=\"right\""_L1;
2834 else if (align & Qt::AlignHCenter)
2835 html += " align=\"center\""_L1;
2836 else if (align & Qt::AlignJustify)
2837 html += " align=\"justify\""_L1;
2838}
2839
2840void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode)
2841{
2842 if (pos == QTextFrameFormat::InFlow)
2843 return;
2844
2845 if (mode == EmitStyleTag)
2846 html += " style=\"float:"_L1;
2847 else
2848 html += " float:"_L1;
2849
2850 if (pos == QTextFrameFormat::FloatLeft)
2851 html += " left;"_L1;
2852 else if (pos == QTextFrameFormat::FloatRight)
2853 html += " right;"_L1;
2854 else
2855 Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type");
2856
2857 if (mode == EmitStyleTag)
2858 html += u'\"';
2859}
2860
2861static QLatin1StringView richtextBorderStyleToHtmlBorderStyle(QTextFrameFormat::BorderStyle style)
2862{
2863 switch (style) {
2864 case QTextFrameFormat::BorderStyle_None:
2865 return "none"_L1;
2866 case QTextFrameFormat::BorderStyle_Dotted:
2867 return "dotted"_L1;
2868 case QTextFrameFormat::BorderStyle_Dashed:
2869 return "dashed"_L1;
2870 case QTextFrameFormat::BorderStyle_Solid:
2871 return "solid"_L1;
2872 case QTextFrameFormat::BorderStyle_Double:
2873 return "double"_L1;
2874 case QTextFrameFormat::BorderStyle_DotDash:
2875 return "dot-dash"_L1;
2876 case QTextFrameFormat::BorderStyle_DotDotDash:
2877 return "dot-dot-dash"_L1;
2878 case QTextFrameFormat::BorderStyle_Groove:
2879 return "groove"_L1;
2880 case QTextFrameFormat::BorderStyle_Ridge:
2881 return "ridge"_L1;
2882 case QTextFrameFormat::BorderStyle_Inset:
2883 return "inset"_L1;
2884 case QTextFrameFormat::BorderStyle_Outset:
2885 return "outset"_L1;
2886 default:
2887 Q_UNREACHABLE();
2888 };
2889 return ""_L1;
2890}
2891
2892void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style)
2893{
2894 Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset);
2895
2896 html += " border-style:"_L1;
2897 html += richtextBorderStyleToHtmlBorderStyle(style);
2898 html += u';';
2899}
2900
2901void QTextHtmlExporter::emitPageBreakPolicy(QTextFormat::PageBreakFlags policy)
2902{
2903 if (policy & QTextFormat::PageBreak_AlwaysBefore)
2904 html += " page-break-before:always;"_L1;
2905
2906 if (policy & QTextFormat::PageBreak_AlwaysAfter)
2907 html += " page-break-after:always;"_L1;
2908}
2909
2910void QTextHtmlExporter::emitFontFamily(const QStringList &families)
2911{
2912 html += " font-family:"_L1;
2913
2914 bool first = true;
2915 for (const QString &family : families) {
2916 auto quote = "\'"_L1;
2917 if (family.contains(u'\''))
2918 quote = "&quot;"_L1;
2919
2920 if (!first)
2921 html += ","_L1;
2922 else
2923 first = false;
2924 html += quote;
2925 html += family.toHtmlEscaped();
2926 html += quote;
2927 }
2928 html += u';';
2929}
2930
2931void QTextHtmlExporter::emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right)
2932{
2933 html += " margin-top:"_L1;
2934 html += top;
2935 html += "px;"_L1;
2936
2937 html += " margin-bottom:"_L1;
2938 html += bottom;
2939 html += "px;"_L1;
2940
2941 html += " margin-left:"_L1;
2942 html += left;
2943 html += "px;"_L1;
2944
2945 html += " margin-right:"_L1;
2946 html += right;
2947 html += "px;"_L1;
2948}
2949
2950void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
2951{
2952 const QTextCharFormat format = fragment.charFormat();
2953
2954 bool closeAnchor = false;
2955
2956 if (format.isAnchor()) {
2957 const auto names = format.anchorNames();
2958 if (!names.isEmpty()) {
2959 html += "<a name=\""_L1;
2960 html += names.constFirst().toHtmlEscaped();
2961 html += "\"></a>"_L1;
2962 }
2963 const QString href = format.anchorHref();
2964 if (!href.isEmpty()) {
2965 html += "<a href=\""_L1;
2966 html += href.toHtmlEscaped();
2967 html += "\">"_L1;
2968 closeAnchor = true;
2969 }
2970 }
2971
2972 QString txt = fragment.text();
2973 const bool isObject = txt.contains(QChar::ObjectReplacementCharacter);
2974 const bool isImage = isObject && format.isImageFormat();
2975
2976 const auto styleTag = "<span style=\""_L1;
2977 html += styleTag;
2978
2979 bool attributesEmitted = false;
2980 if (!isImage)
2981 attributesEmitted = emitCharFormatStyle(format);
2982 if (attributesEmitted)
2983 html += "\">"_L1;
2984 else
2985 html.chop(styleTag.size());
2986
2987 if (isObject) {
2988 for (int i = 0; isImage && i < txt.size(); ++i) {
2989 QTextImageFormat imgFmt = format.toImageFormat();
2990
2991 html += "<img"_L1;
2992
2993 QString maxWidthCss;
2994
2995 if (imgFmt.hasProperty(QTextFormat::ImageMaxWidth)) {
2996 auto length = imgFmt.lengthProperty(QTextFormat::ImageMaxWidth);
2997 maxWidthCss += "max-width:"_L1;
2998 if (length.type() == QTextLength::PercentageLength)
2999 maxWidthCss += QString::number(length.rawValue()) + "%;"_L1;
3000 else if (length.type() == QTextLength::FixedLength)
3001 maxWidthCss += QString::number(length.rawValue()) + "px;"_L1;
3002 }
3003
3004 if (imgFmt.hasProperty(QTextFormat::ImageName))
3005 emitAttribute("src", imgFmt.name());
3006
3007 if (imgFmt.hasProperty(QTextFormat::ImageAltText))
3008 emitAttribute("alt", imgFmt.stringProperty(QTextFormat::ImageAltText));
3009
3010 if (imgFmt.hasProperty(QTextFormat::ImageTitle))
3011 emitAttribute("title", imgFmt.stringProperty(QTextFormat::ImageTitle));
3012
3013 if (imgFmt.hasProperty(QTextFormat::ImageWidth))
3014 emitAttribute("width", QString::number(imgFmt.width()));
3015
3016 if (imgFmt.hasProperty(QTextFormat::ImageHeight))
3017 emitAttribute("height", QString::number(imgFmt.height()));
3018
3019 if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle)
3020 html += " style=\"vertical-align: middle;"_L1 + maxWidthCss + u'\"';
3021 else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop)
3022 html += " style=\"vertical-align: top;"_L1 + maxWidthCss + u'\"';
3023 else if (!maxWidthCss.isEmpty())
3024 html += " style=\""_L1 + maxWidthCss + u'\"';
3025
3026 if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt)))
3027 emitFloatStyle(imageFrame->frameFormat().position());
3028
3029 html += " />"_L1;
3030 }
3031 } else {
3032 Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter));
3033
3034 txt = txt.toHtmlEscaped();
3035
3036 // split for [\n{LineSeparator}]
3037 // space in BR on purpose for compatibility with old-fashioned browsers
3038 txt.replace(u'\n', "<br />"_L1);
3039 txt.replace(QChar::LineSeparator, "<br />"_L1);
3040 html += txt;
3041 }
3042
3043 if (attributesEmitted)
3044 html += "</span>"_L1;
3045
3046 if (closeAnchor)
3047 html += "</a>"_L1;
3048}
3049
3050static bool isOrderedList(int style)
3051{
3052 return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
3053 || style == QTextListFormat::ListUpperAlpha
3054 || style == QTextListFormat::ListUpperRoman
3055 || style == QTextListFormat::ListLowerRoman
3056 ;
3057}
3058
3059void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block)
3060{
3061 QTextBlockFormat format = block.blockFormat();
3062 emitAlignment(format.alignment());
3063
3064 // assume default to not bloat the html too much
3065 // html += " dir='ltr'"_L1;
3066 if (block.textDirection() == Qt::RightToLeft)
3067 html += " dir='rtl'"_L1;
3068
3069 const auto style = " style=\""_L1;
3070 html += style;
3071
3072 const bool emptyBlock = block.begin().atEnd();
3073 if (emptyBlock) {
3074 html += "-qt-paragraph-type:empty;"_L1;
3075 }
3076
3077 emitMargins(QString::number(format.topMargin()),
3078 QString::number(format.bottomMargin()),
3079 QString::number(format.leftMargin()),
3080 QString::number(format.rightMargin()));
3081
3082 html += " -qt-block-indent:"_L1;
3083 html += QString::number(format.indent());
3084 html += u';';
3085
3086 html += " text-indent:"_L1;
3087 html += QString::number(format.textIndent());
3088 html += "px;"_L1;
3089
3090 if (block.userState() != -1) {
3091 html += " -qt-user-state:"_L1;
3092 html += QString::number(block.userState());
3093 html += u';';
3094 }
3095
3096 if (format.lineHeightType() != QTextBlockFormat::SingleHeight) {
3097 html += " line-height:"_L1
3098 + QString::number(format.lineHeight());
3099 switch (format.lineHeightType()) {
3100 case QTextBlockFormat::ProportionalHeight:
3101 html += "%;"_L1;
3102 break;
3103 case QTextBlockFormat::FixedHeight:
3104 html += "; -qt-line-height-type: fixed;"_L1;
3105 break;
3106 case QTextBlockFormat::MinimumHeight:
3107 html += "px;"_L1;
3108 break;
3109 case QTextBlockFormat::LineDistanceHeight:
3110 html += "; -qt-line-height-type: line-distance;"_L1;
3111 break;
3112 default:
3113 html += ";"_L1;
3114 break; // Should never reach here
3115 }
3116 }
3117
3118 emitPageBreakPolicy(format.pageBreakPolicy());
3119
3120 QTextCharFormat diff;
3121 if (emptyBlock) { // only print character properties when we don't expect them to be repeated by actual text in the parag
3122 const QTextCharFormat blockCharFmt = block.charFormat();
3123 diff = formatDifference(defaultCharFormat, blockCharFmt).toCharFormat();
3124 }
3125
3126 diff.clearProperty(QTextFormat::BackgroundBrush);
3127 if (format.hasProperty(QTextFormat::BackgroundBrush)) {
3128 QBrush bg = format.background();
3129 if (bg.style() != Qt::NoBrush)
3130 diff.setProperty(QTextFormat::BackgroundBrush, format.property(QTextFormat::BackgroundBrush));
3131 }
3132
3133 if (!diff.properties().isEmpty())
3134 emitCharFormatStyle(diff);
3135
3136 html += u'"';
3137
3138}
3139
3140void QTextHtmlExporter::emitBlock(const QTextBlock &block)
3141{
3142 if (block.begin().atEnd()) {
3143 // ### HACK, remove once QTextFrame::Iterator is fixed
3144 int p = block.position();
3145 if (p > 0)
3146 --p;
3147
3148 QTextDocumentPrivate::FragmentIterator frag = QTextDocumentPrivate::get(doc)->find(p);
3149 QChar ch = QTextDocumentPrivate::get(doc)->buffer().at(frag->stringPosition);
3150 if (ch == QTextBeginningOfFrame
3151 || ch == QTextEndOfFrame)
3152 return;
3153 }
3154
3155 html += u'\n';
3156
3157 // save and later restore, in case we 'change' the default format by
3158 // emitting block char format information
3159 QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
3160
3161 QTextList *list = block.textList();
3162 if (list) {
3163 if (list->itemNumber(block) == 0) { // first item? emit <ul> or appropriate
3164 const QTextListFormat format = list->format();
3165 const int style = format.style();
3166 bool ordered = false;
3167 switch (style) {
3168 case QTextListFormat::ListDisc: html += "<ul"_L1; break;
3169 case QTextListFormat::ListCircle: html += "<ul type=\"circle\""_L1; break;
3170 case QTextListFormat::ListSquare: html += "<ul type=\"square\""_L1; break;
3171 case QTextListFormat::ListDecimal: html += "<ol"_L1; ordered = true; break;
3172 case QTextListFormat::ListLowerAlpha: html += "<ol type=\"a\""_L1; ordered = true; break;
3173 case QTextListFormat::ListUpperAlpha: html += "<ol type=\"A\""_L1; ordered = true; break;
3174 case QTextListFormat::ListLowerRoman: html += "<ol type=\"i\""_L1; ordered = true; break;
3175 case QTextListFormat::ListUpperRoman: html += "<ol type=\"I\""_L1; ordered = true; break;
3176 default: html += "<ul"_L1; // ### should not happen
3177 }
3178
3179 if (ordered && format.start() != 1) {
3180 html += " start=\""_L1;
3181 html += QString::number(format.start());
3182 html += u'"';
3183 }
3184
3185 QString styleString;
3186 styleString += "margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px;"_L1;
3187
3188 if (format.hasProperty(QTextFormat::ListIndent)) {
3189 styleString += " -qt-list-indent: "_L1;
3190 styleString += QString::number(format.indent());
3191 styleString += u';';
3192 }
3193
3194 if (format.hasProperty(QTextFormat::ListNumberPrefix)) {
3195 QString numberPrefix = format.numberPrefix();
3196 numberPrefix.replace(u'"', "\\22"_L1);
3197 numberPrefix.replace(u'\'', "\\27"_L1); // FIXME: There's a problem in the CSS parser the prevents this from being correctly restored
3198 styleString += " -qt-list-number-prefix: "_L1;
3199 styleString += u'\'';
3200 styleString += numberPrefix;
3201 styleString += u'\'';
3202 styleString += u';';
3203 }
3204
3205 if (format.hasProperty(QTextFormat::ListNumberSuffix)) {
3206 if (format.numberSuffix() != "."_L1) { // this is our default
3207 QString numberSuffix = format.numberSuffix();
3208 numberSuffix.replace(u'"', "\\22"_L1);
3209 numberSuffix.replace(u'\'', "\\27"_L1); // see above
3210 styleString += " -qt-list-number-suffix: "_L1;
3211 styleString += u'\'';
3212 styleString += numberSuffix;
3213 styleString += u'\'';
3214 styleString += u';';
3215 }
3216 }
3217
3218 html += " style=\""_L1;
3219 html += styleString;
3220 html += "\">\n"_L1;
3221 }
3222
3223 html += "<li"_L1;
3224
3225 const QTextCharFormat blockFmt = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat();
3226 if (!blockFmt.properties().isEmpty()) {
3227 html += " style=\""_L1;
3228 emitCharFormatStyle(blockFmt);
3229 html += u'\"';
3230
3231 defaultCharFormat.merge(block.charFormat());
3232 }
3233 if (block.blockFormat().hasProperty(QTextFormat::BlockMarker)) {
3234 switch (block.blockFormat().marker()) {
3235 case QTextBlockFormat::MarkerType::Checked:
3236 html += " class=\"checked\""_L1;
3237 break;
3238 case QTextBlockFormat::MarkerType::Unchecked:
3239 html += " class=\"unchecked\""_L1;
3240 break;
3241 case QTextBlockFormat::MarkerType::NoMarker:
3242 break;
3243 }
3244 }
3245 }
3246
3247 const QTextBlockFormat blockFormat = block.blockFormat();
3248 if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
3249 html += "<hr"_L1;
3250
3251 QTextLength width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth);
3252 if (width.type() != QTextLength::VariableLength)
3253 emitTextLength("width", width);
3254 html += u' ';
3255
3256 if (blockFormat.hasProperty(QTextFormat::BackgroundBrush)) {
3257 html += "style=\""_L1;
3258 html += "background-color:"_L1;
3259 html += colorValue(qvariant_cast<QBrush>(blockFormat.property(QTextFormat::BackgroundBrush)).color());
3260 html += u';';
3261 html += u'\"';
3262 }
3263
3264 html += "/>"_L1;
3265 return;
3266 }
3267
3268 const bool pre = blockFormat.nonBreakableLines();
3269 if (pre) {
3270 if (list)
3271 html += u'>';
3272 html += "<pre"_L1;
3273 } else if (!list) {
3274 int headingLevel = blockFormat.headingLevel();
3275 if (headingLevel > 0 && headingLevel <= 6)
3276 html += "<h"_L1 + QString::number(headingLevel);
3277 else
3278 html += "<p"_L1;
3279 }
3280
3281 emitBlockAttributes(block);
3282
3283 html += u'>';
3284 if (block.begin().atEnd())
3285 html += "<br />"_L1;
3286
3287 QTextBlock::Iterator it = block.begin();
3288 if (fragmentMarkers && !it.atEnd() && block == doc->begin())
3289 html += "<!--StartFragment-->"_L1;
3290
3291 for (; !it.atEnd(); ++it)
3292 emitFragment(it.fragment());
3293
3294 if (fragmentMarkers && block.position() + block.length() == QTextDocumentPrivate::get(doc)->length())
3295 html += "<!--EndFragment-->"_L1;
3296
3297 QString closeTags;
3298
3299 if (pre)
3300 html += "</pre>"_L1;
3301 else if (list)
3302 closeTags += "</li>"_L1;
3303 else {
3304 int headingLevel = blockFormat.headingLevel();
3305 if (headingLevel > 0 && headingLevel <= 6)
3306 html += QString::asprintf("</h%d>", headingLevel);
3307 else
3308 html += "</p>"_L1;
3309 }
3310
3311 if (list) {
3312 if (list->itemNumber(block) == list->count() - 1) { // last item? close list
3313 if (isOrderedList(list->format().style()))
3314 closeTags += "</ol>"_L1;
3315 else
3316 closeTags += "</ul>"_L1;
3317 }
3318 const QTextBlock nextBlock = block.next();
3319 // If the next block is the beginning of a new deeper nested list, then we don't
3320 // want to close the current list item just yet. This should be closed when this
3321 // item is fully finished
3322 if (nextBlock.isValid() && nextBlock.textList() &&
3323 nextBlock.textList()->itemNumber(nextBlock) == 0 &&
3324 nextBlock.textList()->format().indent() > list->format().indent()) {
3325 QString lastTag;
3326 if (!closingTags.isEmpty() && list->itemNumber(block) == list->count() - 1)
3327 lastTag = closingTags.takeLast();
3328 lastTag.prepend(closeTags);
3329 closingTags << lastTag;
3330 } else if (list->itemNumber(block) == list->count() - 1) {
3331 // If we are at the end of the list now then we can add in the closing tags for that
3332 // current block
3333 html += closeTags;
3334 if (!closingTags.isEmpty())
3335 html += closingTags.takeLast();
3336 } else {
3337 html += closeTags;
3338 }
3339 }
3340
3341 defaultCharFormat = oldDefaultCharFormat;
3342}
3343
3344extern bool qHasPixmapTexture(const QBrush& brush);
3345
3346QString QTextHtmlExporter::findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap)
3347{
3348 QString url;
3349 if (!doc)
3350 return url;
3351
3352 if (QTextDocument *parent = qobject_cast<QTextDocument *>(doc->parent()))
3353 return findUrlForImage(parent, cacheKey, isPixmap);
3354
3355 const QTextDocumentPrivate *priv = QTextDocumentPrivate::get(doc);
3356 Q_ASSERT(priv != nullptr);
3357
3358 QMap<QUrl, QVariant>::const_iterator it = priv->cachedResources.constBegin();
3359 for (; it != priv->cachedResources.constEnd(); ++it) {
3360
3361 const QVariant &v = it.value();
3362 if (v.userType() == QMetaType::QImage && !isPixmap) {
3363 if (qvariant_cast<QImage>(v).cacheKey() == cacheKey)
3364 break;
3365 }
3366
3367 if (v.userType() == QMetaType::QPixmap && isPixmap) {
3368 if (qvariant_cast<QPixmap>(v).cacheKey() == cacheKey)
3369 break;
3370 }
3371 }
3372
3373 if (it != priv->cachedResources.constEnd())
3374 url = it.key().toString();
3375
3376 return url;
3377}
3378
3379void QTextDocumentPrivate::mergeCachedResources(const QTextDocumentPrivate *priv)
3380{
3381 if (!priv)
3382 return;
3383
3384 cachedResources.insert(priv->cachedResources);
3385}
3386
3387void QTextHtmlExporter::emitBackgroundAttribute(const QTextFormat &format)
3388{
3389 if (format.hasProperty(QTextFormat::BackgroundImageUrl)) {
3390 QString url = format.property(QTextFormat::BackgroundImageUrl).toString();
3391 emitAttribute("background", url);
3392 } else {
3393 const QBrush &brush = format.background();
3394 if (brush.style() == Qt::SolidPattern) {
3395 emitAttribute("bgcolor", colorValue(brush.color()));
3396 } else if (brush.style() == Qt::TexturePattern) {
3397 const bool isPixmap = qHasPixmapTexture(brush);
3398 const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
3399
3400 const QString url = findUrlForImage(doc, cacheKey, isPixmap);
3401
3402 if (!url.isEmpty())
3403 emitAttribute("background", url);
3404 }
3405 }
3406}
3407
3408void QTextHtmlExporter::emitTable(const QTextTable *table)
3409{
3410 QTextTableFormat format = table->format();
3411
3412 html += "\n<table"_L1;
3413
3414 if (format.hasProperty(QTextFormat::FrameBorder))
3415 emitAttribute("border", QString::number(format.border()));
3416
3417 emitFrameStyle(format, TableFrame);
3418
3419 emitAlignment(format.alignment());
3420 emitTextLength("width", format.width());
3421
3422 if (format.hasProperty(QTextFormat::TableCellSpacing))
3423 emitAttribute("cellspacing", QString::number(format.cellSpacing()));
3424 if (format.hasProperty(QTextFormat::TableCellPadding))
3425 emitAttribute("cellpadding", QString::number(format.cellPadding()));
3426
3427 emitBackgroundAttribute(format);
3428
3429 html += u'>';
3430
3431 const int rows = table->rows();
3432 const int columns = table->columns();
3433
3434 QList<QTextLength> columnWidths = format.columnWidthConstraints();
3435 if (columnWidths.isEmpty()) {
3436 columnWidths.resize(columns);
3437 columnWidths.fill(QTextLength());
3438 }
3439 Q_ASSERT(columnWidths.size() == columns);
3440
3441 QVarLengthArray<bool> widthEmittedForColumn(columns);
3442 for (int i = 0; i < columns; ++i)
3443 widthEmittedForColumn[i] = false;
3444
3445 const int headerRowCount = qMin(format.headerRowCount(), rows);
3446 if (headerRowCount > 0)
3447 html += "<thead>"_L1;
3448
3449 for (int row = 0; row < rows; ++row) {
3450 html += "\n<tr>"_L1;
3451
3452 for (int col = 0; col < columns; ++col) {
3453 const QTextTableCell cell = table->cellAt(row, col);
3454
3455 // for col/rowspans
3456 if (cell.row() != row)
3457 continue;
3458
3459 if (cell.column() != col)
3460 continue;
3461
3462 html += "\n<td"_L1;
3463
3464 if (!widthEmittedForColumn[col] && cell.columnSpan() == 1) {
3465 emitTextLength("width", columnWidths.at(col));
3466 widthEmittedForColumn[col] = true;
3467 }
3468
3469 if (cell.columnSpan() > 1)
3470 emitAttribute("colspan", QString::number(cell.columnSpan()));
3471
3472 if (cell.rowSpan() > 1)
3473 emitAttribute("rowspan", QString::number(cell.rowSpan()));
3474
3475 const QTextTableCellFormat cellFormat = cell.format().toTableCellFormat();
3476 emitBackgroundAttribute(cellFormat);
3477
3478 QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
3479
3480 QTextCharFormat::VerticalAlignment valign = cellFormat.verticalAlignment();
3481
3482 QString styleString;
3483 if (valign >= QTextCharFormat::AlignMiddle && valign <= QTextCharFormat::AlignBottom) {
3484 styleString += " vertical-align:"_L1;
3485 switch (valign) {
3486 case QTextCharFormat::AlignMiddle:
3487 styleString += "middle"_L1;
3488 break;
3489 case QTextCharFormat::AlignTop:
3490 styleString += "top"_L1;
3491 break;
3492 case QTextCharFormat::AlignBottom:
3493 styleString += "bottom"_L1;
3494 break;
3495 default:
3496 break;
3497 }
3498 styleString += u';';
3499
3500 QTextCharFormat temp;
3501 temp.setVerticalAlignment(valign);
3502 defaultCharFormat.merge(temp);
3503 }
3504
3505 if (cellFormat.hasProperty(QTextFormat::TableCellLeftPadding))
3506 styleString += " padding-left:"_L1 + QString::number(cellFormat.leftPadding()) + u';';
3507 if (cellFormat.hasProperty(QTextFormat::TableCellRightPadding))
3508 styleString += " padding-right:"_L1 + QString::number(cellFormat.rightPadding()) + u';';
3509 if (cellFormat.hasProperty(QTextFormat::TableCellTopPadding))
3510 styleString += " padding-top:"_L1 + QString::number(cellFormat.topPadding()) + u';';
3511 if (cellFormat.hasProperty(QTextFormat::TableCellBottomPadding))
3512 styleString += " padding-bottom:"_L1 + QString::number(cellFormat.bottomPadding()) + u';';
3513
3514 if (cellFormat.hasProperty(QTextFormat::TableCellTopBorder))
3515 styleString += " border-top:"_L1 + QString::number(cellFormat.topBorder()) + "px;"_L1;
3516 if (cellFormat.hasProperty(QTextFormat::TableCellRightBorder))
3517 styleString += " border-right:"_L1 + QString::number(cellFormat.rightBorder()) + "px;"_L1;
3518 if (cellFormat.hasProperty(QTextFormat::TableCellBottomBorder))
3519 styleString += " border-bottom:"_L1 + QString::number(cellFormat.bottomBorder()) + "px;"_L1;
3520 if (cellFormat.hasProperty(QTextFormat::TableCellLeftBorder))
3521 styleString += " border-left:"_L1 + QString::number(cellFormat.leftBorder()) + "px;"_L1;
3522
3523 if (cellFormat.hasProperty(QTextFormat::TableCellTopBorderBrush))
3524 styleString += " border-top-color:"_L1 + cellFormat.topBorderBrush().color().name() + u';';
3525 if (cellFormat.hasProperty(QTextFormat::TableCellRightBorderBrush))
3526 styleString += " border-right-color:"_L1 + cellFormat.rightBorderBrush().color().name() + u';';
3527 if (cellFormat.hasProperty(QTextFormat::TableCellBottomBorderBrush))
3528 styleString += " border-bottom-color:"_L1 + cellFormat.bottomBorderBrush().color().name() + u';';
3529 if (cellFormat.hasProperty(QTextFormat::TableCellLeftBorderBrush))
3530 styleString += " border-left-color:"_L1 + cellFormat.leftBorderBrush().color().name() + u';';
3531
3532 if (cellFormat.hasProperty(QTextFormat::TableCellTopBorderStyle))
3533 styleString += " border-top-style:"_L1 + richtextBorderStyleToHtmlBorderStyle(cellFormat.topBorderStyle()) + u';';
3534 if (cellFormat.hasProperty(QTextFormat::TableCellRightBorderStyle))
3535 styleString += " border-right-style:"_L1 + richtextBorderStyleToHtmlBorderStyle(cellFormat.rightBorderStyle()) + u';';
3536 if (cellFormat.hasProperty(QTextFormat::TableCellBottomBorderStyle))
3537 styleString += " border-bottom-style:"_L1 + richtextBorderStyleToHtmlBorderStyle(cellFormat.bottomBorderStyle()) + u';';
3538 if (cellFormat.hasProperty(QTextFormat::TableCellLeftBorderStyle))
3539 styleString += " border-left-style:"_L1 + richtextBorderStyleToHtmlBorderStyle(cellFormat.leftBorderStyle()) + u';';
3540
3541 if (!styleString.isEmpty())
3542 html += " style=\""_L1 + styleString + u'\"';
3543
3544 html += u'>';
3545
3546 emitFrame(cell.begin());
3547
3548 html += "</td>"_L1;
3549
3550 defaultCharFormat = oldDefaultCharFormat;
3551 }
3552
3553 html += "</tr>"_L1;
3554 if (headerRowCount > 0 && row == headerRowCount - 1)
3555 html += "</thead>"_L1;
3556 }
3557
3558 html += "</table>"_L1;
3559}
3560
3561void QTextHtmlExporter::emitFrame(const QTextFrame::Iterator &frameIt)
3562{
3563 if (!frameIt.atEnd()) {
3564 QTextFrame::Iterator next = frameIt;
3565 ++next;
3566 if (next.atEnd()
3567 && frameIt.currentFrame() == nullptr
3568 && frameIt.parentFrame() != doc->rootFrame()
3569 && frameIt.currentBlock().begin().atEnd())
3570 return;
3571 }
3572
3573 for (QTextFrame::Iterator it = frameIt;
3574 !it.atEnd(); ++it) {
3575 if (QTextFrame *f = it.currentFrame()) {
3576 if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
3577 emitTable(table);
3578 } else {
3579 emitTextFrame(f);
3580 }
3581 } else if (it.currentBlock().isValid()) {
3582 emitBlock(it.currentBlock());
3583 }
3584 }
3585}
3586
3587void QTextHtmlExporter::emitTextFrame(const QTextFrame *f)
3588{
3589 FrameType frameType = f->parentFrame() ? TextFrame : RootFrame;
3590
3591 html += "\n<table"_L1;
3592 QTextFrameFormat format = f->frameFormat();
3593
3594 if (format.hasProperty(QTextFormat::FrameBorder))
3595 emitAttribute("border", QString::number(format.border()));
3596
3597 emitFrameStyle(format, frameType);
3598
3599 emitTextLength("width", format.width());
3600 emitTextLength("height", format.height());
3601
3602 // root frame's bcolor goes in the <body> tag
3603 if (frameType != RootFrame)
3604 emitBackgroundAttribute(format);
3605
3606 html += u'>';
3607 html += "\n<tr>\n<td style=\"border: none;\">"_L1;
3608 emitFrame(f->begin());
3609 html += "</td></tr></table>"_L1;
3610}
3611
3612void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType frameType)
3613{
3614 const auto styleAttribute = " style=\""_L1;
3615 html += styleAttribute;
3616 const qsizetype originalHtmlLength = html.size();
3617
3618 if (frameType == TextFrame)
3619 html += "-qt-table-type: frame;"_L1;
3620 else if (frameType == RootFrame)
3621 html += "-qt-table-type: root;"_L1;
3622
3623 const QTextFrameFormat defaultFormat;
3624
3625 emitFloatStyle(format.position(), OmitStyleTag);
3626 emitPageBreakPolicy(format.pageBreakPolicy());
3627
3628 if (format.borderBrush() != defaultFormat.borderBrush()) {
3629 html += " border-color:"_L1;
3630 html += colorValue(format.borderBrush().color());
3631 html += u';';
3632 }
3633
3634 if (format.borderStyle() != defaultFormat.borderStyle())
3635 emitBorderStyle(format.borderStyle());
3636
3637 if (format.hasProperty(QTextFormat::FrameMargin)
3638 || format.hasProperty(QTextFormat::FrameLeftMargin)
3639 || format.hasProperty(QTextFormat::FrameRightMargin)
3640 || format.hasProperty(QTextFormat::FrameTopMargin)
3641 || format.hasProperty(QTextFormat::FrameBottomMargin))
3642 emitMargins(QString::number(format.topMargin()),
3643 QString::number(format.bottomMargin()),
3644 QString::number(format.leftMargin()),
3645 QString::number(format.rightMargin()));
3646
3647 if (format.property(QTextFormat::TableBorderCollapse).toBool())
3648 html += " border-collapse:collapse;"_L1;
3649
3650 if (html.size() == originalHtmlLength) // nothing emitted?
3651 html.chop(styleAttribute.size());
3652 else
3653 html += u'\"';
3654}
3655
3656/*!
3657 Returns a string containing an HTML representation of the document.
3658
3659 The content of the document specifies its encoding to be UTF-8.
3660 If you later on convert the returned html string into a byte array for
3661 transmission over a network or when saving to disk you should use
3662 QString::toUtf8() to convert the string to a QByteArray.
3663
3664 \sa {Supported HTML Subset}
3665*/
3666#ifndef QT_NO_TEXTHTMLPARSER
3667QString QTextDocument::toHtml() const
3668{
3669 return QTextHtmlExporter(this).toHtml();
3670}
3671#endif // QT_NO_TEXTHTMLPARSER
3672
3673/*!
3674 \since 5.14
3675 Returns a string containing a Markdown representation of the document with
3676 the given \a features, or an empty string if writing fails for any reason.
3677
3678 \sa setMarkdown
3679*/
3680#if QT_CONFIG(textmarkdownwriter)
3681QString QTextDocument::toMarkdown(QTextDocument::MarkdownFeatures features) const
3682{
3683 QString ret;
3684 QTextStream s(&ret);
3685 QTextMarkdownWriter w(s, features);
3686 if (w.writeAll(this))
3687 return ret;
3688 return QString();
3689}
3690#endif
3691
3692/*!
3693 \since 5.14
3694 \enum QTextDocument::MarkdownFeature
3695
3696 This enum selects the supported feature set when reading or writing Markdown.
3697
3698 \value MarkdownNoHTML
3699 Any HTML tags in the Markdown text will be discarded
3700 \value MarkdownDialectCommonMark
3701 Only the features standardized by \l {https://spec.commonmark.org/0.31.2/}{CommonMark}
3702 \value MarkdownDialectGitHub
3703 Most features from the
3704 \l {https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax}
3705 {GitHub dialect}
3706
3707 Specifically, the supported subset of the GitHub dialect includes
3708 everything from CommonMark, plus:
3709
3710 \list
3711 \li recognizing URLs, www and email addresses and turning them into links
3712 \li strikethrough
3713 \li underline (distinct from italics; in CommonMark it's the same)
3714 \li tables
3715 \li task lists
3716 \li \l {QTextDocument::metaInformation()}{front matter}
3717 \endlist
3718
3719 "Front matter" is often metadata in YAML format. Qt does not currently
3720 include a parser for that; but you may choose a third-party parser, call
3721 QTextDocument::metaInformation() to get the whole block, and invoke your
3722 own parser after Qt has parsed the Markdown file.
3723
3724 \note The Markdown output from toMarkdown() currently may include GitHub
3725 features even if you attempt to disable them by specifying another enum
3726 value. This may be fixed in a future version of Qt.
3727
3728 \sa toMarkdown(), setMarkdown()
3729*/
3730
3731/*!
3732 \since 5.14
3733 Replaces the entire contents of the document with the given
3734 Markdown-formatted text in the \a markdown string, with the given
3735 \a features supported. By default, all supported GitHub-style
3736 Markdown features are included; pass \c MarkdownDialectCommonMark
3737 for a more basic parse.
3738
3739 The Markdown formatting is respected as much as possible; for example,
3740 "*bold* text" will produce text where the first word has a font weight that
3741 gives it an emphasized appearance.
3742
3743 Parsing of HTML included in the \a markdown string is handled in the same
3744 way as in \l setHtml; however, Markdown formatting inside HTML blocks is
3745 not supported.
3746
3747 Some features of the parser can be enabled or disabled via the \a features
3748 argument. The default is \c MarkdownDialectGitHub.
3749
3750 The undo/redo history is reset when this function is called.
3751*/
3752#if QT_CONFIG(textmarkdownreader)
3753void QTextDocument::setMarkdown(const QString &markdown, QTextDocument::MarkdownFeatures features)
3754{
3755 QTextMarkdownImporter(this, features).import(markdown);
3756}
3757#endif
3758
3759/*!
3760 Returns a list of text formats for all the formats used in the document.
3761*/
3762QList<QTextFormat> QTextDocument::allFormats() const
3763{
3764 Q_D(const QTextDocument);
3765 return d->formatCollection()->formats;
3766}
3767
3768/*!
3769 \since 4.4
3770 \fn QTextDocument::undoCommandAdded()
3771
3772 This signal is emitted every time a new level of undo is added to the QTextDocument.
3773*/
3774
3775QT_END_NAMESPACE
3776
3777#include "moc_qtextdocument.cpp"
friend class QPainter
bool end()
Ends painting.
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:76
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