Coverage Report

Created: 2026-06-14 06:57

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