Forum Samples, Tips and Tricks

Using MathJax with PrinceXML

mn4367
Hello,

since Prince isn't able to work with MathJax yet I've tried to find a workaround. The basic idea is to have the math rendered with something else to a separate HTML document and then process this document with Prince. I also wanted to use as less external tools/installations as possible and only simple, standard JavaScript, no further frameworks like jQuery etc.

I've found a "solution" which may help other users. You'll need PhantomJS (http://phantomjs.org/) and a local copy of MathJax (https://www.mathjax.org/). The PhantomJS download contains a single executable (at least on the Mac) with no further dependencies. I assume an "installation" like this:

myDir/MathJax
myDir/MathJax/MathJax.js
myDir/MathJax/ ... other MathJax files
ConfigMathJax.js
phantomjs
render-math-and-dump.js
test.html


The content of render-math-and-dump.js:

// PhantomJS setup
var webPage = require('webpage');
var page = webPage.create();
var system = require('system');
var waitForMath;
var noLongerExecWaitForMathJax = false;

// Load page
if (system.args.length < 2) {
    console.log("Usage: phantomjs render-math-and-dump.js <filename>");
    console.log("Renders <filename> and dumps the DOM to the console.");
    phantom.exit();
};
page.open(system.args[1], null);
// Alternatives:
// page.open(encodeURI(system.args[1]), null);
// page.open("file://" + encodeURI(system.args[1]), null);

// This also works (to some extent) with web URLs. Be sure to disable
// waitForMath = window.setInterval at the end of this file if you want
// to try this variant of the script.
// page.open(system.args[1], function(status){
//     if (status === "success") {
//         // In this setup a complete local MathJax installation is present in MathJax/
//         page.injectJs("MathJax/MathJax.js");
//         page.injectJs("ConfigMathJax.js");
//         // Execute the query every 20ms
//         waitForMath = window.setInterval(
//             waitForMathJax, 
//             20
//         );
//     };
// });

// Query a variable named 'MathJaxFinished' in the document,
// see also ConfigMathJax.js.
var waitForMathJax = function () {
    if (noLongerExecWaitForMathJax) {
        return;
    };
    var mjFinished = page.evaluate(function () {
        return MathJaxFinished;
    });
    if (mjFinished) {
        // waitForMathJax was called at least twice (?), even after window.clearInterval()
        // This flag prevents it (see above).
        noLongerExecWaitForMathJax = true;
        window.clearInterval(waitForMathJax);
        page.evaluate(function() {
            // Remove all MathJax related script elements
            var MathJaxScriptFound = true;
            var elements = document.getElementsByTagName("script");
            // Repeating this loop until no more matching scripts are found seems to be necessary
            // because removing children from the DOM did not always happen immediately, some
            // of them were still written to the output when dumping 'page.content'.
            while (MathJaxScriptFound) {
                MathJaxScriptFound = false;
                for (var i = elements.length - 1; i >= 0; i--) {
                    if (elements[i].hasAttribute("id")) {
                        var id = elements[i].getAttribute("id");
                        // Remove script if 'id' starts with 'MathJax-Element'
                        if (id.indexOf("MathJax-Element") === 0) {
                            MathJaxScriptFound = true;
                            elements[i].parentNode.removeChild(elements[i]);
                        };
                    } else if (elements[i].hasAttribute("src")) {
                        var src = elements[i].getAttribute("src");
                        // Remove script if 'src' ends on 'ConfigMathJax.js', 'MathJax.js' or 'jax.js'
                        if ((src.indexOf("ConfigMathJax.js", src.length - 16) !== -1)
                            || (src.indexOf("MathJax.js", src.length - 10) !== -1)
                            || (src.indexOf("jax.js", src.length - 6) !== -1)) {
                            MathJaxScriptFound = true;
                            elements[i].parentNode.removeChild(elements[i]);
                        };
                    };
                };
                elements = document.getElementsByTagName("script");
            };
            // Remove div with ID 'MathJax_Font_Test'
            var element = document.getElementById("MathJax_Font_Test");
            if (element) {
                element.parentNode.removeChild(element);
            };
            // Avoid empty elements in HTML-Syntax (<meta>, <link>, <br>, <hr>, ...),
            // this makes it possible to call Prince in XML mode with the generated
            // output from PhantomJS. In mode 'auto' this code can be disabled completely.
            // Please also note that for example the resulting <br></br> is not
            // compliant with HTML, so you should keep an eye on the output
            // generated by Prince. The same is probably true for <meta></meta>,
            // <link></link> and the like.
            elements = document.documentElement.getElementsByTagName("*");
            for (var i = elements.length - 1; i >= 0; i--) {
                if (elements[i].childNodes.length == 0) {
                    elements[i].appendChild(document.createTextNode(""));
                };
            };
        });
        console.log(page.content);
        phantom.exit();
    };
}

// Execute the query every 20ms
waitForMath = window.setInterval(
    waitForMathJax, 
    20
);


This is a sample setup for MathJax (ConfigMathJax.js). The MathJax.Hub.Config part can be changed, but you'll have to use the MathJax HTML-CSS output variant; for the moment I was unable to get it to work with the SVG output.

// A trigger which is queried by the script 
// render-math-and-dump.js in PhantomJS
var MathJaxFinished = false;

// A sample MathJax setup
MathJax.Hub.Config({
    displayAlign: "left",
    displayIndent: "3em",
    extensions: ["tex2jax.js"],
    // HTML Output
    jax: ["input/TeX", "output/HTML-CSS"],
    imageFont: null,
    messageStyle: "none",
    showProcessingMessages: false,
    showMathMenu: false,
    delayStartupUntil: "onload",
    tex2jax: {
        ignoreClass: "tex2jax_ignore",
        processClass: "math",
        inlineMath: [ ["$","$"], ["\\(","\\)"] ],
        displayMath: [ ["$$","$$"], ["\\[","\\]"] ],
        processEscapes: false
    },
    TeX: {
        extensions: ["AMSmath.js","AMSsymbols.js"],
        TagSide: "right",
        TagIndent: "1em",
        MultLineWidth: "85%",
        equationNumbers: {
           autoNumber: "AMS"
        },
        unicode: {
           fonts: "STIXGeneral,'Arial Unicode MS'"
        }
    },
    "HTML-CSS": {
        scale: 100,
        minScaleAdjust: 50,
        linebreaks: {
            automatic: true
        },
        // Anchors in formulas in black
        styles: {
          ".MathJax a": {
            color: "#000000"
          },
          ".MathJax": {
            "vertical-align": "baseline"
          }
        },
        mtextFontInherit: true,
        matchFontHeight: true,
        availableFonts: ["TeX"],
        preferredFont: "TeX",
        webFont: "TeX",
        imageFont: null
    }
});

// This function will be called when MathJax has finished *all* rendering
MathJax.Hub.Queue(
    function () {
        MathJaxFinished = true;
    }
);


The content of test.html:

<!DOCTYPE html>
<html>
<head>
	<script type="text/javascript" src="MathJax/MathJax.js"></script>
	<script type="text/javascript" src="ConfigMathJax.js"></script>
</head>
<body>

<p>Formula 1:
<span class="math">$$
\begin{equation}
    \label{Formula1.Label}
    P (\Omega) = \int_{-\infty}^{+\infty} f (x) \: dx = 1
\end{equation}
$$</span></p>

<p>Formula 2:
<span class="math">$$
\begin{equation}
    \label{Formula2.Label}
    E (X) := \int_{-\infty}^{+\infty} x \: f (x) \; dx
\end{equation}
$$</span></p>

<p>A third one, with references to 1 and 2:
<span class="math">$$
\begin{equation}
\begin{array}{ll}
    E (a X + b) &amp; =  \int_{-\infty}^{+\infty} (a x - b) \: f(x) \: dx
    \\
    \\ &amp; =
    a \int_{-\infty}^{+\infty} x \: f(x) \: dx + b \int_{-\infty}^{+\infty} f(x) \: dx
    \\
    \\ &amp; \underset{\eqref{Formula1.Label}, \eqref{Formula2.Label}}{=}
    a E(X) + b
\end{array}
\end{equation}
$$</span></p>

<p>And another one:
<span class="math">$$
\begin{equation}
    V(X) = \frac{a^2 + b^2 + c^2 - ab - ac - bc}{18}
\end{equation}
$$</span></p>

</body>
</html>


You can now use:

./phantomjs render-math-and-dump.js test.html | prince --javascript -o test.pdf -


The resulting PDF is attached. I also attached a rendering from the web (http://www.feynmanlectures.caltech.edu/I_47.html), due to the nature of processing it with the primitive script locally it lacks images and CSS but it shows that perhaps with some modifications rendering math from the web could be made possible.

The script does the following:

  • Load and render the document given on the commandline with PhantomJS (WebKit based).
  • Wait for MathJax to finish its work (can take quite long). The result is a DOM with all modifications made by MathJax.
  • Remove all MathJax related <script> tags.
  • Optionally create non-self-closing tags which enables the use of -i xml with Prince.
  • Dump the final DOM to the console.

I haven't tried it on Windows yet, but I think it will work the same way.

Please note that this isn't meant to be an out-of-the-box solution, I'm sure the script can be made better (I'm not a JS developer). For further information please look at the comments in the scripts.

And please forgive the formatting, the code area in this forum is a bit narrow, so just paste the code in your favorite editor.
  1. feynmanlectures_47.pdf153.0 kB
    Renedering from the web.
  2. test.pdf68.2 kB
    Rendering from test.html
mn4367
I forgot to mention that I've used MathJax 2.2.
mikeday
Great work! :)
mn4367
I've completely reworked the script to make it work with the MathJax SVG output renderer. It involved adding a bunch of DOM manipulations to get around the "SVG islands" problem in Prince but finally it works. Below is the new script (now wth a better formatting :-)) together with a sample MathJax configuration.

From a developer perspective the script is also a bit better structured, please also look at the comments in the scripts.

I'd recommend to use SVG with MathJax since Prince had problems rendering the classic MathJax HTML-CSS output (up to the point where I had to kill the process after several minutes). I think the script should also work if the MathJax HTML-CSS output is used since it relies on specific SVG elements in the DOM and if they are absent, the SVG handling simply does nothing.

One thing I noticed though: references to other equations don't work, they are rendered but clicking on them does nothing. It seems that Prince renders but ignores <a> in SVG.

Comments, critique, suggestions and reviews are welcome, thanks :-).

The new script:
// PhantomJS setup
var webPage = require('webpage');
var page = webPage.create();
var system = require('system');
var waitForMath;
var noLongerExecWaitForMathJax = false;

// Usage
if (system.args.length < 2) {
    console.log("Usage: phantomjs render-math-and-dump.js <filename>");
    console.log("Renders <filename> and dumps the DOM to the console.");
    phantom.exit();
};

// Open the page and on success set the window interval function
// Alternatives:
// page.open(encodeURI(system.args[1]), function(status) {
// page.open("file://" + encodeURI(system.args[1]), function(status) {
page.open(system.args[1], function(status) {
    if (status === "success") {
        // Execute the query every 20ms
        waitForMath = window.setInterval(
            waitForMathJax, 
            20
        );
    };
});

// This also works (to some extent) with web URLs.
// page.open(system.args[1], function(status){
//     if (status === "success") {
//         // In this setup a complete local MathJax installation
//         // is present in MathJax/
//         page.injectJs("MathJax/MathJax.js");
//         page.injectJs("ConfigMathJax.js");
//         // Execute the query every 20ms
//         waitForMath = window.setInterval(
//             waitForMathJax, 
//             20
//         );
//     };
// });


// Removes all MathJax <script> elements from the DOM.
// This avoids a second processing in PrinceXML, which
//  a) had nothing left to do (MathJax already).
//  b) probably would fail, since PrinceXML at the moment still lacks MathJax support
var removeMathJaxScripts = function() {
    // Does 'str' end with 'suffix'?
    var strEndsWith = function(str, suffix) {
        return str.indexOf(suffix, str.length - suffix.length) !== -1;
    };

    // Get and remove all MathJax related script elements
    var MathJaxScriptFound = true;
    var scripts = document.getElementsByTagName("script");
    // Repeating this loop until no more matching scripts are found seems 
    // to be necessary because removing children from the DOM did not always 
    // happen immediately, some of them were still written to the output when
    // dumping 'page.content'.
    while (MathJaxScriptFound) {
        MathJaxScriptFound = false;
        for (var i = scripts.length - 1; i >= 0; i--) {
            if (scripts[i].hasAttribute("id")) {
                var id = scripts[i].getAttribute("id");
                // Remove script if 'id' starts with 'MathJax-Element'
                if (id.indexOf("MathJax-Element") === 0) {
                    MathJaxScriptFound = true;
                    scripts[i].parentNode.removeChild(scripts[i]);
                };
            } else if (scripts[i].hasAttribute("src")) {
                var src = scripts[i].getAttribute("src");
                // Remove script if 'src' ends on 'ConfigMathJax.js', 
                // 'MathJax.js' or 'jax.js'
                if ((strEndsWith(src, "ConfigMathJax.js")) 
                    || (strEndsWith(src, "MathJax.js")) 
                    || (strEndsWith(src, "jax.js"))) {
                    MathJaxScriptFound = true;
                    scripts[i].parentNode.removeChild(scripts[i]);
                };
            };
        };
        scripts = document.getElementsByTagName("script");
    };

    // Remove div with ID 'MathJax_Font_Test'
    var fontTest = document.getElementById("MathJax_Font_Test");
    if (fontTest) {
        fontTest.parentNode.removeChild(fontTest);
    };

    // Avoid empty HTML-Syntax elements (<meta>, <link>, <br>, <hr>, ...),
    // this makes it possible to call PrinceXML in XML mode with the generated
    // output from PhantomJS. In mode 'auto' this code can be disabled 
    // completely. Please also note that for example the resulting <br></br> 
    // is not compliant with HTML, so you should keep an eye on the output
    // generated by PrinceXML. The same is probably true for <meta></meta>,
    // <link></link> and the like.
    elements = document.documentElement.getElementsByTagName("*");
    for (var i = elements.length - 1; i >= 0; i--) {
        if (elements[i].childNodes.length == 0) {
            elements[i].appendChild(document.createTextNode(""));
        };
    };
};


// The problem with processing SVG formulas generated by MathJax in PrinceXML
// is the location of the SVG glyphs which are used by MathJax to construct
// formulas. MathJax creates an SVG path for every symbol/letter in a hidden
// div at the beginning of the document. Every glyph (actually an SVG path)
// has an ID. In every formula the glyphs are only referenced by this ID and
// *not* recreated every time the glyph is needed. This keeps the amount of 
// markup added to the document small.
//
// Currently PrinceXML isn't able to use links to elements wich are defined
// in another SVG element (the so called SVG "islands" problem, mentioned for
// example here /forum/topic/1402/multiple-svg-regions-with-common-defs and
// here /forum/topic/2912/what-forms-does-prince-support-css-clip-path).
//
// This function collects all MathJax glyph definitions and copies them to any
// SVG formula element where they are used. The function does not copy *all*
// definitions to *all* formulas, only those needed for the current formula
// are copied.
//
// The function still has (at least) one "bug": By copying a glyph to more 
// than one formula you'll end up with elements that have the same ID in the 
// resulting document. For PrinceXML it doesn't seem to be a problem, but a
// validator will surely find it faulty.
var relocateMathJaxGlyphs = function () {
    // Container holding all MathJax glyphs, declared here for later removal.
    var mathJaxSVGGlyphContainer = null;

    // Get an array of all MathJax SVG paths which form a glyph.
    var getMathJaxGlyphs = function () {
        // These glyphs are located inside an SVG 'defs' element and
        // are later used/referenced to layout every formula.
        // This code currently only checks for the presence of one 
        // glyph container (the first).
        var glyphContainer = document.getElementById("MathJax_SVG_glyphs");
        if (!glyphContainer) {
            return [];
        };
        // Hold the container for later removal to avoid too many DOM queries.
        var gcpn = glyphContainer.parentNode;
        if (gcpn) {
            mathJaxSVGGlyphContainer = gcpn;
        };
        // This could be changed to use '*' instead of 'path', but so far
        // I haven't seen that MathJax uses something different than 'path'.
        var paths = glyphContainer.getElementsByTagName("path");
        var result = [];
        for (var i = paths.length - 1; i >= 0; i--) {
            var id = paths[i].getAttribute("id");
            if (id) {
                result.push({id:id, path:paths[i]});
            };
        };
        return result;
    };

    // Get an array of all MathJax SVG display areas (formulas).
    var getMathJaxSVGs = function () {
        // SVG formulas are located in a span with class name 'MathJax_SVG'.
        var svgContainers = document.getElementsByClassName("MathJax_SVG");
        if (svgContainers.length === 0) {
            return [];
        };
        var result = [];
        for (var i = svgContainers.length - 1; i >= 0; i--) {
            // Handle all embedded SVG containers (normally there should be
            // only one, but it doesn't hurt to visit all).
            var svgs = svgContainers[i].getElementsByTagName("svg");
            for (var a = svgs.length - 1; a >= 0; a--) {
                // At this stage we can already create the <defs> element (if
                // not present). The element is supposed holds copies of the
                // glyphs and later will be appended to *this* SVG element.
                var defs;
                var defsElements = svgs[a].getElementsByTagName("defs");
                if (defsElements.length === 0) {
                    defs = document.createElement("defs");
                } else {
                    defs = defsElements[0];
                };
                result.push({svg:svgs[a], defs:defs});
            };
        };
        return result;
    };

    // Find the matching glyph path by id in 'glyphs' and return a clone
    var getGlyphById = function (id, glyphs) {
        for (var i = glyphs.length - 1; i >= 0; i--) {
            // id and path are assumed to be always non-null
            if (id === glyphs[i].id) {
                return glyphs[i].path.cloneNode(true);
            };
        };
        return null;
    };

    // Copy every SVG glyph referenced in the SVG element into the <defs>
    // element (this was already created in getMathJaxSVGs, if not present).
    var copySVGGlyphsToSVGDefsElement = function (svgObject, glyphs) {
        // Get all nested <use> elements
        var useElements = svgObject.svg.getElementsByTagName("use");
        for (var i = useElements.length - 1; i >= 0; i--) {
            // First lookup the xlink:href attribute. If the attribute is 
            // present verbatim in this form we'll take this one.
            var id = useElements[i].getAttribute("xlink:href");
            if (!id) {
                // Next try the ordinary href attribute. Although PhantomJS 
                // later writes this as 'xlink:href' it seems that it only 
                // can be accessed using 'href'.
                id = useElements[i].getAttribute("href");
            };
            // No suitable id found...
            if ((!id) || (id.length < 2)) {
                continue;
            };
            // Get the matching glyph as a clone from the list of all glyps
            // and append it to the <defs> element.
            var glyph = getGlyphById(id.slice(1), glyphs);
            if (glyph) {
                svgObject.defs.appendChild(glyph);
            } else {
            };
        };
        // Finally append the <defs> element to its parent SVG element
        svgObject.svg.appendChild(svgObject.defs);
    };

    // This is the main loop which ...
    // ... gets all glyphs ...
    var mathJaxGlyphs = getMathJaxGlyphs();
    if (mathJaxGlyphs.length > 0) {
        // ... gets all formulas ...
        var mathJaxSVGs = getMathJaxSVGs();
        if (mathJaxSVGs.length > 0) {
            for (var i = mathJaxSVGs.length - 1; i >= 0; i--) {
                // ... and copies matching glyphs into every formula.
                copySVGGlyphsToSVGDefsElement(mathJaxSVGs[i], mathJaxGlyphs);
            };
        };
    };

    // Finally we can remove the glyph container from the DOM
    if (mathJaxSVGGlyphContainer) {
        mathJaxSVGGlyphContainer
          .parentNode
          .removeChild(mathJaxSVGGlyphContainer);
    };

};


// Query a variable named 'MathJaxFinished' in the document,
// see also ConfigMathJax.js.
var waitForMathJax = function () {
    if (noLongerExecWaitForMathJax) {
        return;
    };
    var mjFinished = page.evaluate(function () {
        return MathJaxFinished;
    });
    if (mjFinished) {
        // waitForMathJax was called at least twice (?), even after
        // window.clearInterval(). This flag prevents it (see above).
        noLongerExecWaitForMathJax = true;
        window.clearInterval(waitForMathJax);

        // Remove the scripts
        page.evaluate(removeMathJaxScripts);

        // Relocate/copy all SVG glyphs (paths)
        page.evaluate(relocateMathJaxGlyphs);

        // Dump the DOM to the console and exit.
        console.log(page.content);
        phantom.exit();
    };
}


MathJax with SVG output:
// A trigger which is queried by render-math-and-dump.js in PhantomJS
var MathJaxFinished = false;

// This function will be called when MathJax has finished *all* rendering
MathJax.Hub.Queue(
    function () {
        MathJaxFinished = true;
    }
);

// A sample MathJax setup for SVG
MathJax.Hub.Config({
  displayAlign: "left",
  displayIndent: "3em",
  extensions: ["tex2jax.js"],
  // Tex input, SVG output
  jax: ["input/TeX", "output/SVG"],
  imageFont: null,
  messageStyle: "none",
  showProcessingMessages: false,
  showMathMenu: false,
  delayStartupUntil: "onload",
  tex2jax: {
    ignoreClass: "tex2jax_ignore",
    processClass: "math",
    inlineMath: [ ["$","$"], ["\\(","\\)"] ],
    displayMath: [ ["$$","$$"], ["\\[","\\]"] ],
    processEscapes: false,
    preview: "none"
  },
  TeX: {
    extensions: ["AMSmath.js","AMSsymbols.js", "color.js"],
    TagSide: "left",
    TagIndent: "0em",
    MultLineWidth: "85%",
    equationNumbers: {
      autoNumber: "AMS"
    },
    unicode: {
      fonts: "STIXGeneral,'Arial Unicode MS'"
    }
  },
  SVG: {
    font: "TeX",
    //scale: 90,
    minScaleAdjust: 50,
    linebreaks: {
      automatic: true
    },
    styles: {
      // Avoid rectangles with a black surrounding stroke in PrinceXML
      ".mjx-svg-href" : {
        fill: "none",
        stroke: "none"
      },
      // Links to other formulas in black
      ".MathJax_ref" : {
        fill: "#000000",
        stroke: "none"
      },
      ".MathJax_SVG": {
        //"vertical-align": "baseline"
        //"vertical-align": "-1px"
      }
    },
      matchFontHeight: true,
      mtextFontInherit: true,
      addMMLclasses: false
  }
});

mikeday
<a> links should work in SVG, but there may be limits on what you can link to. I think at the moment links into other SVG elements do not work.
mn4367
Yes, that seems to be the problem.
skycaptain
I'm using a similar approach, but instead of phantomjs I'm using slimerjs. Using phantomjs I'm running into some problems with the JavaScriptCore engine from time to time. Since slimerjs is just a headless Firefox browser, it interprets javascript code exactly like Firefox, which makes debugging a snap.
mn4367
A follow up to this post:

Adding this snippet
// Set missing SVG namespace
svgs[a].setAttribute("xmlns", "http://www.w3.org/2000/svg");

immediately after this line
for (var a = svgs.length - 1; a >= 0; a--) {

in the script above fixes the missing SVG namespace issue.
yaxhpal
Hey mn4367,

First of all, great work! I have been looking for such a solution for last couple of weeks. Earlier what I did was to render entire html on the actual browser and then get the document posted to server where server would process that with prince and it worked. Now, I tried your solution and it worked like charm. But, still I am not able to solve the issue of thin lines which come after every formulae rendered using HTML-CSS output processor. Please see below



I can see same problem with your own feynmanlectures_47.pdf‎. Although, SVG output gets rid of this but due to some constraints, I want to use only HTML-CSS.

Any Suggestions?

Thank you.

mn4367
Hi yaxhpal,

the only suggestion I have is to try to tweak the CSS styles for the HTML-CSS renderer. You can do this in the "HTML-CSS:" section of the MathJax.Hub.Config call, it contains a sub-section named "styles:".

I had similar issues with the SVG renderer (black background boxes for equation references) which I could turn off with adapting some styles in the same "styles:" sub-section (for SVG).

You could try the following:
  • Create a document with a very simple equation.
  • Process the document with PhantomJS and save the generated DOM to a local file.
  • Open this file in your regular browser and inspect it with the developer tools.
  • Try to find the "offending" element and see if there is a CSS style for it.
  • In the MathJax.Hub.Config call: tweak this style or create an appropriate CSS selector for it.
My assumption is that there is a block level container element for every equation for which the background and/or margin color could be "turned off".

Please keep in mind that the HTML-CSS output takes a lot longer to render in Prince due to the sheer amount of elements and CSS styles which this renderer creates, at least that's what I've seen, if I recall correctly.

What are the constraints you see with the SVG renderer?

Thanks and kind regards,
Michael
yaxhpal
Hi Michael,

Thank you for your response, immensely appreciate your suggestion. I followed your suggestion regarding finding out offending "element", and indeed there was one. It is this one:

<span style="border-left-width: 0.003em; border-left-style: solid; ...."></span>



In my HTML-CSS output, every formula has following structure

<div class="MathJax_Display">
  <span class="MathJax" id="MathJax-Element-...">
    <nobr>
      <span class="math" id="MathJax-Span-..." ....>
        <span style="display: inline-block;..."> ...</span>
        <span style="border-left-width: 0.003em; border-left-style: solid; ...."></span>
      </span>
    </nobr>
  </span>
</div>



In above code, nobr block has one span block, which in turn has two span blocks. The first one holds the equation and second one is just empty block. I am at this stage not sure for what purpose it is used. As, there is no id or class, I couldn't use usual selectors.So, what I did is to have small css snippet at document level as shown below:


    <style>
      nobr > span > span:last-child[style] {
        border-left-width: 0em !important; 
        border-left-style: none !important;
      }
    </style>


I am not sure if this is the right approach but it worked for now.

Regarding the constraints, there are not so much constraints as such but I am working on old project, they already have their configurations regarding HTML-CSS processor which is being used throughout application. May be some day, I will be able to convince them regarding SVG processor and your points would be the reference :)

Thank you once again.

Kind Regards,

Yashpal.




mn4367
Hi Yashpal,

you're welcome, glad to hear that it works for you now :-)!

And thank you for your detailed description, I hope it helps others using PhantomJS for math processing in Prince.

Kind regards,
Michael
mikeday
By the way, links into SVG elements now work in Prince latest builds.
Balaa
Hi, I have tried with windows, but it's not working for me.

argument:
phantomjs render-math-and-dump.js test.html | prince --javascript -o test.pdf

Attached the js files and html file.
  1. ConfigMathJax.js1.7 kB
  2. render-math-and-dump.js11.9 kB
  3. test.html4.6 kB
mn4367
What exactly is the problem? Don't you get any output, or is the content of the resulting PDF not what you expect?

Which versions of Prince and PhantomJS are you using?
Balaa
Equations are not work with mathJax library. MathML/Tex equations are not resulted properly in prince natrual flow, so i'm try with MathJax, I have gone through above subject and try with the same.

Please find the attached resulted output PDF.

PhantomJS version: phantomjs-2.1.1-windows
Prince : prince-20170629-win32 (Latest Build)
  1. output.pdf51.6 kB
Balaa
I have attached the javascript files and html file. Unable to attach the mathJax library.
  1. ConfigMathJax.js1.7 kB
  2. render-math-and-dump.js11.9 kB
  3. test.html4.6 kB
hallvord
Hi, this is an issue that happens while PhantomJS runs, well before Prince starts rendering any content. I think most likely the part of the script intended to detect when MathJax is "finished" jumps to this conclusion too early. Experiment with editing ConfigMathJax.js and try calling the MathJaxFinished = true part from a timeout instead..?

Announcement: repos for tests/utils

mn4367
No, according to the MathJax docs (at the end of the page) the Queue function ensures that when called, MathJax will have finished all of its work so
MathJax.Hub.Queue(
    function () {
        MathJaxFinished = true;
    }
);

is still correct in my opinion.

The problem is a different one. Your test file is named test.html.xml, if you just rename it to test.html it works (at least on my system here). Note: removing the XML-Header at the top of your file and all other attempts to remove any XML related declarations didn't help. It seems that PhantomJS (as well as my version of Safari) don't care about it at all and only rely on the file ending.

If you stick to XML format there are also other problems, Safari (and I believe PhantomJS as well) claim about missing entity definitions (like &nbsp;). As a consequence PhantomJS never returned from processing and thus never set MathJaxFinished to true making the script waiting forever. Adding a different DOCTYPE which makes the browser happy didn't solve the problem, I was unable to display the formulas in any browser.

I did all tests with the latest versions of PhantomJS, Prince and MathJax (albeit on a Mac).

Maybe you can submit your document to the MathJax community and ask how it has to be changed to be accepted in XML format in browsers?
  1. test.pdf76.7 kB
Balaa
Thanks, I've tried with HTML file and it's working for me.

MathJax with HTML output
  1. test.pdf391.3 kB
mn4367
Fine, good to hear that it works.
lukasbestle
I found another workaround that is a lot easier to implement as it doesn't require PhantomJS.
Instead I'm using the mathjax-node-page npm module.

You can install it like this:
npm install -g mathjax-node-page


After that, you can run your HTML files (in this example the file input.html) through it like so:
mjpage --output MML < input.html > output.html


It generates an HTML file called output.html. This file can then be run through Prince without any further changes.
Because it uses the MathML output of MathJax, the result is also a lot cleaner and doesn't trigger any unsupported CSS property warnings in Prince.
Balaa
Thanks for the update. I will try this method and get back.

Can I run XML file?
lukasbestle
Balaa wrote:
Can I run XML file?

I haven't tested this, but I think the tool will parse the XML as HTML (which will mostly work just fine) and apply its MathJax conversion magic to the parsed DOM just like normal. It would be interesting to test this.

Edited by lukasbestle

Bala
Have you tested this?
Bala
This method is not working for me, attached input/output files.
  1. input.html3.7 kB
  2. output.html29.0 kB
lukasbestle
Your input file already contains MathML code, it doesn't need to be converted using MathJax.
You can run the file through Prince directly.
EPO
Hi all

I am having problems rendering MathJax 2.7 with Prince 12, following the instructions provided by mn4367.

When viewing test.html in my browser (Chrome) the equations show up like i would expect, but when using Prince to create a pdf, the equations turn out way to bold. The equation numbers also end up to big.

I have attached test.html and the output test.pdf

I am running the html through Phantomjs, but the output html shows up just fine like test.html.

Any help would be greatly appreciated.
  1. ConfigMathJax.js1.8 kB
  2. render-math-and-dump.js11.9 kB
  3. test.html1.1 kB
  4. test.pdf53.3 kB
Eara_2018
Prince doesn't support the MathJax directly in HTML, then how you have created a PDF.
mn4367
@EPO: Maybe this thread can help?

With regard to the equation numbers, have you checked, if there is a CSS style applied to the numbers? It's well possible that yo can adjust the size for these numbers wit a custom style.

@Eara_2018: Prince does not yet support MathJax, but you can work around it using the instructions in this thread :-).
Eara_2018
It's not working for MathML inputs.


  1. ConfigMathJax.js1.7 kB
  2. render-math-and-dump.js4.9 kB
  3. test.htm5.0 kB
mn4367
It's not working for MathML inputs.


I never intended to support MahtML as an output format, the solution only targets SVG output.
EPO
@mn4367 thank you very much for your help. I was able to solve the problem by using the file you used in the thread you linked.

I had to use blacker: 0 and mtextFontInherit: false in the svg output processor options in MathJax.

It is working great now :)