95void QTextMarkdownWriter::writeFrame(
const QTextFrame *frame)
98 const QTextTable *table = qobject_cast<
const QTextTable*> (frame);
99 QTextFrame::iterator iterator = frame->begin();
100 QTextFrame *child =
nullptr;
102 bool lastWasList =
false;
103 QList<
int> tableColumnWidths;
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);
110 auto it = cell.begin();
111 while (it != cell.end()) {
112 QTextBlock block = it.currentBlock();
114 cellTextLen += block.text().size();
117 if (cell.columnSpan() == 1 && tableColumnWidths[col] < cellTextLen)
118 tableColumnWidths[col] = cellTextLen;
122 while (!iterator.atEnd()) {
123 if (iterator.currentFrame() && child != iterator.currentFrame())
124 writeFrame(iterator.currentFrame());
126 QTextBlock block = iterator.currentBlock();
129 bool nextIsDifferent =
false;
131 int blockQuoteIndent = 0;
132 int nextBlockQuoteIndent = 0;
134 QTextFrame::iterator next = iterator;
136 QTextBlockFormat format = iterator.currentBlock().blockFormat();
137 QTextBlockFormat nextFormat = next.currentBlock().blockFormat();
138 blockQuoteIndent = format.intProperty(QTextFormat::BlockQuoteLevel);
139 nextBlockQuoteIndent = nextFormat.intProperty(QTextFormat::BlockQuoteLevel);
141 nextIsDifferent =
true;
144 if (nextFormat.indent() != format.indent() ||
145 nextFormat.property(QTextFormat::BlockCodeLanguage) !=
146 format.property(QTextFormat::BlockCodeLanguage))
147 nextIsDifferent =
true;
151 QTextTableCell cell = table->cellAt(block.position());
152 if (tableRow < cell.row()) {
154 m_stream << qtmw_Newline;
155 for (
int col = 0; col < tableColumnWidths.size(); ++col)
156 m_stream <<
'|' << QString(tableColumnWidths[col], u'-');
159 m_stream << qtmw_Newline <<
'|';
160 tableRow = cell.row();
162 }
else if (!block.textList()) {
164 m_stream << qtmw_Newline;
165 m_linePrefixWritten =
false;
168 int endingCol = writeBlock(block, !table, table && tableRow == 0,
169 nextIsDifferent && !block.textList());
170 m_doubleNewlineWritten =
false;
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];
178 m_stream << QString(paddingLen, qtmw_Space);
179 for (
int col = cell.column(); col < spanEndCol; ++col)
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) {
188 if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) {
189 m_stream << qtmw_Newline;
190 if (block.textList()) {
191 m_stream << m_linePrefix;
192 m_linePrefixWritten =
true;
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;
203 lastWasList = block.textList();
205 child = iterator.currentFrame();
209 m_stream << qtmw_Newline << qtmw_Newline;
210 m_doubleNewlineWritten =
true;
408int QTextMarkdownWriter::writeBlock(
const QTextBlock &block,
bool wrap,
bool ignoreFormat,
bool ignoreEmpty)
410 if (block.text().isEmpty() && ignoreEmpty)
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;
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;
433 if (block.textList()) {
434 auto fmt = block.textList()->format();
435 const int listLevel = fmt.indent();
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()) {
442 case QTextListFormat::ListDisc:
444 m_wrappedLineIndent = 2;
446 case QTextListFormat::ListCircle:
448 m_wrappedLineIndent = 2;
450 case QTextListFormat::ListSquare:
452 m_wrappedLineIndent = 2;
454 case QTextListFormat::ListStyleUndefined:
break;
455 case QTextListFormat::ListDecimal:
456 case QTextListFormat::ListLowerAlpha:
457 case QTextListFormat::ListUpperAlpha:
458 case QTextListFormat::ListLowerRoman:
459 case QTextListFormat::ListUpperRoman:
461 m_wrappedLineIndent = 4;
464 switch (blockFmt.marker()) {
465 case QTextBlockFormat::MarkerType::Checked:
468 case QTextBlockFormat::MarkerType::Unchecked:
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);
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;
489 prefix += QLatin1StringView(bullet) + qtmw_Space;
492 }
else if (blockFmt.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
493 m_stream <<
"- - -\n";
495 }
else if (codeBlock) {
498 if (!blockFmt.hasProperty(QTextFormat::BlockQuoteLevel))
499 missedBlankCodeBlockLine =
true;
500 if (!m_fencedCodeBlock) {
501 QString fenceChar = blockFmt.stringProperty(QTextFormat::BlockCodeFence);
502 if (fenceChar.isEmpty())
504 m_codeBlockFence = QString(3, fenceChar.at(0));
505 if (blockFmt.hasProperty(QTextFormat::BlockIndent))
506 m_codeBlockFence = QString(m_wrappedLineIndent, qtmw_Space) + m_codeBlockFence;
508 m_stream << m_codeBlockFence << blockFmt.stringProperty(QTextFormat::BlockCodeLanguage)
509 << qtmw_Newline << m_linePrefix;
510 m_fencedCodeBlock =
true;
513 }
else if (!blockFmt.indent()) {
514 m_wrappedLineIndent = 0;
515 if (blockFmt.hasProperty(QTextFormat::BlockCodeLanguage)) {
517 m_linePrefix += QString(4, qtmw_Space);
518 m_indentedCodeBlock =
true;
520 if (!m_linePrefixWritten) {
521 m_stream << m_linePrefix;
522 m_linePrefixWritten =
true;
525 if (blockFmt.headingLevel()) {
526 m_stream << QByteArray(blockFmt.headingLevel(),
'#') <<
' ';
530 QString wrapIndentString = m_linePrefix + QString(m_wrappedLineIndent, qtmw_Space);
534 int col = wrapIndentString.size();
536 bool startsOrEndsWithBacktick =
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);
552 if (block.textList()) {
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) {
557 m_stream << QString(m_wrappedLineIndent, qtmw_Space);
559 fragmentText.replace(qtmw_LineBreak, qtmw_Newline);
561 startsOrEndsWithBacktick |=
562 fragmentText.startsWith(qtmw_Backtick) || fragmentText.endsWith(qtmw_Backtick);
563 QTextCharFormat fmt = frag.fragment().charFormat();
564 if (fmt.isImageFormat()) {
565 QTextImageFormat ifmt = fmt.toImageFormat();
566 QString desc = ifmt.stringProperty(QTextFormat::ImageAltText);
569 QString s =
";
570 QString title = ifmt.stringProperty(QTextFormat::ImageTitle);
571 if (!title.isEmpty())
572 s += qtmw_Space + qtmw_DoubleQuote + title + qtmw_DoubleQuote;
574 if (wrap && col + s.size() > ColumnLimit) {
575 m_stream << qtmw_Newline << wrapIndentString;
576 col = m_wrappedLineIndent;
580 }
else if (fmt.hasProperty(QTextFormat::AnchorHref)) {
581 const auto href = fmt.property(QTextFormat::AnchorHref).toString();
582 const bool hasToolTip = fmt.hasProperty(QTextFormat::TextToolTip);
584 if (!hasToolTip && href == fragmentText && !QUrl(href, QUrl::StrictMode).scheme().isEmpty()) {
585 s = u'<' + href + u'>';
587 s = u'[' + fragmentText +
"]("_L1 + href;
590 s += createLinkTitle(fmt.property(QTextFormat::TextToolTip).toString());
594 if (wrap && col + s.size() > ColumnLimit) {
595 m_stream << qtmw_Newline << wrapIndentString;
596 col = m_wrappedLineIndent;
601 QFontInfo fontInfo(fmt.font());
602 bool monoFrag = fontInfo.fixedPitch() || fmt.fontFixedPitch();
605 if (monoFrag != mono && !m_indentedCodeBlock && !m_fencedCodeBlock) {
608 QString(adjacentBackticksCount(fragmentText) + 1, qtmw_Backtick);
609 markers += backticks;
610 if (startsOrEndsWithBacktick)
611 markers += qtmw_Space;
614 endingMarkers =
true;
616 if (!blockFmt.headingLevel() && !mono) {
617 if (fontInfo.bold() != bold) {
619 bold = fontInfo.bold();
621 endingMarkers =
true;
623 if (fontInfo.italic() != italic) {
625 italic = fontInfo.italic();
627 endingMarkers =
true;
629 if (fontInfo.strikeOut() != strikeOut) {
631 strikeOut = fontInfo.strikeOut();
633 endingMarkers =
true;
635 if (fontInfo.underline() != underline) {
640 underline = fontInfo.underline();
642 endingMarkers =
true;
646 if (wrap && col + markers.size() * 2 + fragmentText.size() > ColumnLimit) {
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;
654 col = m_wrappedLineIndent;
655 while (i < fragLen && fragmentText[i].isSpace())
658 int j = i + ColumnLimit - col;
660 int wi = nearestWordWrapIndex(fragmentText, j);
668 m_stream << qtmw_Newline << wrapIndentString;
669 col = m_wrappedLineIndent;
670 }
else if (wi >= i) {
676 breakingLine =
false;
678 QString subfrag = fragmentText.mid(i, j - i);
681 col += markers.size();
683 if (col == m_wrappedLineIndent)
684 maybeEscapeFirstChar(subfrag);
687 m_stream << qtmw_Newline << wrapIndentString;
688 col = m_wrappedLineIndent;
690 col += subfrag.size();
695 if (!m_linePrefixWritten && col == wrapIndentString.size()) {
696 m_stream << m_linePrefix;
697 col += m_linePrefix.size();
699 m_stream << markers << fragmentText;
700 col += markers.size() + fragmentText.size();
705 if (startsOrEndsWithBacktick) {
706 m_stream << qtmw_Space;
709 m_stream << backticks;
710 col += backticks.size();
728 if (missedBlankCodeBlockLine)
729 m_stream << qtmw_Newline;
730 m_linePrefixWritten =
false;