排版与数学转换

mathjax有两个主要用途:

  • 在网页中排版所有的数学,以及

  • 将包含数学的字符串转换成另一种形式。

在版本2中,mathjax可以很好地执行第一个函数,但是执行第二个函数要困难得多。mathjax版本3使这两种方法都很容易实现。下面将描述这两个任务。

网页排版数学

mathjax使在web页面中排版所有的数学变得很容易,事实上,它将在第一次加载时自动进行排版,除非您将其配置为不排版。所以这是在mathjax中执行的最简单的操作之一;如果页面是静态的,那么除了加载mathjax之外别无选择。

如果您的页面是动态的,并且您可能正在加载页面后添加数学,那么您将需要告诉mathjax在数学插入页面后对其进行排版。有两种方法: MathJax.typeset()MathJax.typesetPromise() .

第一个, MathJax.typeset() ,对页进行排版,并立即同步进行排版,因此当调用完成时,页将被排版。但是,请注意,如果数学包含需要加载其他文件的操作(例如,使用 require ,或者包含自动加载的扩展),则将引发错误。你可以使用 try/catch 捕获此条件的命令。

第二, Mathjax.typesetPromise() ,异步执行排版,并返回排版完成时解析的承诺。这可以正确地处理外部文件的加载,因此如果希望处理可以包括 require 或者自动加载的扩展名,您应该使用这种排版形式。它可以和 await 作为一个更大的 async 功能。

这两个函数都有一个可选参数,它是一个元素数组,其内容应该被处理。元素可以是实际的dom元素,也可以是元素或元素集合的css选择器字符串。提供元素数组将限制排版仅限于这些元素的内容。

处理异步排版

尝试同时执行多个异步排版调用通常是一个坏主意,因此如果您使用 MathJax.typesetPromise() 要打几个排版电话,你应该用他们的回信把他们连起来。例如:

MathJax.typesetPromise().then(() => {
  // modify the DOM here
  MathJax.typesetPromise();
}).catch((err) => console.log(err.message));

然而,这种方法可能会很快变得复杂,因此您可能希望保持一个承诺,可以用于链接以后的排版调用。例如,

let promise = Promise.resolve();  // Used to hold chain of typesetting calls

function typeset(code) {
  promise = promise.then(() => MathJax.typesetPromise(code()))
                   .catch((err) => console.log('Typeset failed: ' + err.message));
  return promise;
}

那你就可以用 typeset() 运行更改dom和设置结果的代码。这个 code() 您传递它来执行dom修改并将元素数组返回到typeset,或者 null 排版整页。例如。,

typeset(() => {
  const math = document.querySelector('#math');
  math.innerHTML = '$$\\frac{a}{1-a^2}$$';
  return math;
});

将元素的内容替换为 id="math" 使用指定的分数并让mathjax对其进行排版(异步)。因为 then() 调用返回的结果 MathJax.typesetPromise() ,这本身就是一个承诺, then() 在这一承诺得到解决之前不会解决;也就是说,在排版完成之前不会解决。最后,自从 typeset() 函数返回 promise ,你可以使用 await 在一个 async 等待排版完成的函数:

await typeset(...);

注意,这并没有考虑mathjax执行的初始排版,因此您可能希望使用 MathJax.startup.promise 代替 promise 上面。即,简单地使用

function typeset(code) {
  MathJax.startup.promise = MathJax.startup.promise
    .then(() => MathJax.typesetPromise(code()))
    .catch((err) => console.log('Typeset failed: ' + err.message));
  return MathJax.startup.promise;
}

这就避免了 promise 变量,并确保在初始排版完成之前不会进行排版。

重置自动公式编号

tex input jax允许您自动对方程进行编号。修改页面时,这可能会导致问题,因为编号的公式可能会被删除和添加;最常见的是,重复的标签会导致问题。

可以使用命令重置公式编号

MathJax.texReset([start])

在哪里? start 开始公式编号的编号。

更新以前的排版内容

MathJax跟踪它在页面中排版的所有数学。这是因为,如果您更改输出呈现器(使用MathJax上下文菜单),例如,可以将其更改为使用新格式;或者,如果您更改辅助功能设置,例如启用表达式资源管理器,则可以更新所有数学运算以包含它使用的语音字符串。如果修改该页以包括新的数学运算并调用 MathJax.typeset()MathJax.typesetPromise() ,如您所料,新排版的数学将添加到已排版的数学列表中。

如果您修改页面以删除包含排版数学的内容,则需要将此情况告知MathJax,以便它知道您要删除的排版数学不再位于页面上。要执行此操作,请使用 MathJax.typesetClear() 方法。

在不带参数的情况下调用时, MathJax.typesetClear() 告诉MathJax忘掉到目前为止排版的所有数学。请注意,数学将作为排版数学保留在页面中,但是MathJax将不再知道任何有关它的内容。例如,这意味着对输出渲染器或辅助功能设置的更改不会影响以前排版的任何数学。

如果仅从页面的一部分移除数学,则可以调用 MathJax.typesetClear() 向它传递一个已经(或将被)删除的容器元素数组,MathJax将忘记这些容器中的数学运算,同时记住页面上数学运算的睡觉。例如,如果您有一个具有 id="has-math" ,并且您计划用新内容(存储在变量中)替换此元素的内容 new_html )需要排版,您可以使用如下内容:

const node = document.getElementById('has-math');
MathJax.typesetClear([node]);
node.innerHTML = new_html;
MathJax.typesetPromise([node]).then(() => {
  // the new content is has been typeset
});

争论传给 MathJax.typestClear() 可以是实际的DOM元素,如上面的例子,也可以是CSS选择器字符串(例如。, '#has-math' ),或一个数组。选择器可以指定多个容器元素(例如,通过类选择器)。

如果您使用的是自动公式编号并在页面中间插入新内容,则可能需要在整个页面中调整公式编号。在这种情况下,您可以这样做

MathJax.startup.document.state(0);
MathJax.texReset();
MathJax.typeset();

强制MathJax将页面重置为MathJax处理之前的状态(即删除其排版数学),重置TeX自动行号和标签,然后从头开始重新排版页面内容。

在页面上查找数学

MathJax将有关其排版的特定表达式的信息保存在名为 MathItem ;每个排版表达式都有一个关联的MathItem。您可以使用 MathJax.startup.document.getMathItemsWithin() 功能。将其传递给容器元素(或元素或元素集合的CSS选择器,或容器或选择器的数组),它将返回这些容器中的MathItem数组。例如,

MathJax.startup.document.getMathItemsWithin(document.body);

将返回页上排版数学的所有MathItems数组。见 MathItem definition 有关MathItem结构内容的详细信息。MathItem是v2的v3替代品 ElementJax 对象,以及 getMathItemsWithin() 执行与v2功能类似的功能 MathJax.Hub.getAllJax() .

排版用户提供的内容

像LaTeX和MathML这样的数学格式允许强大的布局选项,包括访问超链接、CSS样式、字体选择和大小、间距等等。这些功能为您的页面提供了很大的灵活性,但是如果您的读者被允许在您的页面中输入数学(例如,对于问答网站,或者在博客的评论中),这些功能可能会被滥用来给其他读者带来问题,并给他们带来潜在的安全风险。例如,Tex \href 命令可用于插入 javascript: 链接到页中,而 \style 宏可用于中断页面的用户界面或布局。

为了限制读者输入的数学可能导致的潜在干扰,MathJax提供了 ui/safe 扩展。此扩展过滤页面上的数学,以便尝试删除有问题的属性,如javascript链接、太大或太小的字体大小,或会破坏页面布局的样式设置。如果您的页面允许读者发布包含MathJax处理的数学的内容,那么您应该强烈考虑使用 ui/safe 延伸。

安全扩展选项 有关如何加载和配置的详细信息 ui/safe 延伸。

仅在带有math的页面上加载mathjax

mathjax组合配置文件很大,因此您可能希望仅在必要时在页面中包含mathjax。如果使用的内容管理系统自动将页眉和页脚放入页面,则可能不希望直接包含mathjax,除非大多数页面都包含math,因为这样会加载mathjax all 你的页面。一旦mathjax被加载,它应该在浏览器的缓存中,并在随后的页面上快速加载,但是读者看到的第一个页面将加载得更慢。为了避免出现这种情况,可以使用如下脚本检查页面内容是否包含math,如果包含mathjax,则只加载mathjax。请注意,这不是一个非常复杂的测试,它可能会认为在某些情况下确实存在数学问题,但它应该减少必须加载mathjax的页数。

创建一个名为 check-for-tex.js 包含以下内容:

(function () {
  var body = document.body.textContent;
  if (body.match(/(?:\$|\\\(|\\\[|\\begin\{.*?})/)) {
    if (!window.MathJax) {
      window.MathJax = {
        tex: {
          inlineMath: {'[+]': [['$', '$']]}
        }
      };
    }
    var script = document.createElement('script');
    script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js';
    document.head.appendChild(script);
  }
})();

然后使用

<script src="check-for-tex.js" defer></script>

以便在页面内容就绪时加载脚本。请注意,您将要包含到存储位置的路径 check-mathjax.js ,你应该改变 tex-chtml.js 到任何你想使用的组件文件,并且 window.MathJax 值应设置为要使用的任何配置。在本例中,它只是将美元符号添加到行内数学分隔符中。最后,调整 body.match() 正则表达式以匹配用于数学分隔符的任何内容。

这只是检查是否有类似于tex in-line或显示的数学分隔符的内容,如果有,则加载mathjax。如果使用不同的分隔符,则需要更改模式以包含这些分隔符(并排除不使用的任何分隔符)。如果使用ascimath而不是tex,则更改模式以查找ascimath分隔符。

如果您使用的是mathml,则可能需要使用

if (document.body.querySelector('math')) {...}

而不是测试(前提是不使用名称空间前缀,如 <m:math>


将数学字符串转换为其他格式

mathjax的一个重要用例是转换包含数学的字符串(用mathjax理解的三种格式之一),并将其转换为另一种格式(mathml或mathjax支持的输出格式之一)。这在mathjax版本2中很难做到,但在版本3中很容易做到。

当MathJax启动时,它将创建方法,用于将输入格式转换为已加载的输出格式,并转换为MathML格式。例如,如果您已经加载了MathML输入jax和SVG输出jax(比如使用 mml-svg ,然后mathjax将为您创建以下转换方法:

MathJax.mathml2svg(math[,options])
MathJax.mathml2svgPromise(math[,options])
MathJax.mathml2mml(math[,options])
MathJax.mathml2mmlPromise(math[,options])

如果您也加载了tex input jax,您还将获得另外四个方法 tex 代替 mathml .

顾名思义, Promise 函数异步执行转换并返回承诺,而其他函数同步操作并立即返回转换后的表单。前两个函数(以及其他类似的函数)生成dom元素作为转换的结果,promise版本将其传递给 then() 函数作为其参数(请参见 异步转换 以及直接返回的非承诺版本。您可以直接将这些dom元素插入到文档中,也可以使用它们 outerHTML 属性获取其序列化字符串形式。

转换为mathml的函数会自动生成序列化的mathml字符串,而不是生成dom元素。(您可以使用浏览器的 DOMParser 对象将字符串转换为mathml dom树(如果需要)。

转换选项

所有这四个函数都需要一个参数,该参数是要转换的数学字符串(例如,序列化的mathml字符串,或者在 tex2chtml() ,特克斯或 Latex 线)。您还可以传递第二个参数,该参数是包含控制转换过程的选项的对象。可包括的选项包括:

  • display ,一个布尔值,指定数学是否处于显示模式(对于tex输入)。默认是 true .

  • em ,表示 em 对于周围的字体。默认是 16 .

  • ex ,表示 ex 对于周围的字体。默认是 8 .

  • containerWidth ,表示容器宽度的数字,以像素为单位。默认值是 ex 价值。

  • lineWidth' ,表示换行宽度的数字。 em 单位。默认值是一个非常大的数字(100000),因此实际上没有换行。

  • scale ,一个数字,给出应用于结果转换的缩放因子。默认值为1。

例如,

let html = MathJax.tex2chtml('\\sqrt{x^2+1}', {em: 12, ex: 6, display: false});

将转换tex表达式 \sqrt{{x^2+1}} 将HTML作为一个内联表达式,使用 em 大小为12像素 ex 大小为6像素。结果将是一个包含表达式html的dom元素。同样地,

let html = MathJax.tex2chtml('\\sqrt{x^2+1}', {em: 12, ex: 6, display: false});
let text = html.outerHTML;

集合 text 作为表达式的序列化HTML字符串。

获取输出指标

自从 emexcontainerWidth 所有这些都取决于数学将放在文档中的位置(它们是基于周围文本字体和容器元素宽度的值),mathjax提供了一种从给定dom元素获取这些值的方法。方法

MathJax.getMetricsFor(node, display)

获取dom元素 (node )一个布尔值 (display ),指示数学是否处于显示模式,并返回包含上面列出的所有六个选项的对象。您可以将此对象直接传递给上面讨论的转换方法。所以你可以做些

let node = document.querySelector('#math');
let options = MathJax.getMetricsFor(node, true);
let html = MathJax.tex2svg('\\sqrt{x^2+1}', options);
node.appendChild(html);

以便获得正在转换的数学(最终)位置的正确度量。当然,只需将TeX代码插入页面并使用 MathJax.typeset() 来排版,但这只是一个示例,向您展示如何从页面中的特定位置获取指标。

请注意,获取度量值会导致页面刷新,因此这样做成本高昂。如果您需要从许多不同的位置获取度量,有更有效的方法,但这些是其他地方要处理的高级主题。

获取输出样式表

svg和commonhtml输出jax的输出都依赖于css样式表,以便正确格式化它们的结果。您可以通过调用

MathJax.svgStylesheet();

以及来自

MathJax.chtmlStylesheet();

commonhtml输出jax-css可能非常大,因此输出jax尝试通过只包含输出jax处理的数学实际需要的样式来最小化样式表。这意味着您应该只请求样式表 之后 你自己把数学排版好了。

此外,如果您排版了几个表达式,样式表将包含您排版的所有表达式所需的所有内容。如果要重置样式表,请使用

MathJax.startup.output.clearCache();

如果输出jax是commonhtml输出jax。因此,如果要为单个表达式生成样式表,请发出 clearCache() 命令就在 tex2chtml() 打电话。

异步转换

如果你正在转换可能使用的tex或 Latex require 要加载扩展,或者在扩展可能被自动加载的地方,您将需要使用包含所有扩展的“完整”组件之一,或者如果您计划使用上面列出的同步调用,则需要预加载所需的所有扩展。否则,您可以使用基于承诺的调用,它可以透明地处理扩展的加载。

例如,

let node = document.querySelector('#math');
let options = MathJax.getMetricsFor(node, true);
MathJax.tex2chtmlPromise('\\require{bbox}\\bbox[red]{\\sqrt{x^2+1}}', options)
  .then((html) => {
    node.appendChild(html);
    let sheet = document.querySelector('#MJX-CHTML-styles');
    if (sheet) sheet.parentNode.removeChild(sheet);
    document.head.appendChild(MathJax.chtmlStylesheet());
  });

将获取元素的度量 id="math" ,使用这些度量转换tex表达式(正确处理 \require 命令);然后当表达式被排版时,它被添加到文档中,chtml样式表被更新。