Coverage Report

Created: 2025-07-23 06:48

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