How to build JavaScript code to convert a Latex formula to Inkscape as SVG format

When I’m writing a blog about mathematics, I often put mathematical expressions into graphs created with Inkscape, but there is no tool that I think can be used easily, so I made one myself. I use Mathjax to draw the equations.

In this lesson, you will learn how to insert Mathjax-generated SVG code into Inkscape and display the formulas on your blog.

It also explains how to download Mathjax-generated SVG code into a format that can be inserted into Inkscape, and how to copy the code to the clipboard.

Features

The downloaded SVG file is scaled so that it will be displayed at a reasonable size when imported by Inkscape: without scaling, it will be imported as a large object that extends far beyond the paper.

In addition, you can copy the displayed code to the clipboard. In this case, no scale adjustment will be performed.

One way to use this feature is to paste the copied code directly into your HTML. In this way, you can easily insert mathematical expressions into your website or blog without installing Mathjax.

In the case of WordPress, you can add formulas to your blog posts by writing the copied code in “Add Block” -> “Custom HTML”.

This blog is also built on WordPress. The above is a sample. As long as your browser is not too old, you should be able to see it without any problems. This formula is displayed by writing SVG code directly into the HTML, instead of installing Mathjax.

Unresolved problem

For some reason, the wrapping function that was available in ver2 is not supported in ver3. I use Mathjax ver3 for this project.

How the code works

<script>
MathJax = {
  loader: {load: ["input/tex", "output/svg"]}
};
MathJax = {
  svg: {
    scale: 1,                      // global scaling factor for all expressions
    minScale: .001,                  // smallest scaling factor to use
    mtextInheritFont: false,       // true to make mtext elements use surrounding font
    merrorInheritFont: true,       // true to make merror text use surrounding font
    mathmlSpacing: false,          // true for MathML spacing rules, false for TeX rules
    skipAttributes: {},            // RFDa and other attributes NOT to copy to the output
    exFactor: .5,                  // default size of ex in em units
    displayAlign: 'left',        // default for indentalign when set to 'auto'
    displayIndent: '0',            // default for indentshift when set to 'auto'
    fontCache: 'local',            // or 'global' or 'none'
    localID: null,                 // ID to use for local font cache (for single equation processing)
    internalSpeechTitles: true,    // insert <title> tags with speech content
    titleID: 0                     // initial id number to use for aria-labeledby titles
  }
};
</script>

Write the default settings in the header. If you change the value of scale, you can change the size of the displayed formula.

<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-svg.js"></script>

There are several types of mathjax, but in this case we need the SVG format, so we load tex-mml-svg.js.

<script
  src="https://code.jquery.com/jquery-3.5.1.min.js"
  integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
  crossorigin="anonymous"></script>

In the code, jquery is used.


    <p class="index">Latex math equation</p>
    <textarea id="equation" name="equation" rows="10" cols="80">\int_0^1 x^2\enspace dx</textarea>
    <p class="index">SVG image</p>
    <div id="svg_image">$$\int_0^1 x^2\enspace dx$$</div>
    <div class="button_wrapper">
      <button type="button" id="save_as_svg" class="button" name="save_as_svg">Download as SVG</button>
      <button type="button" id="copy_to_clipboard" class="button" name="copy_to_clipboard">Copy to clipboard</button>
    </div>
    <textarea id="svg_code" name="equation" rows="10" cols="80">SVG code</textarea>

This is a part of the content of the body. are used to set up the input field for the formula and the output field for the code. <button> are used to set up the buttons for downloading and copying to the clipboard.

Input field – Redraw the formula when a character is entered

$('#equation').on('keyup', function() {

We set up an event listener for the formula input field. This is written in jquery. By setting $(target selector).on(), you can describe the process to be executed when an event such as a mouse click or a key press occurs in the target selector. 'keyup' executes the following function() when a key on the keyboard is pressed and released. When placing an event listener in , use 'keyup' instead of 'keypress'. If you use 'keypress', the process will be executed before the text in the <textarea> is rewritten. If you want to execute the process after the content of the input field is rewritten, you need to use 'keyup'.

  let input = $('#equation').val();

Extracts the content of the input field with .val() and stores it in the string input.

  $('#svg_image').html('$$'+input+'$$');

Add $$ before and after the string input and write it to the block element svg_image. At this stage, it is not yet drawn as an SVG equation.

After the entire page is loaded, Mathjax rewrites the string enclosed by $$~$$ in the body as an SVG image and displays it. This action is performed only once when the page is loaded.

  MathJax.typesetPromise();

MathJax.typesetPromise() is a function that draws an equation dynamically. It is used when the page has been displayed and then the content has changed and you want to draw the equation again. There are various ways to set up the function, but if you simply write MathJax.typesetPromise(), all the equations in the page will be redrawn.

Redrawing replaces the contents of the block element with the SVG code. The $$latex formula$$ will be replaced with code like SVG code.

Extract the SVG code

  let svg_code = $('#svg_image').html();

Extracts the SVG code from the block element and stores it in the string svg_code.

This is the key part of our code: Mathjax generates SVG code from the latex math string and writes it into the HTML, so we’re going to take that code after it’s displayed and use it elsewhere.

In fact, the tag is surrounded by other tags.

svg_code =

'<mjx-container class="MathJax CtxtMenu_Attached_0" jax="SVG" display="true" justify="left" role="presentation" tabindex="0" ctxtmenu_counter="2" style="position: relative;"><svg ~> ・・・・・ </svg><mjx-assistive-mml role="presentation" unselectable="on" display="block"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><msubsup><mo data-mjx-texclass="OP">∫</mo><mn>0</mn><mn>1</mn></msubsup><msup><mi>x</mi><mn>2</mn></msup><mstyle scriptlevel="0"><mspace width=".5em"></mspace></mstyle><mi>d</mi><mi>x</mi></math></mjx-assistive-mml></mjx-container>'

If you leave the tags outside of <SVG>~</SVG>, Inkscake will not be able to load them, so delete the unnecessary parts.

  svg_code = svg_code.match(/<svg(.*)svg>/g);
  svg_code = svg_code[0].replace(/<svg.*width="\d+(\.\d+)?ex" height="\d+(\.\d+)?ex"/g, '<svg xmlns="http://www.w3.org/2000/svg" width="210mm" height="297mm"');
  svg_code = svg_code.replace(/<svg.*?>/g, '$&<g transform="scale(0.1)">');
  svg_code = svg_code.replace(/<\/svg>/g, '</g>$&');

.match() extracts the part of the string that matches the regular expression. The regular expression is not explained here, but here the <svg>~</svg> parts are extracted and stored in svg_code again.

Besides, some descriptions have been set up to prevent the file from failing to load and to load it at the proper size.

  $('#svg_code').text(svg_code);

Display the extracted code in the output field. That’s all for the input field.

Downloading

$('#save_as_svg').on('click', function() {

Next, let’s move on to the process when the download button is clicked. As above, an event listener is set up, and the process is executed when the button is clicked.

  let svg_code = $('#svg_image').html();
  svg_code = svg_code.match(/<svg(.*)svg>/g);

As before, we will extract the SVG code.

  svg_code = svg_code[0].replace(/<svg.*?>/g, '$&<g transform="scale(0.1)">');

The next step is to add the scale setting to the code. .replace() can use a regular expression and will replace the string that matches the regular expression with the specified string. Here, we replace <svg ~> with <svg ~><g transform="scale(0,1)> and add the scale setting. The scale is set to 0.1, but you can change this value to adjust the size of the image when it is imported by Inkscape.

The reason why we use svg_code[0] is because the .match() that we just executed returns a regular expression object.

  svg_code = svg_code.replace(/<\/svg>/g, '</g>$&');

In addition, we can close the tag by replacing </svg> with </g></svg>.

  let blob = new Blob([svg_code],{type:"image/svg+xml"});

In order to download SVG code as a file, a Blob object is created, which is an object for handling binary data, and the string must be converted to binary data form in order to download it as a file.

To give the Blob the SVG code string svg_code created above, write [svg_code].

Also, type: specifies the format of the data. type: "image/svg+xml" indicates that the data is in SVG format.

The object created is stored in a blob.

Next, move on to the download process.

In general, to download a file from a website, you need to put a tag like <a href=~>, and when you click on the tag, the file will be downloaded.

The same concept applies to the procedure I am about to explain.

  let link = document.createElement('a');

The <a> tag is created by createElement(). At this stage, the link will not be displayed anywhere on the screen. If you want to display the link on the screen, use .appendChild(). But in this case, we don’t need to display it on the screen, so we won’t use it. The download will work even if it doesn’t exist on the screen.

  link.href = URL.createObjectURL(blob);

An object containing the URL of the link is created using createObjectURL(). link.href is the URL of the link destination to be specified in the href of the <a> tag.

  let date_obj = new Date();
  let milliseconds = date_obj.getTime();

I would like to put a number representing the time in the name of the file to be downloaded so that the file name will not be duplicated when downloaded continuously. You will see that when you download the file, it will be named like svg1609657877327.svg, which is a file with many numbers.

This number indicates the number of milliseconds that have passed since January 1, 1970. This is done simply to distinguish between files that have been downloaded consecutively, and has no deeper meaning.

Here is where objects come into play again: Date() creates an object containing date and time information, which is stored in date_obj. GetTime() retrieves the elapsed time and stores it in the variable milliseconds.

  link.download = 'svg'+milliseconds+'.svg';

Specify the file name for the download in the object link that contains the <a> tag.

  link.click();

Finally, click on the <a> tag. click() is a function that simulates a mouse click, and automatically performs the same action as clicking on a link.

Copy to clipboard

Move on to the process when the Copy to Clipboard button is pressed.

$('#copy_to_clipboard').on('click', function() {

An event listener is set up.

  $('#svg_code').select();

The entire string of SVG code displayed at the bottom of the screen is selected.

  document.execCommand('copy');

execCommand('copy') works the same as when you press Ctrl+C on the keyboard. In other words, the selected string will be copied to the clipboard.

Summary

In this lesson, we learned how to use extracted SVG code with Mathjax, which can display latex mathematical expressions on a website. This is not the original usage of Mathjax, but it can be widely applied to import the created mathematical expressions into Inkscape or the latest word, or to display mathematical expressions on blogs, etc.

Finally, the entire code is shown below. This code is open source and can be freely modified and redistributed without permission of the copyright holder.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Latex math equation to SVG</title>
<script>
MathJax = {
  loader: {load: ["input/tex", "output/svg"]}
};
MathJax = {
  svg: {
    scale: 1,                      // global scaling factor for all expressions
    minScale: .001,                  // smallest scaling factor to use
    mtextInheritFont: false,       // true to make mtext elements use surrounding font
    merrorInheritFont: true,       // true to make merror text use surrounding font
    mathmlSpacing: false,          // true for MathML spacing rules, false for TeX rules
    skipAttributes: {},            // RFDa and other attributes NOT to copy to the output
    exFactor: .5,                  // default size of ex in em units
    displayAlign: 'left',        // default for indentalign when set to 'auto'
    displayIndent: '0',            // default for indentshift when set to 'auto'
    fontCache: 'local',            // or 'global' or 'none'
    localID: null,                 // ID to use for local font cache (for single equation processing)
    internalSpeechTitles: true,    // insert <title> tags with speech content
    titleID: 0                     // initial id number to use for aria-labeledby titles
  }
};
</script>
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-svg.js"></script>
<script
  src="https://code.jquery.com/jquery-3.5.1.min.js"
  integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
  crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
  <header>Latex math equation to SVG</header>
  <article>
    <p class="index">Latex math equation</p>
    <textarea id="equation" name="equation" rows="10" cols="80">\int_0^1 x^2\enspace dx</textarea>
    <p class="index">SVG image</p>
    <div id="svg_image">$$\int_0^1 x^2\enspace dx$$</div>
    <div class="button_wrapper">
      <button type="button" id="save_as_svg" class="button" name="save_as_svg">Download as SVG</button>
      <button type="button" id="copy_to_clipboard" class="button" name="copy_to_clipboard">Copy to clipboard</button>
    </div>
    <textarea id="svg_code" name="equation" rows="10" cols="80">SVG code</textarea>
  </article>
  <footer>2021 Powered by <a href="https://mmsankosho.com/en/">Mof e-Textbook</a></footer>
</div>
<script>
$('#equation').on('keyup', function() {
  let input = $('#equation').val();
  $('#svg_image').html('$$'+input+'$$');
  MathJax.typesetPromise();
  let svg_code = $('#svg_image').html();
  svg_code = svg_code.match(/<svg(.*)svg>/g);
  $('#svg_code').text(svg_code);
});
$('#save_as_svg').on('click', function() {
  let svg_code = $('#svg_image').html();
  svg_code = svg_code.match(/<svg(.*)svg>/g);
  svg_code = svg_code[0].replace(/<svg.*width="\d+(\.\d+)?ex" height="\d+(\.\d+)?ex"/g, '<svg xmlns="http://www.w3.org/2000/svg" width="210mm" height="297mm"');
  svg_code = svg_code.replace(/<svg.*?>/g, '$&<g transform="scale(0.1)">');
  svg_code = svg_code.replace(/<\/svg>/g, '</g>$&');
  let blob = new Blob([svg_code],{type:"image/svg+xml"});
  let link = document.createElement('a');
  link.href = URL.createObjectURL(blob);
  let date_obj = new Date();
  let milliseconds = date_obj.getTime();
  link.download = 'svg'+milliseconds+'.svg';
  link.click();
});
$('#copy_to_clipboard').on('click', function() {
  $('#svg_code').select();
  document.execCommand('copy');
});
</script>
</body>
</html>