Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qtextmarkdownwriter.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6#include "qfontinfo.h"
7#include "qfontmetrics.h"
8#include "qtextdocument_p.h"
9#include "qtextlist.h"
10#include "qtexttable.h"
11#include "qtextcursor.h"
12#include "qtextimagehandler_p.h"
14#include "qloggingcategory.h"
15#include <QtCore/QRegularExpression>
16#if QT_CONFIG(itemmodel)
17#include "qabstractitemmodel.h"
18#endif
19
21
22using namespace Qt::StringLiterals;
23
24Q_LOGGING_CATEGORY(lcMDW, "qt.text.markdown.writer")
25
26static const QChar qtmw_Space = u' ';
27static const QChar qtmw_Tab = u'\t';
28static const QChar qtmw_Newline = u'\n';
29static const QChar qtmw_CarriageReturn = u'\r';
30static const QChar qtmw_LineBreak = u'\x2028';
31static const QChar qtmw_DoubleQuote = u'"';
32static const QChar qtmw_Backtick = u'`';
33static const QChar qtmw_Backslash = u'\\';
34static const QChar qtmw_Period = u'.';
35
37 : m_stream(stream), m_features(features)
38{
39}
40
42{
43 writeFrontMatter(document->metaInformation(QTextDocument::FrontMatter));
44 writeFrame(document->rootFrame());
45 return true;
46}
47
48#if QT_CONFIG(itemmodel)
49void QTextMarkdownWriter::writeTable(const QAbstractItemModel *table)
50{
51 QList<int> tableColumnWidths(table->columnCount());
52 for (int col = 0; col < table->columnCount(); ++col) {
53 tableColumnWidths[col] = table->headerData(col, Qt::Horizontal).toString().size();
54 for (int row = 0; row < table->rowCount(); ++row) {
55 tableColumnWidths[col] = qMax(tableColumnWidths[col],
56 table->data(table->index(row, col)).toString().size());
57 }
58 }
59
60 // write the header and separator
61 for (int col = 0; col < table->columnCount(); ++col) {
62 QString s = table->headerData(col, Qt::Horizontal).toString();
63 m_stream << '|' << s << QString(tableColumnWidths[col] - s.size(), qtmw_Space);
64 }
65 m_stream << "|" << Qt::endl;
66 for (int col = 0; col < tableColumnWidths.size(); ++col)
67 m_stream << '|' << QString(tableColumnWidths[col], u'-');
68 m_stream << '|'<< Qt::endl;
69
70 // write the body
71 for (int row = 0; row < table->rowCount(); ++row) {
72 for (int col = 0; col < table->columnCount(); ++col) {
73 QString s = table->data(table->index(row, col)).toString();
74 m_stream << '|' << s << QString(tableColumnWidths[col] - s.size(), qtmw_Space);
75 }
76 m_stream << '|'<< Qt::endl;
77 }
78 m_listInfo.clear();
79}
80#endif
81
83{
84 const bool featureEnabled = m_features.testFlag(
85 static_cast<QTextDocument::MarkdownFeature>(QTextMarkdownImporter::FeatureFrontMatter));
86 qCDebug(lcMDW) << "writing FrontMatter?" << featureEnabled << "size" << fm.size();
87 if (fm.isEmpty() || !featureEnabled)
88 return;
89 m_stream << "---\n"_L1 << fm;
90 if (!fm.endsWith(qtmw_Newline))
91 m_stream << qtmw_Newline;
92 m_stream << "---\n"_L1;
93}
94
96{
98 const QTextTable *table = qobject_cast<const QTextTable*> (frame);
99 QTextFrame::iterator iterator = frame->begin();
100 QTextFrame *child = nullptr;
101 int tableRow = -1;
102 bool lastWasList = false;
103 QList<int> tableColumnWidths;
104 if (table) {
105 tableColumnWidths.resize(table->columns());
106 for (int col = 0; col < table->columns(); ++col) {
107 for (int row = 0; row < table->rows(); ++ row) {
108 QTextTableCell cell = table->cellAt(row, col);
109 int cellTextLen = 0;
110 auto it = cell.begin();
111 while (it != cell.end()) {
112 QTextBlock block = it.currentBlock();
113 if (block.isValid())
114 cellTextLen += block.text().size();
115 ++it;
116 }
117 if (cell.columnSpan() == 1 && tableColumnWidths[col] < cellTextLen)
118 tableColumnWidths[col] = cellTextLen;
119 }
120 }
121 }
122 while (!iterator.atEnd()) {
123 if (iterator.currentFrame() && child != iterator.currentFrame())
124 writeFrame(iterator.currentFrame());
125 else { // no frame, it's a block
126 QTextBlock block = iterator.currentBlock();
127 // Look ahead and detect some cases when we should
128 // suppress needless blank lines, when there will be a big change in block format
129 bool nextIsDifferent = false;
130 bool ending = false;
131 int blockQuoteIndent = 0;
132 int nextBlockQuoteIndent = 0;
133 {
134 QTextFrame::iterator next = iterator;
135 ++next;
136 QTextBlockFormat format = iterator.currentBlock().blockFormat();
137 QTextBlockFormat nextFormat = next.currentBlock().blockFormat();
138 blockQuoteIndent = format.intProperty(QTextFormat::BlockQuoteLevel);
139 nextBlockQuoteIndent = nextFormat.intProperty(QTextFormat::BlockQuoteLevel);
140 if (next.atEnd()) {
141 nextIsDifferent = true;
142 ending = true;
143 } else {
144 if (nextFormat.indent() != format.indent() ||
145 nextFormat.property(QTextFormat::BlockCodeLanguage) !=
147 nextIsDifferent = true;
148 }
149 }
150 if (table) {
151 QTextTableCell cell = table->cellAt(block.position());
152 if (tableRow < cell.row()) {
153 if (tableRow == 0) {
154 m_stream << qtmw_Newline;
155 for (int col = 0; col < tableColumnWidths.size(); ++col)
156 m_stream << '|' << QString(tableColumnWidths[col], u'-');
157 m_stream << '|';
158 }
159 m_stream << qtmw_Newline << '|';
160 tableRow = cell.row();
161 }
162 } else if (!block.textList()) {
163 if (lastWasList) {
164 m_stream << qtmw_Newline;
165 m_linePrefixWritten = false;
166 }
167 }
168 int endingCol = writeBlock(block, !table, table && tableRow == 0,
169 nextIsDifferent && !block.textList());
170 m_doubleNewlineWritten = false;
171 if (table) {
172 QTextTableCell cell = table->cellAt(block.position());
173 int paddingLen = -endingCol;
174 int spanEndCol = cell.column() + cell.columnSpan();
175 for (int col = cell.column(); col < spanEndCol; ++col)
176 paddingLen += tableColumnWidths[col];
177 if (paddingLen > 0)
178 m_stream << QString(paddingLen, qtmw_Space);
179 for (int col = cell.column(); col < spanEndCol; ++col)
180 m_stream << "|";
181 } else if (m_fencedCodeBlock && ending) {
182 m_stream << qtmw_Newline << m_linePrefix << QString(m_wrappedLineIndent, qtmw_Space)
183 << m_codeBlockFence << qtmw_Newline << qtmw_Newline;
184 m_codeBlockFence.clear();
185 } else if (m_indentedCodeBlock && nextIsDifferent) {
186 m_stream << qtmw_Newline << qtmw_Newline;
187 } else if (endingCol > 0) {
189 m_stream << qtmw_Newline;
190 if (block.textList()) {
191 m_stream << m_linePrefix;
192 m_linePrefixWritten = true;
193 }
194 } else {
195 m_stream << qtmw_Newline;
196 if (nextBlockQuoteIndent < blockQuoteIndent)
197 setLinePrefixForBlockQuote(nextBlockQuoteIndent);
198 m_stream << m_linePrefix;
199 m_stream << qtmw_Newline;
200 m_doubleNewlineWritten = true;
201 }
202 }
203 lastWasList = block.textList();
204 }
205 child = iterator.currentFrame();
206 ++iterator;
207 }
208 if (table) {
209 m_stream << qtmw_Newline << qtmw_Newline;
210 m_doubleNewlineWritten = true;
211 }
212 m_listInfo.clear();
213}
214
215QTextMarkdownWriter::ListInfo QTextMarkdownWriter::listInfo(QTextList *list)
216{
217 if (!m_listInfo.contains(list)) {
218 // decide whether this list is loose or tight
219 ListInfo info;
220 info.loose = false;
221 if (list->count() > 1) {
222 QTextBlock first = list->item(0);
223 QTextBlock last = list->item(list->count() - 1);
224 QTextBlock next = first.next();
225 while (next.isValid()) {
226 if (next == last)
227 break;
228 qCDebug(lcMDW) << "next block in list" << list << next.text() << "part of list?" << next.textList();
229 if (!next.textList()) {
230 // If we find a continuation paragraph, this list is "loose"
231 // because it will need a blank line to separate that paragraph.
232 qCDebug(lcMDW) << "decided list beginning with" << first.text() << "is loose after" << next.text();
233 info.loose = true;
234 break;
235 }
236 next = next.next();
237 }
238 }
239 m_listInfo.insert(list, info);
240 return info;
241 }
242 return m_listInfo.value(list);
243}
244
245void QTextMarkdownWriter::setLinePrefixForBlockQuote(int level)
246{
247 m_linePrefix.clear();
248 if (level > 0) {
249 m_linePrefix.reserve(level * 2);
250 for (int i = 0; i < level; ++i)
251 m_linePrefix += u"> ";
252 }
253}
254
255static int nearestWordWrapIndex(const QString &s, int before)
256{
257 before = qMin(before, s.size());
258 int fragBegin = qMax(before - 15, 0);
259 if (lcMDW().isDebugEnabled()) {
260 QString frag = s.mid(fragBegin, 30);
261 qCDebug(lcMDW) << frag << before;
262 qCDebug(lcMDW) << QString(before - fragBegin, qtmw_Period) + u'<';
263 }
264 for (int i = before - 1; i >= 0; --i) {
265 if (s.at(i).isSpace()) {
266 qCDebug(lcMDW) << QString(i - fragBegin, qtmw_Period) + u'^' << i;
267 return i;
268 }
269 }
270 qCDebug(lcMDW, "not possible");
271 return -1;
272}
273
275{
276 int start = -1, len = s.size();
277 int ret = 0;
278 for (int i = 0; i < len; ++i) {
279 if (s.at(i) == qtmw_Backtick) {
280 if (start < 0)
281 start = i;
282 } else if (start >= 0) {
283 ret = qMax(ret, i - start);
284 start = -1;
285 }
286 }
287 if (s.at(len - 1) == qtmw_Backtick)
288 ret = qMax(ret, len - start);
289 return ret;
290}
291
299{
300 static const QRegularExpression numericListRe(uR"(\d+([\.)])\s)"_s);
301 static const QLatin1StringView specialFirstCharacters("#*+-");
302
303 QString sTrimmed = s.trimmed();
304 if (sTrimmed.isEmpty())
305 return;
306 QChar firstChar = sTrimmed.at(0);
307 if (specialFirstCharacters.contains(firstChar)) {
308 int i = s.indexOf(firstChar); // == 0 unless s got trimmed
309 s.insert(i, u'\\');
310 } else {
311 auto match = numericListRe.match(s, 0, QRegularExpression::NormalMatch,
313 if (match.hasMatch())
314 s.insert(match.capturedStart(1), qtmw_Backslash);
315 }
316}
317
324{
325 static const QRegularExpression spaceRe(uR"(\s+)"_s);
326 static const QRegularExpression specialRe(uR"([<!*[`&]+[/\w])"_s);
327
328 s.replace("\\"_L1, "\\\\"_L1);
329
330 int i = 0;
331 while (i >= 0) {
332 if (int j = s.indexOf(specialRe, i); j >= 0) {
333 s.insert(j, qtmw_Backslash);
334 i = j + 3;
335 }
336 i = s.indexOf(spaceRe, i);
337 if (i >= 0)
338 ++i; // past the whitespace, if found
339 }
340}
341
346
348{
350
351 while (begin < end) {
352 if (*begin == qtmw_Newline) {
354 result.nextLineBegin = begin + 1;
355 break;
356 } else if (*begin == qtmw_CarriageReturn) {
357 result.lineEnd = begin;
358 result.nextLineBegin = begin + 1;
359 if (((begin + 1) < end) && begin[1] == qtmw_Newline)
360 ++result.nextLineBegin;
361 break;
362 }
363
364 ++begin;
365 }
366
367 return result;
368}
369
370static bool isBlankLine(const QChar *begin, const QChar *end)
371{
372 while (begin < end) {
373 if (*begin != qtmw_Space && *begin != qtmw_Tab)
374 return false;
375 ++begin;
376 }
377 return true;
378}
379
381{
383 result.reserve(title.size() + 2);
385
386 const QChar *data = title.data();
387 const QChar *end = data + title.size();
388
389 while (data < end) {
390 const auto lineEndPositions = findLineEnd(data, end);
391
392 if (!isBlankLine(data, lineEndPositions.lineEnd)) {
393 while (data < lineEndPositions.nextLineBegin) {
394 if (*data == qtmw_DoubleQuote)
396 result += *data;
397 ++data;
398 }
399 }
400
401 data = lineEndPositions.nextLineBegin;
402 }
403
405 return result;
406}
407
408int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ignoreFormat, bool ignoreEmpty)
409{
410 if (block.text().isEmpty() && ignoreEmpty)
411 return 0;
412 const int ColumnLimit = 80;
413 QTextBlockFormat blockFmt = block.blockFormat();
414 bool missedBlankCodeBlockLine = false;
415 const bool codeBlock = blockFmt.hasProperty(QTextFormat::BlockCodeFence) ||
416 blockFmt.stringProperty(QTextFormat::BlockCodeLanguage).size() > 0 ||
417 blockFmt.nonBreakableLines();
418 const int blockQuoteLevel = blockFmt.intProperty(QTextFormat::BlockQuoteLevel);
419 if (m_fencedCodeBlock && !codeBlock) {
420 m_stream << m_linePrefix << m_codeBlockFence << qtmw_Newline;
421 m_fencedCodeBlock = false;
422 m_codeBlockFence.clear();
423 m_linePrefixWritten = m_linePrefix.size() > 0;
424 }
425 m_linePrefix.clear();
426 if (!blockFmt.headingLevel() && blockQuoteLevel > 0) {
427 setLinePrefixForBlockQuote(blockQuoteLevel);
428 if (!m_linePrefixWritten) {
429 m_stream << m_linePrefix;
430 m_linePrefixWritten = true;
431 }
432 }
433 if (block.textList()) { // it's a list-item
434 auto fmt = block.textList()->format();
435 const int listLevel = fmt.indent();
436 // Negative numbers don't start a list in Markdown, so ignore them.
437 const int start = fmt.start() >= 0 ? fmt.start() : 1;
438 const int number = block.textList()->itemNumber(block) + start;
439 QByteArray bullet = " ";
440 bool numeric = false;
441 switch (fmt.style()) {
443 bullet = "-";
444 m_wrappedLineIndent = 2;
445 break;
447 bullet = "*";
448 m_wrappedLineIndent = 2;
449 break;
451 bullet = "+";
452 m_wrappedLineIndent = 2;
453 break;
460 numeric = true;
461 m_wrappedLineIndent = 4;
462 break;
463 }
464 switch (blockFmt.marker()) {
466 bullet += " [x]";
467 break;
469 bullet += " [ ]";
470 break;
471 default:
472 break;
473 }
474 int indentFirstLine = (listLevel - 1) * (numeric ? 4 : 2);
475 m_wrappedLineIndent += indentFirstLine;
476 if (m_lastListIndent != listLevel && !m_doubleNewlineWritten && listInfo(block.textList()).loose)
477 m_stream << qtmw_Newline;
478 m_lastListIndent = listLevel;
479 QString prefix(indentFirstLine, qtmw_Space);
480 if (numeric) {
481 QString suffix = fmt.numberSuffix();
482 if (suffix.isEmpty())
483 suffix = QString(qtmw_Period);
484 QString numberStr = QString::number(number) + suffix + qtmw_Space;
485 if (numberStr.size() == 3)
486 numberStr += qtmw_Space;
487 prefix += numberStr;
488 } else {
489 prefix += QLatin1StringView(bullet) + qtmw_Space;
490 }
491 m_stream << prefix;
492 } else if (blockFmt.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
493 m_stream << "- - -\n"; // unambiguous horizontal rule, not an underline under a heading
494 return 0;
495 } else if (codeBlock) {
496 // It's important to preserve blank lines in code blocks. But blank lines in code blocks
497 // inside block quotes are getting preserved anyway (along with the "> " prefix).
498 if (!blockFmt.hasProperty(QTextFormat::BlockQuoteLevel))
499 missedBlankCodeBlockLine = true; // only if we don't get any fragments below
500 if (!m_fencedCodeBlock) {
501 QString fenceChar = blockFmt.stringProperty(QTextFormat::BlockCodeFence);
502 if (fenceChar.isEmpty())
503 fenceChar = "`"_L1;
504 m_codeBlockFence = QString(3, fenceChar.at(0));
505 if (blockFmt.hasProperty(QTextFormat::BlockIndent))
506 m_codeBlockFence = QString(m_wrappedLineIndent, qtmw_Space) + m_codeBlockFence;
507 // A block quote can contain an indented code block, but not vice-versa.
508 m_stream << m_codeBlockFence << blockFmt.stringProperty(QTextFormat::BlockCodeLanguage)
509 << qtmw_Newline << m_linePrefix;
510 m_fencedCodeBlock = true;
511 }
512 wrap = false;
513 } else if (!blockFmt.indent()) {
514 m_wrappedLineIndent = 0;
515 if (blockFmt.hasProperty(QTextFormat::BlockCodeLanguage)) {
516 // A block quote can contain an indented code block, but not vice-versa.
517 m_linePrefix += QString(4, qtmw_Space);
518 m_indentedCodeBlock = true;
519 }
520 if (!m_linePrefixWritten) {
521 m_stream << m_linePrefix;
522 m_linePrefixWritten = true;
523 }
524 }
525 if (blockFmt.headingLevel()) {
526 m_stream << QByteArray(blockFmt.headingLevel(), '#') << ' ';
527 wrap = false;
528 }
529
530 QString wrapIndentString = m_linePrefix + QString(m_wrappedLineIndent, qtmw_Space);
531 // It would be convenient if QTextStream had a lineCharPos() accessor,
532 // to keep track of how many characters (not bytes) have been written on the current line,
533 // but it doesn't. So we have to keep track with this col variable.
534 int col = wrapIndentString.size();
535 bool mono = false;
536 bool startsOrEndsWithBacktick = false;
537 bool bold = false;
538 bool italic = false;
539 bool underline = false;
540 bool strikeOut = false;
541 bool endingMarkers = false;
542 QString backticks(qtmw_Backtick);
543 for (QTextBlock::Iterator frag = block.begin(); !frag.atEnd(); ++frag) {
544 missedBlankCodeBlockLine = false;
545 QString fragmentText = frag.fragment().text();
546 while (fragmentText.endsWith(qtmw_Newline))
547 fragmentText.chop(1);
548 if (!(m_fencedCodeBlock || m_indentedCodeBlock)) {
549 escapeSpecialCharacters(fragmentText);
550 maybeEscapeFirstChar(fragmentText);
551 }
552 if (block.textList()) { // <li>first line</br>continuation</li>
553 QString newlineIndent =
554 QString(qtmw_Newline) + QString(m_wrappedLineIndent, qtmw_Space);
555 fragmentText.replace(QString(qtmw_LineBreak), newlineIndent);
556 } else if (blockFmt.indent() > 0) { // <li>first line<p>continuation</p></li>
557 m_stream << QString(m_wrappedLineIndent, qtmw_Space);
558 } else {
559 fragmentText.replace(qtmw_LineBreak, qtmw_Newline);
560 }
561 startsOrEndsWithBacktick |=
562 fragmentText.startsWith(qtmw_Backtick) || fragmentText.endsWith(qtmw_Backtick);
563 QTextCharFormat fmt = frag.fragment().charFormat();
564 if (fmt.isImageFormat()) {
566 QString desc = ifmt.stringProperty(QTextFormat::ImageAltText);
567 if (desc.isEmpty())
568 desc = "image"_L1;
569 QString s = "!["_L1 + desc + "]("_L1 + ifmt.name();
570 QString title = ifmt.stringProperty(QTextFormat::ImageTitle);
571 if (!title.isEmpty())
573 s += u')';
574 if (wrap && col + s.size() > ColumnLimit) {
575 m_stream << qtmw_Newline << wrapIndentString;
576 col = m_wrappedLineIndent;
577 }
578 m_stream << s;
579 col += s.size();
580 } else if (fmt.hasProperty(QTextFormat::AnchorHref)) {
581 const auto href = fmt.property(QTextFormat::AnchorHref).toString();
582 const bool hasToolTip = fmt.hasProperty(QTextFormat::TextToolTip);
583 QString s;
584 if (!hasToolTip && href == fragmentText && !QUrl(href, QUrl::StrictMode).scheme().isEmpty()) {
585 s = u'<' + href + u'>';
586 } else {
587 s = u'[' + fragmentText + "]("_L1 + href;
588 if (hasToolTip) {
589 s += qtmw_Space;
590 s += createLinkTitle(fmt.property(QTextFormat::TextToolTip).toString());
591 }
592 s += u')';
593 }
594 if (wrap && col + s.size() > ColumnLimit) {
595 m_stream << qtmw_Newline << wrapIndentString;
596 col = m_wrappedLineIndent;
597 }
598 m_stream << s;
599 col += s.size();
600 } else {
601 QFontInfo fontInfo(fmt.font());
602 bool monoFrag = fontInfo.fixedPitch() || fmt.fontFixedPitch();
603 QString markers;
604 if (!ignoreFormat) {
605 if (monoFrag != mono && !m_indentedCodeBlock && !m_fencedCodeBlock) {
606 if (monoFrag)
607 backticks =
608 QString(adjacentBackticksCount(fragmentText) + 1, qtmw_Backtick);
609 markers += backticks;
610 if (startsOrEndsWithBacktick)
611 markers += qtmw_Space;
612 mono = monoFrag;
613 if (!mono)
614 endingMarkers = true;
615 }
616 if (!blockFmt.headingLevel() && !mono) {
617 if (fontInfo.bold() != bold) {
618 markers += "**"_L1;
619 bold = fontInfo.bold();
620 if (!bold)
621 endingMarkers = true;
622 }
623 if (fontInfo.italic() != italic) {
624 markers += u'*';
625 italic = fontInfo.italic();
626 if (!italic)
627 endingMarkers = true;
628 }
629 if (fontInfo.strikeOut() != strikeOut) {
630 markers += "~~"_L1;
631 strikeOut = fontInfo.strikeOut();
632 if (!strikeOut)
633 endingMarkers = true;
634 }
635 if (fontInfo.underline() != underline) {
636 // CommonMark specifies underline as another way to get emphasis (italics):
637 // https://spec.commonmark.org/0.31.2/#example-148
638 // but md4c allows us to distinguish them; so we support underlining (in GitHub dialect).
639 markers += u'_';
640 underline = fontInfo.underline();
641 if (!underline)
642 endingMarkers = true;
643 }
644 }
645 }
646 if (wrap && col + markers.size() * 2 + fragmentText.size() > ColumnLimit) {
647 int i = 0;
648 const int fragLen = fragmentText.size();
649 bool breakingLine = false;
650 while (i < fragLen) {
651 if (col >= ColumnLimit) {
652 m_stream << markers << qtmw_Newline << wrapIndentString;
653 markers.clear();
654 col = m_wrappedLineIndent;
655 while (i < fragLen && fragmentText[i].isSpace())
656 ++i;
657 }
658 int j = i + ColumnLimit - col;
659 if (j < fragLen) {
660 int wi = nearestWordWrapIndex(fragmentText, j);
661 if (wi < 0) {
662 j = fragLen;
663 // can't break within the fragment: we need to break already _before_ it
664 if (endingMarkers) {
665 m_stream << markers;
666 markers.clear();
667 }
668 m_stream << qtmw_Newline << wrapIndentString;
669 col = m_wrappedLineIndent;
670 } else if (wi >= i) {
671 j = wi;
672 breakingLine = true;
673 }
674 } else {
675 j = fragLen;
676 breakingLine = false;
677 }
678 QString subfrag = fragmentText.mid(i, j - i);
679 if (!i) {
680 m_stream << markers;
681 col += markers.size();
682 }
683 if (col == m_wrappedLineIndent)
684 maybeEscapeFirstChar(subfrag);
685 m_stream << subfrag;
686 if (breakingLine) {
687 m_stream << qtmw_Newline << wrapIndentString;
688 col = m_wrappedLineIndent;
689 } else {
690 col += subfrag.size();
691 }
692 i = j + 1;
693 } // loop over fragment characters (we know we need to break somewhere)
694 } else {
695 if (!m_linePrefixWritten && col == wrapIndentString.size()) {
696 m_stream << m_linePrefix;
697 col += m_linePrefix.size();
698 }
699 m_stream << markers << fragmentText;
700 col += markers.size() + fragmentText.size();
701 }
702 }
703 }
704 if (mono) {
705 if (startsOrEndsWithBacktick) {
706 m_stream << qtmw_Space;
707 col += 1;
708 }
709 m_stream << backticks;
710 col += backticks.size();
711 }
712 if (bold) {
713 m_stream << "**";
714 col += 2;
715 }
716 if (italic) {
717 m_stream << "*";
718 col += 1;
719 }
720 if (underline) {
721 m_stream << "_";
722 col += 1;
723 }
724 if (strikeOut) {
725 m_stream << "~~";
726 col += 2;
727 }
728 if (missedBlankCodeBlockLine)
729 m_stream << qtmw_Newline;
730 m_linePrefixWritten = false;
731 return col;
732}
733
\inmodule QtCore
Definition qbytearray.h:57
\inmodule QtCore
\reentrant
Definition qfontinfo.h:16
bool italic() const
Returns the italic value of the matched window system font.
Definition qfont.cpp:3155
bool fixedPitch() const
Returns the fixed pitch value of the matched window system font.
Definition qfont.cpp:3266
bool bold() const
Returns true if weight() would return a value greater than QFont::Normal; otherwise returns false.
Definition qfontinfo.h:34
bool strikeOut() const
Returns the strikeout value of the matched window system font.
Definition qfont.cpp:3256
bool underline() const
Returns the underline value of the matched window system font.
Definition qfont.cpp:3228
qsizetype count() const noexcept
Definition qlist.h:398
iterator insert(const Key &key, const T &value)
Definition qmap.h:688
T value(const Key &key, const T &defaultValue=T()) const
Definition qmap.h:357
bool contains(const Key &key) const
Definition qmap.h:341
void clear()
Definition qmap.h:289
\inmodule QtCore \reentrant
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
void reserve(qsizetype size)
Ensures the string has space for at least size characters.
Definition qstring.h:1325
void chop(qsizetype n)
Removes n characters from the end of the string.
Definition qstring.cpp:6340
QString mid(qsizetype position, qsizetype n=-1) const &
Definition qstring.cpp:5300
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1252
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
QChar * data()
Returns a pointer to the data stored in the QString.
Definition qstring.h:1240
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8084
QString trimmed() const &
Definition qstring.h:447
\reentrant
iterator begin() const
Returns a text block iterator pointing to the beginning of the text block.
QTextBlockFormat blockFormat() const
Returns the QTextBlockFormat that describes block-specific properties.
bool isValid() const
Returns true if this text block is valid; otherwise returns false.
int position() const
Returns the index of the block's first character within the document.
QString text() const
Returns the block's contents as plain text.
QTextList * textList() const
If the block represents a list item, returns the list that the item belongs to; otherwise returns \nu...
\reentrant \inmodule QtGui
@ BlockTrailingHorizontalRulerWidth
QTextImageFormat toImageFormat() const
Returns this format as an image format.
bool hasProperty(int propertyId) const
Returns true if the text format has a property with the given propertyId; otherwise returns false.
\reentrant
Definition qtextobject.h:81
int indent() const
Returns the list format's indentation.
\reentrant
Definition qtextlist.h:18
int itemNumber(const QTextBlock &) const
Returns the index of the list item that corresponds to the given block.
QTextListFormat format() const
Returns the list's format.
Definition qtextlist.h:37
bool writeAll(const QTextDocument *document)
int writeBlock(const QTextBlock &block, bool table, bool ignoreFormat, bool ignoreEmpty)
void writeFrontMatter(const QString &fm)
void writeFrame(const QTextFrame *frame)
\inmodule QtCore
\reentrant
Definition qtexttable.h:19
int columnSpan() const
Returns the number of columns this cell spans.
QTextFrame::iterator end() const
Returns a frame iterator pointing to the end of the table's cell.
int row() const
Returns the number of the row in the table that contains this cell.
int column() const
Returns the number of the column in the table that contains this cell.
QTextFrame::iterator begin() const
Returns a frame iterator pointing to the beginning of the table's cell.
\reentrant
Definition qtexttable.h:63
\inmodule QtCore
Definition qurl.h:94
@ StrictMode
Definition qurl.h:98
QSet< QString >::iterator it
short next
Definition keywords.cpp:445
Combined button and popup list for selecting options.
@ Horizontal
Definition qnamespace.h:99
QTextStream & endl(QTextStream &stream)
Writes '\n' to the stream and flushes the stream.
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLStreamKHR stream
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
return ret
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLenum GLuint GLint level
GLboolean r
[2]
GLuint GLuint end
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint start
GLint first
GLfloat n
GLint GLsizei GLsizei GLenum format
GLdouble s
[6]
Definition qopenglext.h:235
GLdouble GLdouble t
Definition qopenglext.h:243
GLenum GLenum GLsizei void * row
GLuint64EXT * result
[6]
GLenum GLsizei len
GLenum GLenum GLsizei void * table
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QtPrivate::QRegularExpressionMatchIteratorRangeBasedForIterator begin(const QRegularExpressionMatchIterator &iterator)
static QT_BEGIN_NAMESPACE QAsn1Element wrap(quint8 type, const QAsn1Element &child)
static const QChar qtmw_Backtick
static const QChar qtmw_Backslash
static const QChar qtmw_DoubleQuote
static const QChar qtmw_CarriageReturn
static int nearestWordWrapIndex(const QString &s, int before)
static const QChar qtmw_Newline
static int adjacentBackticksCount(const QString &s)
static QString createLinkTitle(const QString &title)
static bool isBlankLine(const QChar *begin, const QChar *end)
static const QChar qtmw_Tab
static void maybeEscapeFirstChar(QString &s)
static const QChar qtmw_Period
static LineEndPositions findLineEnd(const QChar *begin, const QChar *end)
static const QChar qtmw_Space
static const QChar qtmw_LineBreak
static void escapeSpecialCharacters(QString &s)
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
QVideoFrameFormat::PixelFormat fmt
QList< int > list
[14]
QString title
[35]
QLayoutItem * child
[0]
QFrame frame
[0]
QHostInfo info
[0]