Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qtextmarkdownimporter.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6#include <QLoggingCategory>
7#if QT_CONFIG(regularexpression)
8#include <QRegularExpression>
9#endif
10#include <QTextCursor>
11#include <QTextDocument>
12#include <QTextDocumentFragment>
13#include <QTextList>
14#include <QTextTable>
15#if QT_CONFIG(system_textmarkdownreader)
16#include <md4c.h>
17#else
18#include "../../3rdparty/md4c/md4c.h"
19#endif
20
22
23using namespace Qt::StringLiterals;
24
25Q_LOGGING_CATEGORY(lcMD, "qt.text.markdown")
26
27static const QChar qtmi_Newline = u'\n';
28static const QChar qtmi_Space = u' ';
29
30static constexpr auto markerString() noexcept { return "---"_L1; }
31
32// TODO maybe eliminate the margins after all views recognize BlockQuoteLevel, CSS can format it, etc.
33static const int qtmi_BlockQuoteIndent =
34 40; // pixels, same as in QTextHtmlParserNode::initializeProperties
35
36static_assert(int(QTextMarkdownImporter::FeatureCollapseWhitespace) == MD_FLAG_COLLAPSEWHITESPACE);
37static_assert(int(QTextMarkdownImporter::FeaturePermissiveATXHeaders) == MD_FLAG_PERMISSIVEATXHEADERS);
38static_assert(int(QTextMarkdownImporter::FeaturePermissiveURLAutoLinks) == MD_FLAG_PERMISSIVEURLAUTOLINKS);
39static_assert(int(QTextMarkdownImporter::FeaturePermissiveMailAutoLinks) == MD_FLAG_PERMISSIVEEMAILAUTOLINKS);
40static_assert(int(QTextMarkdownImporter::FeatureNoIndentedCodeBlocks) == MD_FLAG_NOINDENTEDCODEBLOCKS);
41static_assert(int(QTextMarkdownImporter::FeatureNoHTMLBlocks) == MD_FLAG_NOHTMLBLOCKS);
42static_assert(int(QTextMarkdownImporter::FeatureNoHTMLSpans) == MD_FLAG_NOHTMLSPANS);
43static_assert(int(QTextMarkdownImporter::FeatureTables) == MD_FLAG_TABLES);
44static_assert(int(QTextMarkdownImporter::FeatureStrikeThrough) == MD_FLAG_STRIKETHROUGH);
45static_assert(int(QTextMarkdownImporter::FeatureUnderline) == MD_FLAG_UNDERLINE);
46static_assert(int(QTextMarkdownImporter::FeaturePermissiveWWWAutoLinks) == MD_FLAG_PERMISSIVEWWWAUTOLINKS);
47static_assert(int(QTextMarkdownImporter::FeaturePermissiveAutoLinks) == MD_FLAG_PERMISSIVEAUTOLINKS);
48static_assert(int(QTextMarkdownImporter::FeatureTasklists) == MD_FLAG_TASKLISTS);
49static_assert(int(QTextMarkdownImporter::FeatureNoHTML) == MD_FLAG_NOHTML);
50static_assert(int(QTextMarkdownImporter::DialectCommonMark) == MD_DIALECT_COMMONMARK);
51static_assert(int(QTextMarkdownImporter::DialectGitHub) ==
52 (MD_DIALECT_GITHUB | MD_FLAG_UNDERLINE | QTextMarkdownImporter::FeatureFrontMatter));
53
54// --------------------------------------------------------
55// MD4C callback function wrappers
56
57static int CbEnterBlock(MD_BLOCKTYPE type, void *detail, void *userdata)
58{
59 QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
60 return mdi->cbEnterBlock(int(type), detail);
61}
62
63static int CbLeaveBlock(MD_BLOCKTYPE type, void *detail, void *userdata)
64{
65 QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
66 return mdi->cbLeaveBlock(int(type), detail);
67}
68
69static int CbEnterSpan(MD_SPANTYPE type, void *detail, void *userdata)
70{
71 QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
72 return mdi->cbEnterSpan(int(type), detail);
73}
74
75static int CbLeaveSpan(MD_SPANTYPE type, void *detail, void *userdata)
76{
77 QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
78 return mdi->cbLeaveSpan(int(type), detail);
79}
80
81static int CbText(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *userdata)
82{
83 QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
84 return mdi->cbText(int(type), text, size);
85}
86
87static void CbDebugLog(const char *msg, void *userdata)
88{
89 Q_UNUSED(userdata);
90 qCDebug(lcMD) << msg;
91}
92
93// MD4C callback function wrappers
94// --------------------------------------------------------
95
96static Qt::Alignment MdAlignment(MD_ALIGN a, Qt::Alignment defaultAlignment = Qt::AlignLeft | Qt::AlignVCenter)
97{
98 switch (a) {
99 case MD_ALIGN_LEFT:
101 case MD_ALIGN_CENTER:
103 case MD_ALIGN_RIGHT:
105 default: // including MD_ALIGN_DEFAULT
106 return defaultAlignment;
107 }
108}
109
110QTextMarkdownImporter::QTextMarkdownImporter(QTextDocument *doc, QTextMarkdownImporter::Features features)
111 : m_cursor(doc)
112 , m_monoFont(QFontDatabase::systemFont(QFontDatabase::FixedFont))
113 , m_features(features)
114{
115}
116
117QTextMarkdownImporter::QTextMarkdownImporter(QTextDocument *doc, QTextDocument::MarkdownFeatures features)
118 : QTextMarkdownImporter(doc, static_cast<QTextMarkdownImporter::Features>(int(features)))
119{
120}
121
123{
124 MD_PARSER callbacks = {
125 0, // abi_version
126 unsigned(m_features),
131 &CbText,
132 &CbDebugLog,
133 nullptr // syntax
134 };
135 QTextDocument *doc = m_cursor.document();
136 const auto defaultFont = doc->defaultFont();
137 m_paragraphMargin = defaultFont.pointSize() * 2 / 3;
138 doc->clear();
139 if (defaultFont.pointSize() != -1)
140 m_monoFont.setPointSize(defaultFont.pointSize());
141 else
142 m_monoFont.setPixelSize(defaultFont.pixelSize());
143 qCDebug(lcMD) << "default font" << defaultFont << "mono font" << m_monoFont;
144 QStringView md = markdown;
145
146 if (m_features.testFlag(QTextMarkdownImporter::FeatureFrontMatter) && md.startsWith(markerString())) {
147 qsizetype endMarkerPos = md.indexOf(markerString(), markerString().size() + 1);
148 if (endMarkerPos > 4) {
149 qsizetype firstLinePos = 4; // first line of yaml
150 while (md.at(firstLinePos) == '\n'_L1 || md.at(firstLinePos) == '\r'_L1)
151 ++firstLinePos;
152 auto frontMatter = md.sliced(firstLinePos, endMarkerPos - firstLinePos);
153 firstLinePos = endMarkerPos + 4; // first line of markdown after yaml
154 while (md.size() > firstLinePos && (md.at(firstLinePos) == '\n'_L1 || md.at(firstLinePos) == '\r'_L1))
155 ++firstLinePos;
156 md = md.sliced(firstLinePos);
157 doc->setMetaInformation(QTextDocument::FrontMatter, frontMatter.toString());
158 qCDebug(lcMD) << "extracted FrontMatter: size" << frontMatter.size();
159 }
160 }
161 const auto mdUtf8 = md.toUtf8();
162 m_cursor.beginEditBlock();
163 md_parse(mdUtf8.constData(), MD_SIZE(mdUtf8.size()), &callbacks, this);
164 m_cursor.endEditBlock();
165}
166
167int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
168{
169 m_blockType = blockType;
170 switch (blockType) {
171 case MD_BLOCK_P:
172 if (!m_listStack.isEmpty())
173 qCDebug(lcMD, m_listItem ? "P of LI at level %d" : "P continuation inside LI at level %d", int(m_listStack.size()));
174 else
175 qCDebug(lcMD, "P");
176 m_needsInsertBlock = true;
177 break;
178 case MD_BLOCK_QUOTE:
179 ++m_blockQuoteDepth;
180 qCDebug(lcMD, "QUOTE level %d", m_blockQuoteDepth);
181 break;
182 case MD_BLOCK_CODE: {
183 MD_BLOCK_CODE_DETAIL *detail = static_cast<MD_BLOCK_CODE_DETAIL *>(det);
184 m_codeBlock = true;
185 m_blockCodeLanguage = QLatin1StringView(detail->lang.text, int(detail->lang.size));
186 m_blockCodeFence = detail->fence_char;
187 QString info = QLatin1StringView(detail->info.text, int(detail->info.size));
188 m_needsInsertBlock = true;
189 if (m_blockQuoteDepth)
190 qCDebug(lcMD, "CODE lang '%s' info '%s' fenced with '%c' inside QUOTE %d", qPrintable(m_blockCodeLanguage), qPrintable(info), m_blockCodeFence, m_blockQuoteDepth);
191 else
192 qCDebug(lcMD, "CODE lang '%s' info '%s' fenced with '%c'", qPrintable(m_blockCodeLanguage), qPrintable(info), m_blockCodeFence);
193 } break;
194 case MD_BLOCK_H: {
195 MD_BLOCK_H_DETAIL *detail = static_cast<MD_BLOCK_H_DETAIL *>(det);
196 QTextBlockFormat blockFmt;
197 QTextCharFormat charFmt;
198 int sizeAdjustment = 4 - int(detail->level); // H1 to H6: +3 to -2
199 charFmt.setProperty(QTextFormat::FontSizeAdjustment, sizeAdjustment);
200 charFmt.setFontWeight(QFont::Bold);
201 blockFmt.setHeadingLevel(int(detail->level));
202 m_needsInsertBlock = false;
203 if (m_cursor.document()->isEmpty()) {
204 m_cursor.setBlockFormat(blockFmt);
205 m_cursor.setCharFormat(charFmt);
206 } else {
207 m_cursor.insertBlock(blockFmt, charFmt);
208 }
209 qCDebug(lcMD, "H%d", detail->level);
210 } break;
211 case MD_BLOCK_LI: {
212 m_needsInsertBlock = true;
213 m_listItem = true;
214 MD_BLOCK_LI_DETAIL *detail = static_cast<MD_BLOCK_LI_DETAIL *>(det);
215 m_markerType = detail->is_task ?
218 qCDebug(lcMD) << "LI";
219 } break;
220 case MD_BLOCK_UL: {
221 if (m_needsInsertList) // list nested in an empty list
222 m_listStack.push(m_cursor.insertList(m_listFormat));
223 else
224 m_needsInsertList = true;
225 MD_BLOCK_UL_DETAIL *detail = static_cast<MD_BLOCK_UL_DETAIL *>(det);
226 m_listFormat = QTextListFormat();
227 m_listFormat.setIndent(m_listStack.size() + 1);
228 switch (detail->mark) {
229 case '*':
231 break;
232 case '+':
234 break;
235 default: // including '-'
237 break;
238 }
239 qCDebug(lcMD, "UL %c level %d", detail->mark, int(m_listStack.size()) + 1);
240 } break;
241 case MD_BLOCK_OL: {
242 if (m_needsInsertList) // list nested in an empty list
243 m_listStack.push(m_cursor.insertList(m_listFormat));
244 else
245 m_needsInsertList = true;
246 MD_BLOCK_OL_DETAIL *detail = static_cast<MD_BLOCK_OL_DETAIL *>(det);
247 m_listFormat = QTextListFormat();
248 m_listFormat.setIndent(m_listStack.size() + 1);
249 m_listFormat.setNumberSuffix(QChar::fromLatin1(detail->mark_delimiter));
251 m_listFormat.setStart(detail->start);
252 qCDebug(lcMD, "OL xx%d level %d start %d", detail->mark_delimiter, int(m_listStack.size()) + 1, detail->start);
253 } break;
254 case MD_BLOCK_TD: {
255 MD_BLOCK_TD_DETAIL *detail = static_cast<MD_BLOCK_TD_DETAIL *>(det);
256 ++m_tableCol;
257 // absolute movement (and storage of m_tableCol) shouldn't be necessary, but
258 // movePosition(QTextCursor::NextCell) doesn't work
259 QTextTableCell cell = m_currentTable->cellAt(m_tableRowCount - 1, m_tableCol);
260 if (!cell.isValid()) {
261 qWarning("malformed table in Markdown input");
262 return 1;
263 }
264 m_cursor = cell.firstCursorPosition();
265 QTextBlockFormat blockFmt = m_cursor.blockFormat();
266 blockFmt.setAlignment(MdAlignment(detail->align));
267 m_cursor.setBlockFormat(blockFmt);
268 qCDebug(lcMD) << "TD; align" << detail->align << MdAlignment(detail->align) << "col" << m_tableCol;
269 } break;
270 case MD_BLOCK_TH: {
271 ++m_tableColumnCount;
272 ++m_tableCol;
273 if (m_currentTable->columns() < m_tableColumnCount)
274 m_currentTable->appendColumns(1);
275 auto cell = m_currentTable->cellAt(m_tableRowCount - 1, m_tableCol);
276 if (!cell.isValid()) {
277 qWarning("malformed table in Markdown input");
278 return 1;
279 }
280 auto fmt = cell.format();
281 fmt.setFontWeight(QFont::Bold);
282 cell.setFormat(fmt);
283 } break;
284 case MD_BLOCK_TR: {
285 ++m_tableRowCount;
286 m_nonEmptyTableCells.clear();
287 if (m_currentTable->rows() < m_tableRowCount)
288 m_currentTable->appendRows(1);
289 m_tableCol = -1;
290 qCDebug(lcMD) << "TR" << m_currentTable->rows();
291 } break;
292 case MD_BLOCK_TABLE:
293 m_tableColumnCount = 0;
294 m_tableRowCount = 0;
295 m_currentTable = m_cursor.insertTable(1, 1); // we don't know the dimensions yet
296 break;
297 case MD_BLOCK_HR: {
298 qCDebug(lcMD, "HR");
299 QTextBlockFormat blockFmt;
301 m_cursor.insertBlock(blockFmt, QTextCharFormat());
302 } break;
303 default:
304 break; // nothing to do for now
305 }
306 return 0; // no error
307}
308
310{
312 switch (blockType) {
313 case MD_BLOCK_P:
314 m_listItem = false;
315 break;
316 case MD_BLOCK_UL:
317 case MD_BLOCK_OL:
318 if (Q_UNLIKELY(m_needsInsertList))
319 m_listStack.push(m_cursor.createList(m_listFormat));
320 if (Q_UNLIKELY(m_listStack.isEmpty())) {
321 qCWarning(lcMD, "list ended unexpectedly");
322 } else {
323 qCDebug(lcMD, "list at level %d ended", int(m_listStack.size()));
324 m_listStack.pop();
325 }
326 break;
327 case MD_BLOCK_TR: {
328 // https://github.com/mity/md4c/issues/29
329 // MD4C doesn't tell us explicitly which cells are merged, so merge empty cells
330 // with previous non-empty ones
331 int mergeEnd = -1;
332 int mergeBegin = -1;
333 for (int col = m_tableCol; col >= 0; --col) {
334 if (m_nonEmptyTableCells.contains(col)) {
335 if (mergeEnd >= 0 && mergeBegin >= 0) {
336 qCDebug(lcMD) << "merging cells" << mergeBegin << "to" << mergeEnd << "inclusive, on row" << m_currentTable->rows() - 1;
337 m_currentTable->mergeCells(m_currentTable->rows() - 1, mergeBegin - 1, 1, mergeEnd - mergeBegin + 2);
338 }
339 mergeEnd = -1;
340 mergeBegin = -1;
341 } else {
342 if (mergeEnd < 0)
343 mergeEnd = col;
344 else
345 mergeBegin = col;
346 }
347 }
348 } break;
349 case MD_BLOCK_QUOTE: {
350 qCDebug(lcMD, "QUOTE level %d ended", m_blockQuoteDepth);
351 --m_blockQuoteDepth;
352 m_needsInsertBlock = true;
353 } break;
354 case MD_BLOCK_TABLE:
355 qCDebug(lcMD) << "table ended with" << m_currentTable->columns() << "cols and" << m_currentTable->rows() << "rows";
356 m_currentTable = nullptr;
358 break;
359 case MD_BLOCK_LI:
360 qCDebug(lcMD, "LI at level %d ended", int(m_listStack.size()));
361 m_listItem = false;
362 break;
363 case MD_BLOCK_CODE: {
364 m_codeBlock = false;
365 m_blockCodeLanguage.clear();
366 m_blockCodeFence = 0;
367 if (m_blockQuoteDepth)
368 qCDebug(lcMD, "CODE ended inside QUOTE %d", m_blockQuoteDepth);
369 else
370 qCDebug(lcMD, "CODE ended");
371 m_needsInsertBlock = true;
372 } break;
373 case MD_BLOCK_H:
374 m_cursor.setCharFormat(QTextCharFormat());
375 break;
376 default:
377 break;
378 }
379 return 0; // no error
380}
381
382int QTextMarkdownImporter::cbEnterSpan(int spanType, void *det)
383{
384 QTextCharFormat charFmt;
385 if (!m_spanFormatStack.isEmpty())
386 charFmt = m_spanFormatStack.top();
387 switch (spanType) {
388 case MD_SPAN_EM:
389 charFmt.setFontItalic(true);
390 break;
391 case MD_SPAN_STRONG:
392 charFmt.setFontWeight(QFont::Bold);
393 break;
394 case MD_SPAN_U:
395 charFmt.setFontUnderline(true);
396 break;
397 case MD_SPAN_A: {
398 MD_SPAN_A_DETAIL *detail = static_cast<MD_SPAN_A_DETAIL *>(det);
399 QString url = QString::fromUtf8(detail->href.text, int(detail->href.size));
400 QString title = QString::fromUtf8(detail->title.text, int(detail->title.size));
401 charFmt.setAnchor(true);
402 charFmt.setAnchorHref(url);
403 if (!title.isEmpty())
404 charFmt.setToolTip(title);
405 charFmt.setForeground(m_palette.link());
406 qCDebug(lcMD) << "anchor" << url << title;
407 } break;
408 case MD_SPAN_IMG: {
409 m_imageSpan = true;
410 m_imageFormat = QTextImageFormat();
411 MD_SPAN_IMG_DETAIL *detail = static_cast<MD_SPAN_IMG_DETAIL *>(det);
412 m_imageFormat.setName(QString::fromUtf8(detail->src.text, int(detail->src.size)));
413 m_imageFormat.setProperty(QTextFormat::ImageTitle, QString::fromUtf8(detail->title.text, int(detail->title.size)));
414 break;
415 }
416 case MD_SPAN_CODE:
417 charFmt.setFont(m_monoFont);
418 charFmt.setFontFixedPitch(true);
419 break;
420 case MD_SPAN_DEL:
421 charFmt.setFontStrikeOut(true);
422 break;
423 }
424 m_spanFormatStack.push(charFmt);
425 qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().families().constFirst()
426 << charFmt.fontWeight() << (charFmt.fontItalic() ? "italic" : "")
427 << charFmt.foreground().color().name();
428 m_cursor.setCharFormat(charFmt);
429 return 0; // no error
430}
431
433{
435 QTextCharFormat charFmt;
436 if (!m_spanFormatStack.isEmpty()) {
437 m_spanFormatStack.pop();
438 if (!m_spanFormatStack.isEmpty())
439 charFmt = m_spanFormatStack.top();
440 }
441 m_cursor.setCharFormat(charFmt);
442 qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().families().constFirst()
443 << charFmt.fontWeight() << (charFmt.fontItalic() ? "italic" : "")
444 << charFmt.foreground().color().name();
445 if (spanType == int(MD_SPAN_IMG))
446 m_imageSpan = false;
447 return 0; // no error
448}
449
450int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size)
451{
452 if (m_needsInsertBlock)
453 insertBlock();
454#if QT_CONFIG(regularexpression)
455 static const QRegularExpression openingBracket(QStringLiteral("<[a-zA-Z]"));
456 static const QRegularExpression closingBracket(QStringLiteral("(/>|</)"));
457#endif
459
460 switch (textType) {
461 case MD_TEXT_NORMAL:
462#if QT_CONFIG(regularexpression)
463 if (m_htmlTagDepth) {
464 m_htmlAccumulator += s;
465 s = QString();
466 }
467#endif
468 break;
469 case MD_TEXT_NULLCHAR:
470 s = QString(QChar(u'\xFFFD')); // CommonMark-required replacement for null
471 break;
472 case MD_TEXT_BR:
474 break;
475 case MD_TEXT_SOFTBR:
477 break;
478 case MD_TEXT_CODE:
479 // We'll see MD_SPAN_CODE too, which will set the char format, and that's enough.
480 break;
481#if QT_CONFIG(texthtmlparser)
482 case MD_TEXT_ENTITY:
483 if (m_htmlTagDepth)
484 m_htmlAccumulator += s;
485 else
486 m_cursor.insertHtml(s);
487 s = QString();
488 break;
489#endif
490 case MD_TEXT_HTML:
491 // count how many tags are opened and how many are closed
492#if QT_CONFIG(regularexpression) && QT_CONFIG(texthtmlparser)
493 {
494 int startIdx = 0;
495 while ((startIdx = s.indexOf(openingBracket, startIdx)) >= 0) {
496 ++m_htmlTagDepth;
497 startIdx += 2;
498 }
499 startIdx = 0;
500 while ((startIdx = s.indexOf(closingBracket, startIdx)) >= 0) {
501 --m_htmlTagDepth;
502 startIdx += 2;
503 }
504 }
505 m_htmlAccumulator += s;
506 if (!m_htmlTagDepth) { // all open tags are now closed
507 qCDebug(lcMD) << "HTML" << m_htmlAccumulator;
508 m_cursor.insertHtml(m_htmlAccumulator);
509 if (m_spanFormatStack.isEmpty())
510 m_cursor.setCharFormat(QTextCharFormat());
511 else
512 m_cursor.setCharFormat(m_spanFormatStack.top());
513 m_htmlAccumulator = QString();
514 }
515#endif
516 s = QString();
517 break;
518 }
519
520 switch (m_blockType) {
521 case MD_BLOCK_TD:
522 m_nonEmptyTableCells.append(m_tableCol);
523 break;
524 case MD_BLOCK_CODE:
525 if (s == qtmi_Newline) {
526 // defer a blank line until we see something else in the code block,
527 // to avoid ending every code block with a gratuitous blank line
528 m_needsInsertBlock = true;
529 s = QString();
530 }
531 break;
532 default:
533 break;
534 }
535
536 if (m_imageSpan) {
537 // TODO we don't yet support alt text with formatting, because of the cases where m_cursor
538 // already inserted the text above. Rather need to accumulate it in case we need it here.
540 qCDebug(lcMD) << "image" << m_imageFormat.name()
541 << "title" << m_imageFormat.stringProperty(QTextFormat::ImageTitle)
542 << "alt" << s << "relative to" << m_cursor.document()->baseUrl();
543 m_cursor.insertImage(m_imageFormat);
544 return 0; // no error
545 }
546
547 if (!s.isEmpty())
548 m_cursor.insertText(s);
549 if (m_cursor.currentList()) {
550 // The list item will indent the list item's text, so we don't need indentation on the block.
551 QTextBlockFormat bfmt = m_cursor.blockFormat();
552 bfmt.setIndent(0);
553 m_cursor.setBlockFormat(bfmt);
554 }
555 if (lcMD().isEnabled(QtDebugMsg)) {
556 QTextBlockFormat bfmt = m_cursor.blockFormat();
557 QString debugInfo;
558 if (m_cursor.currentList())
559 debugInfo = "in list at depth "_L1 + QString::number(m_cursor.currentList()->format().indent());
560 if (bfmt.hasProperty(QTextFormat::BlockQuoteLevel))
561 debugInfo += "in blockquote at depth "_L1 +
563 if (bfmt.hasProperty(QTextFormat::BlockCodeLanguage))
564 debugInfo += "in a code block"_L1;
565 qCDebug(lcMD) << textType << "in block" << m_blockType << s << qPrintable(debugInfo)
566 << "bindent" << bfmt.indent() << "tindent" << bfmt.textIndent()
567 << "margins" << bfmt.leftMargin() << bfmt.topMargin() << bfmt.bottomMargin() << bfmt.rightMargin();
568 }
569 return 0; // no error
570}
571
583void QTextMarkdownImporter::insertBlock()
584{
585 QTextCharFormat charFormat;
586 if (!m_spanFormatStack.isEmpty())
587 charFormat = m_spanFormatStack.top();
588 QTextBlockFormat blockFormat;
589 if (!m_listStack.isEmpty() && !m_needsInsertList && m_listItem) {
590 QTextList *list = m_listStack.top();
591 if (list)
592 blockFormat = list->item(list->count() - 1).blockFormat();
593 else
594 qWarning() << "attempted to insert into a list that no longer exists";
595 }
596 if (m_blockQuoteDepth) {
597 blockFormat.setProperty(QTextFormat::BlockQuoteLevel, m_blockQuoteDepth);
598 blockFormat.setLeftMargin(qtmi_BlockQuoteIndent * m_blockQuoteDepth);
600 }
601 if (m_codeBlock) {
602 blockFormat.setProperty(QTextFormat::BlockCodeLanguage, m_blockCodeLanguage);
603 if (m_blockCodeFence) {
604 blockFormat.setNonBreakableLines(true);
605 blockFormat.setProperty(QTextFormat::BlockCodeFence, QString(QLatin1Char(m_blockCodeFence)));
606 }
607 charFormat.setFont(m_monoFont);
608 } else {
609 blockFormat.setTopMargin(m_paragraphMargin);
610 blockFormat.setBottomMargin(m_paragraphMargin);
611 }
612 if (m_markerType == QTextBlockFormat::MarkerType::NoMarker)
614 else
615 blockFormat.setMarker(m_markerType);
616 if (!m_listStack.isEmpty())
617 blockFormat.setIndent(m_listStack.size());
618 if (m_cursor.document()->isEmpty()) {
619 m_cursor.setBlockFormat(blockFormat);
620 m_cursor.setCharFormat(charFormat);
621 } else if (m_listItem) {
622 m_cursor.insertBlock(blockFormat, QTextCharFormat());
623 m_cursor.setCharFormat(charFormat);
624 } else {
625 m_cursor.insertBlock(blockFormat, charFormat);
626 }
627 if (m_needsInsertList) {
628 m_listStack.push(m_cursor.createList(m_listFormat));
629 } else if (!m_listStack.isEmpty() && m_listItem && m_listStack.top()) {
630 m_listStack.top()->add(m_cursor.block());
631 }
632 m_needsInsertList = false;
633 m_needsInsertBlock = false;
634}
635
\inmodule QtCore
\threadsafe \inmodule QtGui
void setPointSize(int)
Sets the point size to pointSize.
Definition qfont.cpp:985
void setPixelSize(int)
Sets the font size to pixelSize pixels, with a maxiumum size of an unsigned 16-bit integer.
Definition qfont.cpp:1049
@ Bold
Definition qfont.h:70
qsizetype size() const noexcept
Definition qlist.h:397
bool isEmpty() const noexcept
Definition qlist.h:401
qsizetype count() const noexcept
Definition qlist.h:398
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
const QBrush & link() const
Returns the unvisited link text brush of the current color group.
Definition qpalette.h:100
\inmodule QtCore \reentrant
T & top()
Returns a reference to the stack's top item.
Definition qstack.h:19
T pop()
Removes the top item from the stack and returns it.
Definition qstack.h:18
void push(const T &t)
Adds element t to the top of the stack.
Definition qstack.h:17
\inmodule QtCore
Definition qstringview.h:78
qsizetype indexOf(QChar c, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1252
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8084
void setLeftMargin(qreal margin)
Sets the paragraph's left margin.
void setAlignment(Qt::Alignment alignment)
Sets the paragraph's alignment.
void setMarker(MarkerType marker)
void setBottomMargin(qreal margin)
Sets the paragraph's bottom margin.
void setNonBreakableLines(bool b)
If b is true, the lines in the paragraph are treated as non-breakable; otherwise they are breakable.
void setRightMargin(qreal margin)
Sets the paragraph's right margin.
void setIndent(int indent)
Sets the paragraph's indentation.
void setTopMargin(qreal margin)
Sets the paragraph's top margin.
void setFontItalic(bool italic)
If italic is true, sets the text format's font to be italic; otherwise the font will be non-italic.
void setFont(const QFont &font, FontPropertiesInheritanceBehavior behavior=FontPropertiesAll)
QTextBlockFormat blockFormat() const
Returns the block format of the block the cursor is in.
QTextDocument * document() const
QTextBlock block() const
Returns the block that contains the cursor.
void beginEditBlock()
Indicates the start of a block of editing operations on the document that should appear as a single o...
bool movePosition(MoveOperation op, MoveMode=MoveAnchor, int n=1)
Moves the cursor by performing the given operation n times, using the specified mode,...
void insertHtml(const QString &html)
void setBlockFormat(const QTextBlockFormat &format)
Sets the block format of the current block (or all blocks that are contained in the selection) to for...
void setCharFormat(const QTextCharFormat &format)
Sets the cursor's current character format to the given format.
void insertText(const QString &text)
Inserts text at the current position, using the current character format.
QTextList * currentList() const
Returns the current list if the cursor position() is inside a block that is part of a list; otherwise...
void insertImage(const QTextImageFormat &format, QTextFrameFormat::Position alignment)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void insertBlock()
Inserts a new empty block at the cursor position() with the current blockFormat() and charFormat().
void endEditBlock()
Indicates the end of a block of editing operations on the document that should appear as a single ope...
QTextList * insertList(const QTextListFormat &format)
Inserts a new block at the current position and makes it the first list item of a newly created list ...
QTextTable * insertTable(int rows, int cols, const QTextTableFormat &format)
Creates a new table with the given number of rows and columns in the specified format,...
QTextList * createList(const QTextListFormat &format)
Creates and returns a new list with the given format, and makes the current paragraph the cursor is i...
\reentrant \inmodule QtGui
bool isEmpty() const
Returns true if the document is empty; otherwise returns false.
QFont defaultFont
the default font used to display the document's text
virtual void clear()
Clears the document.
QUrl baseUrl
the base URL used to resolve relative resource URLs within the document.
void setMetaInformation(MetaInformation info, const QString &)
Sets the document's meta information of the type specified by info to the given string.
QString stringProperty(int propertyId) const
Returns the value of the property given by propertyId; if the property isn't of QMetaType::QString ty...
@ BlockTrailingHorizontalRulerWidth
void setProperty(int propertyId, const QVariant &value)
Sets the property specified by the propertyId to the given value.
void clearProperty(int propertyId)
Clears the value of the property given by propertyId.
QString name() const
Returns the name of the image.
void setName(const QString &name)
Sets the name of the image.
void setStyle(Style style)
Sets the list format's style.
void setIndent(int indent)
Sets the list format's indentation.
void setNumberSuffix(const QString &numberSuffix)
int indent() const
Returns the list format's indentation.
void setStart(int indent)
\reentrant
Definition qtextlist.h:18
void add(const QTextBlock &block)
Makes the given block part of the list.
QTextListFormat format() const
Returns the list's format.
Definition qtextlist.h:37
QTextMarkdownImporter(QTextDocument *doc, Features features)
int cbLeaveBlock(int blockType, void *detail)
int cbText(int textType, const char *text, unsigned size)
int cbEnterBlock(int blockType, void *detail)
int cbEnterSpan(int spanType, void *detail)
int cbLeaveSpan(int spanType, void *detail)
void import(const QString &markdown)
\reentrant
Definition qtexttable.h:19
QTextCursor firstCursorPosition() const
Returns the first valid cursor position in this cell.
bool isValid() const
Returns true if this is a valid table cell; otherwise returns false.
Definition qtexttable.h:36
int columns() const
Returns the number of columns in the table.
void appendRows(int count)
void appendColumns(int count)
int rows() const
Returns the number of rows in the table.
QTextTableCell cellAt(int row, int col) const
Returns the table cell at the given row and column in the table.
void mergeCells(int row, int col, int numRows, int numCols)
QString text
Combined button and popup list for selecting options.
@ AlignRight
Definition qnamespace.h:146
@ AlignVCenter
Definition qnamespace.h:155
@ AlignHCenter
Definition qnamespace.h:148
@ AlignLeft
Definition qnamespace.h:144
#define Q_UNLIKELY(x)
@ QtDebugMsg
Definition qlogging.h:30
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum type
GLfloat n
GLdouble s
[6]
Definition qopenglext.h:235
PromiseCallbacks callbacks
Definition qstdweb.cpp:275
#define qPrintable(string)
Definition qstring.h:1531
#define QStringLiteral(str)
static void CbDebugLog(const char *msg, void *userdata)
static int CbEnterSpan(MD_SPANTYPE type, void *detail, void *userdata)
static int CbLeaveBlock(MD_BLOCKTYPE type, void *detail, void *userdata)
static int CbText(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *userdata)
static const QChar qtmi_Newline
static const QChar qtmi_Space
static const int qtmi_BlockQuoteIndent
static constexpr auto markerString() noexcept
static Qt::Alignment MdAlignment(MD_ALIGN a, Qt::Alignment defaultAlignment=Qt::AlignLeft|Qt::AlignVCenter)
static int CbEnterBlock(MD_BLOCKTYPE type, void *detail, void *userdata)
static int CbLeaveSpan(MD_SPANTYPE type, void *detail, void *userdata)
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:165
QVideoFrameFormat::PixelFormat fmt
QList< int > list
[14]
QUrl url("example.com")
[constructor-url-reference]
QString title
[35]
QHostInfo info
[0]
\inmodule QtCore \reentrant
Definition qchar.h:18
bool contains(const AT &t) const noexcept
Definition qlist.h:45