Qt 6.x
The Qt SDK
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
30// TODO maybe eliminate the margins after all views recognize BlockQuoteLevel, CSS can format it, etc.
31static const int qtmi_BlockQuoteIndent =
32 40; // pixels, same as in QTextHtmlParserNode::initializeProperties
33
34static_assert(int(QTextMarkdownImporter::FeatureCollapseWhitespace) == MD_FLAG_COLLAPSEWHITESPACE);
35static_assert(int(QTextMarkdownImporter::FeaturePermissiveATXHeaders) == MD_FLAG_PERMISSIVEATXHEADERS);
36static_assert(int(QTextMarkdownImporter::FeaturePermissiveURLAutoLinks) == MD_FLAG_PERMISSIVEURLAUTOLINKS);
37static_assert(int(QTextMarkdownImporter::FeaturePermissiveMailAutoLinks) == MD_FLAG_PERMISSIVEEMAILAUTOLINKS);
38static_assert(int(QTextMarkdownImporter::FeatureNoIndentedCodeBlocks) == MD_FLAG_NOINDENTEDCODEBLOCKS);
39static_assert(int(QTextMarkdownImporter::FeatureNoHTMLBlocks) == MD_FLAG_NOHTMLBLOCKS);
40static_assert(int(QTextMarkdownImporter::FeatureNoHTMLSpans) == MD_FLAG_NOHTMLSPANS);
41static_assert(int(QTextMarkdownImporter::FeatureTables) == MD_FLAG_TABLES);
42static_assert(int(QTextMarkdownImporter::FeatureStrikeThrough) == MD_FLAG_STRIKETHROUGH);
43static_assert(int(QTextMarkdownImporter::FeatureUnderline) == MD_FLAG_UNDERLINE);
44static_assert(int(QTextMarkdownImporter::FeaturePermissiveWWWAutoLinks) == MD_FLAG_PERMISSIVEWWWAUTOLINKS);
45static_assert(int(QTextMarkdownImporter::FeaturePermissiveAutoLinks) == MD_FLAG_PERMISSIVEAUTOLINKS);
46static_assert(int(QTextMarkdownImporter::FeatureTasklists) == MD_FLAG_TASKLISTS);
47static_assert(int(QTextMarkdownImporter::FeatureNoHTML) == MD_FLAG_NOHTML);
48static_assert(int(QTextMarkdownImporter::DialectCommonMark) == MD_DIALECT_COMMONMARK);
49static_assert(int(QTextMarkdownImporter::DialectGitHub) == (MD_DIALECT_GITHUB | MD_FLAG_UNDERLINE));
50
51// --------------------------------------------------------
52// MD4C callback function wrappers
53
54static int CbEnterBlock(MD_BLOCKTYPE type, void *detail, void *userdata)
55{
56 QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
57 return mdi->cbEnterBlock(int(type), detail);
58}
59
60static int CbLeaveBlock(MD_BLOCKTYPE type, void *detail, void *userdata)
61{
62 QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
63 return mdi->cbLeaveBlock(int(type), detail);
64}
65
66static int CbEnterSpan(MD_SPANTYPE type, void *detail, void *userdata)
67{
68 QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
69 return mdi->cbEnterSpan(int(type), detail);
70}
71
72static int CbLeaveSpan(MD_SPANTYPE type, void *detail, void *userdata)
73{
74 QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
75 return mdi->cbLeaveSpan(int(type), detail);
76}
77
78static int CbText(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *userdata)
79{
80 QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
81 return mdi->cbText(int(type), text, size);
82}
83
84static void CbDebugLog(const char *msg, void *userdata)
85{
86 Q_UNUSED(userdata);
87 qCDebug(lcMD) << msg;
88}
89
90// MD4C callback function wrappers
91// --------------------------------------------------------
92
93static Qt::Alignment MdAlignment(MD_ALIGN a, Qt::Alignment defaultAlignment = Qt::AlignLeft | Qt::AlignVCenter)
94{
95 switch (a) {
96 case MD_ALIGN_LEFT:
98 case MD_ALIGN_CENTER:
100 case MD_ALIGN_RIGHT:
102 default: // including MD_ALIGN_DEFAULT
103 return defaultAlignment;
104 }
105}
106
107QTextMarkdownImporter::QTextMarkdownImporter(QTextMarkdownImporter::Features features)
108 : m_monoFont(QFontDatabase::systemFont(QFontDatabase::FixedFont))
109 , m_features(features)
110{
111}
112
113QTextMarkdownImporter::QTextMarkdownImporter(QTextDocument::MarkdownFeatures features)
114 : QTextMarkdownImporter(static_cast<QTextMarkdownImporter::Features>(int(features)))
115{
116}
117
119{
120 MD_PARSER callbacks = {
121 0, // abi_version
122 unsigned(m_features),
127 &CbText,
128 &CbDebugLog,
129 nullptr // syntax
130 };
131 m_doc = doc;
132 m_paragraphMargin = m_doc->defaultFont().pointSize() * 2 / 3;
133 m_cursor = new QTextCursor(doc);
134 doc->clear();
135 if (doc->defaultFont().pointSize() != -1)
136 m_monoFont.setPointSize(doc->defaultFont().pointSize());
137 else
138 m_monoFont.setPixelSize(doc->defaultFont().pixelSize());
139 qCDebug(lcMD) << "default font" << doc->defaultFont() << "mono font" << m_monoFont;
140 QByteArray md = markdown.toUtf8();
141 m_cursor->beginEditBlock();
142 md_parse(md.constData(), MD_SIZE(md.size()), &callbacks, this);
143 m_cursor->endEditBlock();
144 delete m_cursor;
145 m_cursor = nullptr;
146}
147
148int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
149{
150 m_blockType = blockType;
151 switch (blockType) {
152 case MD_BLOCK_P:
153 if (!m_listStack.isEmpty())
154 qCDebug(lcMD, m_listItem ? "P of LI at level %d" : "P continuation inside LI at level %d", int(m_listStack.size()));
155 else
156 qCDebug(lcMD, "P");
157 m_needsInsertBlock = true;
158 break;
159 case MD_BLOCK_QUOTE:
160 ++m_blockQuoteDepth;
161 qCDebug(lcMD, "QUOTE level %d", m_blockQuoteDepth);
162 break;
163 case MD_BLOCK_CODE: {
164 MD_BLOCK_CODE_DETAIL *detail = static_cast<MD_BLOCK_CODE_DETAIL *>(det);
165 m_codeBlock = true;
166 m_blockCodeLanguage = QLatin1StringView(detail->lang.text, int(detail->lang.size));
167 m_blockCodeFence = detail->fence_char;
168 QString info = QLatin1StringView(detail->info.text, int(detail->info.size));
169 m_needsInsertBlock = true;
170 if (m_blockQuoteDepth)
171 qCDebug(lcMD, "CODE lang '%s' info '%s' fenced with '%c' inside QUOTE %d", qPrintable(m_blockCodeLanguage), qPrintable(info), m_blockCodeFence, m_blockQuoteDepth);
172 else
173 qCDebug(lcMD, "CODE lang '%s' info '%s' fenced with '%c'", qPrintable(m_blockCodeLanguage), qPrintable(info), m_blockCodeFence);
174 } break;
175 case MD_BLOCK_H: {
176 MD_BLOCK_H_DETAIL *detail = static_cast<MD_BLOCK_H_DETAIL *>(det);
177 QTextBlockFormat blockFmt;
178 QTextCharFormat charFmt;
179 int sizeAdjustment = 4 - int(detail->level); // H1 to H6: +3 to -2
180 charFmt.setProperty(QTextFormat::FontSizeAdjustment, sizeAdjustment);
181 charFmt.setFontWeight(QFont::Bold);
182 blockFmt.setHeadingLevel(int(detail->level));
183 m_needsInsertBlock = false;
184 if (m_doc->isEmpty()) {
185 m_cursor->setBlockFormat(blockFmt);
186 m_cursor->setCharFormat(charFmt);
187 } else {
188 m_cursor->insertBlock(blockFmt, charFmt);
189 }
190 qCDebug(lcMD, "H%d", detail->level);
191 } break;
192 case MD_BLOCK_LI: {
193 m_needsInsertBlock = true;
194 m_listItem = true;
195 MD_BLOCK_LI_DETAIL *detail = static_cast<MD_BLOCK_LI_DETAIL *>(det);
196 m_markerType = detail->is_task ?
199 qCDebug(lcMD) << "LI";
200 } break;
201 case MD_BLOCK_UL: {
202 if (m_needsInsertList) // list nested in an empty list
203 m_listStack.push(m_cursor->insertList(m_listFormat));
204 else
205 m_needsInsertList = true;
206 MD_BLOCK_UL_DETAIL *detail = static_cast<MD_BLOCK_UL_DETAIL *>(det);
207 m_listFormat = QTextListFormat();
208 m_listFormat.setIndent(m_listStack.size() + 1);
209 switch (detail->mark) {
210 case '*':
212 break;
213 case '+':
215 break;
216 default: // including '-'
218 break;
219 }
220 qCDebug(lcMD, "UL %c level %d", detail->mark, int(m_listStack.size()) + 1);
221 } break;
222 case MD_BLOCK_OL: {
223 if (m_needsInsertList) // list nested in an empty list
224 m_listStack.push(m_cursor->insertList(m_listFormat));
225 else
226 m_needsInsertList = true;
227 MD_BLOCK_OL_DETAIL *detail = static_cast<MD_BLOCK_OL_DETAIL *>(det);
228 m_listFormat = QTextListFormat();
229 m_listFormat.setIndent(m_listStack.size() + 1);
230 m_listFormat.setNumberSuffix(QChar::fromLatin1(detail->mark_delimiter));
232 m_listFormat.setStart(detail->start);
233 qCDebug(lcMD, "OL xx%d level %d start %d", detail->mark_delimiter, int(m_listStack.size()) + 1, detail->start);
234 } break;
235 case MD_BLOCK_TD: {
236 MD_BLOCK_TD_DETAIL *detail = static_cast<MD_BLOCK_TD_DETAIL *>(det);
237 ++m_tableCol;
238 // absolute movement (and storage of m_tableCol) shouldn't be necessary, but
239 // movePosition(QTextCursor::NextCell) doesn't work
240 QTextTableCell cell = m_currentTable->cellAt(m_tableRowCount - 1, m_tableCol);
241 if (!cell.isValid()) {
242 qWarning("malformed table in Markdown input");
243 return 1;
244 }
245 *m_cursor = cell.firstCursorPosition();
246 QTextBlockFormat blockFmt = m_cursor->blockFormat();
247 blockFmt.setAlignment(MdAlignment(detail->align));
248 m_cursor->setBlockFormat(blockFmt);
249 qCDebug(lcMD) << "TD; align" << detail->align << MdAlignment(detail->align) << "col" << m_tableCol;
250 } break;
251 case MD_BLOCK_TH: {
252 ++m_tableColumnCount;
253 ++m_tableCol;
254 if (m_currentTable->columns() < m_tableColumnCount)
255 m_currentTable->appendColumns(1);
256 auto cell = m_currentTable->cellAt(m_tableRowCount - 1, m_tableCol);
257 if (!cell.isValid()) {
258 qWarning("malformed table in Markdown input");
259 return 1;
260 }
261 auto fmt = cell.format();
262 fmt.setFontWeight(QFont::Bold);
263 cell.setFormat(fmt);
264 } break;
265 case MD_BLOCK_TR: {
266 ++m_tableRowCount;
267 m_nonEmptyTableCells.clear();
268 if (m_currentTable->rows() < m_tableRowCount)
269 m_currentTable->appendRows(1);
270 m_tableCol = -1;
271 qCDebug(lcMD) << "TR" << m_currentTable->rows();
272 } break;
273 case MD_BLOCK_TABLE:
274 m_tableColumnCount = 0;
275 m_tableRowCount = 0;
276 m_currentTable = m_cursor->insertTable(1, 1); // we don't know the dimensions yet
277 break;
278 case MD_BLOCK_HR: {
279 qCDebug(lcMD, "HR");
280 QTextBlockFormat blockFmt;
282 m_cursor->insertBlock(blockFmt, QTextCharFormat());
283 } break;
284 default:
285 break; // nothing to do for now
286 }
287 return 0; // no error
288}
289
291{
293 switch (blockType) {
294 case MD_BLOCK_P:
295 m_listItem = false;
296 break;
297 case MD_BLOCK_UL:
298 case MD_BLOCK_OL:
299 if (Q_UNLIKELY(m_needsInsertList))
300 m_listStack.push(m_cursor->createList(m_listFormat));
301 if (Q_UNLIKELY(m_listStack.isEmpty())) {
302 qCWarning(lcMD, "list ended unexpectedly");
303 } else {
304 qCDebug(lcMD, "list at level %d ended", int(m_listStack.size()));
305 m_listStack.pop();
306 }
307 break;
308 case MD_BLOCK_TR: {
309 // https://github.com/mity/md4c/issues/29
310 // MD4C doesn't tell us explicitly which cells are merged, so merge empty cells
311 // with previous non-empty ones
312 int mergeEnd = -1;
313 int mergeBegin = -1;
314 for (int col = m_tableCol; col >= 0; --col) {
315 if (m_nonEmptyTableCells.contains(col)) {
316 if (mergeEnd >= 0 && mergeBegin >= 0) {
317 qCDebug(lcMD) << "merging cells" << mergeBegin << "to" << mergeEnd << "inclusive, on row" << m_currentTable->rows() - 1;
318 m_currentTable->mergeCells(m_currentTable->rows() - 1, mergeBegin - 1, 1, mergeEnd - mergeBegin + 2);
319 }
320 mergeEnd = -1;
321 mergeBegin = -1;
322 } else {
323 if (mergeEnd < 0)
324 mergeEnd = col;
325 else
326 mergeBegin = col;
327 }
328 }
329 } break;
330 case MD_BLOCK_QUOTE: {
331 qCDebug(lcMD, "QUOTE level %d ended", m_blockQuoteDepth);
332 --m_blockQuoteDepth;
333 m_needsInsertBlock = true;
334 } break;
335 case MD_BLOCK_TABLE:
336 qCDebug(lcMD) << "table ended with" << m_currentTable->columns() << "cols and" << m_currentTable->rows() << "rows";
337 m_currentTable = nullptr;
339 break;
340 case MD_BLOCK_LI:
341 qCDebug(lcMD, "LI at level %d ended", int(m_listStack.size()));
342 m_listItem = false;
343 break;
344 case MD_BLOCK_CODE: {
345 m_codeBlock = false;
346 m_blockCodeLanguage.clear();
347 m_blockCodeFence = 0;
348 if (m_blockQuoteDepth)
349 qCDebug(lcMD, "CODE ended inside QUOTE %d", m_blockQuoteDepth);
350 else
351 qCDebug(lcMD, "CODE ended");
352 m_needsInsertBlock = true;
353 } break;
354 case MD_BLOCK_H:
355 m_cursor->setCharFormat(QTextCharFormat());
356 break;
357 default:
358 break;
359 }
360 return 0; // no error
361}
362
363int QTextMarkdownImporter::cbEnterSpan(int spanType, void *det)
364{
365 QTextCharFormat charFmt;
366 if (!m_spanFormatStack.isEmpty())
367 charFmt = m_spanFormatStack.top();
368 switch (spanType) {
369 case MD_SPAN_EM:
370 charFmt.setFontItalic(true);
371 break;
372 case MD_SPAN_STRONG:
373 charFmt.setFontWeight(QFont::Bold);
374 break;
375 case MD_SPAN_U:
376 charFmt.setFontUnderline(true);
377 break;
378 case MD_SPAN_A: {
379 MD_SPAN_A_DETAIL *detail = static_cast<MD_SPAN_A_DETAIL *>(det);
380 QString url = QString::fromUtf8(detail->href.text, int(detail->href.size));
381 QString title = QString::fromUtf8(detail->title.text, int(detail->title.size));
382 charFmt.setAnchor(true);
383 charFmt.setAnchorHref(url);
384 if (!title.isEmpty())
385 charFmt.setToolTip(title);
386 charFmt.setForeground(m_palette.link());
387 qCDebug(lcMD) << "anchor" << url << title;
388 } break;
389 case MD_SPAN_IMG: {
390 m_imageSpan = true;
391 m_imageFormat = QTextImageFormat();
392 MD_SPAN_IMG_DETAIL *detail = static_cast<MD_SPAN_IMG_DETAIL *>(det);
393 m_imageFormat.setName(QString::fromUtf8(detail->src.text, int(detail->src.size)));
394 m_imageFormat.setProperty(QTextFormat::ImageTitle, QString::fromUtf8(detail->title.text, int(detail->title.size)));
395 break;
396 }
397 case MD_SPAN_CODE:
398 charFmt.setFont(m_monoFont);
399 charFmt.setFontFixedPitch(true);
400 break;
401 case MD_SPAN_DEL:
402 charFmt.setFontStrikeOut(true);
403 break;
404 }
405 m_spanFormatStack.push(charFmt);
406 qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().families().first()
407 << charFmt.fontWeight() << (charFmt.fontItalic() ? "italic" : "")
408 << charFmt.foreground().color().name();
409 m_cursor->setCharFormat(charFmt);
410 return 0; // no error
411}
412
414{
416 QTextCharFormat charFmt;
417 if (!m_spanFormatStack.isEmpty()) {
418 m_spanFormatStack.pop();
419 if (!m_spanFormatStack.isEmpty())
420 charFmt = m_spanFormatStack.top();
421 }
422 m_cursor->setCharFormat(charFmt);
423 qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().families().first()
424 << charFmt.fontWeight() << (charFmt.fontItalic() ? "italic" : "")
425 << charFmt.foreground().color().name();
426 if (spanType == int(MD_SPAN_IMG))
427 m_imageSpan = false;
428 return 0; // no error
429}
430
431int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size)
432{
433 if (m_needsInsertBlock)
434 insertBlock();
435#if QT_CONFIG(regularexpression)
436 static const QRegularExpression openingBracket(QStringLiteral("<[a-zA-Z]"));
437 static const QRegularExpression closingBracket(QStringLiteral("(/>|</)"));
438#endif
440
441 switch (textType) {
442 case MD_TEXT_NORMAL:
443#if QT_CONFIG(regularexpression)
444 if (m_htmlTagDepth) {
445 m_htmlAccumulator += s;
446 s = QString();
447 }
448#endif
449 break;
450 case MD_TEXT_NULLCHAR:
451 s = QString(QChar(u'\xFFFD')); // CommonMark-required replacement for null
452 break;
453 case MD_TEXT_BR:
455 break;
456 case MD_TEXT_SOFTBR:
458 break;
459 case MD_TEXT_CODE:
460 // We'll see MD_SPAN_CODE too, which will set the char format, and that's enough.
461 break;
462#if QT_CONFIG(texthtmlparser)
463 case MD_TEXT_ENTITY:
464 if (m_htmlTagDepth)
465 m_htmlAccumulator += s;
466 else
467 m_cursor->insertHtml(s);
468 s = QString();
469 break;
470#endif
471 case MD_TEXT_HTML:
472 // count how many tags are opened and how many are closed
473#if QT_CONFIG(regularexpression) && QT_CONFIG(texthtmlparser)
474 {
475 int startIdx = 0;
476 while ((startIdx = s.indexOf(openingBracket, startIdx)) >= 0) {
477 ++m_htmlTagDepth;
478 startIdx += 2;
479 }
480 startIdx = 0;
481 while ((startIdx = s.indexOf(closingBracket, startIdx)) >= 0) {
482 --m_htmlTagDepth;
483 startIdx += 2;
484 }
485 }
486 m_htmlAccumulator += s;
487 if (!m_htmlTagDepth) { // all open tags are now closed
488 qCDebug(lcMD) << "HTML" << m_htmlAccumulator;
489 m_cursor->insertHtml(m_htmlAccumulator);
490 if (m_spanFormatStack.isEmpty())
491 m_cursor->setCharFormat(QTextCharFormat());
492 else
493 m_cursor->setCharFormat(m_spanFormatStack.top());
494 m_htmlAccumulator = QString();
495 }
496#endif
497 s = QString();
498 break;
499 }
500
501 switch (m_blockType) {
502 case MD_BLOCK_TD:
503 m_nonEmptyTableCells.append(m_tableCol);
504 break;
505 case MD_BLOCK_CODE:
506 if (s == qtmi_Newline) {
507 // defer a blank line until we see something else in the code block,
508 // to avoid ending every code block with a gratuitous blank line
509 m_needsInsertBlock = true;
510 s = QString();
511 }
512 break;
513 default:
514 break;
515 }
516
517 if (m_imageSpan) {
518 // TODO we don't yet support alt text with formatting, because of the cases where m_cursor
519 // already inserted the text above. Rather need to accumulate it in case we need it here.
521 qCDebug(lcMD) << "image" << m_imageFormat.name()
522 << "title" << m_imageFormat.stringProperty(QTextFormat::ImageTitle)
523 << "alt" << s << "relative to" << m_doc->baseUrl();
524 m_cursor->insertImage(m_imageFormat);
525 return 0; // no error
526 }
527
528 if (!s.isEmpty())
529 m_cursor->insertText(s);
530 if (m_cursor->currentList()) {
531 // The list item will indent the list item's text, so we don't need indentation on the block.
532 QTextBlockFormat bfmt = m_cursor->blockFormat();
533 bfmt.setIndent(0);
534 m_cursor->setBlockFormat(bfmt);
535 }
536 if (lcMD().isEnabled(QtDebugMsg)) {
537 QTextBlockFormat bfmt = m_cursor->blockFormat();
538 QString debugInfo;
539 if (m_cursor->currentList())
540 debugInfo = "in list at depth "_L1 + QString::number(m_cursor->currentList()->format().indent());
542 debugInfo += "in blockquote at depth "_L1 +
545 debugInfo += "in a code block"_L1;
546 qCDebug(lcMD) << textType << "in block" << m_blockType << s << qPrintable(debugInfo)
547 << "bindent" << bfmt.indent() << "tindent" << bfmt.textIndent()
548 << "margins" << bfmt.leftMargin() << bfmt.topMargin() << bfmt.bottomMargin() << bfmt.rightMargin();
549 }
550 return 0; // no error
551}
552
564void QTextMarkdownImporter::insertBlock()
565{
566 QTextCharFormat charFormat;
567 if (!m_spanFormatStack.isEmpty())
568 charFormat = m_spanFormatStack.top();
569 QTextBlockFormat blockFormat;
570 if (!m_listStack.isEmpty() && !m_needsInsertList && m_listItem) {
571 QTextList *list = m_listStack.top();
572 if (list)
573 blockFormat = list->item(list->count() - 1).blockFormat();
574 else
575 qWarning() << "attempted to insert into a list that no longer exists";
576 }
577 if (m_blockQuoteDepth) {
578 blockFormat.setProperty(QTextFormat::BlockQuoteLevel, m_blockQuoteDepth);
579 blockFormat.setLeftMargin(qtmi_BlockQuoteIndent * m_blockQuoteDepth);
581 }
582 if (m_codeBlock) {
583 blockFormat.setProperty(QTextFormat::BlockCodeLanguage, m_blockCodeLanguage);
584 if (m_blockCodeFence) {
585 blockFormat.setNonBreakableLines(true);
586 blockFormat.setProperty(QTextFormat::BlockCodeFence, QString(QLatin1Char(m_blockCodeFence)));
587 }
588 charFormat.setFont(m_monoFont);
589 } else {
590 blockFormat.setTopMargin(m_paragraphMargin);
591 blockFormat.setBottomMargin(m_paragraphMargin);
592 }
593 if (m_markerType == QTextBlockFormat::MarkerType::NoMarker)
595 else
596 blockFormat.setMarker(m_markerType);
597 if (!m_listStack.isEmpty())
598 blockFormat.setIndent(m_listStack.size());
599 if (m_doc->isEmpty()) {
600 m_cursor->setBlockFormat(blockFormat);
601 m_cursor->setCharFormat(charFormat);
602 } else if (m_listItem) {
603 m_cursor->insertBlock(blockFormat, QTextCharFormat());
604 m_cursor->setCharFormat(charFormat);
605 } else {
606 m_cursor->insertBlock(blockFormat, charFormat);
607 }
608 if (m_needsInsertList) {
609 m_listStack.push(m_cursor->createList(m_listFormat));
610 } else if (!m_listStack.isEmpty() && m_listItem && m_listStack.top()) {
611 m_listStack.top()->add(m_cursor->block());
612 }
613 m_needsInsertList = false;
614 m_needsInsertBlock = false;
615}
616
const QColor & color() const
Returns the brush color.
Definition qbrush.h:121
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
\inmodule QtCore
Definition qchar.h:48
static constexpr QChar fromLatin1(char c) noexcept
Converts the Latin-1 character c to its equivalent QChar.
Definition qchar.h:461
QString name(NameFormat format=HexRgb) const
Definition qcolor.cpp:834
\threadsafe \inmodule QtGui
void setPointSize(int)
Sets the point size to pointSize.
Definition qfont.cpp:970
int pixelSize() const
Returns the pixel size of the font if it was set with setPixelSize().
Definition qfont.cpp:1059
QStringList families() const
Definition qfont.cpp:2469
int pointSize() const
Returns the point size of the font.
Definition qfont.cpp:863
void setPixelSize(int)
Sets the font size to pixelSize pixels, with a maxiumum size of an unsigned 16-bit integer.
Definition qfont.cpp:1034
@ Bold
Definition qfont.h:67
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
qsizetype count() const noexcept
Definition qlist.h:387
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
const QBrush & link() const
Returns the unvisited link text brush of the current color group.
Definition qpalette.h:99
\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
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1107
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
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:7822
QByteArray toUtf8() const &
Definition qstring.h:563
qreal bottomMargin() const
Returns the paragraph's bottom margin.
qreal topMargin() const
Returns the paragraph's top margin.
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.
int indent() const
Returns the paragraph's indent.
qreal leftMargin() const
Returns the paragraph's left margin.
qreal textIndent() const
Returns the paragraph's text indent.
void setHeadingLevel(int alevel)
qreal rightMargin() const
Returns the paragraph's right margin.
void setTopMargin(qreal margin)
Sets the paragraph's top margin.
int fontWeight() const
Returns the text format's font weight.
void setAnchor(bool anchor)
If anchor is true, text with this format represents an anchor, and is formatted in the appropriate wa...
void setFontUnderline(bool underline)
If underline is true, sets the text format's font to be underlined; otherwise it is displayed non-und...
void setToolTip(const QString &tip)
QFont font() const
Returns the font for this character format.
void setFontFixedPitch(bool fixedPitch)
If fixedPitch is true, sets the text format's font to be fixed pitch; otherwise a non-fixed pitch fon...
void setFontStrikeOut(bool strikeOut)
If strikeOut is true, sets the text format's font with strike-out enabled (with a horizontal line thr...
bool fontItalic() const
Returns true if the text format's font is italic; otherwise returns false.
void setAnchorHref(const QString &value)
Sets the hypertext link for the text format to the given value.
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 setFontWeight(int weight)
Sets the text format's font weight to weight.
void setFont(const QFont &font, FontPropertiesInheritanceBehavior behavior=FontPropertiesAll)
\reentrant \inmodule QtGui
Definition qtextcursor.h:30
QTextBlockFormat blockFormat() const
Returns the block format of the block the cursor is in.
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.
QString stringProperty(int propertyId) const
Returns the value of the property given by propertyId; if the property isn't of QMetaType::QString ty...
void setForeground(const QBrush &brush)
Sets the foreground brush to the specified brush.
@ BlockTrailingHorizontalRulerWidth
int intProperty(int propertyId) const
Returns the value of the property specified by propertyId.
void setProperty(int propertyId, const QVariant &value)
Sets the property specified by the propertyId to the given value.
bool hasProperty(int propertyId) const
Returns true if the text format has a property with the given propertyId; otherwise returns false.
void clearProperty(int propertyId)
Clears the value of the property given by propertyId.
QBrush foreground() const
Returns the brush used to render foreground details, such as text, frame outlines,...
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
int cbLeaveBlock(int blockType, void *detail)
int cbText(int textType, const char *text, unsigned size)
QTextMarkdownImporter(Features features)
int cbEnterBlock(int blockType, void *detail)
void import(QTextDocument *doc, const QString &markdown)
int cbEnterSpan(int spanType, void *detail)
int cbLeaveSpan(int spanType, void *detail)
\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:145
@ AlignVCenter
Definition qnamespace.h:154
@ AlignHCenter
Definition qnamespace.h:147
@ AlignLeft
Definition qnamespace.h:143
#define Q_UNLIKELY(x)
@ QtDebugMsg
Definition qlogging.h:30
#define qWarning
Definition qlogging.h:162
#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:270
#define qPrintable(string)
Definition qstring.h:1391
#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 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)
QVideoFrameFormat::PixelFormat fmt
QList< int > list
[14]
QFileInfo info(fileName)
[8]
QUrl url("example.com")
[constructor-url-reference]
QString title
[35]
\inmodule QtCore \reentrant
Definition qchar.h:17
bool contains(const AT &t) const noexcept
Definition qlist.h:44