Dekko
MarkdownHighlighter.cpp
1 #include "MarkdownHighlighter.h"
2 #include <QFont>
3 
4 MarkdownHighlighter::MarkdownHighlighter(QTextDocument *document, FormattingOptions *options) : QSyntaxHighlighter(document),
5  m_tokenizer(new MarkdownTokenizer),
6  m_inBlockquote(false),
7  m_options(options)
8 {
9  connect(this, &MarkdownHighlighter::highlightBlockAtPosition, this, &MarkdownHighlighter::onHighlightBlockAtPosition);
10 
11  QFont font;
12  font.setFamily(m_options->get_fontFamily());
13  font.setWeight(m_options->get_fontWeight());
14  font.setItalic(false);
15  font.setPointSizeF(12.0);
16  font.setStyleStrategy(QFont::PreferAntialias);
17  defaultFormat.setFont(font);
18  defaultFormat.setForeground(QBrush(m_options->get_textColor()));
19 
20  setupTokenColors();
21 
22  for (int i = 0; i < MarkdownToken::Last; i++)
23  {
24  applyStyleToMarkup[i] = false;
25  emphasizeToken[i] = false;
26  strongToken[i] = false;
27  strongMarkup[i] = false;
28  strikethroughToken[i] = false;
29  fontSizeIncrease[i] = 0;
30  }
31 
32  for (int i = MarkdownToken::AtxHeading1; i <= MarkdownToken::AtxHeading6; i++)
33  {
34  applyStyleToMarkup[i] = true;
35  }
36 
37  applyStyleToMarkup[MarkdownToken::Emphasis] = true;
38  applyStyleToMarkup[MarkdownToken::Strong] = true;
39  applyStyleToMarkup[MarkdownToken::AtxHeading1] = true;
40  applyStyleToMarkup[MarkdownToken::AtxHeading2] = true;
41  applyStyleToMarkup[MarkdownToken::AtxHeading3] = true;
42  applyStyleToMarkup[MarkdownToken::AtxHeading4] = true;
43  applyStyleToMarkup[MarkdownToken::AtxHeading5] = true;
44  applyStyleToMarkup[MarkdownToken::AtxHeading6] = true;
45 
46  emphasizeToken[MarkdownToken::Emphasis] = true;
47  emphasizeToken[MarkdownToken::Blockquote] = false;
48  strongToken[MarkdownToken::Strong] = true;
49  strongToken[MarkdownToken::Mention] = true;
50  strongToken[MarkdownToken::AtxHeading1] = true;
51  strongToken[MarkdownToken::AtxHeading2] = true;
52  strongToken[MarkdownToken::AtxHeading3] = true;
53  strongToken[MarkdownToken::AtxHeading4] = true;
54  strongToken[MarkdownToken::AtxHeading5] = true;
55  strongToken[MarkdownToken::AtxHeading6] = true;
56  strongToken[MarkdownToken::SetextHead1Line1] = true;
57  strongToken[MarkdownToken::SetextHead2Line1] = true;
58  strongToken[MarkdownToken::SetextHead1Line2] = true;
59  strongToken[MarkdownToken::SetextHead2Line2] = true;
60  strongToken[MarkdownToken::TableHeader] = true;
61  strikethroughToken[MarkdownToken::Strikethrough] = true;
62 
63  setupHeadingFontSize(m_options->get_enableLargeHeadingSizes());
64 
65  strongMarkup[MarkdownToken::NumberedList] = true;
66  strongMarkup[MarkdownToken::Blockquote] = true;
67  strongMarkup[MarkdownToken::BulletList] = true;
68 }
69 
70 void MarkdownHighlighter::highlightBlock(const QString &text)
71 {
72  MarkdownTokenizer::TokenState lastState = (MarkdownTokenizer::TokenState)currentBlockState();
73 
74  setFormat(0, text.length(), defaultFormat);
75 
76  if (!m_tokenizer.isNull())
77  {
78  m_tokenizer.data()->clear();
79 
80  QTextBlock block = currentBlock();
81  MarkdownTokenizer::TokenState nextState = MarkdownTokenizer::Unknown;
82  MarkdownTokenizer::TokenState previousState = (MarkdownTokenizer::TokenState)previousBlockState();
83 
84  if (block.next().isValid())
85  {
86  nextState = (MarkdownTokenizer::TokenState)block.next().userState();
87  }
88 
89  m_tokenizer->tokenize(text, lastState, previousState, nextState);
90  setCurrentBlockState(m_tokenizer->state());
91 
92  if (m_tokenizer->state() == MarkdownTokenizer::Blockquote)
93  {
94  m_inBlockquote = true;
95  }
96  else
97  {
98  m_inBlockquote = false;
99  }
100 
101  for (const auto token : m_tokenizer->tokens()) {
102  switch (token.type())
103  {
104  case MarkdownToken::Unknown:
105  qWarning("Highlighter found unknown token type in text block.");
106  break;
107  default:
108  applyFormattingForToken(token);
109  break;
110  }
111  }
112 
113  if (m_tokenizer->shouldBackTrack())
114  {
115  QTextBlock previous = currentBlock().previous();
116  emit highlightBlockAtPosition(previous.position());
117  }
118  }
119 }
120 
121 void MarkdownHighlighter::onHighlightBlockAtPosition(int pos)
122 {
123  QTextBlock block = document()->findBlock(pos);
124  rehighlightBlock(block);
125 }
126 
127 void MarkdownHighlighter::setupTokenColors()
128 {
129  for (int i = 0; i < MarkdownToken::Last; i++)
130  {
131  colorForToken[i] = m_options->get_textColor();
132  }
133 
134  colorForToken[MarkdownToken::HtmlTag] = m_options->get_markupColor();
135  colorForToken[MarkdownToken::HtmlEntity] = m_options->get_markupColor();
136  colorForToken[MarkdownToken::AutomaticLink] = m_options->get_linkColor();
137  colorForToken[MarkdownToken::InlineLink] = m_options->get_linkColor();
138  colorForToken[MarkdownToken::ReferenceLink] = m_options->get_linkColor();
139  colorForToken[MarkdownToken::ReferenceDefinition] = m_options->get_linkColor();
140  colorForToken[MarkdownToken::Image] = m_options->get_linkColor();
141  colorForToken[MarkdownToken::Mention] = m_options->get_linkColor();
142  colorForToken[MarkdownToken::HtmlComment] = m_options->get_markupColor();
143  colorForToken[MarkdownToken::HorizontalRule] = m_options->get_markupColor();
144  colorForToken[MarkdownToken::GFMCodeFence] = m_options->get_markupColor();
145  colorForToken[MarkdownToken::CodeFenceEnd] = m_options->get_markupColor();
146  colorForToken[MarkdownToken::SetextHead1Line2] = m_options->get_markupColor();
147  colorForToken[MarkdownToken::SetextHead2Line2] = m_options->get_markupColor();
148  colorForToken[MarkdownToken::TableDiv] = m_options->get_markupColor();
149  colorForToken[MarkdownToken::TablePipe] = m_options->get_markupColor();
150 }
151 
152 void MarkdownHighlighter::setupHeadingFontSize(bool useLargeHeadings)
153 {
154  if (useLargeHeadings)
155  {
156  fontSizeIncrease[MarkdownToken::SetextHead1Line1] = 6;
157  fontSizeIncrease[MarkdownToken::SetextHead2Line1] = 5;
158  fontSizeIncrease[MarkdownToken::SetextHead1Line2] = 6;
159  fontSizeIncrease[MarkdownToken::SetextHead2Line2] = 5;
160  fontSizeIncrease[MarkdownToken::AtxHeading1] = 6;
161  fontSizeIncrease[MarkdownToken::AtxHeading2] = 5;
162  fontSizeIncrease[MarkdownToken::AtxHeading3] = 4;
163  fontSizeIncrease[MarkdownToken::AtxHeading4] = 3;
164  fontSizeIncrease[MarkdownToken::AtxHeading5] = 2;
165  fontSizeIncrease[MarkdownToken::AtxHeading6] = 1;
166  }
167  else
168  {
169  fontSizeIncrease[MarkdownToken::SetextHead1Line1] = 0;
170  fontSizeIncrease[MarkdownToken::SetextHead2Line1] = 0;
171  fontSizeIncrease[MarkdownToken::SetextHead1Line2] = 0;
172  fontSizeIncrease[MarkdownToken::SetextHead2Line2] = 0;
173  fontSizeIncrease[MarkdownToken::AtxHeading1] = 0;
174  fontSizeIncrease[MarkdownToken::AtxHeading2] = 0;
175  fontSizeIncrease[MarkdownToken::AtxHeading3] = 0;
176  fontSizeIncrease[MarkdownToken::AtxHeading4] = 0;
177  fontSizeIncrease[MarkdownToken::AtxHeading5] = 0;
178  fontSizeIncrease[MarkdownToken::AtxHeading6] = 0;
179  }
180 }
181 
182 bool MarkdownHighlighter::isHeadingBlockState(MarkdownTokenizer::TokenState state) const
183 {
184  switch (state) {
185  case MarkdownTokenizer::AtxHeading1:
186  case MarkdownTokenizer::AtxHeading2:
187  case MarkdownTokenizer::AtxHeading3:
188  case MarkdownTokenizer::AtxHeading4:
189  case MarkdownTokenizer::AtxHeading5:
190  case MarkdownTokenizer::AtxHeading6:
191  case MarkdownTokenizer::SetextHead1Line1:
192  case MarkdownTokenizer::SetextHead2Line1:
193  return true;
194  default:
195  return false;
196  }
197 }
198 
199 void MarkdownHighlighter::applyFormattingForToken(const MarkdownToken &token)
200 {
201  if (token.type() != MarkdownToken::Unknown) {
202  MarkdownToken::TokenType tokenType = token.type();
203  QTextCharFormat fmt = this->format(token.position());
204 
205  QColor tokenColor = colorForToken[tokenType];
206 
207  fmt.setForeground(QBrush(tokenColor));
208 
209  if (strongToken[tokenType])
210  {
211  fmt.setFontWeight(QFont::Bold);
212  }
213 
214  if (emphasizeToken[tokenType])
215  {
216  if (m_options->get_useUnderlineForEmp() && (tokenType != MarkdownToken::Blockquote))
217  {
218  fmt.setFontUnderline(true);
219  }
220  else
221  {
222  fmt.setFontItalic(true);
223  }
224  }
225 
226  if (strikethroughToken[tokenType])
227  {
228  fmt.setFontStrikeOut(true);
229  }
230 
231  fmt.setFontPointSize(fmt.fontPointSize()
232  + (qreal) fontSizeIncrease[tokenType]);
233 
234  QTextCharFormat markupFormat;
235 
236  if
237  (
238  applyStyleToMarkup[tokenType] &&
239  (!emphasizeToken[tokenType] || !m_options->get_useUnderlineForEmp())
240  )
241  {
242  markupFormat = fmt;
243  }
244  else
245  {
246  markupFormat = this->format(token.position());
247  }
248 
249  QColor adjustedMarkupColor = m_options->get_markupColor().lighter();
250 
251  markupFormat.setForeground(QBrush(adjustedMarkupColor));
252 
253  if (strongMarkup[tokenType])
254  {
255  markupFormat.setFontWeight(QFont::Bold);
256  }
257 
258  if (token.openingLength() > 0)
259  {
260  if
261  (
262  (MarkdownToken::Blockquote == tokenType)
263  )
264  {
265  markupFormat.setBackground(QBrush(adjustedMarkupColor.lighter()));
266  QString text = currentBlock().text();
267 
268  for (int i = token.position(); i < token.openingLength(); i++)
269  {
270  if (!text[i].isSpace())
271  {
272  setFormat
273  (
274  i,
275  1,
276  markupFormat
277  );
278  }
279  }
280  }
281  else
282  {
283  setFormat
284  (
285  token.position(),
286  token.openingLength(),
287  markupFormat
288  );
289  }
290  }
291 
292  setFormat
293  (
294  token.position() + token.openingLength(),
295  token.length()
296  - token.openingLength()
297  - token.closingLength(),
298  fmt
299  );
300 
301  if (token.closingLength() > 0)
302  {
303  setFormat
304  (
305  token.position() + token.length()
306  - token.closingLength(),
307  token.closingLength(),
308  markupFormat
309  );
310  }
311  }
312  else
313  {
314  qWarning("Highlighter::applyFormattingForToken() was passed in a "
315  "token of unknown type.");
316  }
317 }
MarkdownTokenizer
Definition: MarkdownTokenizer.h:10
FormattingOptions
Definition: FormattingOptions.h:9
MarkdownToken
Definition: MarkdownToken.h:4