130void QTextMarkdownWriter::writeFrame(
const QTextFrame *frame)
133 const QTextTable *table = qobject_cast<
const QTextTable*> (frame);
134 QTextFrame::iterator iterator = frame->begin();
135 QTextFrame *child =
nullptr;
137 bool lastWasList =
false;
138 QList<
int> tableColumnWidths;
140 tableColumnWidths.resize(table->columns());
141 for (
int col = 0; col < table->columns(); ++col) {
142 for (
int row = 0; row < table->rows(); ++ row) {
143 QTextTableCell cell = table->cellAt(row, col);
144 if (cell.columnSpan() != 1)
150 const int cellTextLen = measureCellWidth(cell, row == 0);
151 if (tableColumnWidths[col] < cellTextLen)
152 tableColumnWidths[col] = cellTextLen;
156 while (!iterator.atEnd()) {
157 if (iterator.currentFrame() && child != iterator.currentFrame())
158 writeFrame(iterator.currentFrame());
160 QTextBlock block = iterator.currentBlock();
163 bool nextIsDifferent =
false;
165 int blockQuoteIndent = 0;
166 int nextBlockQuoteIndent = 0;
168 QTextFrame::iterator next = iterator;
170 QTextBlockFormat format = iterator.currentBlock().blockFormat();
171 QTextBlockFormat nextFormat = next.currentBlock().blockFormat();
172 blockQuoteIndent = format.intProperty(QTextFormat::BlockQuoteLevel);
173 nextBlockQuoteIndent = nextFormat.intProperty(QTextFormat::BlockQuoteLevel);
175 nextIsDifferent =
true;
178 if (nextFormat.indent() != format.indent() ||
179 nextFormat.property(QTextFormat::BlockCodeLanguage) !=
180 format.property(QTextFormat::BlockCodeLanguage))
181 nextIsDifferent =
true;
185 QTextTableCell cell = table->cellAt(block.position());
186 if (tableRow < cell.row()) {
188 (*m_stream) << qtmw_Newline;
189 for (
int col = 0; col < tableColumnWidths.size(); ++col)
190 (*m_stream) <<
'|' << QString(tableColumnWidths[col], u'-');
193 (*m_stream) << qtmw_Newline <<
'|';
194 tableRow = cell.row();
196 }
else if (!block.textList()) {
198 (*m_stream) << qtmw_Newline;
199 m_blockState.linePrefixWritten =
false;
202 int endingCol = writeBlock(block, !table, table && tableRow == 0,
203 nextIsDifferent && !block.textList());
204 m_blockState.doubleNewlineWritten =
false;
206 QTextTableCell cell = table->cellAt(block.position());
207 int paddingLen = -endingCol;
208 int spanEndCol = cell.column() + cell.columnSpan();
209 for (
int col = cell.column(); col < spanEndCol; ++col)
210 paddingLen += tableColumnWidths[col];
212 (*m_stream) << QString(paddingLen, qtmw_Space);
213 for (
int col = cell.column(); col < spanEndCol; ++col)
215 }
else if (m_blockState.fencedCodeBlock && ending) {
216 (*m_stream) << qtmw_Newline << m_blockState.linePrefix << QString(m_blockState.wrappedLineIndent, qtmw_Space)
217 << m_blockState.codeBlockFence << qtmw_Newline << qtmw_Newline;
218 m_blockState.codeBlockFence.clear();
219 }
else if (m_blockState.indentedCodeBlock && nextIsDifferent) {
220 (*m_stream) << qtmw_Newline << qtmw_Newline;
221 }
else if (endingCol > 0) {
222 if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) {
223 (*m_stream) << qtmw_Newline;
226 const bool quoteEndsWithItem = block.blockFormat().boolProperty(
227 QTextFormat::BlockQuoteInsideListItem);
228 if (block.textList() && !quoteEndsWithItem) {
229 (*m_stream) << m_blockState.linePrefix;
230 m_blockState.linePrefixWritten =
true;
233 (*m_stream) << qtmw_Newline;
234 if (nextBlockQuoteIndent < blockQuoteIndent)
235 setLinePrefixForBlockQuote(nextBlockQuoteIndent);
236 (*m_stream) << m_blockState.linePrefix;
237 (*m_stream) << qtmw_Newline;
238 m_blockState.doubleNewlineWritten =
true;
241 lastWasList = block.textList();
243 child = iterator.currentFrame();
247 (*m_stream) << qtmw_Newline << qtmw_Newline;
248 m_blockState.doubleNewlineWritten =
true;
446int QTextMarkdownWriter::writeBlock(
const QTextBlock &block,
bool wrap,
bool ignoreFormat,
bool ignoreEmpty)
448 if (block.text().isEmpty() && ignoreEmpty)
450 const int ColumnLimit = 80;
451 QTextBlockFormat blockFmt = block.blockFormat();
452 bool missedBlankCodeBlockLine =
false;
453 const bool codeBlock = blockFmt.hasProperty(QTextFormat::BlockCodeFence) ||
454 blockFmt.stringProperty(QTextFormat::BlockCodeLanguage).size() > 0 ||
455 blockFmt.nonBreakableLines();
456 const int blockQuoteLevel = blockFmt.intProperty(QTextFormat::BlockQuoteLevel);
457 if (m_blockState.fencedCodeBlock && !codeBlock) {
458 (*m_stream) << m_blockState.linePrefix << m_blockState.codeBlockFence << qtmw_Newline;
459 m_blockState.fencedCodeBlock =
false;
460 m_blockState.codeBlockFence.clear();
461 m_blockState.linePrefixWritten = m_blockState.linePrefix.size() > 0;
463 m_blockState.linePrefix.clear();
466 const bool quoteInListItem = blockQuoteLevel > 0 && block.textList()
467 && blockFmt.boolProperty(QTextFormat::BlockQuoteInsideListItem);
468 if (!blockFmt.headingLevel() && blockQuoteLevel > 0) {
469 setLinePrefixForBlockQuote(blockQuoteLevel);
470 if (!m_blockState.linePrefixWritten && !quoteInListItem) {
471 (*m_stream) << m_blockState.linePrefix;
472 m_blockState.linePrefixWritten =
true;
475 if (block.textList()) {
476 auto fmt = block.textList()->format();
477 const int listLevel = fmt.indent();
479 const int start = fmt.start() >= 0 ? fmt.start() : 1;
480 const int number = block.textList()->itemNumber(block) + start;
481 QByteArray bullet =
" ";
482 bool numeric =
false;
483 switch (fmt.style()) {
484 case QTextListFormat::ListDisc:
486 m_blockState.wrappedLineIndent = 2;
488 case QTextListFormat::ListCircle:
490 m_blockState.wrappedLineIndent = 2;
492 case QTextListFormat::ListSquare:
494 m_blockState.wrappedLineIndent = 2;
496 case QTextListFormat::ListStyleUndefined:
break;
497 case QTextListFormat::ListDecimal:
498 case QTextListFormat::ListLowerAlpha:
499 case QTextListFormat::ListUpperAlpha:
500 case QTextListFormat::ListLowerRoman:
501 case QTextListFormat::ListUpperRoman:
503 m_blockState.wrappedLineIndent = 4;
506 switch (blockFmt.marker()) {
507 case QTextBlockFormat::MarkerType::Checked:
510 case QTextBlockFormat::MarkerType::Unchecked:
516 int indentFirstLine = (listLevel - 1) * (numeric ? 4 : 2);
517 m_blockState.wrappedLineIndent += indentFirstLine;
524 const bool cannotInterruptParagraph =
525 numeric && number != 1 && listLevel > m_blockState.lastListIndent;
526 if (m_blockState.lastListIndent != listLevel && !m_blockState.doubleNewlineWritten
527 && (cannotInterruptParagraph || listInfo(block.textList()).loose))
528 (*m_stream) << qtmw_Newline;
529 m_blockState.lastListIndent = listLevel;
530 QString prefix(indentFirstLine, qtmw_Space);
532 QString suffix = fmt.numberSuffix();
533 if (suffix.isEmpty())
534 suffix = QString(qtmw_Period);
535 QString numberStr = QString::number(number) + suffix + qtmw_Space;
536 if (numberStr.size() == 3)
537 numberStr += qtmw_Space;
540 prefix += QLatin1StringView(bullet) + qtmw_Space;
542 if (quoteInListItem) {
544 prefix += m_blockState.linePrefix;
545 m_blockState.linePrefixWritten =
true;
547 (*m_stream) << prefix;
548 }
else if (blockFmt.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
549 (*m_stream) <<
"- - -\n";
551 }
else if (codeBlock) {
554 if (!blockFmt.hasProperty(QTextFormat::BlockQuoteLevel))
555 missedBlankCodeBlockLine =
true;
556 if (!m_blockState.fencedCodeBlock) {
557 QString fenceChar = blockFmt.stringProperty(QTextFormat::BlockCodeFence);
558 if (fenceChar.isEmpty())
560 m_blockState.codeBlockFence = QString(3, fenceChar.at(0));
561 if (blockFmt.hasProperty(QTextFormat::BlockIndent))
562 m_blockState.codeBlockFence = QString(m_blockState.wrappedLineIndent, qtmw_Space) + m_blockState.codeBlockFence;
564 (*m_stream) << m_blockState.codeBlockFence << blockFmt.stringProperty(QTextFormat::BlockCodeLanguage)
565 << qtmw_Newline << m_blockState.linePrefix;
566 m_blockState.fencedCodeBlock =
true;
569 }
else if (!blockFmt.indent()) {
570 m_blockState.wrappedLineIndent = 0;
571 if (blockFmt.hasProperty(QTextFormat::BlockCodeLanguage)) {
573 m_blockState.linePrefix += QString(4, qtmw_Space);
574 m_blockState.indentedCodeBlock =
true;
576 if (!m_blockState.linePrefixWritten) {
577 (*m_stream) << m_blockState.linePrefix;
578 m_blockState.linePrefixWritten =
true;
581 if (blockFmt.headingLevel()) {
582 (*m_stream) << QByteArray(blockFmt.headingLevel(),
'#') <<
' ';
588 const QString wrappedLineIndentString(m_blockState.wrappedLineIndent, qtmw_Space);
589 QString wrapIndentString;
591 wrapIndentString = wrappedLineIndentString + m_blockState.linePrefix;
593 wrapIndentString = m_blockState.linePrefix + wrappedLineIndentString;
597 int col = wrapIndentString.size();
599 bool startsOrEndsWithBacktick =
false;
602 bool underline =
false;
603 bool strikeOut =
false;
604 bool endingMarkers =
false;
605 QString backticks(qtmw_Backtick);
606 for (QTextBlock::Iterator frag = block.begin(); !frag.atEnd(); ++frag) {
607 missedBlankCodeBlockLine =
false;
608 QString fragmentText = frag.fragment().text();
609 while (fragmentText.endsWith(qtmw_Newline))
610 fragmentText.chop(1);
611 if (!(m_blockState.fencedCodeBlock || m_blockState.indentedCodeBlock)) {
612 escapeSpecialCharacters(fragmentText);
613 maybeEscapeFirstChar(fragmentText);
615 if (block.textList()) {
616 QString newlineIndent =
617 QString(qtmw_Newline) + QString(m_blockState.wrappedLineIndent, qtmw_Space);
618 fragmentText.replace(QString(qtmw_LineBreak), newlineIndent);
619 }
else if (blockFmt.indent() > 0) {
620 (*m_stream) << QString(m_blockState.wrappedLineIndent, qtmw_Space);
622 fragmentText.replace(qtmw_LineBreak, qtmw_Newline);
624 startsOrEndsWithBacktick |=
625 fragmentText.startsWith(qtmw_Backtick) || fragmentText.endsWith(qtmw_Backtick);
626 QTextCharFormat fmt = frag.fragment().charFormat();
627 if (fmt.isImageFormat()) {
628 QTextImageFormat ifmt = fmt.toImageFormat();
629 QString desc = ifmt.stringProperty(QTextFormat::ImageAltText);
632 QString s =
";
633 QString title = ifmt.stringProperty(QTextFormat::ImageTitle);
634 if (!title.isEmpty())
635 s += qtmw_Space + qtmw_DoubleQuote + title + qtmw_DoubleQuote;
637 if (wrap && col + s.size() > ColumnLimit) {
638 (*m_stream) << qtmw_Newline << wrapIndentString;
639 col = m_blockState.wrappedLineIndent;
643 }
else if (fmt.hasProperty(QTextFormat::AnchorHref)) {
644 const auto href = fmt.property(QTextFormat::AnchorHref).toString();
645 const bool hasToolTip = fmt.hasProperty(QTextFormat::TextToolTip);
647 if (!hasToolTip && href == fragmentText && !QUrl(href, QUrl::StrictMode).scheme().isEmpty()) {
648 s = u'<' + href + u'>';
650 s = u'[' + fragmentText +
"]("_L1 + href;
653 s += createLinkTitle(fmt.property(QTextFormat::TextToolTip).toString());
657 if (wrap && col + s.size() > ColumnLimit) {
658 (*m_stream) << qtmw_Newline << wrapIndentString;
659 col = m_blockState.wrappedLineIndent;
664 QFontInfo fontInfo(fmt.font());
665 bool monoFrag = fontInfo.fixedPitch() || fmt.fontFixedPitch();
668 if (monoFrag != mono && !m_blockState.indentedCodeBlock && !m_blockState.fencedCodeBlock) {
671 QString(adjacentBackticksCount(fragmentText) + 1, qtmw_Backtick);
672 markers += backticks;
673 if (startsOrEndsWithBacktick)
674 markers += qtmw_Space;
677 endingMarkers =
true;
679 if (!blockFmt.headingLevel() && !mono) {
680 if (fontInfo.bold() != bold) {
682 bold = fontInfo.bold();
684 endingMarkers =
true;
686 if (fontInfo.italic() != italic) {
688 italic = fontInfo.italic();
690 endingMarkers =
true;
692 if (fontInfo.strikeOut() != strikeOut) {
694 strikeOut = fontInfo.strikeOut();
696 endingMarkers =
true;
698 if (fontInfo.underline() != underline) {
703 underline = fontInfo.underline();
705 endingMarkers =
true;
709 if (wrap && col + markers.size() * 2 + fragmentText.size() > ColumnLimit) {
711 const int fragLen = fragmentText.size();
712 bool breakingLine =
false;
713 while (i < fragLen) {
714 if (col >= ColumnLimit) {
718 (*m_stream) << markers;
721 (*m_stream) << qtmw_Newline << wrapIndentString;
722 col = m_blockState.wrappedLineIndent;
723 while (i < fragLen && fragmentText[i].isSpace())
726 int j = i + ColumnLimit - col;
728 int wi = nearestWordWrapIndex(fragmentText, j);
733 (*m_stream) << markers;
736 (*m_stream) << qtmw_Newline << wrapIndentString;
737 col = m_blockState.wrappedLineIndent;
738 }
else if (wi >= i) {
744 breakingLine =
false;
746 QString subfrag = fragmentText.mid(i, j - i);
748 (*m_stream) << markers;
749 col += markers.size();
751 if (col == m_blockState.wrappedLineIndent)
752 maybeEscapeFirstChar(subfrag);
753 (*m_stream) << subfrag;
755 (*m_stream) << qtmw_Newline << wrapIndentString;
756 col = m_blockState.wrappedLineIndent;
758 col += subfrag.size();
763 if (!m_blockState.linePrefixWritten && col == wrapIndentString.size()) {
764 (*m_stream) << m_blockState.linePrefix;
765 col += m_blockState.linePrefix.size();
767 (*m_stream) << markers << fragmentText;
768 col += markers.size() + fragmentText.size();
773 if (startsOrEndsWithBacktick) {
774 (*m_stream) << qtmw_Space;
777 (*m_stream) << backticks;
778 col += backticks.size();
796 if (missedBlankCodeBlockLine)
797 (*m_stream) << qtmw_Newline;
798 m_blockState.linePrefixWritten =
false;