Markdown and HTML: support image alt text and title

It's a required CommonMark feature:
https://spec.commonmark.org/0.29/#images
and alt text is also required in HTML:
https://www.w3.org/wiki/Html/Elements/img#Requirements_for_providing_text_to_act_as_an_alternative_for_images
Now we are able to read these attributes from either html or markdown
and rewrite either an html or markdown document that preserves them.

This patch does not add viewing or editing support in QTextEdit etc.

Change-Id: I51307389f8f9fc00809808390e583a83111a7b33
Reviewed-by: Gatis Paeglis <gatis.paeglis@qt.io>
Shawn Rutledge 2019-05-20 12:58:36 +02:00
parent ca0c9f82cb
commit 237fa21fee
10 changed files with 51 additions and 12 deletions

View File

@ -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()));

View File

@ -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);

View File

@ -659,7 +659,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

View File

@ -253,6 +253,8 @@ public:
// image properties
ImageName = 0x5000,
ImageTitle = 0x5001,
ImageAltText = 0x5002,
ImageWidth = 0x5010,
ImageHeight = 0x5011,
ImageQuality = 0x5014,

View File

@ -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:

View File

@ -184,6 +184,7 @@ struct QTextHtmlParserNode {
QString textListNumberPrefix;
QString textListNumberSuffix;
QString imageName;
QString imageAlt;
qreal imageWidth;
qreal imageHeight;
QTextLength width;

View File

@ -362,15 +362,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 +401,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 +474,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()) {

View File

@ -125,6 +125,7 @@ private:
int m_paragraphMargin = 0;
int m_blockType = 0;
Features m_features;
QTextImageFormat m_imageFormat;
QTextListFormat m_listFormat;
QTextBlockFormat::MarkerType m_markerType = QTextBlockFormat::NoMarker;
bool m_needsInsertBlock = false;

View File

@ -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('.');
@ -372,7 +373,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;

View File

@ -416,6 +416,9 @@ void tst_QTextMarkdownWriter::fromHtml_data()
QTest::newRow("block quote") <<
"<p>In 1958, Mahatma Gandhi was quoted as follows:</p><blockquote>The Earth provides enough to satisfy every man's need but not for every man's greed.</blockquote>" <<
"In 1958, Mahatma Gandhi was quoted as follows:\n\n> The Earth provides enough to satisfy every man's need but not for every man's\n> greed.\n\n";
QTest::newRow("image") <<
"<img src=\"/url\" alt=\"foo\" title=\"title\"/>" <<
"![foo](/url \"title\")\n\n";
// TODO
// QTest::newRow("escaped number and paren after double newline") <<
// "<p>(The first sentence of this paragraph is a line, the next paragraph has a number</p>13) but that's not part of an ordered list" <<