Coverage Report

Created: 2025-07-23 08:13

/src/qtbase/src/gui/text/qtextmarkdownwriter.cpp
Line
Count
Source (jump to first uncovered line)
1
/****************************************************************************
2
**
3
** Copyright (C) 2019 The Qt Company Ltd.
4
** Contact: https://www.qt.io/licensing/
5
**
6
** This file is part of the QtGui module of the Qt Toolkit.
7
**
8
** $QT_BEGIN_LICENSE:LGPL$
9
** Commercial License Usage
10
** Licensees holding valid commercial Qt licenses may use this file in
11
** accordance with the commercial license agreement provided with the
12
** Software or, alternatively, in accordance with the terms contained in
13
** a written agreement between you and The Qt Company. For licensing terms
14
** and conditions see https://www.qt.io/terms-conditions. For further
15
** information use the contact form at https://www.qt.io/contact-us.
16
**
17
** GNU Lesser General Public License Usage
18
** Alternatively, this file may be used under the terms of the GNU Lesser
19
** General Public License version 3 as published by the Free Software
20
** Foundation and appearing in the file LICENSE.LGPL3 included in the
21
** packaging of this file. Please review the following information to
22
** ensure the GNU Lesser General Public License version 3 requirements
23
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24
**
25
** GNU General Public License Usage
26
** Alternatively, this file may be used under the terms of the GNU
27
** General Public License version 2.0 or (at your option) the GNU General
28
** Public license version 3 or any later version approved by the KDE Free
29
** Qt Foundation. The licenses are as published by the Free Software
30
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31
** included in the packaging of this file. Please review the following
32
** information to ensure the GNU General Public License requirements will
33
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34
** https://www.gnu.org/licenses/gpl-3.0.html.
35
**
36
** $QT_END_LICENSE$
37
**
38
****************************************************************************/
39
40
#include "qtextmarkdownwriter_p.h"
41
#include "qtextdocumentlayout_p.h"
42
#include "qfontinfo.h"
43
#include "qfontmetrics.h"
44
#include "qtextdocument_p.h"
45
#include "qtextlist.h"
46
#include "qtexttable.h"
47
#include "qtextcursor.h"
48
#include "qtextimagehandler_p.h"
49
#include "qloggingcategory.h"
50
#if QT_CONFIG(itemmodel)
51
#include "qabstractitemmodel.h"
52
#endif
53
54
QT_BEGIN_NAMESPACE
55
56
Q_LOGGING_CATEGORY(lcMDW, "qt.text.markdown.writer")
57
58
static const QChar Space = QLatin1Char(' ');
59
static const QChar Tab = QLatin1Char('\t');
60
static const QChar Newline = QLatin1Char('\n');
61
static const QChar CarriageReturn = QLatin1Char('\r');
62
static const QChar LineBreak = QChar(0x2028);
63
static const QChar DoubleQuote = QLatin1Char('"');
64
static const QChar Backtick = QLatin1Char('`');
65
static const QChar Backslash = QLatin1Char('\\');
66
static const QChar Period = QLatin1Char('.');
67
68
QTextMarkdownWriter::QTextMarkdownWriter(QTextStream &stream, QTextDocument::MarkdownFeatures features)
69
0
  : m_stream(stream), m_features(features)
70
0
{
71
0
}
72
73
bool QTextMarkdownWriter::writeAll(const QTextDocument *document)
74
0
{
75
0
    writeFrame(document->rootFrame());
76
0
    return true;
77
0
}
78
79
#if QT_CONFIG(itemmodel)
80
void QTextMarkdownWriter::writeTable(const QAbstractItemModel *table)
81
0
{
82
0
    QVector<int> tableColumnWidths(table->columnCount());
83
0
    for (int col = 0; col < table->columnCount(); ++col) {
84
0
        tableColumnWidths[col] = table->headerData(col, Qt::Horizontal).toString().length();
85
0
        for (int row = 0; row < table->rowCount(); ++row) {
86
0
            tableColumnWidths[col] = qMax(tableColumnWidths[col],
87
0
                table->data(table->index(row, col)).toString().length());
88
0
        }
89
0
    }
90
91
    // write the header and separator
92
0
    for (int col = 0; col < table->columnCount(); ++col) {
93
0
        QString s = table->headerData(col, Qt::Horizontal).toString();
94
0
        m_stream << "|" << s << QString(tableColumnWidths[col] - s.length(), Space);
95
0
    }
96
0
    m_stream << "|" << Qt::endl;
97
0
    for (int col = 0; col < tableColumnWidths.length(); ++col)
98
0
        m_stream << '|' << QString(tableColumnWidths[col], QLatin1Char('-'));
99
0
    m_stream << '|'<< Qt::endl;
100
101
    // write the body
102
0
    for (int row = 0; row < table->rowCount(); ++row) {
103
0
        for (int col = 0; col < table->columnCount(); ++col) {
104
0
            QString s = table->data(table->index(row, col)).toString();
105
0
            m_stream << "|" << s << QString(tableColumnWidths[col] - s.length(), Space);
106
0
        }
107
0
        m_stream << '|'<< Qt::endl;
108
0
    }
109
0
    m_listInfo.clear();
110
0
}
111
#endif
112
113
void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
114
0
{
115
0
    Q_ASSERT(frame);
116
0
    const QTextTable *table = qobject_cast<const QTextTable*> (frame);
117
0
    QTextFrame::iterator iterator = frame->begin();
118
0
    QTextFrame *child = nullptr;
119
0
    int tableRow = -1;
120
0
    bool lastWasList = false;
121
0
    QVector<int> tableColumnWidths;
122
0
    if (table) {
123
0
        tableColumnWidths.resize(table->columns());
124
0
        for (int col = 0; col < table->columns(); ++col) {
125
0
            for (int row = 0; row < table->rows(); ++ row) {
126
0
                QTextTableCell cell = table->cellAt(row, col);
127
0
                int cellTextLen = 0;
128
0
                auto it = cell.begin();
129
0
                while (it != cell.end()) {
130
0
                    QTextBlock block = it.currentBlock();
131
0
                    if (block.isValid())
132
0
                        cellTextLen += block.text().length();
133
0
                    ++it;
134
0
                }
135
0
                if (cell.columnSpan() == 1 && tableColumnWidths[col] < cellTextLen)
136
0
                    tableColumnWidths[col] = cellTextLen;
137
0
            }
138
0
        }
139
0
    }
140
0
    while (!iterator.atEnd()) {
141
0
        if (iterator.currentFrame() && child != iterator.currentFrame())
142
0
            writeFrame(iterator.currentFrame());
143
0
        else { // no frame, it's a block
144
0
            QTextBlock block = iterator.currentBlock();
145
            // Look ahead and detect some cases when we should
146
            // suppress needless blank lines, when there will be a big change in block format
147
0
            bool nextIsDifferent = false;
148
0
            bool ending = false;
149
0
            {
150
0
                QTextFrame::iterator next = iterator;
151
0
                ++next;
152
0
                if (next.atEnd()) {
153
0
                    nextIsDifferent = true;
154
0
                    ending = true;
155
0
                } else {
156
0
                    QTextBlockFormat format = iterator.currentBlock().blockFormat();
157
0
                    QTextBlockFormat nextFormat = next.currentBlock().blockFormat();
158
0
                    if (nextFormat.indent() != format.indent() ||
159
0
                            nextFormat.property(QTextFormat::BlockCodeLanguage) != format.property(QTextFormat::BlockCodeLanguage))
160
0
                        nextIsDifferent = true;
161
0
                }
162
0
            }
163
0
            if (table) {
164
0
                QTextTableCell cell = table->cellAt(block.position());
165
0
                if (tableRow < cell.row()) {
166
0
                    if (tableRow == 0) {
167
0
                        m_stream << Newline;
168
0
                        for (int col = 0; col < tableColumnWidths.length(); ++col)
169
0
                            m_stream << '|' << QString(tableColumnWidths[col], QLatin1Char('-'));
170
0
                        m_stream << '|';
171
0
                    }
172
0
                    m_stream << Newline << "|";
173
0
                    tableRow = cell.row();
174
0
                }
175
0
            } else if (!block.textList()) {
176
0
                if (lastWasList)
177
0
                    m_stream << Newline;
178
0
            }
179
0
            int endingCol = writeBlock(block, !table, table && tableRow == 0,
180
0
                                       nextIsDifferent && !block.textList());
181
0
            m_doubleNewlineWritten = false;
182
0
            if (table) {
183
0
                QTextTableCell cell = table->cellAt(block.position());
184
0
                int paddingLen = -endingCol;
185
0
                int spanEndCol = cell.column() + cell.columnSpan();
186
0
                for (int col = cell.column(); col < spanEndCol; ++col)
187
0
                    paddingLen += tableColumnWidths[col];
188
0
                if (paddingLen > 0)
189
0
                    m_stream << QString(paddingLen, Space);
190
0
                for (int col = cell.column(); col < spanEndCol; ++col)
191
0
                    m_stream << "|";
192
0
            } else if (m_fencedCodeBlock && ending) {
193
0
                m_stream << m_linePrefix << QString(m_wrappedLineIndent, Space)
194
0
                         << m_codeBlockFence << Newline << Newline;
195
0
                m_codeBlockFence.clear();
196
0
            } else if (m_indentedCodeBlock && nextIsDifferent) {
197
0
                m_stream << Newline;
198
0
            } else if (endingCol > 0) {
199
0
                if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) {
200
0
                    m_stream << Newline;
201
0
                } else {
202
0
                    m_stream << Newline << Newline;
203
0
                    m_doubleNewlineWritten = true;
204
0
                }
205
0
            }
206
0
            lastWasList = block.textList();
207
0
        }
208
0
        child = iterator.currentFrame();
209
0
        ++iterator;
210
0
    }
211
0
    if (table) {
212
0
        m_stream << Newline << Newline;
213
0
        m_doubleNewlineWritten = true;
214
0
    }
215
0
    m_listInfo.clear();
216
0
}
217
218
QTextMarkdownWriter::ListInfo QTextMarkdownWriter::listInfo(QTextList *list)
219
0
{
220
0
    if (!m_listInfo.contains(list)) {
221
        // decide whether this list is loose or tight
222
0
        ListInfo info;
223
0
        info.loose = false;
224
0
        if (list->count() > 1) {
225
0
            QTextBlock first = list->item(0);
226
0
            QTextBlock last = list->item(list->count() - 1);
227
0
            QTextBlock next = first.next();
228
0
            while (next.isValid()) {
229
0
                if (next == last)
230
0
                    break;
231
0
                qCDebug(lcMDW) << "next block in list" << list << next.text() << "part of list?" << next.textList();
232
0
                if (!next.textList()) {
233
                    // If we find a continuation paragraph, this list is "loose"
234
                    // because it will need a blank line to separate that paragraph.
235
0
                    qCDebug(lcMDW) << "decided list beginning with" << first.text() << "is loose after" << next.text();
236
0
                    info.loose = true;
237
0
                    break;
238
0
                }
239
0
                next = next.next();
240
0
            }
241
0
        }
242
0
        m_listInfo.insert(list, info);
243
0
        return info;
244
0
    }
245
0
    return m_listInfo.value(list);
246
0
}
247
248
static int nearestWordWrapIndex(const QString &s, int before)
249
0
{
250
0
    before = qMin(before, s.length());
251
0
    int fragBegin = qMax(before - 15, 0);
252
0
    if (lcMDW().isDebugEnabled()) {
253
0
        QString frag = s.mid(fragBegin, 30);
254
0
        qCDebug(lcMDW) << frag << before;
255
0
        qCDebug(lcMDW) << QString(before - fragBegin, Period) + QLatin1Char('<');
256
0
    }
257
0
    for (int i = before - 1; i >= 0; --i) {
258
0
        if (s.at(i).isSpace()) {
259
0
            qCDebug(lcMDW) << QString(i - fragBegin, Period) + QLatin1Char('^') << i;
260
0
            return i;
261
0
        }
262
0
    }
263
0
    qCDebug(lcMDW, "not possible");
264
0
    return -1;
265
0
}
266
267
static int adjacentBackticksCount(const QString &s)
268
0
{
269
0
    int start = -1, len = s.length();
270
0
    int ret = 0;
271
0
    for (int i = 0; i < len; ++i) {
272
0
        if (s.at(i) == Backtick) {
273
0
            if (start < 0)
274
0
                start = i;
275
0
        } else if (start >= 0) {
276
0
            ret = qMax(ret, i - start);
277
0
            start = -1;
278
0
        }
279
0
    }
280
0
    if (s.at(len - 1) == Backtick)
281
0
        ret = qMax(ret, len - start);
282
0
    return ret;
283
0
}
284
285
static void maybeEscapeFirstChar(QString &s)
286
0
{
287
0
    QString sTrimmed = s.trimmed();
288
0
    if (sTrimmed.isEmpty())
289
0
        return;
290
0
    char firstChar = sTrimmed.at(0).toLatin1();
291
0
    if (firstChar == '*' || firstChar == '+' || firstChar == '-') {
292
0
        int i = s.indexOf(QLatin1Char(firstChar));
293
0
        s.insert(i, QLatin1Char('\\'));
294
0
    }
295
0
}
296
297
struct LineEndPositions {
298
    const QChar *lineEnd;
299
    const QChar *nextLineBegin;
300
};
301
302
static LineEndPositions findLineEnd(const QChar *begin, const QChar *end)
303
0
{
304
0
    LineEndPositions result{ end, end };
305
306
0
    while (begin < end) {
307
0
        if (*begin == Newline) {
308
0
            result.lineEnd = begin;
309
0
            result.nextLineBegin = begin + 1;
310
0
            break;
311
0
        } else if (*begin == CarriageReturn) {
312
0
            result.lineEnd = begin;
313
0
            result.nextLineBegin = begin + 1;
314
0
            if (((begin + 1) < end) && begin[1] == Newline)
315
0
                ++result.nextLineBegin;
316
0
            break;
317
0
        }
318
319
0
        ++begin;
320
0
    }
321
322
0
    return result;
323
0
}
324
325
static bool isBlankLine(const QChar *begin, const QChar *end)
326
0
{
327
0
    while (begin < end) {
328
0
        if (*begin != Space && *begin != Tab)
329
0
            return false;
330
0
        ++begin;
331
0
    }
332
0
    return true;
333
0
}
334
335
static QString createLinkTitle(const QString &title)
336
0
{
337
0
    QString result;
338
0
    result.reserve(title.size() + 2);
339
0
    result += DoubleQuote;
340
341
0
    const QChar *data = title.data();
342
0
    const QChar *end = data + title.size();
343
344
0
    while (data < end) {
345
0
        const auto lineEndPositions = findLineEnd(data, end);
346
347
0
        if (!isBlankLine(data, lineEndPositions.lineEnd)) {
348
0
            while (data < lineEndPositions.nextLineBegin) {
349
0
                if (*data == DoubleQuote)
350
0
                    result += Backslash;
351
0
                result += *data;
352
0
                ++data;
353
0
            }
354
0
        }
355
356
0
        data = lineEndPositions.nextLineBegin;
357
0
    }
358
359
0
    result += DoubleQuote;
360
0
    return result;
361
0
}
362
363
int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ignoreFormat, bool ignoreEmpty)
364
0
{
365
0
    if (block.text().isEmpty() && ignoreEmpty)
366
0
        return 0;
367
0
    const int ColumnLimit = 80;
368
0
    QTextBlockFormat blockFmt = block.blockFormat();
369
0
    bool missedBlankCodeBlockLine = false;
370
0
    const bool codeBlock = blockFmt.hasProperty(QTextFormat::BlockCodeFence) ||
371
0
            blockFmt.stringProperty(QTextFormat::BlockCodeLanguage).length() > 0;
372
0
    if (m_fencedCodeBlock && !codeBlock) {
373
0
        m_stream << m_linePrefix << QString(m_wrappedLineIndent, Space)
374
0
                 << m_codeBlockFence << Newline;
375
0
        m_fencedCodeBlock = false;
376
0
        m_codeBlockFence.clear();
377
0
    }
378
0
    if (block.textList()) { // it's a list-item
379
0
        auto fmt = block.textList()->format();
380
0
        const int listLevel = fmt.indent();
381
0
        const int number = block.textList()->itemNumber(block) + 1;
382
0
        QByteArray bullet = " ";
383
0
        bool numeric = false;
384
0
        switch (fmt.style()) {
385
0
        case QTextListFormat::ListDisc:
386
0
            bullet = "-";
387
0
            m_wrappedLineIndent = 2;
388
0
            break;
389
0
        case QTextListFormat::ListCircle:
390
0
            bullet = "*";
391
0
            m_wrappedLineIndent = 2;
392
0
            break;
393
0
        case QTextListFormat::ListSquare:
394
0
            bullet = "+";
395
0
            m_wrappedLineIndent = 2;
396
0
            break;
397
0
        case QTextListFormat::ListStyleUndefined: break;
398
0
        case QTextListFormat::ListDecimal:
399
0
        case QTextListFormat::ListLowerAlpha:
400
0
        case QTextListFormat::ListUpperAlpha:
401
0
        case QTextListFormat::ListLowerRoman:
402
0
        case QTextListFormat::ListUpperRoman:
403
0
            numeric = true;
404
0
            m_wrappedLineIndent = 4;
405
0
            break;
406
0
        }
407
0
        switch (blockFmt.marker()) {
408
0
        case QTextBlockFormat::MarkerType::Checked:
409
0
            bullet += " [x]";
410
0
            break;
411
0
        case QTextBlockFormat::MarkerType::Unchecked:
412
0
            bullet += " [ ]";
413
0
            break;
414
0
        default:
415
0
            break;
416
0
        }
417
0
        int indentFirstLine = (listLevel - 1) * (numeric ? 4 : 2);
418
0
        m_wrappedLineIndent += indentFirstLine;
419
0
        if (m_lastListIndent != listLevel && !m_doubleNewlineWritten && listInfo(block.textList()).loose)
420
0
            m_stream << Newline;
421
0
        m_lastListIndent = listLevel;
422
0
        QString prefix(indentFirstLine, Space);
423
0
        if (numeric) {
424
0
            QString suffix = fmt.numberSuffix();
425
0
            if (suffix.isEmpty())
426
0
                suffix = QString(Period);
427
0
            QString numberStr = QString::number(number) + suffix + Space;
428
0
            if (numberStr.length() == 3)
429
0
                numberStr += Space;
430
0
            prefix += numberStr;
431
0
        } else {
432
0
            prefix += QLatin1String(bullet) + Space;
433
0
        }
434
0
        m_stream << prefix;
435
0
    } else if (blockFmt.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
436
0
        m_stream << "- - -\n"; // unambiguous horizontal rule, not an underline under a heading
437
0
        return 0;
438
0
    } else if (codeBlock) {
439
        // It's important to preserve blank lines in code blocks.  But blank lines in code blocks
440
        // inside block quotes are getting preserved anyway (along with the "> " prefix).
441
0
        if (!blockFmt.hasProperty(QTextFormat::BlockQuoteLevel))
442
0
            missedBlankCodeBlockLine = true; // only if we don't get any fragments below
443
0
        if (!m_fencedCodeBlock) {
444
0
            QString fenceChar = blockFmt.stringProperty(QTextFormat::BlockCodeFence);
445
0
            if (fenceChar.isEmpty())
446
0
                fenceChar = QLatin1String("`");
447
0
            m_codeBlockFence = QString(3, fenceChar.at(0));
448
            // A block quote can contain an indented code block, but not vice-versa.
449
0
            m_stream << m_linePrefix << QString(m_wrappedLineIndent, Space) << m_codeBlockFence
450
0
                     << blockFmt.stringProperty(QTextFormat::BlockCodeLanguage) << Newline;
451
0
            m_fencedCodeBlock = true;
452
0
        }
453
0
        wrap = false;
454
0
    } else if (!blockFmt.indent()) {
455
0
        m_wrappedLineIndent = 0;
456
0
        m_linePrefix.clear();
457
0
        if (blockFmt.hasProperty(QTextFormat::BlockQuoteLevel)) {
458
0
            int level = blockFmt.intProperty(QTextFormat::BlockQuoteLevel);
459
0
            QString quoteMarker = QStringLiteral("> ");
460
0
            m_linePrefix.reserve(level * 2);
461
0
            for (int i = 0; i < level; ++i)
462
0
                m_linePrefix += quoteMarker;
463
0
        }
464
0
        if (blockFmt.hasProperty(QTextFormat::BlockCodeLanguage)) {
465
            // A block quote can contain an indented code block, but not vice-versa.
466
0
            m_linePrefix += QString(4, Space);
467
0
            m_indentedCodeBlock = true;
468
0
        }
469
0
    }
470
0
    if (blockFmt.headingLevel())
471
0
        m_stream << QByteArray(blockFmt.headingLevel(), '#') << ' ';
472
0
    else
473
0
        m_stream << m_linePrefix;
474
475
0
    QString wrapIndentString = m_linePrefix + QString(m_wrappedLineIndent, Space);
476
    // It would be convenient if QTextStream had a lineCharPos() accessor,
477
    // to keep track of how many characters (not bytes) have been written on the current line,
478
    // but it doesn't.  So we have to keep track with this col variable.
479
0
    int col = wrapIndentString.length();
480
0
    bool mono = false;
481
0
    bool startsOrEndsWithBacktick = false;
482
0
    bool bold = false;
483
0
    bool italic = false;
484
0
    bool underline = false;
485
0
    bool strikeOut = false;
486
0
    QString backticks(Backtick);
487
0
    for (QTextBlock::Iterator frag = block.begin(); !frag.atEnd(); ++frag) {
488
0
        missedBlankCodeBlockLine = false;
489
0
        QString fragmentText = frag.fragment().text();
490
0
        while (fragmentText.endsWith(Newline))
491
0
            fragmentText.chop(1);
492
0
        if (block.textList()) { // <li>first line</br>continuation</li>
493
0
            QString newlineIndent = QString(Newline) + QString(m_wrappedLineIndent, Space);
494
0
            fragmentText.replace(QString(LineBreak), newlineIndent);
495
0
        } else if (blockFmt.indent() > 0) { // <li>first line<p>continuation</p></li>
496
0
            m_stream << QString(m_wrappedLineIndent, Space);
497
0
        } else {
498
0
            fragmentText.replace(LineBreak, Newline);
499
0
        }
500
0
        startsOrEndsWithBacktick |= fragmentText.startsWith(Backtick) || fragmentText.endsWith(Backtick);
501
0
        QTextCharFormat fmt = frag.fragment().charFormat();
502
0
        if (fmt.isImageFormat()) {
503
0
            QTextImageFormat ifmt = fmt.toImageFormat();
504
0
            QString desc = ifmt.stringProperty(QTextFormat::ImageAltText);
505
0
            if (desc.isEmpty())
506
0
                desc = QLatin1String("image");
507
0
            QString s = QLatin1String("![") + desc + QLatin1String("](") + ifmt.name();
508
0
            QString title = ifmt.stringProperty(QTextFormat::ImageTitle);
509
0
            if (!title.isEmpty())
510
0
                s += Space + DoubleQuote + title + DoubleQuote;
511
0
            s += QLatin1Char(')');
512
0
            if (wrap && col + s.length() > ColumnLimit) {
513
0
                m_stream << Newline << wrapIndentString;
514
0
                col = m_wrappedLineIndent;
515
0
            }
516
0
            m_stream << s;
517
0
            col += s.length();
518
0
        } else if (fmt.hasProperty(QTextFormat::AnchorHref)) {
519
0
            QString s = QLatin1Char('[') + fragmentText + QLatin1String("](") +
520
0
                    fmt.property(QTextFormat::AnchorHref).toString();
521
0
            if (fmt.hasProperty(QTextFormat::TextToolTip)) {
522
0
                s += Space;
523
0
                s += createLinkTitle(fmt.property(QTextFormat::TextToolTip).toString());
524
0
            }
525
0
            s += QLatin1Char(')');
526
0
            if (wrap && col + s.length() > ColumnLimit) {
527
0
                m_stream << Newline << wrapIndentString;
528
0
                col = m_wrappedLineIndent;
529
0
            }
530
0
            m_stream << s;
531
0
            col += s.length();
532
0
        } else {
533
0
            QFontInfo fontInfo(fmt.font());
534
0
            bool monoFrag = fontInfo.fixedPitch() || fmt.fontFixedPitch();
535
0
            QString markers;
536
0
            if (!ignoreFormat) {
537
0
                if (monoFrag != mono && !m_indentedCodeBlock && !m_fencedCodeBlock) {
538
0
                    if (monoFrag)
539
0
                        backticks = QString(adjacentBackticksCount(fragmentText) + 1, Backtick);
540
0
                    markers += backticks;
541
0
                    if (startsOrEndsWithBacktick)
542
0
                        markers += Space;
543
0
                    mono = monoFrag;
544
0
                }
545
0
                if (!blockFmt.headingLevel() && !mono) {
546
0
                    if (fontInfo.bold() != bold) {
547
0
                        markers += QLatin1String("**");
548
0
                        bold = fontInfo.bold();
549
0
                    }
550
0
                    if (fontInfo.italic() != italic) {
551
0
                        markers += QLatin1Char('*');
552
0
                        italic = fontInfo.italic();
553
0
                    }
554
0
                    if (fontInfo.strikeOut() != strikeOut) {
555
0
                        markers += QLatin1String("~~");
556
0
                        strikeOut = fontInfo.strikeOut();
557
0
                    }
558
0
                    if (fontInfo.underline() != underline) {
559
                        // Markdown doesn't support underline, but the parser will treat a single underline
560
                        // the same as a single asterisk, and the marked fragment will be rendered in italics.
561
                        // That will have to do.
562
0
                        markers += QLatin1Char('_');
563
0
                        underline = fontInfo.underline();
564
0
                    }
565
0
                }
566
0
            }
567
0
            if (wrap && col + markers.length() * 2 + fragmentText.length() > ColumnLimit) {
568
0
                int i = 0;
569
0
                int fragLen = fragmentText.length();
570
0
                bool breakingLine = false;
571
0
                while (i < fragLen) {
572
0
                    if (col >= ColumnLimit) {
573
0
                        m_stream << Newline << wrapIndentString;
574
0
                        col = m_wrappedLineIndent;
575
0
                        while (fragmentText[i].isSpace())
576
0
                            ++i;
577
0
                    }
578
0
                    int j = i + ColumnLimit - col;
579
0
                    if (j < fragLen) {
580
0
                        int wi = nearestWordWrapIndex(fragmentText, j);
581
0
                        if (wi < 0) {
582
0
                            j = fragLen;
583
0
                        } else if (wi >= i) {
584
0
                            j = wi;
585
0
                            breakingLine = true;
586
0
                        }
587
0
                    } else {
588
0
                        j = fragLen;
589
0
                        breakingLine = false;
590
0
                    }
591
0
                    QString subfrag = fragmentText.mid(i, j - i);
592
0
                    if (!i) {
593
0
                        m_stream << markers;
594
0
                        col += markers.length();
595
0
                    }
596
0
                    if (col == m_wrappedLineIndent)
597
0
                        maybeEscapeFirstChar(subfrag);
598
0
                    m_stream << subfrag;
599
0
                    if (breakingLine) {
600
0
                        m_stream << Newline << wrapIndentString;
601
0
                        col = m_wrappedLineIndent;
602
0
                    } else {
603
0
                        col += subfrag.length();
604
0
                    }
605
0
                    i = j + 1;
606
0
                }
607
0
            } else {
608
0
                m_stream << markers << fragmentText;
609
0
                col += markers.length() + fragmentText.length();
610
0
            }
611
0
        }
612
0
    }
613
0
    if (mono) {
614
0
        if (startsOrEndsWithBacktick) {
615
0
            m_stream << Space;
616
0
            col += 1;
617
0
        }
618
0
        m_stream << backticks;
619
0
        col += backticks.size();
620
0
    }
621
0
    if (bold) {
622
0
        m_stream << "**";
623
0
        col += 2;
624
0
    }
625
0
    if (italic) {
626
0
        m_stream << "*";
627
0
        col += 1;
628
0
    }
629
0
    if (underline) {
630
0
        m_stream << "_";
631
0
        col += 1;
632
0
    }
633
0
    if (strikeOut) {
634
0
        m_stream << "~~";
635
0
        col += 2;
636
0
    }
637
0
    if (missedBlankCodeBlockLine)
638
0
        m_stream << Newline;
639
0
    return col;
640
0
}
641
642
QT_END_NAMESPACE