Search code examples
phppdfsvgmathjaxmpdf

MathJax + mPDF SVG Equations not visible


I've got a problem with converting MathJax SVGs to PDF using mPDF.

HTML is just a simple formula from mPDF example:

\[ \left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right) \]

which should be rendered as:

example

But of course on final PDF file there's nothing.

HTML is send by POST to PHP file

$path = (getenv('MPDF_ROOT')) ? getenv('MPDF_ROOT') : __DIR__;
require_once $path . '/vendor/autoload.php';
$mpdf = new \Mpdf\Mpdf(['debug' => false, 'allow_output_buffering' => true]);
$sizeConverter = new \Mpdf\SizeConverter($mpdf->dpi, $mpdf->default_font_size);

if (strpos($_REQUEST['bodydata'], 'id%3D%22MathJax_SVG_Hidden%22') === false) {
    die('Hacking attempt');
}

$html = $_POST['bodydata'];
$html = urldecode($html);

preg_match('/<svg[^>]*>\s*(<defs.*?>.*?<\/defs>)\s*<\/svg>/', $html, $m);
$defs = $m[1];

$html = preg_replace('/<svg[^>]*>\s*<defs.*?<\/defs>\s*<\/svg>/', '', $html);
$html = preg_replace('/(<svg[^>]*>)/', "\\1" . $defs, $html);

preg_match_all('/<svg([^>]*)style="(.*?)"/', $html, $m);

for ($i = 0; $i < count($m[0]); $i++) {
    $style = $m[2][$i];
    preg_match('/width: (.*?);/', $style, $wr);
    $w = $sizeConverter->convert($wr[1], 0, $mpdf->FontSize) * $mpdf->dpi / 25.4;

    preg_match('/height: (.*?);/', $style, $hr);
    $h = $sizeConverter->convert($hr[1], 0, $mpdf->FontSize) * $mpdf->dpi / 25.4;

    $replace = '<svg' . $m[1][$i] . ' width="' . $w . '" height="' . $h . '" style="' . $m[2][$i] . '"';
    $html = str_replace($m[0][$i], $replace, $html);
}
if ($_POST['PDF'] === 'PDF') {
    $stylesheet = '
        img {
            vertical-align: middle;
        }
        .MathJax_SVG_Display {
            padding: 1em 0;
        }
        #mpdf-create {
            display: none;
        }
    ';
    $mpdf->WriteHTML($stylesheet, 1);
    $mpdf->WriteHTML($html);
    $mpdf->Output();
}
exit;

It's copy of: official mPDF example, pretty buggy, before POST request JS copy 'width' and 'height' attribute to 'styles' attribute so mPDF can correctly read it.

Generated SVG:

<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="35.62ex" height="7.841ex" style="vertical-align: -3.082ex; width: 35.62ex; height: 7.841ex;" viewBox="0 -2049.4 15336.2 3376.2"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="matrix(1 0 0 -1 0 0)"><use xlink:href="#MJSZ4-28"></use><g transform="translate(792,0)"><use xlink:href="#MJSZ2-2211" x="0" y="0"></use><g transform="translate(85,-1110)"><use transform="scale(0.707)" xlink:href="#MJMATHI-6B" x="0" y="0"></use><use transform="scale(0.707)" xlink:href="#MJMAIN-3D" x="521" y="0"></use><use transform="scale(0.707)" xlink:href="#MJMAIN-31" x="1300" y="0"></use></g><use transform="scale(0.707)" xlink:href="#MJMATHI-6E" x="721" y="1627"></use><g transform="translate(1611,0)"><use xlink:href="#MJMATHI-61" x="0" y="0"></use><use transform="scale(0.707)" xlink:href="#MJMATHI-6B" x="748" y="-213"></use></g><g transform="translate(2609,0)"><use xlink:href="#MJMATHI-62" x="0" y="0"></use><use transform="scale(0.707)" xlink:href="#MJMATHI-6B" x="607" y="-213"></use></g></g><use xlink:href="#MJSZ4-29" x="4300" y="0"></use><use transform="scale(0.707)" xlink:href="#MJMAIN-32" x="7202" y="2090"></use><use xlink:href="#MJMAIN-2264" x="5824" y="0"></use><g transform="translate(6880,0)"><use xlink:href="#MJSZ4-28"></use><g transform="translate(792,0)"><use xlink:href="#MJSZ2-2211" x="0" y="0"></use><g transform="translate(85,-1110)"><use transform="scale(0.707)" xlink:href="#MJMATHI-6B" x="0" y="0"></use><use transform="scale(0.707)" xlink:href="#MJMAIN-3D" x="521" y="0"></use><use transform="scale(0.707)" xlink:href="#MJMAIN-31" x="1300" y="0"></use></g><use transform="scale(0.707)" xlink:href="#MJMATHI-6E" x="721" y="1627"></use><g transform="translate(1611,0)"><use xlink:href="#MJMATHI-61" x="0" y="0"></use><use transform="scale(0.707)" xlink:href="#MJMAIN-32" x="748" y="488"></use><use transform="scale(0.707)" xlink:href="#MJMATHI-6B" x="748" y="-463"></use></g></g><use xlink:href="#MJSZ4-29" x="3401" y="0"></use></g><g transform="translate(11241,0)"><use xlink:href="#MJSZ4-28"></use><g transform="translate(792,0)"><use xlink:href="#MJSZ2-2211" x="0" y="0"></use><g transform="translate(85,-1110)"><use transform="scale(0.707)" xlink:href="#MJMATHI-6B" x="0" y="0"></use><use transform="scale(0.707)" xlink:href="#MJMAIN-3D" x="521" y="0"></use><use transform="scale(0.707)" xlink:href="#MJMAIN-31" x="1300" y="0"></use></g><use transform="scale(0.707)" xlink:href="#MJMATHI-6E" x="721" y="1627"></use><g transform="translate(1611,0)"><use xlink:href="#MJMATHI-62" x="0" y="0"></use><use transform="scale(0.707)" xlink:href="#MJMAIN-32" x="607" y="488"></use><use transform="scale(0.707)" xlink:href="#MJMATHI-6B" x="607" y="-463"></use></g></g><use xlink:href="#MJSZ4-29" x="3301" y="0"></use></g></g></svg>

And of course warnings:

[29-Dec-2017 21:42:39 UTC] PHP Notice:  Undefined index: x in D:\Praca\ENVIROMENTS\laragon-apache\laragon\www\wp-content\plugins\szalpdf\pdf\vendor\mpdf\mpdf\src\Image\Svg.php on line 3027
[29-Dec-2017 21:42:39 UTC] PHP Notice:  Undefined index: y in D:\Praca\ENVIROMENTS\laragon-apache\laragon\www\wp-content\plugins\szalpdf\pdf\vendor\mpdf\mpdf\src\Image\Svg.php on line 3027
[29-Dec-2017 21:42:39 UTC] PHP Notice:  Undefined index: w in D:\Praca\ENVIROMENTS\laragon-apache\laragon\www\wp-content\plugins\szalpdf\pdf\vendor\mpdf\mpdf\src\Image\Svg.php on line 3027
[29-Dec-2017 21:42:39 UTC] PHP Notice:  Undefined index: h in D:\Praca\ENVIROMENTS\laragon-apache\laragon\www\wp-content\plugins\szalpdf\pdf\vendor\mpdf\mpdf\src\Image\Svg.php on line 3027
[29-Dec-2017 21:42:39 UTC] PHP Warning:  Division by zero in D:\Praca\ENVIROMENTS\laragon-apache\laragon\www\wp-content\plugins\szalpdf\pdf\vendor\mpdf\mpdf\src\Tag.php on line 4015

It's probably error with reading viewBox data but even with rigid values PDF file seems to be empty. Without errors, just ignores SVG. I've been sitting with this since yesterday and I'm still in the same place. MathJax is must have here. I'm considering changing the mPDF to something else but I prefer to avoid it.

No ideas, I'm stuck.

Latest MathJax /
mPDF v7.0.0 /
PHP: 7.1.1 /
PHP GD2 extension enabled


Solution

  • So, I figured it out. If someone was looking for an answer (we're talking now about mPDF MathJaxProcess.php file)

    1. The damn fill="currentColor" attribute in SVG doesn't work in mPDF.
    2. Examples are totally broken.

      • First to avoid errors and warnings with NULLs in mPDF sizeConverter you have to copy width and height attributes to inline SVG styles. I did it using JS before sending the form with html. It's actually mistake in example rather than lib bug.
      • Second, Line 29, $m[1][$i] brokes everything. Variable has it's own width and height attribute, with unconverted values wich using ex units. This creates conflict with our own width and height attribute and then notices like Undefined Index and Division by zero appears.

    After fixing above mPDF should work with MathJax correctly. I'm also sanitizing SVGs before sending form. By sanitizing i mean:

    1. Copy styles attributes to inline styles
    2. Remove attributes like focusable, aria-hidden, role. (Non-numeric values)
    3. Setting fill attribute of g tags to #000
    4. Removing .MJX_Assistive_MathML class element to avoid duplicated content.

    Pretty messy JS code for that, but you'll get the idea.

    We're skipping first SVG in our loop and that's because it has only definitions that will be copied directly into SVG tag in PHP script.

    <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_SVG">
       MathJax.Hub.Register.StartupHook("End",function () {
         var list = document.getElementsByTagName("svg");
         for (var i = 1; i < list.length; i++) {
           var w = list[i].getAttribute("width");
           var h = list[i].getAttribute("height");
            list[i].style.width = w;
            list[i].style.height = h;
            list[i].removeAttribute("focusable");
            list[i].removeAttribute("aria-hidden");
            list[i].removeAttribute("role");
          }
    
          var gList = document.getElementsByTagName("g");
          for (var i = 0; i < gList.length; i++) {
            gList[i].setAttribute("fill", "#000");
           }
    
           document.querySelectorAll(".MJX_Assistive_MathML").forEach(function(a){
             a.remove()
           });
    
         document.getElementById("bodydata").value=encodeURIComponent(document.body.innerHTML);
         var el = document.getElementById("pdfform");
         el.submit();
       });
     </script>
    

    Related mPDF issue on Github

    Edit: If you've got broken svg in chrome or firefox: When rendering from Firefox mPDF expects parameter viewbox When rendering from Chrome mPDF expects parameter viewBox It's case sensitive. I was hunting this bug from a week now.