UNPKG

7.05 kBJavaScriptView Raw
1/* Process inline math */
2/*
3Like markdown-it-simplemath, this is a stripped down, simplified version of:
4https://github.com/runarberg/markdown-it-math
5
6It differs in that it takes (a subset of) LaTeX as input and relies on KaTeX
7for rendering output.
8*/
9
10'use strict';
11
12var katex = require('katex');
13
14
15function scanDelims(state, start, delimLength) {
16 var pos = start, lastChar, nextChar, count, can_open, can_close,
17 isLastWhiteSpace, isLastPunctChar,
18 isNextWhiteSpace, isNextPunctChar,
19 left_flanking = true,
20 right_flanking = true,
21 max = state.posMax,
22 isWhiteSpace = state.md.utils.isWhiteSpace,
23 isPunctChar = state.md.utils.isPunctChar,
24 isMdAsciiPunct = state.md.utils.isMdAsciiPunct;
25
26 // treat beginning of the line as a whitespace
27 lastChar = start > 0 ? state.src.charCodeAt(start - 1) : 0x20;
28
29 if (pos >= max) {
30 can_open = false;
31 }
32
33 pos += delimLength;
34
35 count = pos - start;
36
37 // treat end of the line as a whitespace
38 nextChar = pos < max ? state.src.charCodeAt(pos) : 0x20;
39
40 isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar));
41 isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar));
42
43 isLastWhiteSpace = isWhiteSpace(lastChar);
44 isNextWhiteSpace = isWhiteSpace(nextChar);
45
46 if (isNextWhiteSpace) {
47 left_flanking = false;
48 } else if (isNextPunctChar) {
49 if (!(isLastWhiteSpace || isLastPunctChar)) {
50 left_flanking = false;
51 }
52 }
53
54 if (isLastWhiteSpace) {
55 right_flanking = false;
56 } else if (isLastPunctChar) {
57 if (!(isNextWhiteSpace || isNextPunctChar)) {
58 right_flanking = false;
59 }
60 }
61
62 can_open = left_flanking;
63 can_close = right_flanking;
64
65 return {
66 can_open: can_open,
67 can_close: can_close,
68 delims: count
69 };
70}
71
72
73function makeMath_inline(open, close) {
74 return function math_inline(state, silent) {
75 var startCount,
76 found,
77 res,
78 token,
79 closeDelim,
80 max = state.posMax,
81 start = state.pos,
82 openDelim = state.src.slice(start, start + open.length);
83
84 if (openDelim !== open) { return false; }
85 if (silent) { return false; } // Don’t run any pairs in validation mode
86
87 res = scanDelims(state, start, openDelim.length);
88 startCount = res.delims;
89
90 if (!res.can_open) {
91 state.pos += startCount;
92 // Earlier we checked !silent, but this implementation does not need it
93 state.pending += state.src.slice(start, state.pos);
94 return true;
95 }
96
97 state.pos = start + open.length;
98
99 while (state.pos < max) {
100 closeDelim = state.src.slice(state.pos, state.pos + close.length);
101 if (closeDelim === close) {
102 res = scanDelims(state, state.pos, close.length);
103 if (res.can_close) {
104 found = true;
105 break;
106 }
107 }
108
109 state.md.inline.skipToken(state);
110 }
111
112 if (!found) {
113 // Parser failed to find ending tag, so it is not a valid math
114 state.pos = start;
115 return false;
116 }
117
118 // Found!
119 state.posMax = state.pos;
120 state.pos = start + close.length;
121
122 // Earlier we checked !silent, but this implementation does not need it
123 token = state.push('math_inline', 'math', 0);
124 token.content = state.src.slice(state.pos, state.posMax);
125 token.markup = open;
126
127 state.pos = state.posMax + close.length;
128 state.posMax = max;
129
130 return true;
131 };
132}
133
134function makeMath_block(open, close) {
135 return function math_block(state, startLine, endLine, silent) {
136 var openDelim, len, params, nextLine, token, firstLine, lastLine, lastLinePos,
137 haveEndMarker = false,
138 pos = state.bMarks[startLine] + state.tShift[startLine],
139 max = state.eMarks[startLine];
140
141 if (pos + open.length > max) { return false; }
142
143 openDelim = state.src.slice(pos, pos + open.length);
144
145 if (openDelim !== open) { return false; }
146
147 pos += open.length;
148 firstLine = state.src.slice(pos, max);
149
150 // Since start is found, we can report success here in validation mode
151 if (silent) { return true; }
152
153 if (firstLine.trim().slice(-close.length) === close) {
154 // Single line expression
155 firstLine = firstLine.trim().slice(0, -close.length);
156 haveEndMarker = true;
157 }
158
159 // search end of block
160 nextLine = startLine;
161
162 for (;;) {
163 if (haveEndMarker) { break; }
164
165 nextLine++;
166
167 if (nextLine >= endLine) {
168 // unclosed block should be autoclosed by end of document.
169 // also block seems to be autoclosed by end of parent
170 break;
171 }
172
173 pos = state.bMarks[nextLine] + state.tShift[nextLine];
174 max = state.eMarks[nextLine];
175
176 if (pos < max && state.tShift[nextLine] < state.blkIndent) {
177 // non-empty line with negative indent should stop the list:
178 break;
179 }
180
181 if (state.src.slice(pos, max).trim().slice(-close.length) !== close) {
182 continue;
183 }
184
185 if (state.tShift[nextLine] - state.blkIndent >= 4) {
186 // closing block math should be indented less then 4 spaces
187 continue;
188 }
189
190 lastLinePos = state.src.slice(0, max).lastIndexOf(close);
191 lastLine = state.src.slice(pos, lastLinePos);
192
193 pos += lastLine.length + close.length;
194
195 // make sure tail has spaces only
196 pos = state.skipSpaces(pos);
197
198 if (pos < max) { continue; }
199
200 // found!
201 haveEndMarker = true;
202 }
203
204 // If math block has heading spaces, they should be removed from its inner block
205 len = state.tShift[startLine];
206
207 state.line = nextLine + (haveEndMarker ? 1 : 0);
208
209 token = state.push('math_block', 'math', 0);
210 token.block = true;
211 token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') +
212 state.getLines(startLine + 1, nextLine, len, true) +
213 (lastLine && lastLine.trim() ? lastLine : '');
214 token.info = params;
215 token.map = [ startLine, state.line ];
216 token.markup = open;
217
218 return true;
219 };
220}
221
222
223module.exports = function math_plugin(md) {
224 // Default options
225
226 var inlineOpen = '$',
227 inlineClose = '$',
228 blockOpen = '$$',
229 blockClose = '$$';
230 // set KaTeX as the renderer for markdown-it-simplemath
231 var katexInline = function(latex){
232 return katex.renderToString(latex, {"displayMode" : false});
233 };
234
235 var inlineRenderer = function(tokens, idx){
236 return katexInline(tokens[idx].content);
237 };
238
239 var katexBlock = function(latex){
240 return katex.renderToString(latex, {"displayMode" : true});
241 }
242
243 var blockRenderer = function(tokens, idx){
244 return katexBlock(tokens[idx].content) + '\n';
245 }
246
247 var math_inline = makeMath_inline(inlineOpen, inlineClose);
248 var math_block = makeMath_block(blockOpen, blockClose);
249
250 md.inline.ruler.before('escape', 'math_inline', math_inline);
251 md.block.ruler.after('blockquote', 'math_block', math_block, {
252 alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
253 });
254 md.renderer.rules.math_inline = inlineRenderer;
255 md.renderer.rules.math_block = blockRenderer;
256};