katex_render.html 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <script type="text/javascript">
  2. /* global katex */
  3. var findEndOfMath = function(delimiter, text, startIndex) {
  4. // Adapted from
  5. // https://github.com/Khan/perseus/blob/master/src/perseus-markdown.jsx
  6. var index = startIndex;
  7. var braceLevel = 0;
  8. var delimLength = delimiter.length;
  9. while (index < text.length) {
  10. var character = text[index];
  11. if (braceLevel <= 0 &&
  12. text.slice(index, index + delimLength) === delimiter) {
  13. return index;
  14. } else if (character === "\\") {
  15. index++;
  16. } else if (character === "{") {
  17. braceLevel++;
  18. } else if (character === "}") {
  19. braceLevel--;
  20. }
  21. index++;
  22. }
  23. return -1;
  24. };
  25. var splitAtDelimiters = function(startData, leftDelim, rightDelim, display) {
  26. var finalData = [];
  27. for (var i = 0; i < startData.length; i++) {
  28. if (startData[i].type === "text") {
  29. var text = startData[i].data;
  30. var lookingForLeft = true;
  31. var currIndex = 0;
  32. var nextIndex;
  33. nextIndex = text.indexOf(leftDelim);
  34. if (nextIndex !== -1) {
  35. currIndex = nextIndex;
  36. finalData.push({
  37. type: "text",
  38. data: text.slice(0, currIndex)
  39. });
  40. lookingForLeft = false;
  41. }
  42. while (true) {
  43. if (lookingForLeft) {
  44. nextIndex = text.indexOf(leftDelim, currIndex);
  45. if (nextIndex === -1) {
  46. break;
  47. }
  48. finalData.push({
  49. type: "text",
  50. data: text.slice(currIndex, nextIndex)
  51. });
  52. currIndex = nextIndex;
  53. } else {
  54. nextIndex = findEndOfMath(
  55. rightDelim,
  56. text,
  57. currIndex + leftDelim.length);
  58. if (nextIndex === -1) {
  59. break;
  60. }
  61. finalData.push({
  62. type: "math",
  63. data: text.slice(
  64. currIndex + leftDelim.length,
  65. nextIndex),
  66. rawData: text.slice(
  67. currIndex,
  68. nextIndex + rightDelim.length),
  69. display: display
  70. });
  71. currIndex = nextIndex + rightDelim.length;
  72. }
  73. lookingForLeft = !lookingForLeft;
  74. }
  75. finalData.push({
  76. type: "text",
  77. data: text.slice(currIndex)
  78. });
  79. } else {
  80. finalData.push(startData[i]);
  81. }
  82. }
  83. return finalData;
  84. };
  85. var splitWithDelimiters = function(text, delimiters) {
  86. var data = [{type: "text", data: text}];
  87. for (var i = 0; i < delimiters.length; i++) {
  88. var delimiter = delimiters[i];
  89. data = splitAtDelimiters(
  90. data, delimiter.left, delimiter.right,
  91. delimiter.display || false);
  92. }
  93. return data;
  94. };
  95. var renderMathInText = function(text, delimiters) {
  96. var data = splitWithDelimiters(text, delimiters);
  97. var fragment = document.createDocumentFragment();
  98. for (var i = 0; i < data.length; i++) {
  99. if (data[i].type === "text") {
  100. fragment.appendChild(document.createTextNode(data[i].data));
  101. } else {
  102. var span = document.createElement("span");
  103. var math = data[i].data;
  104. try {
  105. katex.render(math, span, {
  106. displayMode: data[i].display
  107. });
  108. } catch (e) {
  109. if (!(e instanceof katex.ParseError)) {
  110. throw e;
  111. }
  112. console.error(
  113. "KaTeX auto-render: Failed to parse `" + data[i].data +
  114. "` with ",
  115. e
  116. );
  117. fragment.appendChild(document.createTextNode(data[i].rawData));
  118. continue;
  119. }
  120. fragment.appendChild(span);
  121. }
  122. }
  123. return fragment;
  124. };
  125. var renderElem = function(elem, delimiters, ignoredTags) {
  126. for (var i = 0; i < elem.childNodes.length; i++) {
  127. var childNode = elem.childNodes[i];
  128. if (childNode.nodeType === 3) {
  129. // Text node
  130. var frag = renderMathInText(childNode.textContent, delimiters);
  131. i += frag.childNodes.length - 1;
  132. elem.replaceChild(frag, childNode);
  133. } else if (childNode.nodeType === 1) {
  134. // Element node
  135. var shouldRender = ignoredTags.indexOf(
  136. childNode.nodeName.toLowerCase()) === -1;
  137. if (shouldRender) {
  138. renderElem(childNode, delimiters, ignoredTags);
  139. }
  140. }
  141. // Otherwise, it's something else, and ignore it.
  142. }
  143. };
  144. var defaultOptions = {
  145. delimiters: [
  146. {left: "$$", right: "$$", display: true},
  147. {left: "\\[", right: "\\]", display: true},
  148. {left: "\\(", right: "\\)", display: false}
  149. // LaTeX uses this, but it ruins the display of normal `$` in text:
  150. // {left: "$", right: "$", display: false}
  151. ],
  152. ignoredTags: [
  153. "script", "noscript", "style", "textarea", "pre", "code"
  154. ]
  155. };
  156. var extend = function(obj) {
  157. // Adapted from underscore.js' `_.extend`. See LICENSE.txt for license.
  158. var source, prop;
  159. for (var i = 1, length = arguments.length; i < length; i++) {
  160. source = arguments[i];
  161. for (prop in source) {
  162. if (Object.prototype.hasOwnProperty.call(source, prop)) {
  163. obj[prop] = source[prop];
  164. }
  165. }
  166. }
  167. return obj;
  168. };
  169. var renderMathInElement = function(elem, options) {
  170. if (!elem) {
  171. throw new Error("No element provided to render");
  172. }
  173. options = extend({}, defaultOptions, options);
  174. renderElem(elem, options.delimiters, options.ignoredTags);
  175. };
  176. renderMathInElement(document.body);
  177. </script>