QtBase  v6.3.1
qtextmarkdownimporter.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
42 #include <QLoggingCategory>
43 #if QT_CONFIG(regularexpression)
44 #include <QRegularExpression>
45 #endif
46 #include <QTextCursor>
47 #include <QTextDocument>
48 #include <QTextDocumentFragment>
49 #include <QTextList>
50 #include <QTextTable>
51 #if QT_CONFIG(system_textmarkdownreader)
52 #include <md4c.h>
53 #else
54 #include "../../3rdparty/md4c/md4c.h"
55 #endif
56 
58 
59 Q_LOGGING_CATEGORY(lcMD, "qt.text.markdown")
60 
61 static const QChar Newline = QLatin1Char('\n');
62 static const QChar Space = QLatin1Char(' ');
63 
64 // TODO maybe eliminate the margins after all views recognize BlockQuoteLevel, CSS can format it, etc.
65 static const int BlockQuoteIndent = 40; // pixels, same as in QTextHtmlParserNode::initializeProperties
66 
67 static_assert(int(QTextMarkdownImporter::FeatureCollapseWhitespace) == MD_FLAG_COLLAPSEWHITESPACE);
68 static_assert(int(QTextMarkdownImporter::FeaturePermissiveATXHeaders) == MD_FLAG_PERMISSIVEATXHEADERS);
69 static_assert(int(QTextMarkdownImporter::FeaturePermissiveURLAutoLinks) == MD_FLAG_PERMISSIVEURLAUTOLINKS);
70 static_assert(int(QTextMarkdownImporter::FeaturePermissiveMailAutoLinks) == MD_FLAG_PERMISSIVEEMAILAUTOLINKS);
71 static_assert(int(QTextMarkdownImporter::FeatureNoIndentedCodeBlocks) == MD_FLAG_NOINDENTEDCODEBLOCKS);
72 static_assert(int(QTextMarkdownImporter::FeatureNoHTMLBlocks) == MD_FLAG_NOHTMLBLOCKS);
73 static_assert(int(QTextMarkdownImporter::FeatureNoHTMLSpans) == MD_FLAG_NOHTMLSPANS);
74 static_assert(int(QTextMarkdownImporter::FeatureTables) == MD_FLAG_TABLES);
75 static_assert(int(QTextMarkdownImporter::FeatureStrikeThrough) == MD_FLAG_STRIKETHROUGH);
76 static_assert(int(QTextMarkdownImporter::FeatureUnderline) == MD_FLAG_UNDERLINE);
77 static_assert(int(QTextMarkdownImporter::FeaturePermissiveWWWAutoLinks) == MD_FLAG_PERMISSIVEWWWAUTOLINKS);
78 static_assert(int(QTextMarkdownImporter::FeaturePermissiveAutoLinks) == MD_FLAG_PERMISSIVEAUTOLINKS);
79 static_assert(int(QTextMarkdownImporter::FeatureTasklists) == MD_FLAG_TASKLISTS);
80 static_assert(int(QTextMarkdownImporter::FeatureNoHTML) == MD_FLAG_NOHTML);
81 static_assert(int(QTextMarkdownImporter::DialectCommonMark) == MD_DIALECT_COMMONMARK);
82 static_assert(int(QTextMarkdownImporter::DialectGitHub) == (MD_DIALECT_GITHUB | MD_FLAG_UNDERLINE));
83 
84 // --------------------------------------------------------
85 // MD4C callback function wrappers
86 
87 static int CbEnterBlock(MD_BLOCKTYPE type, void *detail, void *userdata)
88 {
89  QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
90  return mdi->cbEnterBlock(int(type), detail);
91 }
92 
93 static int CbLeaveBlock(MD_BLOCKTYPE type, void *detail, void *userdata)
94 {
95  QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
96  return mdi->cbLeaveBlock(int(type), detail);
97 }
98 
99 static int CbEnterSpan(MD_SPANTYPE type, void *detail, void *userdata)
100 {
101  QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
102  return mdi->cbEnterSpan(int(type), detail);
103 }
104 
105 static int CbLeaveSpan(MD_SPANTYPE type, void *detail, void *userdata)
106 {
107  QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
108  return mdi->cbLeaveSpan(int(type), detail);
109 }
110 
111 static int CbText(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *userdata)
112 {
113  QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata);
114  return mdi->cbText(int(type), text, size);
115 }
116 
117 static void CbDebugLog(const char *msg, void *userdata)
118 {
119  Q_UNUSED(userdata);
120  qCDebug(lcMD) << msg;
121 }
122 
123 // MD4C callback function wrappers
124 // --------------------------------------------------------
125 
126 static Qt::Alignment MdAlignment(MD_ALIGN a, Qt::Alignment defaultAlignment = Qt::AlignLeft | Qt::AlignVCenter)
127 {
128  switch (a) {
129  case MD_ALIGN_LEFT:
131  case MD_ALIGN_CENTER:
133  case MD_ALIGN_RIGHT:
135  default: // including MD_ALIGN_DEFAULT
136  return defaultAlignment;
137  }
138 }
139 
140 QTextMarkdownImporter::QTextMarkdownImporter(QTextMarkdownImporter::Features features)
141  : m_monoFont(QFontDatabase::systemFont(QFontDatabase::FixedFont))
142  , m_features(features)
143 {
144 }
145 
146 QTextMarkdownImporter::QTextMarkdownImporter(QTextDocument::MarkdownFeatures features)
147  : QTextMarkdownImporter(static_cast<QTextMarkdownImporter::Features>(int(features)))
148 {
149 }
150 
152 {
153  MD_PARSER callbacks = {
154  0, // abi_version
155  unsigned(m_features),
156  &CbEnterBlock,
157  &CbLeaveBlock,
158  &CbEnterSpan,
159  &CbLeaveSpan,
160  &CbText,
161  &CbDebugLog,
162  nullptr // syntax
163  };
164  m_doc = doc;
165  m_paragraphMargin = m_doc->defaultFont().pointSize() * 2 / 3;
166  m_cursor = new QTextCursor(doc);
167  doc->clear();
168  if (doc->defaultFont().pointSize() != -1)
169  m_monoFont.setPointSize(doc->defaultFont().pointSize());
170  else
171  m_monoFont.setPixelSize(doc->defaultFont().pixelSize());
172  qCDebug(lcMD) << "default font" << doc->defaultFont() << "mono font" << m_monoFont;
173  QByteArray md = markdown.toUtf8();
174  md_parse(md.constData(), MD_SIZE(md.size()), &callbacks, this);
175  delete m_cursor;
176  m_cursor = nullptr;
177 }
178 
179 int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
180 {
181  m_blockType = blockType;
182  switch (blockType) {
183  case MD_BLOCK_P:
184  if (!m_listStack.isEmpty())
185  qCDebug(lcMD, m_listItem ? "P of LI at level %d" : "P continuation inside LI at level %d", int(m_listStack.count()));
186  else
187  qCDebug(lcMD, "P");
188  m_needsInsertBlock = true;
189  break;
190  case MD_BLOCK_QUOTE:
191  ++m_blockQuoteDepth;
192  qCDebug(lcMD, "QUOTE level %d", m_blockQuoteDepth);
193  break;
194  case MD_BLOCK_CODE: {
195  MD_BLOCK_CODE_DETAIL *detail = static_cast<MD_BLOCK_CODE_DETAIL *>(det);
196  m_codeBlock = true;
197  m_blockCodeLanguage = QLatin1String(detail->lang.text, int(detail->lang.size));
198  m_blockCodeFence = detail->fence_char;
199  QString info = QLatin1String(detail->info.text, int(detail->info.size));
200  m_needsInsertBlock = true;
201  if (m_blockQuoteDepth)
202  qCDebug(lcMD, "CODE lang '%s' info '%s' fenced with '%c' inside QUOTE %d", qPrintable(m_blockCodeLanguage), qPrintable(info), m_blockCodeFence, m_blockQuoteDepth);
203  else
204  qCDebug(lcMD, "CODE lang '%s' info '%s' fenced with '%c'", qPrintable(m_blockCodeLanguage), qPrintable(info), m_blockCodeFence);
205  } break;
206  case MD_BLOCK_H: {
207  MD_BLOCK_H_DETAIL *detail = static_cast<MD_BLOCK_H_DETAIL *>(det);
208  QTextBlockFormat blockFmt;
209  QTextCharFormat charFmt;
210  int sizeAdjustment = 4 - int(detail->level); // H1 to H6: +3 to -2
211  charFmt.setProperty(QTextFormat::FontSizeAdjustment, sizeAdjustment);
212  charFmt.setFontWeight(QFont::Bold);
213  blockFmt.setHeadingLevel(int(detail->level));
214  m_needsInsertBlock = false;
215  if (m_doc->isEmpty()) {
216  m_cursor->setBlockFormat(blockFmt);
217  m_cursor->setCharFormat(charFmt);
218  } else {
219  m_cursor->insertBlock(blockFmt, charFmt);
220  }
221  qCDebug(lcMD, "H%d", detail->level);
222  } break;
223  case MD_BLOCK_LI: {
224  m_needsInsertBlock = true;
225  m_listItem = true;
226  MD_BLOCK_LI_DETAIL *detail = static_cast<MD_BLOCK_LI_DETAIL *>(det);
227  m_markerType = detail->is_task ?
230  qCDebug(lcMD) << "LI";
231  } break;
232  case MD_BLOCK_UL: {
233  if (m_needsInsertList) // list nested in an empty list
234  m_listStack.push(m_cursor->insertList(m_listFormat));
235  else
236  m_needsInsertList = true;
237  MD_BLOCK_UL_DETAIL *detail = static_cast<MD_BLOCK_UL_DETAIL *>(det);
238  m_listFormat = QTextListFormat();
239  m_listFormat.setIndent(m_listStack.count() + 1);
240  switch (detail->mark) {
241  case '*':
243  break;
244  case '+':
246  break;
247  default: // including '-'
248  m_listFormat.setStyle(QTextListFormat::ListDisc);
249  break;
250  }
251  qCDebug(lcMD, "UL %c level %d", detail->mark, int(m_listStack.count()) + 1);
252  } break;
253  case MD_BLOCK_OL: {
254  if (m_needsInsertList) // list nested in an empty list
255  m_listStack.push(m_cursor->insertList(m_listFormat));
256  else
257  m_needsInsertList = true;
258  MD_BLOCK_OL_DETAIL *detail = static_cast<MD_BLOCK_OL_DETAIL *>(det);
259  m_listFormat = QTextListFormat();
260  m_listFormat.setIndent(m_listStack.count() + 1);
261  m_listFormat.setNumberSuffix(QChar::fromLatin1(detail->mark_delimiter));
263  qCDebug(lcMD, "OL xx%d level %d", detail->mark_delimiter, int(m_listStack.count()) + 1);
264  } break;
265  case MD_BLOCK_TD: {
266  MD_BLOCK_TD_DETAIL *detail = static_cast<MD_BLOCK_TD_DETAIL *>(det);
267  ++m_tableCol;
268  // absolute movement (and storage of m_tableCol) shouldn't be necessary, but
269  // movePosition(QTextCursor::NextCell) doesn't work
270  QTextTableCell cell = m_currentTable->cellAt(m_tableRowCount - 1, m_tableCol);
271  if (!cell.isValid()) {
272  qWarning("malformed table in Markdown input");
273  return 1;
274  }
275  *m_cursor = cell.firstCursorPosition();
276  QTextBlockFormat blockFmt = m_cursor->blockFormat();
277  blockFmt.setAlignment(MdAlignment(detail->align));
278  m_cursor->setBlockFormat(blockFmt);
279  qCDebug(lcMD) << "TD; align" << detail->align << MdAlignment(detail->align) << "col" << m_tableCol;
280  } break;
281  case MD_BLOCK_TH: {
282  ++m_tableColumnCount;
283  ++m_tableCol;
284  if (m_currentTable->columns() < m_tableColumnCount)
285  m_currentTable->appendColumns(1);
286  auto cell = m_currentTable->cellAt(m_tableRowCount - 1, m_tableCol);
287  if (!cell.isValid()) {
288  qWarning("malformed table in Markdown input");
289  return 1;
290  }
291  auto fmt = cell.format();
292  fmt.setFontWeight(QFont::Bold);
293  cell.setFormat(fmt);
294  } break;
295  case MD_BLOCK_TR: {
296  ++m_tableRowCount;
297  m_nonEmptyTableCells.clear();
298  if (m_currentTable->rows() < m_tableRowCount)
299  m_currentTable->appendRows(1);
300  m_tableCol = -1;
301  qCDebug(lcMD) << "TR" << m_currentTable->rows();
302  } break;
303  case MD_BLOCK_TABLE:
304  m_tableColumnCount = 0;
305  m_tableRowCount = 0;
306  m_currentTable = m_cursor->insertTable(1, 1); // we don't know the dimensions yet
307  break;
308  case MD_BLOCK_HR: {
309  qCDebug(lcMD, "HR");
310  QTextBlockFormat blockFmt;
312  m_cursor->insertBlock(blockFmt, QTextCharFormat());
313  } break;
314  default:
315  break; // nothing to do for now
316  }
317  return 0; // no error
318 }
319 
320 int QTextMarkdownImporter::cbLeaveBlock(int blockType, void *detail)
321 {
322  Q_UNUSED(detail);
323  switch (blockType) {
324  case MD_BLOCK_P:
325  m_listItem = false;
326  break;
327  case MD_BLOCK_UL:
328  case MD_BLOCK_OL:
329  if (Q_UNLIKELY(m_needsInsertList))
330  m_listStack.push(m_cursor->createList(m_listFormat));
331  if (Q_UNLIKELY(m_listStack.isEmpty())) {
332  qCWarning(lcMD, "list ended unexpectedly");
333  } else {
334  qCDebug(lcMD, "list at level %d ended", int(m_listStack.count()));
335  m_listStack.pop();
336  }
337  break;
338  case MD_BLOCK_TR: {
339  // https://github.com/mity/md4c/issues/29
340  // MD4C doesn't tell us explicitly which cells are merged, so merge empty cells
341  // with previous non-empty ones
342  int mergeEnd = -1;
343  int mergeBegin = -1;
344  for (int col = m_tableCol; col >= 0; --col) {
345  if (m_nonEmptyTableCells.contains(col)) {
346  if (mergeEnd >= 0 && mergeBegin >= 0) {
347  qCDebug(lcMD) << "merging cells" << mergeBegin << "to" << mergeEnd << "inclusive, on row" << m_currentTable->rows() - 1;
348  m_currentTable->mergeCells(m_currentTable->rows() - 1, mergeBegin - 1, 1, mergeEnd - mergeBegin + 2);
349  }
350  mergeEnd = -1;
351  mergeBegin = -1;
352  } else {
353  if (mergeEnd < 0)
354  mergeEnd = col;
355  else
356  mergeBegin = col;
357  }
358  }
359  } break;
360  case MD_BLOCK_QUOTE: {
361  qCDebug(lcMD, "QUOTE level %d ended", m_blockQuoteDepth);
362  --m_blockQuoteDepth;
363  m_needsInsertBlock = true;
364  } break;
365  case MD_BLOCK_TABLE:
366  qCDebug(lcMD) << "table ended with" << m_currentTable->columns() << "cols and" << m_currentTable->rows() << "rows";
367  m_currentTable = nullptr;
368  m_cursor->movePosition(QTextCursor::End);
369  break;
370  case MD_BLOCK_LI:
371  qCDebug(lcMD, "LI at level %d ended", int(m_listStack.count()));
372  m_listItem = false;
373  break;
374  case MD_BLOCK_CODE: {
375  m_codeBlock = false;
376  m_blockCodeLanguage.clear();
377  m_blockCodeFence = 0;
378  if (m_blockQuoteDepth)
379  qCDebug(lcMD, "CODE ended inside QUOTE %d", m_blockQuoteDepth);
380  else
381  qCDebug(lcMD, "CODE ended");
382  m_needsInsertBlock = true;
383  } break;
384  case MD_BLOCK_H:
385  m_cursor->setCharFormat(QTextCharFormat());
386  break;
387  default:
388  break;
389  }
390  return 0; // no error
391 }
392 
393 int QTextMarkdownImporter::cbEnterSpan(int spanType, void *det)
394 {
395  QTextCharFormat charFmt;
396  if (!m_spanFormatStack.isEmpty())
397  charFmt = m_spanFormatStack.top();
398  switch (spanType) {
399  case MD_SPAN_EM:
400  charFmt.setFontItalic(true);
401  break;
402  case MD_SPAN_STRONG:
403  charFmt.setFontWeight(QFont::Bold);
404  break;
405  case MD_SPAN_U:
406  charFmt.setFontUnderline(true);
407  break;
408  case MD_SPAN_A: {
409  MD_SPAN_A_DETAIL *detail = static_cast<MD_SPAN_A_DETAIL *>(det);
410  QString url = QString::fromUtf8(detail->href.text, int(detail->href.size));
411  QString title = QString::fromUtf8(detail->title.text, int(detail->title.size));
412  charFmt.setAnchor(true);
413  charFmt.setAnchorHref(url);
414  if (!title.isEmpty())
415  charFmt.setToolTip(title);
416  charFmt.setForeground(m_palette.link());
417  qCDebug(lcMD) << "anchor" << url << title;
418  } break;
419  case MD_SPAN_IMG: {
420  m_imageSpan = true;
421  m_imageFormat = QTextImageFormat();
422  MD_SPAN_IMG_DETAIL *detail = static_cast<MD_SPAN_IMG_DETAIL *>(det);
423  m_imageFormat.setName(QString::fromUtf8(detail->src.text, int(detail->src.size)));
424  m_imageFormat.setProperty(QTextFormat::ImageTitle, QString::fromUtf8(detail->title.text, int(detail->title.size)));
425  break;
426  }
427  case MD_SPAN_CODE:
428  charFmt.setFont(m_monoFont);
429  charFmt.setFontFixedPitch(true);
430  break;
431  case MD_SPAN_DEL:
432  charFmt.setFontStrikeOut(true);
433  break;
434  }
435  m_spanFormatStack.push(charFmt);
436  qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().families().first()
437  << charFmt.fontWeight() << (charFmt.fontItalic() ? "italic" : "")
438  << charFmt.foreground().color().name();
439  m_cursor->setCharFormat(charFmt);
440  return 0; // no error
441 }
442 
443 int QTextMarkdownImporter::cbLeaveSpan(int spanType, void *detail)
444 {
445  Q_UNUSED(detail);
446  QTextCharFormat charFmt;
447  if (!m_spanFormatStack.isEmpty()) {
448  m_spanFormatStack.pop();
449  if (!m_spanFormatStack.isEmpty())
450  charFmt = m_spanFormatStack.top();
451  }
452  m_cursor->setCharFormat(charFmt);
453  qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().families().first()
454  << charFmt.fontWeight() << (charFmt.fontItalic() ? "italic" : "")
455  << charFmt.foreground().color().name();
456  if (spanType == int(MD_SPAN_IMG))
457  m_imageSpan = false;
458  return 0; // no error
459 }
460 
461 int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size)
462 {
463  if (m_needsInsertBlock)
464  insertBlock();
465 #if QT_CONFIG(regularexpression)
466  static const QRegularExpression openingBracket(QStringLiteral("<[a-zA-Z]"));
467  static const QRegularExpression closingBracket(QStringLiteral("(/>|</)"));
468 #endif
470 
471  switch (textType) {
472  case MD_TEXT_NORMAL:
473 #if QT_CONFIG(regularexpression)
474  if (m_htmlTagDepth) {
475  m_htmlAccumulator += s;
476  s = QString();
477  }
478 #endif
479  break;
480  case MD_TEXT_NULLCHAR:
481  s = QString(QChar(u'\xFFFD')); // CommonMark-required replacement for null
482  break;
483  case MD_TEXT_BR:
484  s = QString(Newline);
485  break;
486  case MD_TEXT_SOFTBR:
487  s = QString(Space);
488  break;
489  case MD_TEXT_CODE:
490  // We'll see MD_SPAN_CODE too, which will set the char format, and that's enough.
491  break;
492 #if QT_CONFIG(texthtmlparser)
493  case MD_TEXT_ENTITY:
494  if (m_htmlTagDepth)
495  m_htmlAccumulator += s;
496  else
497  m_cursor->insertHtml(s);
498  s = QString();
499  break;
500 #endif
501  case MD_TEXT_HTML:
502  // count how many tags are opened and how many are closed
503 #if QT_CONFIG(regularexpression) && QT_CONFIG(texthtmlparser)
504  {
505  int startIdx = 0;
506  while ((startIdx = s.indexOf(openingBracket, startIdx)) >= 0) {
507  ++m_htmlTagDepth;
508  startIdx += 2;
509  }
510  startIdx = 0;
511  while ((startIdx = s.indexOf(closingBracket, startIdx)) >= 0) {
512  --m_htmlTagDepth;
513  startIdx += 2;
514  }
515  }
516  m_htmlAccumulator += s;
517  if (!m_htmlTagDepth) { // all open tags are now closed
518  qCDebug(lcMD) << "HTML" << m_htmlAccumulator;
519  m_cursor->insertHtml(m_htmlAccumulator);
520  if (m_spanFormatStack.isEmpty())
521  m_cursor->setCharFormat(QTextCharFormat());
522  else
523  m_cursor->setCharFormat(m_spanFormatStack.top());
524  m_htmlAccumulator = QString();
525  }
526 #endif
527  s = QString();
528  break;
529  }
530 
531  switch (m_blockType) {
532  case MD_BLOCK_TD:
533  m_nonEmptyTableCells.append(m_tableCol);
534  break;
535  case MD_BLOCK_CODE:
536  if (s == Newline) {
537  // defer a blank line until we see something else in the code block,
538  // to avoid ending every code block with a gratuitous blank line
539  m_needsInsertBlock = true;
540  s = QString();
541  }
542  break;
543  default:
544  break;
545  }
546 
547  if (m_imageSpan) {
548  // TODO we don't yet support alt text with formatting, because of the cases where m_cursor
549  // already inserted the text above. Rather need to accumulate it in case we need it here.
550  m_imageFormat.setProperty(QTextFormat::ImageAltText, s);
551  qCDebug(lcMD) << "image" << m_imageFormat.name()
552  << "title" << m_imageFormat.stringProperty(QTextFormat::ImageTitle)
553  << "alt" << s << "relative to" << m_doc->baseUrl();
554  m_cursor->insertImage(m_imageFormat);
555  return 0; // no error
556  }
557 
558  if (!s.isEmpty())
559  m_cursor->insertText(s);
560  if (m_cursor->currentList()) {
561  // The list item will indent the list item's text, so we don't need indentation on the block.
562  QTextBlockFormat bfmt = m_cursor->blockFormat();
563  bfmt.setIndent(0);
564  m_cursor->setBlockFormat(bfmt);
565  }
566  if (lcMD().isEnabled(QtDebugMsg)) {
567  QTextBlockFormat bfmt = m_cursor->blockFormat();
568  QString debugInfo;
569  if (m_cursor->currentList())
570  debugInfo = QLatin1String("in list at depth ") + QString::number(m_cursor->currentList()->format().indent());
572  debugInfo += QLatin1String("in blockquote at depth ") +
575  debugInfo += QLatin1String("in a code block");
576  qCDebug(lcMD) << textType << "in block" << m_blockType << s << qPrintable(debugInfo)
577  << "bindent" << bfmt.indent() << "tindent" << bfmt.textIndent()
578  << "margins" << bfmt.leftMargin() << bfmt.topMargin() << bfmt.bottomMargin() << bfmt.rightMargin();
579  }
580  return 0; // no error
581 }
582 
594 void QTextMarkdownImporter::insertBlock()
595 {
596  QTextCharFormat charFormat;
597  if (!m_spanFormatStack.isEmpty())
598  charFormat = m_spanFormatStack.top();
599  QTextBlockFormat blockFormat;
600  if (!m_listStack.isEmpty() && !m_needsInsertList && m_listItem) {
601  QTextList *list = m_listStack.top();
602  if (list)
603  blockFormat = list->item(list->count() - 1).blockFormat();
604  else
605  qWarning() << "attempted to insert into a list that no longer exists";
606  }
607  if (m_blockQuoteDepth) {
608  blockFormat.setProperty(QTextFormat::BlockQuoteLevel, m_blockQuoteDepth);
609  blockFormat.setLeftMargin(BlockQuoteIndent * m_blockQuoteDepth);
610  blockFormat.setRightMargin(BlockQuoteIndent);
611  }
612  if (m_codeBlock) {
613  blockFormat.setProperty(QTextFormat::BlockCodeLanguage, m_blockCodeLanguage);
614  if (m_blockCodeFence) {
615  blockFormat.setNonBreakableLines(true);
616  blockFormat.setProperty(QTextFormat::BlockCodeFence, QString(QLatin1Char(m_blockCodeFence)));
617  }
618  charFormat.setFont(m_monoFont);
619  } else {
620  blockFormat.setTopMargin(m_paragraphMargin);
621  blockFormat.setBottomMargin(m_paragraphMargin);
622  }
623  if (m_markerType == QTextBlockFormat::MarkerType::NoMarker)
625  else
626  blockFormat.setMarker(m_markerType);
627  if (!m_listStack.isEmpty())
628  blockFormat.setIndent(m_listStack.count());
629  if (m_doc->isEmpty()) {
630  m_cursor->setBlockFormat(blockFormat);
631  m_cursor->setCharFormat(charFormat);
632  } else if (m_listItem) {
633  m_cursor->insertBlock(blockFormat, QTextCharFormat());
634  m_cursor->setCharFormat(charFormat);
635  } else {
636  m_cursor->insertBlock(blockFormat, charFormat);
637  }
638  if (m_needsInsertList) {
639  m_listStack.push(m_cursor->createList(m_listFormat));
640  } else if (!m_listStack.isEmpty() && m_listItem && m_listStack.top()) {
641  m_listStack.top()->add(m_cursor->block());
642  }
643  m_needsInsertList = false;
644  m_needsInsertBlock = false;
645 }
646 
small capitals from c petite p scientific f u
Definition: afcover.h:88
const char msg[]
Definition: arch.cpp:46
const QColor & color() const
Definition: qbrush.h:157
The QByteArray class provides an array of bytes.
Definition: qbytearray.h:85
qsizetype size() const noexcept
Definition: qbytearray.h:470
const char * constData() const noexcept
Definition: qbytearray.h:144
The QChar class provides a 16-bit Unicode character.
Definition: qchar.h:84
static constexpr QChar fromLatin1(char c) noexcept
Definition: qchar.h:492
QString name(NameFormat format=HexRgb) const
Definition: qcolor.cpp:864
The QFontDatabase class provides information about the fonts available in the underlying window syste...
Definition: qfontdatabase.h:55
void setPointSize(int)
Definition: qfont.cpp:1006
int pixelSize() const
Definition: qfont.cpp:1094
QStringList families() const
Definition: qfont.cpp:2284
int pointSize() const
Definition: qfont.cpp:899
void setPixelSize(int)
Definition: qfont.cpp:1069
@ Bold
Definition: qfont.h:103
The QLatin1String class provides a thin wrapper around an US-ASCII/Latin-1 encoded string literal.
Definition: qstring.h:84
bool isEmpty() const noexcept
Definition: qlist.h:418
qsizetype count() const noexcept
Definition: qlist.h:415
void append(parameter_type t)
Definition: qlist.h:469
void clear()
Definition: qlist.h:445
const QBrush & link() const
Definition: qpalette.h:134
The QRegularExpression class provides pattern matching using regular expressions.
T & top()
Definition: qstack.h:55
T pop()
Definition: qstack.h:54
void push(const T &t)
Definition: qstack.h:53
The QString class provides a Unicode character string.
Definition: qstring.h:388
void clear()
Definition: qstring.h:1240
static QString fromUtf8(QByteArrayView utf8)
Definition: qstring.cpp:5632
bool isEmpty() const
Definition: qstring.h:1216
static QString number(int, int base=10)
Definition: qstring.cpp:7538
QByteArray toUtf8() const &
Definition: qstring.h:749
The QTextBlockFormat class provides formatting information for blocks of text in a QTextDocument....
Definition: qtextformat.h:640
qreal bottomMargin() const
Definition: qtextformat.h:671
qreal topMargin() const
Definition: qtextformat.h:666
void setLeftMargin(qreal margin)
Definition: qtextformat.h:674
void setAlignment(Qt::Alignment alignment)
Definition: qtextformat.h:733
void setMarker(MarkerType marker)
Definition: qtextformat.h:719
void setBottomMargin(qreal margin)
Definition: qtextformat.h:669
void setNonBreakableLines(bool b)
Definition: qtextformat.h:706
void setRightMargin(qreal margin)
Definition: qtextformat.h:679
void setIndent(int indent)
Definition: qtextformat.h:736
int indent() const
Definition: qtextformat.h:690
qreal leftMargin() const
Definition: qtextformat.h:676
qreal textIndent() const
Definition: qtextformat.h:686
void setHeadingLevel(int alevel)
Definition: qtextformat.h:693
qreal rightMargin() const
Definition: qtextformat.h:681
void setTopMargin(qreal margin)
Definition: qtextformat.h:664
The QTextCharFormat class provides formatting information for characters in a QTextDocument....
Definition: qtextformat.h:416
int fontWeight() const
Definition: qtextformat.h:483
void setAnchor(bool anchor)
Definition: qtextformat.h:593
void setFontUnderline(bool underline)
Definition: qtextformat.h:506
void setToolTip(const QString &tip)
Definition: qtextformat.h:573
QFont font() const
void setFontFixedPitch(bool fixedPitch)
Definition: qtextformat.h:525
void setFontStrikeOut(bool strikeOut)
Definition: qtextformat.h:515
bool fontItalic() const
Definition: qtextformat.h:487
void setAnchorHref(const QString &value)
Definition: qtextformat.h:598
void setFontItalic(bool italic)
Definition: qtextformat.h:485
void setFontWeight(int weight)
Definition: qtextformat.h:481
void setFont(const QFont &font, FontPropertiesInheritanceBehavior behavior=FontPropertiesAll)
The QTextCursor class offers an API to access and modify QTextDocuments.
Definition: qtextcursor.h:67
QTextBlockFormat blockFormat() const
QTextBlock block() const
bool movePosition(MoveOperation op, MoveMode=MoveAnchor, int n=1)
void insertHtml(const QString &html)
void setBlockFormat(const QTextBlockFormat &format)
void setCharFormat(const QTextCharFormat &format)
void insertText(const QString &text)
QTextList * currentList() const
void insertImage(const QTextImageFormat &format, QTextFrameFormat::Position alignment)
void insertBlock()
QTextList * insertList(const QTextListFormat &format)
QTextTable * insertTable(int rows, int cols, const QTextTableFormat &format)
QTextList * createList(const QTextListFormat &format)
The QTextDocument class holds formatted text.
Definition: qtextdocument.h:93
bool isEmpty() const
QFont defaultFont
the default font used to display the document's text
Definition: qtextdocument.h:99
virtual void clear()
QUrl baseUrl
the base URL used to resolve relative resource URLs within the document.
QString stringProperty(int propertyId) const
void setForeground(const QBrush &brush)
Definition: qtextformat.h:391
@ BlockTrailingHorizontalRulerWidth
Definition: qtextformat.h:165
@ FontSizeAdjustment
Definition: qtextformat.h:189
int intProperty(int propertyId) const
void setProperty(int propertyId, const QVariant &value)
bool hasProperty(int propertyId) const
void clearProperty(int propertyId)
QBrush foreground() const
Definition: qtextformat.h:393
The QTextImageFormat class provides formatting information for images in a QTextDocument....
Definition: qtextformat.h:811
QString name() const
Definition: qtextformat.h:818
void setName(const QString &name)
Definition: qtextformat.h:846
The QTextListFormat class provides formatting information for lists in a QTextDocument....
Definition: qtextformat.h:757
void setStyle(Style style)
Definition: qtextformat.h:798
void setIndent(int indent)
Definition: qtextformat.h:801
void setNumberSuffix(const QString &numberSuffix)
Definition: qtextformat.h:807
int indent() const
Definition: qtextformat.h:780
The QTextList class provides a decorated list of items in a QTextDocument. \inmodule QtGui.
Definition: qtextlist.h:54
void add(const QTextBlock &block)
Definition: qtextlist.cpp:301
QTextListFormat format() const
Definition: qtextlist.h:73
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)
The QTextTableCell class represents the properties of a cell in a QTextTable. \inmodule QtGui.
Definition: qtexttable.h:55
QTextCursor firstCursorPosition() const
Definition: qtexttable.cpp:228
bool isValid() const
Definition: qtexttable.h:72
int columns() const
void appendRows(int count)
Definition: qtexttable.cpp:837
void appendColumns(int count)
Definition: qtexttable.cpp:848
int rows() const
QTextTableCell cellAt(int row, int col) const
Definition: qtexttable.cpp:580
void mergeCells(int row, int col, int numRows, int numCols)
Definition: qtexttable.cpp:996
QString text
[meta data]
union Alignment_ Alignment
auto it unsigned count const
Definition: hb-iter.hh:848
backing_store_ptr info
[4]
Definition: jmemsys.h:161
int md_parse(const MD_CHAR *text, MD_SIZE size, const MD_PARSER *parser, void *userdata)
Definition: md4c.c:6340
#define MD_FLAG_TASKLISTS
Definition: md4c.h:315
#define MD_FLAG_NOHTMLBLOCKS
Definition: md4c.h:310
MD_BLOCKTYPE
Definition: md4c.h:54
@ MD_BLOCK_UL
Definition: md4c.h:63
@ MD_BLOCK_QUOTE
Definition: md4c.h:59
@ MD_BLOCK_TABLE
Definition: md4c.h:97
@ MD_BLOCK_TH
Definition: md4c.h:101
@ MD_BLOCK_OL
Definition: md4c.h:67
@ MD_BLOCK_P
Definition: md4c.h:91
@ MD_BLOCK_HR
Definition: md4c.h:74
@ MD_BLOCK_H
Definition: md4c.h:78
@ MD_BLOCK_CODE
Definition: md4c.h:83
@ MD_BLOCK_LI
Definition: md4c.h:71
@ MD_BLOCK_TR
Definition: md4c.h:100
@ MD_BLOCK_TD
Definition: md4c.h:102
char MD_CHAR
Definition: md4c.h:44
unsigned MD_SIZE
Definition: md4c.h:47
#define MD_FLAG_NOINDENTEDCODEBLOCKS
Definition: md4c.h:309
#define MD_FLAG_PERMISSIVEURLAUTOLINKS
Definition: md4c.h:307
#define MD_FLAG_PERMISSIVEWWWAUTOLINKS
Definition: md4c.h:314
#define MD_FLAG_UNDERLINE
Definition: md4c.h:318
MD_ALIGN
Definition: md4c.h:196
@ MD_ALIGN_RIGHT
Definition: md4c.h:200
@ MD_ALIGN_CENTER
Definition: md4c.h:199
@ MD_ALIGN_LEFT
Definition: md4c.h:198
#define MD_FLAG_PERMISSIVEEMAILAUTOLINKS
Definition: md4c.h:308
MD_SPANTYPE
Definition: md4c.h:108
@ MD_SPAN_DEL
Definition: md4c.h:133
@ MD_SPAN_EM
Definition: md4c.h:110
@ MD_SPAN_U
Definition: md4c.h:148
@ MD_SPAN_STRONG
Definition: md4c.h:113
@ MD_SPAN_CODE
Definition: md4c.h:128
@ MD_SPAN_IMG
Definition: md4c.h:125
@ MD_SPAN_A
Definition: md4c.h:117
#define MD_DIALECT_GITHUB
Definition: md4c.h:333
#define MD_FLAG_PERMISSIVEATXHEADERS
Definition: md4c.h:306
#define MD_FLAG_PERMISSIVEAUTOLINKS
Definition: md4c.h:320
#define MD_DIALECT_COMMONMARK
Definition: md4c.h:332
MD_TEXTTYPE
Definition: md4c.h:152
@ MD_TEXT_HTML
Definition: md4c.h:187
@ MD_TEXT_ENTITY
Definition: md4c.h:176
@ MD_TEXT_NORMAL
Definition: md4c.h:154
@ MD_TEXT_SOFTBR
Definition: md4c.h:164
@ MD_TEXT_BR
Definition: md4c.h:163
@ MD_TEXT_NULLCHAR
Definition: md4c.h:158
@ MD_TEXT_CODE
Definition: md4c.h:182
#define MD_FLAG_NOHTMLSPANS
Definition: md4c.h:311
#define MD_FLAG_COLLAPSEWHITESPACE
Definition: md4c.h:305
#define MD_FLAG_TABLES
Definition: md4c.h:312
#define MD_FLAG_NOHTML
Definition: md4c.h:321
#define MD_FLAG_STRIKETHROUGH
Definition: md4c.h:313
@ AlignRight
Definition: qnamespace.h:171
@ AlignVCenter
Definition: qnamespace.h:180
@ AlignHCenter
Definition: qnamespace.h:173
@ AlignLeft
Definition: qnamespace.h:169
#define QString()
Definition: parse-defines.h:51
#define Q_UNLIKELY(x)
@ text
@ QtDebugMsg
Definition: qlogging.h:61
#define qWarning
Definition: qlogging.h:179
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
GLenum type
Definition: qopengl.h:270
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLfloat n
GLdouble s
[6]
Definition: qopenglext.h:235
@ Space
Definition: qsettings.cpp:1482
#define QStringLiteral(str)
Q_UNUSED(salary)
[21]
QUrl url("http://www.example.com/List of holidays.xml")
[0]
QString title
[35]
QStringList list
[0]
MD_SIZE size
Definition: md4c.h:233
const MD_CHAR * text
Definition: md4c.h:232
MD_CHAR fence_char
Definition: md4c.h:268
MD_ATTRIBUTE info
Definition: md4c.h:266
MD_ATTRIBUTE lang
Definition: md4c.h:267
unsigned level
Definition: md4c.h:261
MD_CHAR task_mark
Definition: md4c.h:255
MD_CHAR mark_delimiter
Definition: md4c.h:249
MD_ALIGN align
Definition: md4c.h:280
MD_CHAR mark
Definition: md4c.h:242
MD_ATTRIBUTE href
Definition: md4c.h:285
MD_ATTRIBUTE title
Definition: md4c.h:286
MD_ATTRIBUTE title
Definition: md4c.h:292
MD_ATTRIBUTE src
Definition: md4c.h:291
The QLatin1Char class provides an 8-bit ASCII/Latin-1 character.
Definition: qchar.h:53
bool contains(const AT &t) const noexcept
Definition: qlist.h:78