定制mathjax构建

mathjax提供了许多组合组件,这些组件以给定的输入和输出格式加载运行mathjax所需的所有内容。不过,您可能会发现,我们提供的任何组件都不完全适合您的需要,并且您希望在构建中包含其他组件,或者可能希望包含自定义配置选项。

您可以使用mathjax组件构建工具来创建您自己的自定义组件,该组件具有您所需的片段和配置。您还可以使用它们来创建自定义扩展,例如tex输入扩展,该扩展利用了已加载的组件,但实现了其他功能。这些可能性在 构建自定义组件 下面。

还可以对mathjax进行完全自定义的构建,该构建完全不使用mathjax组件,但包含对mathjax源文件的直接调用。这在 自定义mathjax构建 下面。

如果您希望将mathjax包含在一个更大的项目中,可以使用这两种技术中的任何一种,并制作一个包含您自己的项目代码和mathjax的网页文件。

准备好东西

第一步是通过 npmgit ,如 获取mathjax代码 .

  • 如果你使用 npm ,您将希望安装 mathjax-full 包而不是 mathjax 包,因为前者包括所有的源代码,包括其原始和编译的形式,以及webpacket组件。

无论哪种情况,你都应该 js ,一个 es5 和A components 目录,在 node_modules/mathjax-full 目录(用于 npm 安装)或主目录(用于 git 安装)。

第二步是使用 webpack . 使用命令

npm install webpack
npm install webpack-cli
npm install terser-webpack-plugin
npm install babel-loader
npm install @babel/core
npm install @babel/preset-env

安装 webpack 以及所需的库。一旦完成了这一步,您应该能够制作下面描述的组件。建筑说明假设你使用了 npm 如果你用 git ,则需要删除 node_modules/mathjax-full 从包围他们的道路上。


构建自定义组件

mathjax附带了许多预定义的组件,您可以使用 their definitions 作为自定义组件的起点。在 MathJax web demos repository ,与这里描述的相似。

您可以构建两种组件:

  • A 组合元件 将其他几个组件( tex-chtml 组件是组合组件)

  • A 扩展组件 它包含一个特性所需的内容,可以与其他组件一起加载,以便将该特性添加到mathjax中。

我们将在下面描述如何创建每一个。在这两种情况下,都应该创建一个目录来保存组件的支持文件。您将需要组件的主控制文件(包括定义组件的代码)和一个webpack控制文件,该文件将告诉mathjax的构建工具如何处理组件。这些将在下面的章节中讨论。

自定义组合组件

按照 准备好东西 ,为组件创建目录并 cd 到那个目录。我们假设这个目录名为 custom-mathjax 为了这次讨论。

对于本例,我们将创建一个具有tex input jax和svg output jax的自定义构建,并加载 newcommandamsconfigmacros 扩展,但不包括 requireautoload ,因此用户将无法加载任何其他tex扩展。此组件还包括上下文菜单。

控制文件

创建一个javascript文件来存放组件并调用它 custom-mathjax.js . 文件应该包含以下代码(我们假设您使用 npm 安装mathjax。如果没有,您可能需要调整 require() 命令)。

//
//  Initialize the MathJax startup code
//
require('mathjax-full/components/src/startup/lib/startup.js');

//
//  Get the loader module and indicate the modules that
//  will be loaded by hand below
//
const {Loader} = require('mathjax-full/js/components/loader.js');
Loader.preLoad(
  'loader', 'startup',
  'core',
  'input/tex-base',
  '[tex]/ams',
  '[tex]/newcommand',
  '[tex]/configmacros',
  'output/svg', 'output/svg/fonts/tex.js',
  'ui/menu'
);

//
// Load the components that we want to combine into one component
//   (the ones listed in the preLoad() call above)
//
require('mathjax-full/components/src/core/core.js');

require('mathjax-full/components/src/input/tex-base/tex-base.js');
require('mathjax-full/components/src/input/tex/extensions/ams/ams.js');
require('mathjax-full/components/src/input/tex/extensions/newcommand/newcommand.js');
require('mathjax-full/components/src/input/tex/extensions/configmacros/configmacros.js');

require('mathjax-full/components/src/output/svg/svg.js');
require('mathjax-full/components/src/output/svg/fonts/tex/tex.js');

require('mathjax-full/components/src/ui/menu/menu.js');

//
// Update the configuration to include any updated values
//
const {insert} = require('mathjax-full/js/util/Options.js');
insert(MathJax.config, {
  tex: {
    packages: {'[+]': ['ams', 'newcommand', 'configmacros']}
  }
});

//
// Loading this component will cause all the normal startup
//   operations to be performed
//
require('mathjax-full/components/src/startup/startup.js');

这将加载我们希望包含在组合组件中的各种组件,包括标准启动代码,以便包含通常的启动过程。

网页包配置

接下来,创建文件 webpack.config.js 包括以下内容:

const PACKAGE = require('mathjax-full/components/webpack.common.js');

module.exports = PACKAGE(
  'custom-mathjax',                     // the name of the package to build
  '../node_modules/mathjax-full/js',    // location of the mathjax library
  [],                                   // packages to link to
  __dirname,                            // our directory
  '.'                                   // where to put the packaged component
);

此文件提供将用于此组件的名称 (custom-mathjax 在本例中),指向要找到mathjax javascript代码的位置的指针(调整此项以适应您的设置),我们假设在加载此项时已加载的组件数组(在本例中为“无”),我们正在处理的目录名(始终 __dirname ,以及我们希望最终打包组件所在的目录(默认为 mathjax-full/es5 目录,但我们将其设置为包含源文件的目录,组件将以 .min.js

大部分真正的工作是由 mathjax-full/components/webpack.common.js 文件,它包含在第一行。

构建组件

一旦这两个文件准备好,就可以构建组件了。首先,确保您已获得所需的工具,如中所述 准备好东西 以上。那么您应该能够使用命令

node ../node_modules/mathjax-full/components/bin/makeAll

以处理自定义生成。你最后应该得到一个文件 custom-mathjax.min.js 与其他文件在目录中。如果你把它放在你的web服务器上,你可以把它加载到你的web页面中,而不是从CDN加载MathJax。这个文件将包含在页面上运行MathJax所需的所有内容。只要加上

<script src="custom-mathjax.min.js" id="MathJax-script" async></script>

到您的页面,您应该在业务中(调整url以指向您放置 custom-mathjax.min.js 文件)。

配置组件

请注意,您仍然可以包括 MathJax = {{...}} 如果要自定义特定页的配置,请在加载此自定义mathjax生成之前在网页中定义。您还可以在组件本身中包含配置,就像我们对tex所做的那样 packages 数组。但是,这将覆盖任何页面提供的配置,因此如果要提供仍然可以在页面中覆盖的非标准默认值,请使用

//
// Update the configuration to include any updated values
//
const {insert} = require('mathjax-full/js/util/Options.js');
insert(MathJax.config, {tex: {packages: {'[+]': ['ams', 'newcommand', 'configmacros']}}}, false);
MathJax.config = insert({
  // your default options here
}, MathJax.config);

它将更新tex包,然后将用户的配置选项合并到默认值并设置 MathJax.config 合并选项。

CommonHTML字体

如果在自定义构建中包含commonhtml输出jax,则实际的web字体不会包含在web包文件中,因此可能需要包括 fontURLchtml 配置的块,并让它提供一个可以找到字体的url。他们在 mathjax-full/es5/output/chtml/fonts/woff-v2 目录,您可以将它们放在服务器上,或者直接指向 fontURL 到字体的cdn目录之一。

自定义扩展

创建自定义扩展与创建自定义组合组件非常相似。主要的区别在于扩展可能依赖于其他组件,所以您需要告诉构建系统这一点,这样它就不会包含来自其他组件的代码。您也不会直接加载扩展文件(就像上面的组合组件一样),而是将其包含在 load 数组 loader 配置块,mathjax自己加载它,如下所述。

对于这个例子,我们创建了一个自定义的tex扩展,它定义了由javascript函数实现的新tex命令。

这里实现的命令提供了从TeX中手动生成MathML令牌元素的能力。这允许对生成的元素的内容和属性进行更多的控制。宏是 \mi\mo\mn\ms\mtext ,它们各自接受一个参数,该参数是要用作相应mathml元素内容的文本。文本不由tex进一步处理,但扩展名会转换表单的序列 \uNNNN (在那里 N 是十六进制数字)转换成相应的Unicode字符;例如, \mi{{\u2460}} 会产生u+2460,一个带圆圈的数字1,作为 mi 元素。

扩展文件

按照 准备好东西 ,为名为 custom-extensioncd 去吧。然后创建文件 mml.js 包含以下文本:

import {Configuration}  from '../node_modules/mathjax-full/js/input/tex/Configuration.js';
import {CommandMap} from '../node_modules/mathjax-full/js/input/tex/SymbolMap.js';
import TexError from '../node_modules/mathjax-full/js/input/tex/TexError.js';

/**
 * This function prevents multi-letter mi elements from being
 *   interpreted as TEXCLASS.OP
 */
function classORD(node) {
   this.getPrevClass(node);
   return this;
}

/**
 *  Convert \uXXXX to corresponding unicode characters within a string
 */
function convertEscapes(text) {
   return text.replace(/\\u([0-9A-F]{4})/gi, (match, hex) => String.fromCharCode(parseInt(hex,16)));
}

/**
 * Allowed attributes on any token element other than the ones with default values
 */
const ALLOWED = {
   style: true,
   href: true,
   id: true,
   class: true
};

/**
 * Parse a string as a set of attribute="value" pairs.
 */
function parseAttributes(text, type) {
   const attr = {};
   if (text) {
      let match;
      while ((match = text.match(/^\s*((?:data-)?[a-z][-a-z]*)\s*=\s*(?:"([^"]*)"|(.*?))(?:\s+|,\s*|$)/i))) {
         const name = match[1], value = match[2] || match[3]
         if (type.defaults.hasOwnProperty(name) || ALLOWED.hasOwnProperty(name) || name.substr(0,5) === 'data-') {
            attr[name] = convertEscapes(value);
         } else {
            throw new TexError('BadAttribute', 'Unknown attribute "%1"', name);
         }
         text = text.substr(match[0].length);
      }
      if (text.length) {
         throw new TexError('BadAttributeList', 'Can\'t parse as attributes: %1', text);
      }
   }
   return attr;
}

/**
 *  The mapping of control sequence to function calls
 */
const MmlMap = new CommandMap('mmlMap', {
   mi: ['mmlToken', 'mi'],
   mo: ['mmlToken', 'mo'],
   mn: ['mmlToken', 'mn'],
   ms: ['mmlToken', 'ms'],
   mtext: ['mmlToken', 'mtext']
}, {
   mmlToken(parser, name, type) {
      const typeClass = parser.configuration.nodeFactory.mmlFactory.getNodeClass(type);
      const def = parseAttributes(parser.GetBrackets(name), typeClass);
      const text = convertEscapes(parser.GetArgument(name));
      const mml = parser.create('node', type, [parser.create('text', text)], def);
      if (type === 'mi') mml.setTeXclass = classORD;
      parser.Push(mml);
   }
});

/**
 * The configuration used to enable the MathML macros
 */
const MmlConfiguration = Configuration.create(
   'mml', {handler: {macro: ['mmlMap']}}
);

注释解释了这段代码在做什么。使其成为tex扩展所需的主要部分是 Configuration 在最后几行中创建。它创建一个名为 mml 通过 CommandMap 已命名 mmlMap 它的定义就在它上面。该命令映射定义了本节开头描述的五个宏,每个宏都与一个名为 mmlToken 在下面的对象中,向它传递要创建的MathML标记元素的名称。这个 mmlToken 方法是当 \mi 其他宏被调用。它获取宏的参数和任何可选属性,并使用这些属性创建mathml元素,将参数用作元素的文本。

网页包配置

接下来,创建文件 webpack.config.js 包括以下内容:

const PACKAGE = require('mathjax-full/components/webpack.common.js');

module.exports = PACKAGE(
  'mml',                                // the name of the package to build
  '../node_modules/mathjax-full/js',    // location of the mathjax library
  [                                     // packages to link to
     'components/src/core/lib',
     'components/src/input/tex-base/lib'
  ],
  __dirname,                            // our directory
  '.'                                   // where to put the packaged component
);

此文件提供将用于此组件的名称 (mml 在本例中),指向要找到mathjax javascript代码的位置的指针(调整此项以适应您的设置),我们假设在加载此项时已经加载了一个组件数组(在 coretex-base 在本例中是组件),我们正在使用的目录名(总是 __dirname ,以及我们希望最终打包组件所在的目录(默认为 mathjax-full/es5 目录,但我们将其设置为包含源文件的目录,组件将以 .min.js

大部分真正的工作是由 mathjax-full/components/webpack.common.js 文件,它包含在第一行。

扩建工程

一旦这两个文件准备好,就可以构建组件了。首先,确保您已获得所需的工具,如中所述 准备好东西 以上。那么您应该能够使用命令

node ../node_modules/mathjax-full/components/bin/makeAll

以处理自定义生成。你最后应该得到一个文件 mml.min.js 与其他文件在目录中。如果将其放到Web服务器上,则可以通过将其放入 load 数组 loader 配置块,如下所述。

加载扩展名

要加载自定义扩展名,需要告诉mathjax它的位置,并将其包含在启动时要加载的文件中。mathjax允许您定义到存储扩展的位置的路径,然后可以使用表示该位置的前缀来引用该位置中的扩展。mathjax有一个预定义的前缀, mathjax 这是显式指定none时的默认前缀,它引用加载主mathjax文件的位置(例如,文件 tex-svg.jsstartup.js

通过使用 paths 对象中 loader 配置的块。在我们的例子中(见下面的代码),我们添加了 custom 前缀,并让它指向扩展名的url(在本例中,与加载它的html文件相同的目录,由url表示 . )我们使用 custom 要指定的前缀 [custom]/mml.min.jsload 数组以便加载扩展。

最后,我们添加 mml 扩展到 packages 数组中 tex 通过特殊符号的配置块 {{'[+]': [...]}} 告诉mathjax将给定数组附加到现有的 packages 默认情况下已在配置中的数组。所以这将使用所有已经指定的包,以及 mml 在扩展中定义的包。

mathjax的配置和加载现在如下所示:

<script>
MathJax = {
   loader: {
      load: ['[custom]/mml.min.js'],
      paths: {custom: '.'}
   },
   tex: {
      packages: {'[+]': ['mml']}
   }
};
</script>
<script type="text/javascript" id="MathJax-script" async
  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js">
</script>

你应该换个 custom: '.' 指向服务器的实际URL的行。

此示例加载 tex-chtml.js 组合组件,因此在加载扩展时已加载tex输入。如果你正在使用 startup.js 相反,包括 input/texload 数组,您需要告诉mathjax您的扩展依赖于 input/tex 扩展,以便它等待加载扩展,直到加载tex输入jax之后。为此,添加 dependencies 阻止您的配置,如下所示:

<script>
MathJax = {
   loader: {
      load: ['input/tex', 'output/chtml', '[custom]/mml.min.js'],
      paths: {custom: '.'},
      dependencies: {'[custom]/mml.min.js': ['input/tex']}
   },
   tex: {
      packages: {'[+]': ['mml']}
   }
};
</script>
<script type="text/javascript" id="MathJax-script" async
  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/startup.js">
</script>

这个例子可以在 MathJax 3 demos 储存库。


自定义mathjax构建

完全可以定制一个完全不基于其他mathjax组件的mathjax构建。下面的示例演示如何生成自定义生成,该生成提供一个函数,用于获取给定tex数学字符串的语音字符串。这个例子类似于 MathJax3 demos 储存库。

按照 准备好东西 ,创建名为 mathjax-speechcd 投入其中。

自定义生成文件

创建名为 mathjax-speech.js 包含以下内容:

//
//  Load the desired components
//
const mathjax     = require('mathjax-full/js/mathjax.js').mathjax;      // MathJax core
const TeX         = require('mathjax-full/js/input/tex.js').TeX;        // TeX input
const MathML      = require('mathjax-full/js/input/mathml.js').MathML;  // MathML input
const browser     = require('mathjax-full/js/adaptors/browserAdaptor.js').browserAdaptor; // browser DOM
const Enrich      = require('mathjax-full/js/a11y/semantic-enrich.js').EnrichHandler;     // semantic enrichment
const Register    = require('mathjax-full/js/handlers/html.js').RegisterHTMLHandler;      // the HTML handler
const AllPackages = require('mathjax-full/js/input/tex/AllPackages').AllPackages;         // all TeX packages
const STATE       = require('mathjax-full/js/core/MathItem.js').STATE;

const sreReady    = require('mathjax-full/js/a11y/sre.js').sreReady();    // SRE promise;

//
//  Register the HTML handler with the browser adaptor and add the semantic enrichment
//
Enrich(Register(browser()), new MathML());

//
//  Initialize mathjax with a blank DOM.
//
const html = MathJax.document('', {
   sre: {
     speech: 'shallow',           // add speech to the enriched MathML
   },
   InputJax: new TeX({
      packages: AllPackages.filter((name) => name !== 'bussproofs'),  // Bussproofs needs an output jax
      macros: {require: ['', 1]}      // Make \require a no-op since all packages are loaded
   })
});

//
//  The user's configuration object
//
const CONFIG = window.MathJax || {};

//
//  The global MathJax object
//
window.MathJax = {
   version: mathjax.version,
   html: html,
   sreReady: sreReady,

   tex2speech(tex, display = true) {
      const math = new html.options.MathItem(tex, inputJax, display);
      math.convert(html, STATE.CONVERT);
      return math.root.attributes.get('data-semantic-speech') || 'no speech text generated';
   }
}

//
// Perform ready function, if there is one
//
if (CONFIG.ready) {
   sreReady.then(CONFIG.ready);
}

与上面基于组件的示例不同,此自定义构建直接调用mathjax源文件。这个 require 文件开头的命令加载所需的对象,其余代码指示mathjax创建 MathDocument 对象来处理我们将要进行的转换(使用tex input jax),然后定义一个全局 MathJax 对象具有 tex2speech() 我们定制的功能。

网页包配置

接下来,创建文件 webpack.config.js 包括以下内容:

const PACKAGE = require('mathjax-full/components/webpack.common.js');

module.exports = PACKAGE(
  'mathjax-speech',                     // the name of the package to build
  '../node_modules/mathjax-full/js',    // location of the mathjax library
  [],                                   // packages to link to
  __dirname,                            // our directory
  '.'                                   // where to put the packaged component
);

此文件提供将用于此组件的名称 (mathjax-speech 在本例中),指向要找到mathjax javascript代码的位置的指针(调整此项以适应您的设置),我们假设在加载此组件时已经加载了一个组件数组(无,因为这是一个自包含的构建),我们正在使用的目录名(始终 __dirname ,以及我们希望最终打包组件所在的目录(默认为 mathjax-full/es5 目录,但我们将其设置为包含源文件的目录,组件将以 .min.js

大部分真正的工作是由 mathjax-full/components/webpack.common.js 文件,它包含在第一行。

生成自定义文件

一旦这两个文件准备就绪,就可以进行自定义生成了。首先,确保您已获得所需的工具,如中所述 准备好东西 以上。那么您应该能够使用命令

node ../node_modules/mathjax-full/components/bin/makeAll

以处理自定义生成。你最后应该得到一个文件 mathjax-speech.min.js 与其他文件在目录中。它将只包含实现 MathJax.tex2speech() 上面文件中定义的命令。注意,这不足以进行正常的排版(例如,没有包含输出jax),因此这是一个用于从tex输入生成语音字符串的最小文件。

在网页中使用文件

如果你把 mathjax-speech.min.js 文件在web服务器上,您可以将其加载到web页面中,而不是从cdn加载mathjax。此填充将包括所有您需要使用的 MathJax.tex2speech() 命令在你的页面上。只要添加

<script src="mathjax-speech.min.js" id="MathJax-script" async></script>

到您的页面(调整url以指向您放置 custom-mathjax.min.js 文件)。然后可以使用javascript调用

const speech = MathJax.tex2speech('\\sqrt{x^2+1}', true);

获取一个文本字符串,该字符串包含tex字符串中给定的平方根的语音文本。

但是,请注意,作为语音生成基础的语音规则引擎(sre)是异步加载的,因此在进行此类调用之前,必须确保sre已准备就绪。这个 mathjax-speech.js 文件提供了两种处理与sre同步的方法。首先是使用全局 MathJax 变量包括 ready() 当sre准备好时调用的函数。例如,

window.speechReady = false;
window.MathJax = {
   ready: () => {
      window.speechReady = true;
   }
};

将设置全局变量 speechReady 当sre准备运行时为true(因此您可以检查该值以查看是否可以生成语音)。更复杂的 ready() 函数可以让您将要执行的转换排队,当sre准备好时,它将执行这些转换。或者,如果您有一个允许用户转换tex表达式的用户界面,那么您可以首先禁用触发语音生成的按钮,然后使用 ready() 功能来启用它们。这样,用户就不能要求语音翻译,直到它可以产生。

与sre同步的第二种方法是通过以下事实 MathJax.sreReady 当sre准备好的时候,这个承诺就解决了,你可以用它来确保sre准备好了,当你想做语音生成的时候。例如

function showSpeech(tex, display = false) {
   MathJax.sreReady = MathJax.sreReady.then(() => {
     const speech = MathJax.tex2speech(tex, display);
     const output = document.getElementById('speech');
     output.innerHTML = '';
     output.appendChild(document.createTextNode(speech));
   });
}

提供一个函数,该函数允许您指定要转换的tex字符串,然后(异步)为其生成语音并将其显示为 id="speech" 在页面中。