5

I've spent days about how to render MathJax into PDF on client-side only (using several libraries like jsPDF, etc.) for the open source project Writing. I have tried many different options, without any success.

Here is a code showing the problem in my latest attempt, based on this answer.

  MathJax.Hub.Config({ tex2jax: {inlineMath: [["$","$"],["\\(","\\)"]]} });

document.getElementById("getPdf").addEventListener("click", getPdf);

function getPdf() {
  var svg = document.getElementById('main').innerHTML;
  if (svg)
    svg = svg.replace(/\r?\n|\r/g, '').trim();

  var canvas = document.createElement('canvas');
  var context = canvas.getContext('2d');

  context.clearRect(0, 0, canvas.width, canvas.height);
  canvg(canvas, svg);

  var imgData = canvas.toDataURL('image/png');

  var doc = new jsPDF('p', 'pt', 'a4');
  doc.addImage(imgData, 'PNG', 40, 40, 75, 75);
  doc.save('test.pdf');
}  
<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML-full"></script>
<script src="https://cdn.rawgit.com/canvg/canvg/master/canvg.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.2.61/jspdf.min.js"></script>

<p id="main">
When $a \ne 0$, there are two solutions to \(ax^2 + bx + c = 0\) and they are
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$
</p>

<button id="getPdf">Get PDF</button>

Question:

How to render HTML + MathJax content into PDF, on client-side only?

Community
  • 1
  • 1
Basj
  • 41,386
  • 99
  • 383
  • 673

1 Answers1

1

My short answer would be: don't do this.

The long answer is you can make this work but the result will be inferior to providing a print stylesheet and letting users save the output as PDF. For starters, you will create a PDF with a single (potentially huge) PNG in it; that will be terrible for printing.

The main problem with your code is that canvg can only handle SVG content, not arbitrary web content, so you need to use another tool.

But generally there are limitations for injecting HTML content in canvas elements (for security reasons).

Finally, you will need to force MathJax's AssistiveMML extension to be off to avoid duplicate content.

Below is a snippet but it fails on SO for the security reasons mentioned above; you can try it on codepen though.

MathJax.Hub.Queue(function (){
  var canvas = document.getElementById("canvas");
  var main = document.getElementById("main");
  rasterizeHTML.drawHTML(main.outerHTML,canvas);

})
document.getElementById("getPdf").addEventListener("click", getPdf);

function getPdf() {
  var imgData = canvas.toDataURL('image/png');

  var doc = new jsPDF('p', 'pt', 'a4');
  doc.addImage(imgData, 'PNG', 40, 40, 400, 200);
  doc.save('test.pdf');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.2.61/jspdf.min.js"></script>
<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
    extensions: ["tex2jax.js"],
  "AssistiveMML": {
    disabled: true
  },
  SVG: {
    addMMLclasses: true,
    useGlobalCache: false
  },
  });
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_SVG-full"></script>
<script   src="https://cdnjs.cloudflare.com/ajax/libs/rasterizehtml/1.2.4/rasterizeHTML.allinone.js"></script>
<p id="main">
When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are
$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$
</p>
<h1>As canvas</h1>
    <canvas id="canvas" width="400" height="200"></canvas>

<button id="getPdf">Get PDF</button>
Peter Krautzberger
  • 5,145
  • 19
  • 31
  • Thanks for your answer. It doesn't seem to render correctly (even in Codepen, the part after "As canvas" is rendered with just horizontal lines instead of math). It looks [like this](http://gget.it/dvy4aa6g/8.jpg). – Basj Mar 14 '17 at 15:46
  • Something else: I don't really want to render the whole thing as a big PNG. In fact, I'd like to keep the text as text, and only render the MathJax parts as PNG or SVG, which, then, can be saved into a PDF with jsPDF or similar library. – Basj Mar 14 '17 at 15:48
  • 1
    Last thing: if we look at the big picture, I don't really agree with "don't do this". There *should be a solution* : what I want [here in this page](https://josephernest.github.io/Writing) is to let the user write markdown and math on left. It *should be possible* to export as PDF. I mean, from a user point of view. A user *should* be able to export as PDF :) So I have to find how to do it... ;) – Basj Mar 14 '17 at 15:50
  • The white-with-lines rendering indicates that the configuration isn't picked up. I noticed a trailing comma and fixed it; see if that helps or try a local copy in a modern browser – Peter Krautzberger Mar 14 '17 at 16:16
  • The big PNG issue can be solved but jsPDF is a low-level library that allows you to place content on the PDF canvas. You'd have to re-implement a lot of browser layout functionality on top of jsPDF to get this done. – Peter Krautzberger Mar 14 '17 at 16:17
  • I don't know whether a solution would be useful. AFAIK there isn't a client-side solution right now. And while I could imagine a Web API that gives access to built-in 'print-to-pdf', not all browsers have such a feature so it seems an unlikely proposal (especially with some browsers providing "save as PDF" buttons, e.g., Safari). I suspect a button for triggering the print dialog (which will will use a print stylesheet) might be enough in terms usability. – Peter Krautzberger Mar 14 '17 at 16:27
  • Thanks for your answers. It seems it works on Chrome, but not on FF47 (which is recent) : on FF I still have the horizontal lines. About `You'd have to re-implement a lot of browser layout functionality on top of jsPDF` : If I remember well, jsPDF totally allows mix of HTML and other elements out-of-the-box, or am I wrong? – Basj Mar 14 '17 at 16:42
  • I would like to avoid triggering "Print as PDF" and to avoid using browser's PDF printing feature, because sometimes it won't be available for some users. Also, it won't be consistent from one user to another. – Basj Mar 14 '17 at 16:44
  • Imagine [this tool](https://josephernest.github.io/Writing/) is just a LaTeX+markdown online editor. The user will want to be able to export as PDF and to get consistent results even if he changes his browser. I'm willing to spend time for this, and do open-source contributions. What solution do you think I should investigate to be able to have a consistent MathJax -> PDF (without having one big PNG or SVG) ? Thanks in advance. – Basj Mar 14 '17 at 16:46