WEB-TOOLS-02MathJax渲染

2019-11-02  本文已影响0人  杨强AT南京

  公式渲染当然是鼎鼎大名的MathJax了,在Jupyter使用的也是这个。MathJax支持MathML与Tex(比如LaTex)语法。我们的最终目的是应用MathJax到Vue框架中,本主题体系化的梳理MathJax编程技术,然后就可以整合MathJax到Vue以及其他框架中。MathJax提供很多编程环境的支持(包括Python,C/C++等 ),我们这里只关心Web相关的开发,Web开发目前两种方式;纯前端 + Node.js(使用Webpack打包成纯前端)。内容是
  1. 浏览器使用方式;
  2.Node开发方式;
    2.1. 组件模式;
    2.2. 预加载模式;
    2.3. 直接使用模式;


序言

浏览器开发模式

模块下载

下载的目录结构 npm安装

使用模式

引入模块

使用网络公共资源CDN


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>使用MathJax v3转换LaTex为HTML</title>
    <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
    ......
      
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>使用MathJax v3转换LaTex为HTML</title>
    <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
    <script>
      function convert() {
        /*例子来自官网,没有做修改*/
        // 获取输入的公式
        var input = document.getElementById("input").value.trim();
        // 获取显示组件
        var display = document.getElementById("display");
        // 转换与显示事件按钮
        var button = document.getElementById("render");
        button.disabled = display.disabled = true;
        
        // 清除公式输出组件
        output = document.getElementById('output');
        output.innerHTML = '';

        // 充值MathJax环境,MathJax是全局变量(从js脚本引入的)
        MathJax.texReset();
        
        // 获取选项(根据输出组件回去转换的参数:大小等)
        var options = MathJax.getMetricsFor(output);  
        options.display = display.checked;   //行模式与块模式

        // 转换(异步模式)
        MathJax.tex2chtmlPromise(input, options).then(function (node) {
          // 转换成功,传递渲染好以后的node对象
          output.appendChild(node); // 添加渲染的公式到输出组件
          MathJax.startup.document.clear();   // 清除文档
          MathJax.startup.document.updateDocument();  // 释放文档 
        }).catch(function (err) {
          // 转换失败的处理
          output.appendChild(  // 创建pre组件显示你错误文本
            document.createElement('pre')
          ).appendChild(
            document.createTextNode(err.message)
          );
        }).then(function () {
          // 处理完毕后处理,等价于finally
          button.disabled = display.disabled = false;
        });
      }
    </script>
    <style>
      #frame {
        max-width: 40em;
        margin: auto;
      }
      #input {
        border: 1px solid grey;
        margin: 0 0 .25em;
        width: 100%;
        font-size: 120%;
        box-sizing: border-box;
      }
      #output {
        font-size: 120%;
        margin-top: .75em;
        border: 1px solid grey;
        padding: .25em;
        min-height: 2em;
      }
      #output > pre {
        margin-left: 5px;
      }
      .left {
        float: left;
      }
      .right {
        float: right;
      }
    </style>
  </head>

  <body>
    <div id="frame">

      <h1>MathJax v3: LaTeX 转换为 HTML</h1>
      <!-- 输入组件 -->
      <textarea id="input" rows="15" cols="10">
      %
      % Enter TeX commands below
      %
      x = {-b \pm \sqrt{b^2-4ac} \over 2a}.
      y = \dfrac{1}{x} 
      </textarea>
      <br />
      <!-- 选择框架 -->
      <div class="left">
        <input type="checkbox" id="display" checked onchange="convert()"> 
        <label for="display">行模式与块模式</label>
      </div>
      <!-- 操作按钮 -->
      <div class="right">
        <input type="button" value="渲染LaTex" id="render" onclick="convert()" />
      </div>
      <br clear="all" />
      <!-- 显示组件 -->
      <div id="output">

      </div>
    </div>

  </body>

</html>

使用本地下载的资源

- 本地模块,直接用npm下载的node-modules中获取。
- 除了引入的差异,骐达都是一样的。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>使用MathJax v3转换LaTex为HTML</title>
    <script src="./node_modules//mathjax//es5/tex-chtml.js"></script>
    <script>
MathJax产生的样式表

MathJax全局对象

MathJax得使用步骤

配置选项

        var options = {
            em: 19.200000762939453, 
            ex: 8.533333333333333, 
            containerWidth: 626.40625, 
            lineWidth: 1000000, 
            scale: 1.005530377338936,
            display: false,
        };

把LaTex转换为HTML

       // 3. 转换LaTex为HTML(同步模式,还有Promise类型的异步模式)
        var input = "a^2 + b^2 = \\sqrt{x}";
        var html = MathJax.tex2chtml(input, options);  // 返回的是DOM对象
        // 日志输出
        console.log(html);
        // 显示到页面(作为节点添加)
        var showhtml = document.getElementById("showhtml");
        showhtml.appendChild(html);

Node.js开发模式

模块下载

直接调用模式-高难度

需要加载得模块与作用

    /*
        1. 加载所有需要的底层模块
    */

    // 负责从Tex到HTML DOM的输出
    const mod_mathjax = require('mathjax-full/js/mathjax.js');      
    // 负责LaTex公式解析  
    const mod_tex = require('mathjax-full/js/input/tex.js');   
    // 负责HTML DOM的创建         
    const mod_chtml = require('mathjax-full/js/output/chtml.js'); 
    // HTML DOM的适配器处理接口         
    const mod_adaptor = require('mathjax-full/js/adaptors/liteAdaptor.js');   
    // 负责注册Adaptor,CHTML模块就调用底层DOM操作完成HTML的创建     
    const mod_htmlhandle = require('mathjax-full/js/handlers/html.js');  
    // Tex需要加载的模块(一个字典)  
const mod_allpackages = require('mathjax-full/js/input/tex/AllPackages.js');

构建模块对象

    /*
        2. 构建模块对象
            |- 选项
            |- 构建
    */

    // 定义HTML的创建接口与适配器
    const adaptor = mod_adaptor.liteAdaptor();
    // 把适配器注册为HTML的DOM处理对象
    mod_htmlhandle.RegisterHTMLHandler(adaptor);
    // -------------------------
    // 定义Tex的输入处理
    var tex_options = {  // 配置相关的子模块
        packages: mod_allpackages.AllPackages,    // 这是一个数组类型的模块列表
    };
    const tex = new mod_tex.TeX(tex_options);
    // -------------------------
    // 定义HTML的输出处理
    var html_options = {
        // 可以设置字库路径等,这儿暂时缺省
    };
    const chtml = new mod_chtml.CHTML(html_options);

    // -------------------------
    // 把tex与chtml对象装配为最后的html与css的文档输出对象
    var document_options = {
        InputJax: tex, 
        OutputJax: chtml
    }
    const document = mod_mathjax.mathjax.document('', document_options);

生成HTML节点

    /*
        3. 输出HTML DOM
    */
    var node_options = {
        em: 19.200000762939453, 
        ex: 8.533333333333333, 
        containerWidth: 626.40625, 
        lineWidth: 1000000, 
        scale: 1.005530377338936,
    };
    const node = document.convert('a^2 + b^2 = x', node_options);
    // 使用适配器生成HTML DOM
    var node_html = adaptor.outerHTML(node);
    console.log(node_html);

生成CSS样式

    /*
        4. 输出CSS样式表
    */
    // 样式表与Tex没有关系,可以使用chtml直接获取
    var css = chtml.styleSheet(document);
    //转换为文本
    var css_html = adaptor.textContent(css);  
    console.log(css_html);

完整代码清单

    /*
        1. 加载所有需要的底层模块
    */

    // 负责从Tex到HTML DOM的输出
    const mod_mathjax = require('mathjax-full/js/mathjax.js');      
    // 负责LaTex公式解析  
    const mod_tex = require('mathjax-full/js/input/tex.js');   
    // 负责HTML DOM的创建         
    const mod_chtml = require('mathjax-full/js/output/chtml.js'); 
    // HTML DOM的适配器处理接口         
    const mod_adaptor = require('mathjax-full/js/adaptors/liteAdaptor.js');   
    // 负责注册Adaptor,CHTML模块就调用底层DOM操作完成HTML的创建     
    const mod_htmlhandle = require('mathjax-full/js/handlers/html.js');  
    // Tex需要加载的模块(一个字典)  
    const mod_allpackages = require('mathjax-full/js/input/tex/AllPackages.js');

    /*
        2. 构建模块对象
            |- 选项
            |- 构建
    */

    // 定义HTML的创建接口与适配器
    const adaptor = mod_adaptor.liteAdaptor();
    // 把适配器注册为HTML的DOM处理对象
    mod_htmlhandle.RegisterHTMLHandler(adaptor);
    // -------------------------
    // 定义Tex的输入处理
    var tex_options = {  // 配置相关的子模块
        packages: mod_allpackages.AllPackages,    // 这是一个数组类型的模块列表
    };
    const tex = new mod_tex.TeX(tex_options);
    // -------------------------
    // 定义HTML的输出处理
    var html_options = {
        // 可以设置字库路径等,这儿暂时缺省
    };
    const chtml = new mod_chtml.CHTML(html_options);

    // -------------------------
    // 把tex与chtml对象装配为最后的html与css的文档输出对象
    var document_options = {
        InputJax: tex, 
        OutputJax: chtml
    }
    const document = mod_mathjax.mathjax.document('', document_options);
    /*
        3. 输出HTML DOM
    */
    var node_options = {
        em: 19.200000762939453, 
        ex: 8.533333333333333, 
        containerWidth: 626.40625, 
        lineWidth: 1000000, 
        scale: 1.005530377338936,
    };
    const node = document.convert('a^2 + b^2 = x', node_options);
    // 使用适配器生成HTML DOM
    var node_html = adaptor.outerHTML(node);
    console.log(node_html);
    /*
        4. 输出CSS样式表
    */
    // 样式表与Tex没有关系,可以使用chtml直接获取
    var css = chtml.styleSheet(document);
    //转换为文本
    var css_html = adaptor.textContent(css);  
    console.log(css_html);

API说明

预加载模式-中难度

描述加载前的参数

    /*
        1. 配置MathJax;
    */
    MathJax = {
        tex: {                  // 指定输入Tex需要的参数
            packages: [        // |- 指定Tex需要加载的模块;
                'base', 'autoload', 'require', 'ams', 'newcommand',
            ],
        },
        chtml: {                // 指定输出chtml需要的参数;

        },
        startup: {              // 指定开始转换需要的参数;
            typeset: false,     
        }
    };

加载模块


    /*
        2. 加载需要的模块;
            |- 注意:加载的是src下的模块
            |- 注意:MathJax需要先定义,在下面require的时候会根据参数产生loader中的preLoad函数
     */
    require('mathjax-full/components/src/startup/lib/startup.js');
    require('mathjax-full/components/src/core/core.js');
    require('mathjax-full/components/src/adaptors/liteDOM/liteDOM.js');
    require('mathjax-full/components/src/input/tex-base/tex-base.js');
    require('mathjax-full/components/src/input/tex/extensions/all-packages/all-packages.js');
    require('mathjax-full/components/src/output/chtml/chtml.js');
    require('mathjax-full/components/src/output/chtml/fonts/tex/tex.js');
    require('mathjax-full/components/src/startup/startup.js');

设置Tex需要的已经加载的模块

    /*
        3. 告诉MathJax,已经预加载的模块
    */
    MathJax.loader.preLoad(
        'core',
        'adaptors/liteDOM',
        'input/tex-base',
        '[tex]/all-packages',
        'output/chtml',
        'output/chtml/fonts/tex'
    );

初始化调用input与output的封装适配器

    /*
        4. 初始化调用input与output的封装适配器
            | - adaptor
    */
    MathJax.config.startup.ready();
    const adaptor = MathJax.startup.adaptor;

转换Tex为DOM

    /*
        5. 转换LaTex为DOM
    */
    const options = {
        em: 19.200000762939453, 
        ex: 8.533333333333333, 
        containerWidth: 626.40625, 
        lineWidth: 1000000, 
        scale: 1.005530377338936,
    }

    const node_html = MathJax.tex2chtml('a^2 + b^2 = x', options)
    const node_css = MathJax.chtmlStylesheet()

把DOM转换为CSS

    /*
        6. 转换为CSS
            | - 这个实际是与Tex无关的,样式对所有Tex都是一样
    */
    const css = adaptor.textContent(node_css);
    console.log(css);

把DOM转换为HTML

    /*
        7. 转换为HTML
    */
    const html = adaptor.outerHTML(node_html);
    console.log(html);

执行的注意事项

组件模式-低难度

编程框架

    MathJax = {
        // 模块加载
        loader: {

        },
        // 输入Tex的参数,包含说明已经加载可以使用的模块;
        tex: {

        },
        // 输出CHTML的参数,包含CSS使用的字体库的URL
        chtml: {

        },

        // 转换需要的参数
        startup: {

        },

    };

配置转换需要的选项


    MathJax = {
        // 模块加载
        loader: {
            paths: {mathjax: 'mathjax-full/es5'},
            require: require,                           // 加载方式
            load: ['input/tex-full', 'output/chtml', 'adaptors/liteDOM'],
        },
        // 输入Tex的参数,包含说明已经加载可以使用的模块;
        tex: {
            packages: ['base', 'autoload', 'require', 'ams', 'newcommand',],
        },
        // 输出CHTML的参数,包含CSS使用的字体库的URL
        chtml: {
            // 使用默认的参数
        },

        // 转换需要的参数
        startup: {
            typeset: false,
            pageReady: () => {   // 使用箭头函数
                console.log('开始转换');
            },
        },

    };

加载startup模块


    // 加载startup模块(如果上面没有配置号,这个加载会失败【抛出异常】)
    require('mathjax-full/es5/startup.js');
    // 执行命令:node -r esm node_03componenet.js

编程框架结构的运行结果

转换Tex为HTML与CSS

同步模式

            // console.log('开始转换');
            const options = {
                em: 19.200000762939453, 
                ex: 8.533333333333333, 
                containerWidth: 626.40625, 
                lineWidth: 1000000, 
                scale: 1.005530377338936,
            }
            const node_html = MathJax.tex2chtml('a^2 + b^2 = x', options);
            const adaptor = MathJax.startup.adaptor;
            
            const node_css = MathJax.chtmlStylesheet();
            const css = adaptor.textContent(node_css);
            console.log(css);
            const html = adaptor.outerHTML(node_html);
            console.log(html);

异步模式

            // 异步模式
            const promise = MathJax.tex2chtmlPromise('a^2 + b^2 = x', options);
            promise.then(
                (node_html) => {
                    const adaptor = MathJax.startup.adaptor;
                    const html = adaptor.outerHTML(node_html);
                    console.log(html);   
                    // 这个实际上可以在外面调用,不依赖node_html
                    const node_css = MathJax.chtmlStylesheet();
                    const css = adaptor.textContent(node_css);  
                    console.log(css);
                },
            ).catch(
                (err) => {
                    console.log(err);
                }
            );
            promise.then( 业务处理回调函数 ).catch( 异常处理回调函数 );

完整代码


MathJax = {
    // 模块加载
    loader: {
        paths: {mathjax: 'mathjax-full/es5'},
        require: require,                           // 加载方式
        load: ['input/tex-full', 'output/chtml', 'adaptors/liteDOM'],
    },
    // 输入Tex的参数,包含说明已经加载可以使用的模块;
    tex: {
        packages: ['base', 'autoload', 'require', 'ams', 'newcommand',],
    },
    // 输出CHTML的参数,包含CSS使用的字体库的URL
    chtml: {
        // 使用默认的参数
    },

    // 转换需要的参数
    startup: {
        typeset: false,
        pageReady: () => {   // 使用箭头函数
            // console.log('开始转换');
            const options = {
                em: 19.200000762939453, 
                ex: 8.533333333333333, 
                containerWidth: 626.40625, 
                lineWidth: 1000000, 
                scale: 1.005530377338936,
            }
            // 同步模式
            // const node_html = MathJax.tex2chtml('a^2 + b^2 = x', options);
            // const adaptor = MathJax.startup.adaptor;

            // const node_css = MathJax.chtmlStylesheet();
            // const css = adaptor.textContent(node_css);
            // console.log(css);
            // const html = adaptor.outerHTML(node_html);
            // console.log(html);

            // 异步模式
            const promise = MathJax.tex2chtmlPromise('a^2 + b^2 = x', options);
            promise.then(
                (node_html) => {
                    const adaptor = MathJax.startup.adaptor;
                    const html = adaptor.outerHTML(node_html);
                    console.log(html);   
                    // 这个实际上可以在外面调用,不依赖node_html
                    const node_css = MathJax.chtmlStylesheet();
                    const css = adaptor.textContent(node_css);  
                    console.log(css);
                },
            ).catch(
                (err) => {
                    console.log(err);
                }
            );

        },
    },

};

// 加载startup模块(如果上面没有配置号,这个加载会失败【抛出异常】)
require('mathjax-full/es5/startup.js');

// 源代码方式(记得用-r esm选项)
// require('mathjax-full/components/src/startup/startup.js');

// 执行命令:node  node_03componenet.js   
// 注意:因为没有使用源代码,使用的是es5下打包好的模块,所以esm模块不用预先加载


MathJax产生的CSS与HTML

总结


提示:
  把MathJax产生的CSS与HTML插入到Web页面也不是一件轻松的事情,尤其Vue的编程当时牵涉编译时执行与运行时(runtime)执行,一个理解不清楚,就是纠结啊。


上一篇 下一篇

猜你喜欢

热点阅读