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