| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- /* eslint-disable eqeqeq */
- /* eslint-disable no-unused-vars */
- /* eslint-disable @typescript-eslint/no-unused-vars */
- /* eslint-disable prefer-const */
- /* eslint-disable camelcase */
- /* Process inline math */
- /*
- Like markdown-it-simplemath, this is a stripped down, simplified version of:
- https://github.com/runarberg/markdown-it-math
- It differs in that it takes (a subset of) LaTeX as input and relies on KaTeX
- for rendering output.
- */
- /* jslint node: true */
- 'use strict'
- const katex = require('katex')
- // Test if potential opening or closing delimieter
- // Assumes that there is a "$" at state.src[pos]
- function isValidDelim(state, pos) {
- let prevChar
- let nextChar
- const max = state.posMax
- let can_open = true
- let can_close = true
- prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1
- nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1
- // Check non-whitespace conditions for opening and closing, and
- // check that closing delimeter isn't followed by a number
- if (
- prevChar === 0x20 /* " " */ ||
- prevChar === 0x09 /* \t */ ||
- (nextChar >= 0x30 /* "0" */ && nextChar <= 0x39) /* "9" */
- ) {
- can_close = false
- }
- if (nextChar === 0x20 /* " " */ || nextChar === 0x09 /* \t */) {
- can_open = false
- }
- return {
- can_open,
- can_close
- }
- }
- function math_inline(state, silent) {
- let start, match, token, res, pos, esc_count
- if (state.src[state.pos] !== '$') {
- return false
- }
- res = isValidDelim(state, state.pos)
- if (!res.can_open) {
- if (!silent) {
- state.pending += '$'
- }
- state.pos += 1
- return true
- }
- // First check for and bypass all properly escaped delimieters
- // This loop will assume that the first leading backtick can not
- // be the first character in state.src, which is known since
- // we have found an opening delimieter already.
- start = state.pos + 1
- match = start
- while ((match = state.src.indexOf('$', match)) !== -1) {
- // Found potential $, look for escapes, pos will point to
- // first non escape when complete
- pos = match - 1
- while (state.src[pos] === '\\') {
- pos -= 1
- }
- // Even number of escapes, potential closing delimiter found
- if ((match - pos) % 2 == 1) {
- break
- }
- match += 1
- }
- // No closing delimter found. Consume $ and continue.
- if (match === -1) {
- if (!silent) {
- state.pending += '$'
- }
- state.pos = start
- return true
- }
- // Check if we have empty content, ie: $$. Do not parse.
- if (match - start === 0) {
- if (!silent) {
- state.pending += '$$'
- }
- state.pos = start + 1
- return true
- }
- // Check for valid closing delimiter
- res = isValidDelim(state, match)
- if (!res.can_close) {
- if (!silent) {
- state.pending += '$'
- }
- state.pos = start
- return true
- }
- if (!silent) {
- token = state.push('math_inline', 'math', 0)
- token.markup = '$'
- token.content = state.src.slice(start, match)
- }
- state.pos = match + 1
- return true
- }
- function math_block(state, start, end, silent) {
- let firstLine
- let lastLine
- let next
- let lastPos
- let found = false
- let token
- let pos = state.bMarks[start] + state.tShift[start]
- let max = state.eMarks[start]
- if (pos + 2 > max) {
- return false
- }
- if (state.src.slice(pos, pos + 2) !== '$$') {
- return false
- }
- pos += 2
- firstLine = state.src.slice(pos, max)
- if (silent) {
- return true
- }
- if (firstLine.trim().slice(-2) === '$$') {
- // Single line expression
- firstLine = firstLine.trim().slice(0, -2)
- found = true
- }
- for (next = start; !found;) {
- next++
- if (next >= end) {
- break
- }
- pos = state.bMarks[next] + state.tShift[next]
- max = state.eMarks[next]
- if (pos < max && state.tShift[next] < state.blkIndent) {
- // non-empty line with negative indent should stop the list:
- break
- }
- if (
- state.src
- .slice(pos, max)
- .trim()
- .slice(-2) === '$$'
- ) {
- lastPos = state.src.slice(0, max).lastIndexOf('$$')
- lastLine = state.src.slice(pos, lastPos)
- found = true
- }
- }
- state.line = next + 1
- token = state.push('math_block', 'math', 0)
- token.block = true
- token.content =
- (firstLine && firstLine.trim() ? firstLine + '\n' : '') +
- state.getLines(start + 1, next, state.tShift[start], true) +
- (lastLine && lastLine.trim() ? lastLine : '')
- token.map = [start, state.line]
- token.markup = '$$'
- return true
- }
- module.exports = function math_plugin(md, options) {
- // Default options
- options = options || {}
- // set KaTeX as the renderer for markdown-it-simplemath
- const katexInline = function (latex) {
- options.displayMode = false
- try {
- return katex.renderToString(latex, options)
- } catch (error) {
- if (options.throwOnError) {
- // eslint-disable-next-line no-console
- console.log(error)
- }
- return latex
- }
- }
- const inlineRenderer = function (tokens, idx) {
- return katexInline(tokens[idx].content)
- }
- const katexBlock = function (latex) {
- options.displayMode = true
- try {
- return '<p>' + katex.renderToString(latex, options) + '</p>'
- } catch (error) {
- if (options.throwOnError) {
- // eslint-disable-next-line no-console
- console.log(error)
- }
- return latex
- }
- }
- const blockRenderer = function (tokens, idx) {
- return katexBlock(tokens[idx].content) + '\n'
- }
- md.inline.ruler.after('escape', 'math_inline', math_inline)
- md.block.ruler.after('blockquote', 'math_block', math_block, {
- alt: ['paragraph', 'reference', 'blockquote', 'list']
- })
- md.renderer.rules.math_inline = inlineRenderer
- md.renderer.rules.math_block = blockRenderer
- }
|