texmath

TeXmath support for cc98 markdown posts

目前為 2021-04-28 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/425661/926035/texmath.js

  1. // ==UserScript==
  2. // @name texmath
  3. // @namespace https://www.cc98.org/
  4. // @version 0.1
  5. // @description TeXmath support for cc98 markdown posts
  6. // @author Secant
  7. // @match https://www.cc98.org/topic/*
  8. // @grant none
  9. // jshint esversion: 6
  10. (function () {
  11. "use strict";
  12. function texmath(md, options) {
  13. const delimiters = "dollars";
  14. const imgtexOptions = (options && options.imgtexOptions) || {
  15. apiBase: "https://math.now.sh",
  16. color: "black",
  17. };
  18.  
  19. if (!texmath.imgtex) {
  20. // else ... depricated `use` method was used ...
  21. if (options && typeof options.engine === "object") {
  22. texmath.imgtex = options.engine;
  23. }
  24. // artifical error object.
  25. else
  26. texmath.imgtex = {
  27. renderToString() {
  28. return "No math renderer found.";
  29. },
  30. };
  31. }
  32.  
  33. if (delimiters in texmath.rules) {
  34. for (const rule of texmath.rules[delimiters].inline) {
  35. md.inline.ruler.before("escape", rule.name, texmath.inline(rule)); // ! important
  36. md.renderer.rules[rule.name] = (tokens, idx) =>
  37. rule.tmpl.replace(
  38. /\$1/,
  39. texmath.render(
  40. tokens[idx].content,
  41. !!rule.displayMode,
  42. imgtexOptions
  43. )
  44. );
  45. }
  46.  
  47. for (const rule of texmath.rules[delimiters].block) {
  48. md.block.ruler.before("fence", rule.name, texmath.block(rule)); // ! important for ```math delimiters
  49. md.renderer.rules[rule.name] = (tokens, idx) =>
  50. rule.tmpl.replace(
  51. /\$1/,
  52. texmath.render(tokens[idx].content, true, imgtexOptions)
  53. );
  54. }
  55. }
  56. }
  57.  
  58. texmath.inline = (rule) =>
  59. function (state, silent) {
  60. const pos = state.pos;
  61. const str = state.src;
  62. const pre =
  63. str.startsWith(rule.tag, (rule.rex.lastIndex = pos)) &&
  64. (!rule.pre || rule.pre(str, pos)); // valid pre-condition ...
  65. const match = pre && rule.rex.exec(str);
  66. const res =
  67. !!match &&
  68. pos < rule.rex.lastIndex &&
  69. (!rule.post || rule.post(str, rule.rex.lastIndex - 1));
  70.  
  71. if (res) {
  72. if (!silent) {
  73. const token = state.push(rule.name, "math", 0);
  74. token.content = match[1];
  75. token.markup = rule.tag;
  76. }
  77. state.pos = rule.rex.lastIndex;
  78. }
  79. return res;
  80. };
  81.  
  82. texmath.block = (rule) =>
  83. function block(state, begLine, endLine, silent) {
  84. const pos = state.bMarks[begLine] + state.tShift[begLine];
  85. const str = state.src;
  86. const pre =
  87. str.startsWith(rule.tag, (rule.rex.lastIndex = pos)) &&
  88. (!rule.pre || rule.pre(str, pos)); // valid pre-condition ....
  89. const match = pre && rule.rex.exec(str);
  90. const res =
  91. !!match &&
  92. pos < rule.rex.lastIndex &&
  93. (!rule.post || rule.post(str, rule.rex.lastIndex - 1));
  94.  
  95. if (res && !silent) {
  96. // match and valid post-condition ...
  97. const endpos = rule.rex.lastIndex - 1;
  98. let curline;
  99.  
  100. for (curline = begLine; curline < endLine; curline++)
  101. if (
  102. endpos >= state.bMarks[curline] + state.tShift[curline] &&
  103. endpos <= state.eMarks[curline]
  104. )
  105. // line for end of block math found ...
  106. break;
  107.  
  108. // "this will prevent lazy continuations from ever going past our end marker"
  109. // s. https://github.com/markdown-it/markdown-it-container/blob/master/index.js
  110. const lineMax = state.lineMax;
  111. const parentType = state.parentType;
  112. state.lineMax = curline;
  113. state.parentType = "math";
  114.  
  115. if (parentType === "blockquote")
  116. // remove all leading '>' inside multiline formula
  117. match[1] = match[1].replace(/(\n*?^(?:\s*>)+)/gm, "");
  118. // begin token
  119. let token = state.push(rule.name, "math", 1); // 'math_block'
  120. token.block = true;
  121. token.markup = rule.tag;
  122. token.content = match[1];
  123. token.map = [begLine, curline];
  124. // end token
  125. token = state.push(rule.name + "_end", "math", -1);
  126. token.block = true;
  127. token.markup = rule.tag;
  128.  
  129. state.parentType = parentType;
  130. state.lineMax = lineMax;
  131. state.line = curline + 1;
  132. }
  133. return res;
  134. };
  135.  
  136. texmath.render = function (tex, displayMode, options) {
  137. options.displayMode = displayMode;
  138. let res;
  139. try {
  140. res = texmath.imgtex.renderToString(tex, options);
  141. } catch (err) {
  142. res = tex + ": " + err.message;
  143. }
  144. return res;
  145. };
  146.  
  147. // used for enable/disable math rendering by `markdown-it`
  148. texmath.inlineRuleNames = ["math_inline"];
  149. texmath.blockRuleNames = ["math_block"];
  150.  
  151. texmath.$_pre = (str, beg) => {
  152. const prv = beg > 0 ? str[beg - 1].charCodeAt(0) : false;
  153. return (
  154. !prv ||
  155. (prv !== 0x5c && // no backslash,
  156. (prv < 0x30 || prv > 0x39))
  157. ); // no decimal digit .. before opening '$'
  158. };
  159. texmath.$_post = (str, end) => {
  160. const nxt = str[end + 1] && str[end + 1].charCodeAt(0);
  161. return !nxt || nxt < 0x30 || nxt > 0x39; // no decimal digit .. after closing '$'
  162. };
  163.  
  164. texmath.rules = {
  165. dollars: {
  166. inline: [
  167. {
  168. name: "math_inline",
  169. rex: /\$((?:\S)|(?:\S.*?\S))\$/gy,
  170. tmpl: "$1",
  171. tag: "$",
  172. pre: texmath.$_pre,
  173. post: texmath.$_post,
  174. },
  175. ],
  176. block: [
  177. {
  178. name: "math_block",
  179. rex: /\${2}([^$]+?)\${2}/gmy,
  180. tmpl: "<center>$1</center>",
  181. tag: "$$",
  182. },
  183. ],
  184. },
  185. };
  186.  
  187. window.texmath = texmath;
  188. })();