|
|
|
|
@ -104,18 +104,19 @@ static Qt::Alignment MdAlignment(MD_ALIGN a, Qt::Alignment defaultAlignment = Qt
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTextMarkdownImporter::QTextMarkdownImporter(QTextMarkdownImporter::Features features)
|
|
|
|
|
: m_monoFont(QFontDatabase::systemFont(QFontDatabase::FixedFont))
|
|
|
|
|
QTextMarkdownImporter::QTextMarkdownImporter(QTextDocument *doc, QTextMarkdownImporter::Features features)
|
|
|
|
|
: m_cursor(doc)
|
|
|
|
|
, m_monoFont(QFontDatabase::systemFont(QFontDatabase::FixedFont))
|
|
|
|
|
, m_features(features)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTextMarkdownImporter::QTextMarkdownImporter(QTextDocument::MarkdownFeatures features)
|
|
|
|
|
: QTextMarkdownImporter(static_cast<QTextMarkdownImporter::Features>(int(features)))
|
|
|
|
|
QTextMarkdownImporter::QTextMarkdownImporter(QTextDocument *doc, QTextDocument::MarkdownFeatures features)
|
|
|
|
|
: QTextMarkdownImporter(doc, static_cast<QTextMarkdownImporter::Features>(int(features)))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QTextMarkdownImporter::import(QTextDocument *doc, const QString &markdown)
|
|
|
|
|
void QTextMarkdownImporter::import(const QString &markdown)
|
|
|
|
|
{
|
|
|
|
|
MD_PARSER callbacks = {
|
|
|
|
|
0, // abi_version
|
|
|
|
|
@ -128,21 +129,19 @@ void QTextMarkdownImporter::import(QTextDocument *doc, const QString &markdown)
|
|
|
|
|
&CbDebugLog,
|
|
|
|
|
nullptr // syntax
|
|
|
|
|
};
|
|
|
|
|
m_doc = doc;
|
|
|
|
|
m_paragraphMargin = m_doc->defaultFont().pointSize() * 2 / 3;
|
|
|
|
|
m_cursor = new QTextCursor(doc);
|
|
|
|
|
QTextDocument *doc = m_cursor.document();
|
|
|
|
|
const auto defaultFont = doc->defaultFont();
|
|
|
|
|
m_paragraphMargin = defaultFont.pointSize() * 2 / 3;
|
|
|
|
|
doc->clear();
|
|
|
|
|
if (doc->defaultFont().pointSize() != -1)
|
|
|
|
|
m_monoFont.setPointSize(doc->defaultFont().pointSize());
|
|
|
|
|
if (defaultFont.pointSize() != -1)
|
|
|
|
|
m_monoFont.setPointSize(defaultFont.pointSize());
|
|
|
|
|
else
|
|
|
|
|
m_monoFont.setPixelSize(doc->defaultFont().pixelSize());
|
|
|
|
|
qCDebug(lcMD) << "default font" << doc->defaultFont() << "mono font" << m_monoFont;
|
|
|
|
|
m_monoFont.setPixelSize(defaultFont.pixelSize());
|
|
|
|
|
qCDebug(lcMD) << "default font" << defaultFont << "mono font" << m_monoFont;
|
|
|
|
|
QByteArray md = markdown.toUtf8();
|
|
|
|
|
m_cursor->beginEditBlock();
|
|
|
|
|
m_cursor.beginEditBlock();
|
|
|
|
|
md_parse(md.constData(), MD_SIZE(md.size()), &callbacks, this);
|
|
|
|
|
m_cursor->endEditBlock();
|
|
|
|
|
delete m_cursor;
|
|
|
|
|
m_cursor = nullptr;
|
|
|
|
|
m_cursor.endEditBlock();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
|
|
|
|
|
@ -181,11 +180,11 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
|
|
|
|
|
charFmt.setFontWeight(QFont::Bold);
|
|
|
|
|
blockFmt.setHeadingLevel(int(detail->level));
|
|
|
|
|
m_needsInsertBlock = false;
|
|
|
|
|
if (m_doc->isEmpty()) {
|
|
|
|
|
m_cursor->setBlockFormat(blockFmt);
|
|
|
|
|
m_cursor->setCharFormat(charFmt);
|
|
|
|
|
if (m_cursor.document()->isEmpty()) {
|
|
|
|
|
m_cursor.setBlockFormat(blockFmt);
|
|
|
|
|
m_cursor.setCharFormat(charFmt);
|
|
|
|
|
} else {
|
|
|
|
|
m_cursor->insertBlock(blockFmt, charFmt);
|
|
|
|
|
m_cursor.insertBlock(blockFmt, charFmt);
|
|
|
|
|
}
|
|
|
|
|
qCDebug(lcMD, "H%d", detail->level);
|
|
|
|
|
} break;
|
|
|
|
|
@ -200,7 +199,7 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
|
|
|
|
|
} break;
|
|
|
|
|
case MD_BLOCK_UL: {
|
|
|
|
|
if (m_needsInsertList) // list nested in an empty list
|
|
|
|
|
m_listStack.push(m_cursor->insertList(m_listFormat));
|
|
|
|
|
m_listStack.push(m_cursor.insertList(m_listFormat));
|
|
|
|
|
else
|
|
|
|
|
m_needsInsertList = true;
|
|
|
|
|
MD_BLOCK_UL_DETAIL *detail = static_cast<MD_BLOCK_UL_DETAIL *>(det);
|
|
|
|
|
@ -221,7 +220,7 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
|
|
|
|
|
} break;
|
|
|
|
|
case MD_BLOCK_OL: {
|
|
|
|
|
if (m_needsInsertList) // list nested in an empty list
|
|
|
|
|
m_listStack.push(m_cursor->insertList(m_listFormat));
|
|
|
|
|
m_listStack.push(m_cursor.insertList(m_listFormat));
|
|
|
|
|
else
|
|
|
|
|
m_needsInsertList = true;
|
|
|
|
|
MD_BLOCK_OL_DETAIL *detail = static_cast<MD_BLOCK_OL_DETAIL *>(det);
|
|
|
|
|
@ -242,10 +241,10 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
|
|
|
|
|
qWarning("malformed table in Markdown input");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
*m_cursor = cell.firstCursorPosition();
|
|
|
|
|
QTextBlockFormat blockFmt = m_cursor->blockFormat();
|
|
|
|
|
m_cursor = cell.firstCursorPosition();
|
|
|
|
|
QTextBlockFormat blockFmt = m_cursor.blockFormat();
|
|
|
|
|
blockFmt.setAlignment(MdAlignment(detail->align));
|
|
|
|
|
m_cursor->setBlockFormat(blockFmt);
|
|
|
|
|
m_cursor.setBlockFormat(blockFmt);
|
|
|
|
|
qCDebug(lcMD) << "TD; align" << detail->align << MdAlignment(detail->align) << "col" << m_tableCol;
|
|
|
|
|
} break;
|
|
|
|
|
case MD_BLOCK_TH: {
|
|
|
|
|
@ -273,13 +272,13 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
|
|
|
|
|
case MD_BLOCK_TABLE:
|
|
|
|
|
m_tableColumnCount = 0;
|
|
|
|
|
m_tableRowCount = 0;
|
|
|
|
|
m_currentTable = m_cursor->insertTable(1, 1); // we don't know the dimensions yet
|
|
|
|
|
m_currentTable = m_cursor.insertTable(1, 1); // we don't know the dimensions yet
|
|
|
|
|
break;
|
|
|
|
|
case MD_BLOCK_HR: {
|
|
|
|
|
qCDebug(lcMD, "HR");
|
|
|
|
|
QTextBlockFormat blockFmt;
|
|
|
|
|
blockFmt.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, 1);
|
|
|
|
|
m_cursor->insertBlock(blockFmt, QTextCharFormat());
|
|
|
|
|
m_cursor.insertBlock(blockFmt, QTextCharFormat());
|
|
|
|
|
} break;
|
|
|
|
|
default:
|
|
|
|
|
break; // nothing to do for now
|
|
|
|
|
@ -297,7 +296,7 @@ int QTextMarkdownImporter::cbLeaveBlock(int blockType, void *detail)
|
|
|
|
|
case MD_BLOCK_UL:
|
|
|
|
|
case MD_BLOCK_OL:
|
|
|
|
|
if (Q_UNLIKELY(m_needsInsertList))
|
|
|
|
|
m_listStack.push(m_cursor->createList(m_listFormat));
|
|
|
|
|
m_listStack.push(m_cursor.createList(m_listFormat));
|
|
|
|
|
if (Q_UNLIKELY(m_listStack.isEmpty())) {
|
|
|
|
|
qCWarning(lcMD, "list ended unexpectedly");
|
|
|
|
|
} else {
|
|
|
|
|
@ -335,7 +334,7 @@ int QTextMarkdownImporter::cbLeaveBlock(int blockType, void *detail)
|
|
|
|
|
case MD_BLOCK_TABLE:
|
|
|
|
|
qCDebug(lcMD) << "table ended with" << m_currentTable->columns() << "cols and" << m_currentTable->rows() << "rows";
|
|
|
|
|
m_currentTable = nullptr;
|
|
|
|
|
m_cursor->movePosition(QTextCursor::End);
|
|
|
|
|
m_cursor.movePosition(QTextCursor::End);
|
|
|
|
|
break;
|
|
|
|
|
case MD_BLOCK_LI:
|
|
|
|
|
qCDebug(lcMD, "LI at level %d ended", int(m_listStack.size()));
|
|
|
|
|
@ -352,7 +351,7 @@ int QTextMarkdownImporter::cbLeaveBlock(int blockType, void *detail)
|
|
|
|
|
m_needsInsertBlock = true;
|
|
|
|
|
} break;
|
|
|
|
|
case MD_BLOCK_H:
|
|
|
|
|
m_cursor->setCharFormat(QTextCharFormat());
|
|
|
|
|
m_cursor.setCharFormat(QTextCharFormat());
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
@ -406,7 +405,7 @@ int QTextMarkdownImporter::cbEnterSpan(int spanType, void *det)
|
|
|
|
|
qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().families().first()
|
|
|
|
|
<< charFmt.fontWeight() << (charFmt.fontItalic() ? "italic" : "")
|
|
|
|
|
<< charFmt.foreground().color().name();
|
|
|
|
|
m_cursor->setCharFormat(charFmt);
|
|
|
|
|
m_cursor.setCharFormat(charFmt);
|
|
|
|
|
return 0; // no error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -419,7 +418,7 @@ int QTextMarkdownImporter::cbLeaveSpan(int spanType, void *detail)
|
|
|
|
|
if (!m_spanFormatStack.isEmpty())
|
|
|
|
|
charFmt = m_spanFormatStack.top();
|
|
|
|
|
}
|
|
|
|
|
m_cursor->setCharFormat(charFmt);
|
|
|
|
|
m_cursor.setCharFormat(charFmt);
|
|
|
|
|
qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().families().first()
|
|
|
|
|
<< charFmt.fontWeight() << (charFmt.fontItalic() ? "italic" : "")
|
|
|
|
|
<< charFmt.foreground().color().name();
|
|
|
|
|
@ -464,7 +463,7 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size)
|
|
|
|
|
if (m_htmlTagDepth)
|
|
|
|
|
m_htmlAccumulator += s;
|
|
|
|
|
else
|
|
|
|
|
m_cursor->insertHtml(s);
|
|
|
|
|
m_cursor.insertHtml(s);
|
|
|
|
|
s = QString();
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
@ -486,11 +485,11 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size)
|
|
|
|
|
m_htmlAccumulator += s;
|
|
|
|
|
if (!m_htmlTagDepth) { // all open tags are now closed
|
|
|
|
|
qCDebug(lcMD) << "HTML" << m_htmlAccumulator;
|
|
|
|
|
m_cursor->insertHtml(m_htmlAccumulator);
|
|
|
|
|
m_cursor.insertHtml(m_htmlAccumulator);
|
|
|
|
|
if (m_spanFormatStack.isEmpty())
|
|
|
|
|
m_cursor->setCharFormat(QTextCharFormat());
|
|
|
|
|
m_cursor.setCharFormat(QTextCharFormat());
|
|
|
|
|
else
|
|
|
|
|
m_cursor->setCharFormat(m_spanFormatStack.top());
|
|
|
|
|
m_cursor.setCharFormat(m_spanFormatStack.top());
|
|
|
|
|
m_htmlAccumulator = QString();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
@ -520,24 +519,24 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size)
|
|
|
|
|
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);
|
|
|
|
|
<< "alt" << s << "relative to" << m_cursor.document()->baseUrl();
|
|
|
|
|
m_cursor.insertImage(m_imageFormat);
|
|
|
|
|
return 0; // no error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!s.isEmpty())
|
|
|
|
|
m_cursor->insertText(s);
|
|
|
|
|
if (m_cursor->currentList()) {
|
|
|
|
|
m_cursor.insertText(s);
|
|
|
|
|
if (m_cursor.currentList()) {
|
|
|
|
|
// The list item will indent the list item's text, so we don't need indentation on the block.
|
|
|
|
|
QTextBlockFormat bfmt = m_cursor->blockFormat();
|
|
|
|
|
QTextBlockFormat bfmt = m_cursor.blockFormat();
|
|
|
|
|
bfmt.setIndent(0);
|
|
|
|
|
m_cursor->setBlockFormat(bfmt);
|
|
|
|
|
m_cursor.setBlockFormat(bfmt);
|
|
|
|
|
}
|
|
|
|
|
if (lcMD().isEnabled(QtDebugMsg)) {
|
|
|
|
|
QTextBlockFormat bfmt = m_cursor->blockFormat();
|
|
|
|
|
QTextBlockFormat bfmt = m_cursor.blockFormat();
|
|
|
|
|
QString debugInfo;
|
|
|
|
|
if (m_cursor->currentList())
|
|
|
|
|
debugInfo = "in list at depth "_L1 + QString::number(m_cursor->currentList()->format().indent());
|
|
|
|
|
if (m_cursor.currentList())
|
|
|
|
|
debugInfo = "in list at depth "_L1 + QString::number(m_cursor.currentList()->format().indent());
|
|
|
|
|
if (bfmt.hasProperty(QTextFormat::BlockQuoteLevel))
|
|
|
|
|
debugInfo += "in blockquote at depth "_L1 +
|
|
|
|
|
QString::number(bfmt.intProperty(QTextFormat::BlockQuoteLevel));
|
|
|
|
|
@ -554,7 +553,7 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size)
|
|
|
|
|
Insert a new block based on stored state.
|
|
|
|
|
|
|
|
|
|
m_cursor cannot store the state for the _next_ block ahead of time, because
|
|
|
|
|
m_cursor->setBlockFormat() controls the format of the block that the cursor
|
|
|
|
|
m_cursor.setBlockFormat() controls the format of the block that the cursor
|
|
|
|
|
is already in; so cbLeaveBlock() cannot call setBlockFormat() without
|
|
|
|
|
altering the block that was just added. Therefore cbLeaveBlock() and the
|
|
|
|
|
following cbEnterBlock() set variables to remember what formatting should
|
|
|
|
|
@ -596,19 +595,19 @@ void QTextMarkdownImporter::insertBlock()
|
|
|
|
|
blockFormat.setMarker(m_markerType);
|
|
|
|
|
if (!m_listStack.isEmpty())
|
|
|
|
|
blockFormat.setIndent(m_listStack.size());
|
|
|
|
|
if (m_doc->isEmpty()) {
|
|
|
|
|
m_cursor->setBlockFormat(blockFormat);
|
|
|
|
|
m_cursor->setCharFormat(charFormat);
|
|
|
|
|
if (m_cursor.document()->isEmpty()) {
|
|
|
|
|
m_cursor.setBlockFormat(blockFormat);
|
|
|
|
|
m_cursor.setCharFormat(charFormat);
|
|
|
|
|
} else if (m_listItem) {
|
|
|
|
|
m_cursor->insertBlock(blockFormat, QTextCharFormat());
|
|
|
|
|
m_cursor->setCharFormat(charFormat);
|
|
|
|
|
m_cursor.insertBlock(blockFormat, QTextCharFormat());
|
|
|
|
|
m_cursor.setCharFormat(charFormat);
|
|
|
|
|
} else {
|
|
|
|
|
m_cursor->insertBlock(blockFormat, charFormat);
|
|
|
|
|
m_cursor.insertBlock(blockFormat, charFormat);
|
|
|
|
|
}
|
|
|
|
|
if (m_needsInsertList) {
|
|
|
|
|
m_listStack.push(m_cursor->createList(m_listFormat));
|
|
|
|
|
m_listStack.push(m_cursor.createList(m_listFormat));
|
|
|
|
|
} else if (!m_listStack.isEmpty() && m_listItem && m_listStack.top()) {
|
|
|
|
|
m_listStack.top()->add(m_cursor->block());
|
|
|
|
|
m_listStack.top()->add(m_cursor.block());
|
|
|
|
|
}
|
|
|
|
|
m_needsInsertList = false;
|
|
|
|
|
m_needsInsertBlock = false;
|
|
|
|
|
|