michal-h21 / make4ht

Build system for tex4ht
131 stars 15 forks source link

Feature Request: Embedding of MathJax-script #106

Closed cjohn001 closed 1 year ago

cjohn001 commented 1 year ago

Hello @michal-h21

Would it somehow be possible to embed/inline the MathJax-script? It is referenced in the generated html file as:

src='https://cdn.jsdelivr.net/npm/mathjax@3/es5/mml-chtml.js'

I would like to create a self-contained single html file for offline usage, hence the js file would somehow be required to be included inline. Would be great if you could add a flag or filter to do this. I am unfortunately unfamiliar with lua. Maybe also an additional build step could do it? Seems like I am the first one trying to do this.

michal-h21 commented 1 year ago

It could be relatively easy, as it is possible to insert external files from in the configuration files. So something like this should work:

\Preamble{xhtml,mathjax}
\Configure{MathJaxSource}{}
\Configure{@HEAD}{
\HCode{<script type="text/javascript">\Hnewline}
\special{t4ht*<node_modules/mathjax/es5/tex-svg-full.js}%
\HCode{\Hnewline</script>\Hnewline}
}
\begin{document}
\EndPreamble

Note the \special{t4ht*<...} line. This inserts the external file.

The problem is that the minified MathJax file contains characters that cause LuaXML DOM processing to fail, which is undesirable, as make4ht uses this processing for lot of clean-ups that would be difficult otherwise. It was more complicated than I expected, because the file needs to be inserted only after all DOM processing is done. So here is a build file that should work:


local domfilter = require "make4ht-domfilter"
local filter = require "make4ht-filter"

-- filter can change the page as string, so we can insert anything here 
local filter_process = filter({
  function(text)
    -- put the correct path to MathJax script here
    local mathjax_file = "tex-svg-full.js"
    local f = io.open(mathjax_file, "r")
    local content = f:read("*all")
    f:close()
    -- we use trick with function because content contains string that causes gsub to fail otherwise
    local text = text:gsub("</head>", function()
      return "<script type='text/javascript'>" ..content .. "\n</script>\n</head>"
    end)
    return text
  end
}, "unique")

-- this hooks to the XML DOM processing
local process = domfilter {
  function(dom)
    for _, script in ipairs(dom:query_selector"head script") do
      -- remove the original MathJax script, so it doesn't load the external file
      if script:get_attribute("id") == "MathJax-script" then
        script:remove_node()
      end
    end
    -- now we register the function that inserts MathJax script
    -- we must do it after the DOM processing, because the script contains characters that 
    -- cause LuaXML parser to fail
    Make:match("html$", filter_process)
    return dom
  end
}

Make:match("html$", process)

It uses a trick to insert a filter inside another filter, to ensure that the MathJax script will be inserted after all other processing. Don't forget to put the correct path in local mathjax_file = "tex-svg-full.js"

cjohn001 commented 1 year ago

Hello @michal-h21 thank you very much. Great it works :)