diff options
Diffstat (limited to 'src/gui/text')
-rw-r--r-- | src/gui/text/qdistancefield.cpp | 3 | ||||
-rw-r--r-- | src/gui/text/qdistancefield_p.h | 1 | ||||
-rw-r--r-- | src/gui/text/qtextdocument.cpp | 6 | ||||
-rw-r--r-- | src/gui/text/qtextdocumentfragment.cpp | 4 | ||||
-rw-r--r-- | src/gui/text/qtextformat.cpp | 11 | ||||
-rw-r--r-- | src/gui/text/qtextformat.h | 3 | ||||
-rw-r--r-- | src/gui/text/qtexthtmlparser.cpp | 8 | ||||
-rw-r--r-- | src/gui/text/qtexthtmlparser_p.h | 1 | ||||
-rw-r--r-- | src/gui/text/qtextmarkdownimporter.cpp | 32 | ||||
-rw-r--r-- | src/gui/text/qtextmarkdownimporter_p.h | 2 | ||||
-rw-r--r-- | src/gui/text/qtextmarkdownwriter.cpp | 78 | ||||
-rw-r--r-- | src/gui/text/qtextmarkdownwriter_p.h | 5 |
12 files changed, 131 insertions, 23 deletions
diff --git a/src/gui/text/qdistancefield.cpp b/src/gui/text/qdistancefield.cpp index 82bb617733..5967c8d3de 100644 --- a/src/gui/text/qdistancefield.cpp +++ b/src/gui/text/qdistancefield.cpp @@ -904,6 +904,9 @@ QDistanceField::QDistanceField(const QDistanceField &other) d = other.d; } +QDistanceField &QDistanceField::operator=(const QDistanceField &) + = default; + QDistanceField::QDistanceField(const QRawFont &font, glyph_t glyph, bool doubleResolution) { setGlyph(font, glyph, doubleResolution); diff --git a/src/gui/text/qdistancefield_p.h b/src/gui/text/qdistancefield_p.h index 1a1b6866a2..dc5d5a9a02 100644 --- a/src/gui/text/qdistancefield_p.h +++ b/src/gui/text/qdistancefield_p.h @@ -95,6 +95,7 @@ public: QDistanceField(QFontEngine *fontEngine, glyph_t glyph, bool doubleResolution = false); QDistanceField(const QPainterPath &path, glyph_t glyph, bool doubleResolution = false); QDistanceField(const QDistanceField &other); + QDistanceField &operator=(const QDistanceField &other); bool isNull() const; diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp index c800cea3f6..899801ca39 100644 --- a/src/gui/text/qtextdocument.cpp +++ b/src/gui/text/qtextdocument.cpp @@ -2709,6 +2709,12 @@ void QTextHtmlExporter::emitFragment(const QTextFragment &fragment) if (imgFmt.hasProperty(QTextFormat::ImageName)) emitAttribute("src", imgFmt.name()); + if (imgFmt.hasProperty(QTextFormat::ImageAltText)) + emitAttribute("alt", imgFmt.stringProperty(QTextFormat::ImageAltText)); + + if (imgFmt.hasProperty(QTextFormat::ImageTitle)) + emitAttribute("title", imgFmt.stringProperty(QTextFormat::ImageTitle)); + if (imgFmt.hasProperty(QTextFormat::ImageWidth)) emitAttribute("width", QString::number(imgFmt.width())); diff --git a/src/gui/text/qtextdocumentfragment.cpp b/src/gui/text/qtextdocumentfragment.cpp index aef4ea1522..1905d9a1b1 100644 --- a/src/gui/text/qtextdocumentfragment.cpp +++ b/src/gui/text/qtextdocumentfragment.cpp @@ -715,6 +715,10 @@ QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes() case Html_img: { QTextImageFormat fmt; fmt.setName(currentNode->imageName); + if (!currentNode->text.isEmpty()) + fmt.setProperty(QTextFormat::ImageTitle, currentNode->text); + if (!currentNode->imageAlt.isEmpty()) + fmt.setProperty(QTextFormat::ImageAltText, currentNode->imageAlt); fmt.merge(currentNode->charFormat); diff --git a/src/gui/text/qtextformat.cpp b/src/gui/text/qtextformat.cpp index 895b9034e0..090c6cc4ce 100644 --- a/src/gui/text/qtextformat.cpp +++ b/src/gui/text/qtextformat.cpp @@ -564,6 +564,9 @@ Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt) \value BlockTrailingHorizontalRulerWidth The width of a horizontal ruler element. \value HeadingLevel The level of a heading, for example 1 corresponds to an HTML H1 tag; otherwise 0. This enum value has been added in Qt 5.12. + \value BlockCodeFence The character that was used in the "fences" around a Markdown code block. + If the code block was indented rather than fenced, the block should not have this property. + This enum value has been added in Qt 5.14. \value BlockQuoteLevel The depth of nested quoting on this block: 1 means the block is a top-level block quote. Blocks that are not block quotes should not have this property. @@ -659,7 +662,13 @@ Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt) Image properties - \value ImageName + \value ImageName The filename or source of the image. + \value ImageTitle The title attribute of an HTML image tag, or + the quoted string that comes after the URL in a Markdown image link. + This enum value has been added in Qt 5.14. + \value ImageAltText The alt attribute of an HTML image tag, or + the image description in a Markdown image link. + This enum value has been added in Qt 5.14. \value ImageWidth \value ImageHeight \value ImageQuality diff --git a/src/gui/text/qtextformat.h b/src/gui/text/qtextformat.h index a631309ae0..a91461dcae 100644 --- a/src/gui/text/qtextformat.h +++ b/src/gui/text/qtextformat.h @@ -178,6 +178,7 @@ public: HeadingLevel = 0x1070, BlockQuoteLevel = 0x1080, BlockCodeLanguage = 0x1090, + BlockCodeFence = 0x1091, BlockMarker = 0x10A0, // character properties @@ -253,6 +254,8 @@ public: // image properties ImageName = 0x5000, + ImageTitle = 0x5001, + ImageAltText = 0x5002, ImageWidth = 0x5010, ImageHeight = 0x5011, ImageQuality = 0x5014, diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp index 37051502fa..642f0893b4 100644 --- a/src/gui/text/qtexthtmlparser.cpp +++ b/src/gui/text/qtexthtmlparser.cpp @@ -1556,6 +1556,10 @@ void QTextHtmlParser::applyAttributes(const QStringList &attributes) } else if (key == QLatin1String("height")) { node->imageHeight = -2; // register that there is a value for it. setFloatAttribute(&node->imageHeight, value); + } else if (key == QLatin1String("alt")) { + node->imageAlt = value; + } else if (key == QLatin1String("title")) { + node->text = value; } break; case Html_tr: @@ -1631,6 +1635,10 @@ void QTextHtmlParser::applyAttributes(const QStringList &attributes) else if (key == QLatin1String("type")) linkType = value; break; + case Html_pre: + if (key == QLatin1String("class") && value.startsWith(QLatin1String("language-"))) + node->blockFormat.setProperty(QTextFormat::BlockCodeLanguage, value.mid(9)); + break; default: break; } diff --git a/src/gui/text/qtexthtmlparser_p.h b/src/gui/text/qtexthtmlparser_p.h index 73dac38b82..6ce294d211 100644 --- a/src/gui/text/qtexthtmlparser_p.h +++ b/src/gui/text/qtexthtmlparser_p.h @@ -184,6 +184,7 @@ struct QTextHtmlParserNode { QString textListNumberPrefix; QString textListNumberSuffix; QString imageName; + QString imageAlt; qreal imageWidth; qreal imageHeight; QTextLength width; diff --git a/src/gui/text/qtextmarkdownimporter.cpp b/src/gui/text/qtextmarkdownimporter.cpp index b96acba0e6..8a9bb3953c 100644 --- a/src/gui/text/qtextmarkdownimporter.cpp +++ b/src/gui/text/qtextmarkdownimporter.cpp @@ -165,12 +165,13 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det) MD_BLOCK_CODE_DETAIL *detail = static_cast<MD_BLOCK_CODE_DETAIL *>(det); m_codeBlock = true; m_blockCodeLanguage = QLatin1String(detail->lang.text, int(detail->lang.size)); + m_blockCodeFence = detail->fence_char; QString info = QLatin1String(detail->info.text, int(detail->info.size)); m_needsInsertBlock = true; if (m_blockQuoteDepth) - qCDebug(lcMD, "CODE lang '%s' info '%s' inside QUOTE %d", qPrintable(m_blockCodeLanguage), qPrintable(info), m_blockQuoteDepth); + qCDebug(lcMD, "CODE lang '%s' info '%s' fenced with '%c' inside QUOTE %d", qPrintable(m_blockCodeLanguage), qPrintable(info), m_blockCodeFence, m_blockQuoteDepth); else - qCDebug(lcMD, "CODE lang '%s' info '%s'", qPrintable(m_blockCodeLanguage), qPrintable(info)); + qCDebug(lcMD, "CODE lang '%s' info '%s' fenced with '%c'", qPrintable(m_blockCodeLanguage), qPrintable(info), m_blockCodeFence); } break; case MD_BLOCK_H: { MD_BLOCK_H_DETAIL *detail = static_cast<MD_BLOCK_H_DETAIL *>(det); @@ -326,6 +327,7 @@ int QTextMarkdownImporter::cbLeaveBlock(int blockType, void *detail) case MD_BLOCK_CODE: { m_codeBlock = false; m_blockCodeLanguage.clear(); + m_blockCodeFence = 0; if (m_blockQuoteDepth) qCDebug(lcMD, "CODE ended inside QUOTE %d", m_blockQuoteDepth); else @@ -362,15 +364,10 @@ int QTextMarkdownImporter::cbEnterSpan(int spanType, void *det) } break; case MD_SPAN_IMG: { m_imageSpan = true; + m_imageFormat = QTextImageFormat(); MD_SPAN_IMG_DETAIL *detail = static_cast<MD_SPAN_IMG_DETAIL *>(det); - QString src = QString::fromUtf8(detail->src.text, int(detail->src.size)); - QString title = QString::fromUtf8(detail->title.text, int(detail->title.size)); - QTextImageFormat img; - img.setName(src); - if (m_needsInsertBlock) - insertBlock(); - qCDebug(lcMD) << "image" << src << "title" << title << "relative to" << m_doc->baseUrl(); - m_cursor->insertImage(img); + m_imageFormat.setName(QString::fromUtf8(detail->src.text, int(detail->src.size))); + m_imageFormat.setProperty(QTextFormat::ImageTitle, QString::fromUtf8(detail->title.text, int(detail->title.size))); break; } case MD_SPAN_CODE: @@ -406,8 +403,6 @@ int QTextMarkdownImporter::cbLeaveSpan(int spanType, void *detail) int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size) { - if (m_imageSpan) - return 0; // it's the alt-text if (m_needsInsertBlock) insertBlock(); #if QT_CONFIG(regularexpression) @@ -481,6 +476,17 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size) break; } + if (m_imageSpan) { + // TODO we don't yet support alt text with formatting, because of the cases where m_cursor + // already inserted the text above. Rather need to accumulate it in case we need it here. + m_imageFormat.setProperty(QTextFormat::ImageAltText, s); + qCDebug(lcMD) << "image" << m_imageFormat.name() + << "title" << m_imageFormat.stringProperty(QTextFormat::ImageTitle) + << "alt" << s << "relative to" << m_doc->baseUrl(); + m_cursor->insertImage(m_imageFormat); + return 0; // no error + } + if (!s.isEmpty()) m_cursor->insertText(s); if (m_cursor->currentList()) { @@ -536,6 +542,8 @@ void QTextMarkdownImporter::insertBlock() } if (m_codeBlock) { blockFormat.setProperty(QTextFormat::BlockCodeLanguage, m_blockCodeLanguage); + if (m_blockCodeFence) + blockFormat.setProperty(QTextFormat::BlockCodeFence, QString(QLatin1Char(m_blockCodeFence))); charFormat.setFont(m_monoFont); } else { blockFormat.setTopMargin(m_paragraphMargin); diff --git a/src/gui/text/qtextmarkdownimporter_p.h b/src/gui/text/qtextmarkdownimporter_p.h index f905aa0f87..1b8c2ca354 100644 --- a/src/gui/text/qtextmarkdownimporter_p.h +++ b/src/gui/text/qtextmarkdownimporter_p.h @@ -124,7 +124,9 @@ private: int m_tableCol = -1; // because relative cell movements (e.g. m_cursor->movePosition(QTextCursor::NextCell)) don't work int m_paragraphMargin = 0; int m_blockType = 0; + char m_blockCodeFence = 0; Features m_features; + QTextImageFormat m_imageFormat; QTextListFormat m_listFormat; QTextBlockFormat::MarkerType m_markerType = QTextBlockFormat::NoMarker; bool m_needsInsertBlock = false; diff --git a/src/gui/text/qtextmarkdownwriter.cpp b/src/gui/text/qtextmarkdownwriter.cpp index 02643acdca..f351c8d20b 100644 --- a/src/gui/text/qtextmarkdownwriter.cpp +++ b/src/gui/text/qtextmarkdownwriter.cpp @@ -55,6 +55,7 @@ Q_LOGGING_CATEGORY(lcMDW, "qt.text.markdown.writer") static const QChar Space = QLatin1Char(' '); static const QChar Newline = QLatin1Char('\n'); static const QChar LineBreak = QChar(0x2028); +static const QChar DoubleQuote = QLatin1Char('"'); static const QChar Backtick = QLatin1Char('`'); static const QChar Period = QLatin1Char('.'); @@ -133,6 +134,24 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame) writeFrame(iterator.currentFrame()); else { // no frame, it's a block QTextBlock block = iterator.currentBlock(); + // Look ahead and detect some cases when we should + // suppress needless blank lines, when there will be a big change in block format + bool nextIsDifferent = false; + bool ending = false; + { + QTextFrame::iterator next = iterator; + ++next; + if (next.atEnd()) { + nextIsDifferent = true; + ending = true; + } else { + QTextBlockFormat format = iterator.currentBlock().blockFormat(); + QTextBlockFormat nextFormat = next.currentBlock().blockFormat(); + if (nextFormat.indent() != format.indent() || + nextFormat.property(QTextFormat::BlockCodeLanguage) != format.property(QTextFormat::BlockCodeLanguage)) + nextIsDifferent = true; + } + } if (table) { QTextTableCell cell = table->cellAt(block.position()); if (tableRow < cell.row()) { @@ -149,7 +168,7 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame) if (lastWasList) m_stream << Newline; } - int endingCol = writeBlock(block, !table, table && tableRow == 0); + int endingCol = writeBlock(block, !table, table && tableRow == 0, nextIsDifferent); m_doubleNewlineWritten = false; if (table) { QTextTableCell cell = table->cellAt(block.position()); @@ -161,11 +180,19 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame) m_stream << QString(paddingLen, Space); for (int col = cell.column(); col < spanEndCol; ++col) m_stream << "|"; - } else if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) { + } else if (m_fencedCodeBlock && ending) { + m_stream << m_linePrefix << QString(m_wrappedLineIndent, Space) + << m_codeBlockFence << Newline << Newline; + m_codeBlockFence.clear(); + } else if (m_indentedCodeBlock && nextIsDifferent) { m_stream << Newline; } else if (endingCol > 0) { - m_stream << Newline << Newline; - m_doubleNewlineWritten = true; + if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) { + m_stream << Newline; + } else { + m_stream << Newline << Newline; + m_doubleNewlineWritten = true; + } } lastWasList = block.textList(); } @@ -258,11 +285,13 @@ static void maybeEscapeFirstChar(QString &s) } } -int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ignoreFormat) +int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ignoreFormat, bool ignoreEmpty) { + if (block.text().isEmpty() && ignoreEmpty) + return 0; const int ColumnLimit = 80; QTextBlockFormat blockFmt = block.blockFormat(); - bool indentedCodeBlock = false; + bool missedBlankCodeBlockLine = false; if (block.textList()) { // it's a list-item auto fmt = block.textList()->format(); const int listLevel = fmt.indent(); @@ -323,7 +352,28 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign } else if (blockFmt.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) { m_stream << "- - -\n"; // unambiguous horizontal rule, not an underline under a heading return 0; + } else if (blockFmt.hasProperty(QTextFormat::BlockCodeFence) || blockFmt.stringProperty(QTextFormat::BlockCodeLanguage).length() > 0) { + // It's important to preserve blank lines in code blocks. But blank lines in code blocks + // inside block quotes are getting preserved anyway (along with the "> " prefix). + if (!blockFmt.hasProperty(QTextFormat::BlockQuoteLevel)) + missedBlankCodeBlockLine = true; // only if we don't get any fragments below + if (!m_fencedCodeBlock) { + QString fenceChar = blockFmt.stringProperty(QTextFormat::BlockCodeFence); + if (fenceChar.isEmpty()) + fenceChar = QLatin1String("`"); + m_codeBlockFence = QString(3, fenceChar.at(0)); + // A block quote can contain an indented code block, but not vice-versa. + m_stream << m_linePrefix << QString(m_wrappedLineIndent, Space) << m_codeBlockFence + << Space << blockFmt.stringProperty(QTextFormat::BlockCodeLanguage) << Newline; + m_fencedCodeBlock = true; + } } else if (!blockFmt.indent()) { + if (m_fencedCodeBlock) { + m_stream << m_linePrefix << QString(m_wrappedLineIndent, Space) + << m_codeBlockFence << Newline; + m_fencedCodeBlock = false; + m_codeBlockFence.clear(); + } m_wrappedLineIndent = 0; m_linePrefix.clear(); if (blockFmt.hasProperty(QTextFormat::BlockQuoteLevel)) { @@ -336,7 +386,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign if (blockFmt.hasProperty(QTextFormat::BlockCodeLanguage)) { // A block quote can contain an indented code block, but not vice-versa. m_linePrefix += QString(4, Space); - indentedCodeBlock = true; + m_indentedCodeBlock = true; } } if (blockFmt.headingLevel()) @@ -357,6 +407,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign bool strikeOut = false; QString backticks(Backtick); for (QTextBlock::Iterator frag = block.begin(); !frag.atEnd(); ++frag) { + missedBlankCodeBlockLine = false; QString fragmentText = frag.fragment().text(); while (fragmentText.endsWith(Newline)) fragmentText.chop(1); @@ -372,7 +423,14 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign QTextCharFormat fmt = frag.fragment().charFormat(); if (fmt.isImageFormat()) { QTextImageFormat ifmt = fmt.toImageFormat(); - QString s = QLatin1String("![image](") + ifmt.name() + QLatin1Char(')'); + QString desc = ifmt.stringProperty(QTextFormat::ImageAltText); + if (desc.isEmpty()) + desc = QLatin1String("image"); + QString s = QLatin1String("![") + desc + QLatin1String("](") + ifmt.name(); + QString title = ifmt.stringProperty(QTextFormat::ImageTitle); + if (!title.isEmpty()) + s += Space + DoubleQuote + title + DoubleQuote; + s += QLatin1Char(')'); if (wrap && col + s.length() > ColumnLimit) { m_stream << Newline << wrapIndentString; col = m_wrappedLineIndent; @@ -393,7 +451,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign bool monoFrag = fontInfo.fixedPitch(); QString markers; if (!ignoreFormat) { - if (monoFrag != mono && !indentedCodeBlock) { + if (monoFrag != mono && !m_indentedCodeBlock && !m_fencedCodeBlock) { if (monoFrag) backticks = QString(adjacentBackticksCount(fragmentText) + 1, Backtick); markers += backticks; @@ -493,6 +551,8 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign m_stream << "~~"; col += 2; } + if (missedBlankCodeBlockLine) + m_stream << Newline; return col; } diff --git a/src/gui/text/qtextmarkdownwriter_p.h b/src/gui/text/qtextmarkdownwriter_p.h index 96ceb445cd..90310250ac 100644 --- a/src/gui/text/qtextmarkdownwriter_p.h +++ b/src/gui/text/qtextmarkdownwriter_p.h @@ -67,7 +67,7 @@ public: bool writeAll(const QTextDocument *document); void writeTable(const QAbstractItemModel *table); - int writeBlock(const QTextBlock &block, bool table, bool ignoreFormat); + int writeBlock(const QTextBlock &block, bool table, bool ignoreFormat, bool ignoreEmpty); void writeFrame(const QTextFrame *frame); private: @@ -82,9 +82,12 @@ private: QTextDocument::MarkdownFeatures m_features; QMap<QTextList *, ListInfo> m_listInfo; QString m_linePrefix; + QString m_codeBlockFence; int m_wrappedLineIndent = 0; int m_lastListIndent = 1; bool m_doubleNewlineWritten = false; + bool m_indentedCodeBlock = false; + bool m_fencedCodeBlock = false; }; QT_END_NAMESPACE |