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