latex.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. const config = require('../../../config');
  2. // Test if potential opening or closing delimieter
  3. // Assumes that there is a "$" at state.src[pos]
  4. function isValidDelim(state, pos) {
  5. var prevChar, nextChar,
  6. max = state.posMax,
  7. can_open = true,
  8. can_close = true;
  9. prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1;
  10. nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1;
  11. // Check non-whitespace conditions for opening and closing, and
  12. // check that closing delimeter isn't followed by a number
  13. if (prevChar === 0x20/* " " */ || prevChar === 0x09/* \t */ ||
  14. (nextChar >= 0x30/* "0" */ && nextChar <= 0x39/* "9" */)) {
  15. can_close = false;
  16. }
  17. if (nextChar === 0x20/* " " */ || nextChar === 0x09/* \t */) {
  18. can_open = false;
  19. }
  20. return {
  21. can_open: can_open,
  22. can_close: can_close
  23. };
  24. }
  25. function math_inline(state, silent) {
  26. var start, match, token, res, pos, esc_count;
  27. if (state.src[state.pos] !== "$") { return false; }
  28. res = isValidDelim(state, state.pos);
  29. if (!res.can_open) {
  30. if (!silent) { state.pending += "$"; }
  31. state.pos += 1;
  32. return true;
  33. }
  34. // First check for and bypass all properly escaped delimieters
  35. // This loop will assume that the first leading backtick can not
  36. // be the first character in state.src, which is known since
  37. // we have found an opening delimieter already.
  38. start = state.pos + 1;
  39. match = start;
  40. while ( (match = state.src.indexOf("$", match)) !== -1) {
  41. // Found potential $, look for escapes, pos will point to
  42. // first non escape when complete
  43. pos = match - 1;
  44. while (state.src[pos] === "\\") { pos -= 1; }
  45. // Even number of escapes, potential closing delimiter found
  46. if ( ((match - pos) % 2) == 1 ) { break; }
  47. match += 1;
  48. }
  49. // No closing delimter found. Consume $ and continue.
  50. if (match === -1) {
  51. if (!silent) { state.pending += "$"; }
  52. state.pos = start;
  53. return true;
  54. }
  55. // Check if we have empty content, ie: $$. Do not parse.
  56. if (match - start === 0) {
  57. if (!silent) { state.pending += "$$"; }
  58. state.pos = start + 1;
  59. return true;
  60. }
  61. // Check for valid closing delimiter
  62. res = isValidDelim(state, match);
  63. if (!res.can_close) {
  64. if (!silent) { state.pending += "$"; }
  65. state.pos = start;
  66. return true;
  67. }
  68. if (!silent) {
  69. token = state.push('math_inline', 'math', 0);
  70. token.markup = "$";
  71. token.content = state.src.slice(start, match);
  72. }
  73. state.pos = match + 1;
  74. return true;
  75. }
  76. function math_block(state, start, end, silent){
  77. var firstLine, lastLine, next, lastPos, found = false, token,
  78. pos = state.bMarks[start] + state.tShift[start],
  79. max = state.eMarks[start]
  80. if(pos + 2 > max){ return false; }
  81. if(state.src.slice(pos,pos+2)!=='$$'){ return false; }
  82. pos += 2;
  83. firstLine = state.src.slice(pos,max);
  84. if(silent){ return true; }
  85. if(firstLine.trim().slice(-2)==='$$'){
  86. // Single line expression
  87. firstLine = firstLine.trim().slice(0, -2);
  88. found = true;
  89. }
  90. for(next = start; !found; ){
  91. next++;
  92. if(next >= end){ break; }
  93. pos = state.bMarks[next]+state.tShift[next];
  94. max = state.eMarks[next];
  95. if(pos < max && state.tShift[next] < state.blkIndent){
  96. // non-empty line with negative indent should stop the list:
  97. break;
  98. }
  99. if(state.src.slice(pos,max).trim().slice(-2)==='$$'){
  100. lastPos = state.src.slice(0,max).lastIndexOf('$$');
  101. lastLine = state.src.slice(pos,lastPos);
  102. found = true;
  103. }
  104. }
  105. state.line = next + 1;
  106. token = state.push('math_block', 'math', 0);
  107. token.block = true;
  108. token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '')
  109. + state.getLines(start + 1, next, state.tShift[start], true)
  110. + (lastLine && lastLine.trim() ? lastLine : '');
  111. token.map = [ start, state.line ];
  112. token.markup = '$$';
  113. return true;
  114. }
  115. module.exports = md => {
  116. var inlineRenderer = function(tokens, idx){
  117. return `<latex value="${encodeURIComponent(tokens[idx].content).replace(/'/g,'%27')}" type="line"></latex>`;
  118. };
  119. var blockRenderer = function(tokens, idx){
  120. return `<latex value="${encodeURIComponent(tokens[idx].content).replace(/'/g, '%27')}" type="block"></latex>`;
  121. };
  122. md.inline.ruler.after('escape', 'math_inline', math_inline);
  123. md.block.ruler.after('blockquote', 'math_block', math_block, {
  124. alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
  125. });
  126. md.renderer.rules.math_inline = inlineRenderer;
  127. md.renderer.rules.math_block = blockRenderer;
  128. };