QTextMarkdownImporter: allow nesting text span formatting

The bold+italic combination indicated by ***triple stars*** requires
this; but it enables combinations of italics, bold, strikeout, anchor
text (and associated link formatting), image alternate text, and inline
code formatting (monospace). A code span overrides the formatting from
surrounding spans (which might be a bug to fix in another patch, if we
compare to how md2html formats code nested in bold-italics for example),
but the format stack restores state when any char format span ends.

Task-number: QTBUG-81306
Pick-to: 5.15
Change-Id: I289556fa53de400eb50a4d159b9b344eafc517da
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Shawn Rutledge 2020-01-13 22:45:01 +01:00
parent 2503a59e35
commit 3373aa8b35
2 changed files with 114 additions and 2 deletions

View File

@ -388,6 +388,8 @@ int QTextMarkdownImporter::cbLeaveBlock(int blockType, void *detail)
int QTextMarkdownImporter::cbEnterSpan(int spanType, void *det) int QTextMarkdownImporter::cbEnterSpan(int spanType, void *det)
{ {
QTextCharFormat charFmt; QTextCharFormat charFmt;
if (!m_spanFormatStack.isEmpty())
charFmt = m_spanFormatStack.top();
switch (spanType) { switch (spanType) {
case MD_SPAN_EM: case MD_SPAN_EM:
charFmt.setFontItalic(true); charFmt.setFontItalic(true);

View File

@ -27,13 +27,15 @@
****************************************************************************/ ****************************************************************************/
#include <QtTest/QtTest> #include <QtTest/QtTest>
#include <QBuffer>
#include <QDebug>
#include <QFontInfo>
#include <QTextDocument> #include <QTextDocument>
#include <QTextCursor> #include <QTextCursor>
#include <QTextBlock> #include <QTextBlock>
#include <QTextDocumentFragment>
#include <QTextList> #include <QTextList>
#include <QTextTable> #include <QTextTable>
#include <QBuffer>
#include <QDebug>
#include <private/qtextmarkdownimporter_p.h> #include <private/qtextmarkdownimporter_p.h>
@ -55,12 +57,28 @@ private slots:
void thematicBreaks(); void thematicBreaks();
void lists_data(); void lists_data();
void lists(); void lists();
void nestedSpans_data();
void nestedSpans();
void avoidBlankLineAtBeginning_data(); void avoidBlankLineAtBeginning_data();
void avoidBlankLineAtBeginning(); void avoidBlankLineAtBeginning();
void pathological_data(); void pathological_data();
void pathological(); void pathological();
public:
enum CharFormat {
Normal = 0x0,
Italic = 0x1,
Bold = 0x02,
Strikeout = 0x04,
Mono = 0x08,
Link = 0x10
};
Q_DECLARE_FLAGS(CharFormats, CharFormat)
}; };
Q_DECLARE_METATYPE(tst_QTextMarkdownImporter::CharFormats)
Q_DECLARE_OPERATORS_FOR_FLAGS(tst_QTextMarkdownImporter::CharFormats)
void tst_QTextMarkdownImporter::headingBulletsContinuations() void tst_QTextMarkdownImporter::headingBulletsContinuations()
{ {
const QStringList expectedBlocks = QStringList() << const QStringList expectedBlocks = QStringList() <<
@ -225,6 +243,98 @@ void tst_QTextMarkdownImporter::lists()
QCOMPARE(doc.toMarkdown(), rewrite); QCOMPARE(doc.toMarkdown(), rewrite);
} }
void tst_QTextMarkdownImporter::nestedSpans_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<int>("wordToCheck");
QTest::addColumn<CharFormats>("expectedFormat");
QTest::newRow("bold italic")
<< "before ***bold italic*** after"
<< 1 << (Bold | Italic);
QTest::newRow("bold strikeout")
<< "before **~~bold strikeout~~** after"
<< 1 << (Bold | Strikeout);
QTest::newRow("italic strikeout")
<< "before *~~italic strikeout~~* after"
<< 1 << (Italic | Strikeout);
QTest::newRow("bold italic strikeout")
<< "before ***~~bold italic strikeout~~*** after"
<< 1 << (Bold | Italic | Strikeout);
QTest::newRow("bold link text")
<< "before [**bold link**](https://qt.io) after"
<< 1 << (Bold | Link);
QTest::newRow("italic link text")
<< "before [*italic link*](https://qt.io) after"
<< 1 << (Italic | Link);
QTest::newRow("bold italic link text")
<< "before [***bold italic link***](https://qt.io) after"
<< 1 << (Bold | Italic | Link);
QTest::newRow("strikeout link text")
<< "before [~~strikeout link~~](https://qt.io) after"
<< 1 << (Strikeout | Link);
QTest::newRow("strikeout bold italic link text")
<< "before [~~***strikeout bold italic link***~~](https://qt.io) after"
<< 1 << (Strikeout | Bold | Italic | Link);
QTest::newRow("bold image alt")
<< "before [**bold image alt**](/path/to/image.png) after"
<< 1 << (Bold | Link);
QTest::newRow("bold strikeout italic image alt")
<< "before [**~~*bold strikeout italic image alt*~~**](/path/to/image.png) after"
<< 1 << (Strikeout | Bold | Italic | Link);
// code spans currently override all surrounding formatting
QTest::newRow("code in italic span")
<< "before *italic `code` and* after"
<< 2 << (Mono | Normal);
// but the format after the code span ends should revert to what it was before
QTest::newRow("code in italic strikeout bold span")
<< "before *italic ~~strikeout **bold `code` and**~~* after"
<< 5 << (Bold | Italic | Strikeout);
}
void tst_QTextMarkdownImporter::nestedSpans()
{
QFETCH(QString, input);
QFETCH(int, wordToCheck);
QFETCH(CharFormats, expectedFormat);
QTextDocument doc;
doc.setMarkdown(input);
#ifdef DEBUG_WRITE_HTML
{
QFile out("/tmp/" + QLatin1String(QTest::currentDataTag()) + ".html");
out.open(QFile::WriteOnly);
out.write(doc.toHtml().toLatin1());
out.close();
}
#endif
QTextFrame::iterator iterator = doc.rootFrame()->begin();
QTextFrame *currentFrame = iterator.currentFrame();
while (!iterator.atEnd()) {
// There are no child frames
QCOMPARE(iterator.currentFrame(), currentFrame);
// Check the QTextCharFormat of the specified word
QTextCursor cur(iterator.currentBlock());
cur.movePosition(QTextCursor::NextWord, QTextCursor::MoveAnchor, wordToCheck);
cur.select(QTextCursor::WordUnderCursor);
QTextCharFormat fmt = cur.charFormat();
qCDebug(lcTests) << "word" << wordToCheck << cur.selectedText() << "font" << fmt.font()
<< "weight" << fmt.fontWeight() << "italic" << fmt.fontItalic()
<< "strikeout" << fmt.fontStrikeOut() << "anchor" << fmt.isAnchor()
<< "monospace" << QFontInfo(fmt.font()).fixedPitch() // depends on installed fonts (QTBUG-75649)
<< fmt.fontFixedPitch() // returns false even when font family is "monospace"
<< fmt.hasProperty(QTextFormat::FontFixedPitch); // works
QCOMPARE(fmt.fontWeight() > 50, expectedFormat.testFlag(Bold));
QCOMPARE(fmt.fontItalic(), expectedFormat.testFlag(Italic));
QCOMPARE(fmt.fontStrikeOut(), expectedFormat.testFlag(Strikeout));
QCOMPARE(fmt.isAnchor(), expectedFormat.testFlag(Link));
QCOMPARE(fmt.hasProperty(QTextFormat::FontFixedPitch), expectedFormat.testFlag(Mono));
++iterator;
}
}
void tst_QTextMarkdownImporter::avoidBlankLineAtBeginning_data() void tst_QTextMarkdownImporter::avoidBlankLineAtBeginning_data()
{ {
QTest::addColumn<QString>("input"); QTest::addColumn<QString>("input");