From 3373aa8b351691d395cd15c634ca1b60fd688c6a Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Mon, 13 Jan 2020 22:45:01 +0100 Subject: [PATCH] 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 --- src/gui/text/qtextmarkdownimporter.cpp | 2 + .../tst_qtextmarkdownimporter.cpp | 114 +++++++++++++++++- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/src/gui/text/qtextmarkdownimporter.cpp b/src/gui/text/qtextmarkdownimporter.cpp index a5c5fb3678..b5532696f4 100644 --- a/src/gui/text/qtextmarkdownimporter.cpp +++ b/src/gui/text/qtextmarkdownimporter.cpp @@ -388,6 +388,8 @@ int QTextMarkdownImporter::cbLeaveBlock(int blockType, void *detail) int QTextMarkdownImporter::cbEnterSpan(int spanType, void *det) { QTextCharFormat charFmt; + if (!m_spanFormatStack.isEmpty()) + charFmt = m_spanFormatStack.top(); switch (spanType) { case MD_SPAN_EM: charFmt.setFontItalic(true); diff --git a/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp b/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp index 5eb04af696..78cc1da36e 100644 --- a/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp +++ b/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp @@ -27,13 +27,15 @@ ****************************************************************************/ #include +#include +#include +#include #include #include #include +#include #include #include -#include -#include #include @@ -55,12 +57,28 @@ private slots: void thematicBreaks(); void lists_data(); void lists(); + void nestedSpans_data(); + void nestedSpans(); void avoidBlankLineAtBeginning_data(); void avoidBlankLineAtBeginning(); void pathological_data(); 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() { const QStringList expectedBlocks = QStringList() << @@ -225,6 +243,98 @@ void tst_QTextMarkdownImporter::lists() QCOMPARE(doc.toMarkdown(), rewrite); } +void tst_QTextMarkdownImporter::nestedSpans_data() +{ + QTest::addColumn("input"); + QTest::addColumn("wordToCheck"); + QTest::addColumn("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() { QTest::addColumn("input");