QTextMarkdownWriter: Avoid omitting or misplacing ending indicators
If we need to word-wrap a paragraph after a long formatted span, write any ending markers before the newline (amends 280d679c556ab8ead4748a627d7cd4c1950027fb ). Break before a fragment if the whole thing is past the column limit; in that case, write out any ending format markers before the newline. And now we have test coverage: prepend characters one-at-a-time to a line that already has a two-word formatted span at the end, and watch it successively break after the span, in the middle, and then before, while never putting a newline before the ending markers or failing to write them. Fixes: QTBUG-116927 Change-Id: I140e10d19a491cb599bf7ecf8514af866b5383f3 Pick-to: 6.6 6.5 Reviewed-by: Axel Spoerl <axel.spoerl@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> (cherry picked from commit 908fc2e72b34073dc42ce0f2d6f7cc5adc9651d9) Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
This commit is contained in:
parent
09236abe2d
commit
183ee5c177
@ -482,6 +482,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
|
||||
bool italic = false;
|
||||
bool underline = false;
|
||||
bool strikeOut = false;
|
||||
bool endingMarkers = false;
|
||||
QString backticks(qtmw_Backtick);
|
||||
for (QTextBlock::Iterator frag = block.begin(); !frag.atEnd(); ++frag) {
|
||||
missedBlankCodeBlockLine = false;
|
||||
@ -549,19 +550,27 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
|
||||
if (startsOrEndsWithBacktick)
|
||||
markers += qtmw_Space;
|
||||
mono = monoFrag;
|
||||
if (!mono)
|
||||
endingMarkers = true;
|
||||
}
|
||||
if (!blockFmt.headingLevel() && !mono) {
|
||||
if (fontInfo.bold() != bold) {
|
||||
markers += "**"_L1;
|
||||
bold = fontInfo.bold();
|
||||
if (!bold)
|
||||
endingMarkers = true;
|
||||
}
|
||||
if (fontInfo.italic() != italic) {
|
||||
markers += u'*';
|
||||
italic = fontInfo.italic();
|
||||
if (!italic)
|
||||
endingMarkers = true;
|
||||
}
|
||||
if (fontInfo.strikeOut() != strikeOut) {
|
||||
markers += "~~"_L1;
|
||||
strikeOut = fontInfo.strikeOut();
|
||||
if (!strikeOut)
|
||||
endingMarkers = true;
|
||||
}
|
||||
if (fontInfo.underline() != underline) {
|
||||
// Markdown doesn't support underline, but the parser will treat a single underline
|
||||
@ -569,6 +578,8 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
|
||||
// That will have to do.
|
||||
markers += u'_';
|
||||
underline = fontInfo.underline();
|
||||
if (!underline)
|
||||
endingMarkers = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -578,7 +589,8 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
|
||||
bool breakingLine = false;
|
||||
while (i < fragLen) {
|
||||
if (col >= ColumnLimit) {
|
||||
m_stream << qtmw_Newline << wrapIndentString;
|
||||
m_stream << markers << qtmw_Newline << wrapIndentString;
|
||||
markers.clear();
|
||||
col = m_wrappedLineIndent;
|
||||
while (i < fragLen && fragmentText[i].isSpace())
|
||||
++i;
|
||||
@ -588,6 +600,13 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
|
||||
int wi = nearestWordWrapIndex(fragmentText, j);
|
||||
if (wi < 0) {
|
||||
j = fragLen;
|
||||
// can't break within the fragment: we need to break already _before_ it
|
||||
if (endingMarkers) {
|
||||
m_stream << markers;
|
||||
markers.clear();
|
||||
}
|
||||
m_stream << qtmw_Newline << wrapIndentString;
|
||||
col = m_wrappedLineIndent;
|
||||
} else if (wi >= i) {
|
||||
j = wi;
|
||||
breakingLine = true;
|
||||
@ -611,7 +630,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
|
||||
col += subfrag.size();
|
||||
}
|
||||
i = j + 1;
|
||||
}
|
||||
} // loop over fragment characters (we know we need to break somewhere)
|
||||
} else {
|
||||
if (!m_linePrefixWritten && col == wrapIndentString.size()) {
|
||||
m_stream << m_linePrefix;
|
||||
|
@ -36,6 +36,8 @@ private slots:
|
||||
void testWriteNestedNumericLists();
|
||||
void testWriteNumericListWithStart();
|
||||
void testWriteTable();
|
||||
void charFormatWrapping_data();
|
||||
void charFormatWrapping();
|
||||
void rewriteDocument_data();
|
||||
void rewriteDocument();
|
||||
void fromHtml_data();
|
||||
@ -525,6 +527,86 @@ void tst_QTextMarkdownWriter::testWriteTable()
|
||||
QCOMPARE(md, expected);
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::charFormatWrapping_data()
|
||||
{
|
||||
QTest::addColumn<QTextFormat::Property>("property");
|
||||
QTest::addColumn<QVariant>("propertyValue");
|
||||
QTest::addColumn<QString>("followingText");
|
||||
QTest::addColumn<QString>("expectedIndicator");
|
||||
|
||||
const QString spaced = " after";
|
||||
const QString unspaced = ", and some more after";
|
||||
|
||||
QTest::newRow("FontFixedPitch-spaced")
|
||||
<< QTextFormat::FontFixedPitch << QVariant(true) << spaced << "`";
|
||||
QTest::newRow("FontFixedPitch-unspaced")
|
||||
<< QTextFormat::FontFixedPitch << QVariant(true) << unspaced << "`";
|
||||
QTest::newRow("FontItalic")
|
||||
<< QTextFormat::FontItalic << QVariant(true) << spaced << "*";
|
||||
QTest::newRow("FontUnderline")
|
||||
<< QTextFormat::FontUnderline << QVariant(true) << spaced << "_";
|
||||
QTest::newRow("FontStrikeOut")
|
||||
<< QTextFormat::FontStrikeOut << QVariant(true) << spaced << "~~";
|
||||
QTest::newRow("FontWeight-spaced")
|
||||
<< QTextFormat::FontWeight << QVariant(700) << spaced << "**";
|
||||
QTest::newRow("FontWeight-unspaced")
|
||||
<< QTextFormat::FontWeight << QVariant(700) << unspaced << "**";
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::charFormatWrapping() // QTBUG-116927
|
||||
{
|
||||
QFETCH(QTextFormat::Property, property);
|
||||
QFETCH(QVariant, propertyValue);
|
||||
QFETCH(QString, expectedIndicator);
|
||||
QFETCH(QString, followingText);
|
||||
|
||||
const QString newLine("\n");
|
||||
QTextCursor cursor(document);
|
||||
cursor.insertText("around sixty-four characters to go before some formatted words ");
|
||||
QTextCharFormat fmt;
|
||||
fmt.setProperty(property, propertyValue);
|
||||
cursor.setCharFormat(fmt);
|
||||
cursor.insertText("formatted text");
|
||||
|
||||
cursor.setCharFormat({});
|
||||
cursor.insertText(followingText);
|
||||
qsizetype lastNewLineIndex = 100;
|
||||
|
||||
for (int push = 0; push < 10; ++push) {
|
||||
if (push > 0) {
|
||||
cursor.movePosition(QTextCursor::StartOfBlock);
|
||||
cursor.insertText("a");
|
||||
}
|
||||
|
||||
const QString output = documentToUnixMarkdown().trimmed(); // get rid of trailing newlines
|
||||
const auto nlIdx = output.indexOf(newLine);
|
||||
qCDebug(lcTests) << "push" << push << ":" << output << "newline @" << nlIdx;
|
||||
// we're always wrapping in this test: expect to find a newline
|
||||
QCOMPARE_GT(nlIdx, 70);
|
||||
// don't expect the newline to be more than one character to the right of where we found it last time
|
||||
// i.e. if we already started breaking in the middle: "`formatted\ntext`",
|
||||
// then we would not expect that prepending one more character would make it go
|
||||
// back to breaking afterwards: "`formatted text`\n" (because then the line becomes longer than necessary)
|
||||
QCOMPARE_LE(nlIdx, lastNewLineIndex + 1);
|
||||
lastNewLineIndex = nlIdx;
|
||||
const QString nextChars = output.sliced(nlIdx + newLine.size(), expectedIndicator.size());
|
||||
const auto startingIndicatorIdx = output.indexOf(expectedIndicator);
|
||||
// the starting indicator always exists, except in case of font problems on some CI platforms
|
||||
if (startingIndicatorIdx <= 0)
|
||||
QSKIP("starting indicator not found, probably due to platform font problems (QTBUG-103484 etc.)");
|
||||
const auto endingIndicatorIdx = output.indexOf(expectedIndicator, startingIndicatorIdx + 5);
|
||||
qCDebug(lcTests) << "next chars past newline" << nextChars
|
||||
<< "indicators @" << startingIndicatorIdx << endingIndicatorIdx;
|
||||
// the closing indicator must exist
|
||||
QCOMPARE_GT(endingIndicatorIdx, startingIndicatorIdx);
|
||||
// don't start a new line with an ending indicator:
|
||||
// we can have "**formatted\ntext**" or "**formatted text**\n" or "\n**formatted text**"
|
||||
// but not "**formatted text\n**"
|
||||
if (startingIndicatorIdx < nlIdx)
|
||||
QCOMPARE_NE(nextChars, expectedIndicator);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::rewriteDocument_data()
|
||||
{
|
||||
QTest::addColumn<QString>("inputFile");
|
||||
@ -569,7 +651,7 @@ void tst_QTextMarkdownWriter::fromHtml_data()
|
||||
|
||||
QTest::newRow("long URL") <<
|
||||
"<span style=\"font-style:italic;\">https://www.example.com/dir/subdir/subsubdir/subsubsubdir/subsubsubsubdir/subsubsubsubsubdir/</span>" <<
|
||||
"*https://www.example.com/dir/subdir/subsubdir/subsubsubdir/subsubsubsubdir/subsubsubsubsubdir/*\n\n";
|
||||
"\n*https://www.example.com/dir/subdir/subsubdir/subsubsubdir/subsubsubsubdir/subsubsubsubsubdir/*\n\n";
|
||||
QTest::newRow("non-emphasis inline asterisk") << "3 * 4" << "3 * 4\n\n";
|
||||
QTest::newRow("arithmetic") << "(2 * a * x + b)^2 = b^2 - 4 * a * c" << "(2 * a * x + b)^2 = b^2 - 4 * a * c\n\n";
|
||||
QTest::newRow("escaped asterisk after newline") <<
|
||||
|
Loading…
x
Reference in New Issue
Block a user