Typesetting Math in Gutenberg

In my last post I needed to display some mathematical formulas and knew that I should be able to do that on WordPress.com with the normal LaTeX entry. After some searching to figure out how to do it I was disappointed that I couldn’t find what I wanted. Therefore, after I published that post I set out to create a new type of Gutenberg block to give me a visual formula typesetting block and this is the story of that easy hour I spent hacking away.

Project Requirements

When I started out I knew that I wanted an interactive formula builder. It didn’t need to be a visual builder with buttons to click on and, in fact, I prefer text LaTeX entry because I find it more convenient. I set out then with a few simple requirements, in order:

When writing my post I want to see the rendered formula, not the “source code” of the equations. This is in line with how we prefer visual representations in Gutenberg over text, codes, and shortcodes. It helps me see how my post flows and frankly it’s confusing when I see $latex \Delta T_{\text{heatsink}} = Q_{\text{load}} \cdot 0.15 \frac{K}{W}$ in a paragraph of text when I know it should be something recognizable like this…

…my brain doesn’t handle that disconnect too well.

When typing my formulas I want to be able to instantly see the rendered form so I can quickly correct my mistakes. With the existing system I have to write the LaTeX source and then preview my page to see if it has any errors and if it’s how I want it to appear. That minor change of focus interrupts my thoughts and so I want to close the feedback loop. It needs to be practically instant to get out of my way.

Finally, I want my formulas to appear crisp and beautiful in the rendered page output. The existing LaTeX support creates a PNG image of a given (choosable) size but obviously that image isn’t going to continue looking good when scaled up or down. Ideally I’d have MathML output or something that can be selected, copied, and pasted, but as long as I get some vector-based output I’ll be happy.

PNG output from server

SVG output from block

Writing the formula block

The actual work of creating this block was trivial. I had already found MathJax which looked like it would support all of the typesetting needs I had. It turned out that it was a good choice. I didn’t look at how big the MathJax library is though because my block only loads any code during the edit session itself. Like most of the blocks I create I try to design my blocks so that all of the processing and loading costs stay in the editor while editing the post. This is respectful to our readers, keeps our server loads lower, and makes it easier to transfer the post from one place (like WordPress) to another (like a static web host). That is, my block exists in the editor to make it easy to enter mathematical formula but then stores the rendered output as HTML right there in the post_contents and then disappears.

There was only one issue I ran into and that revolved around the storage and retrieval of SVG in the block content. I didn’t go too far into debugging because I was wanting to build a tool to help me write and not a piece of code to behold. The short story is that when Gutenberg would initialize and load the block contents it was stripping away the SVG content entirely. Something in the code suggests using the <SVG /> component in Gutenberg but I had an SVG string and would have had to parse it out and recreate it in order to do this. Thankfully I had a sneaky workaround which was to store the SVG in an image’s src attribute as a data URI. You can see how I take the raw SVG string, base64-encode it with btoa(), and then save it if you look at the text input’s update function below. There are limits to how much you can store this way but I tried some long formulas and they did fine so I called it a day. While it’s true that the block’s saved output is a jumbled mess it’s also simple and reliable.

<!-- wp:dmsnell/formula {"formula":"\\Delta T_{\\text{heatsink}} = 100W
 \\cdot 0.15 \\frac{K}{W} = 15^\\circ C"} -->
<div class="wp-block-dmsnell-formula"><img src="data:image/svg+xml;
base64,PHN2ZyBzdHlsZT0idmVydGljYWwtYWxpZ246IC0xLjYwMmV4OyIgeG1sbnM9Imh0d
HA6Ly93d3cu...gdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTQzNzMuNywgMCkiPjx1c2UgeGxpb
ms6aHJlZj0iI01KWC0xNzktVEVYLUktNDMiPjwvdXNlPjwvZz48L2c+PC9nPjwvc3ZnPg=="
/></div>
<!-- /wp:dmsnell/formula -->

If you are wondering what build tool I use, if I use the @wordpress packages, if I use ES1138 syntax, and whether I prefer webpack or fastpak, then I hope you aren’t disappointed to find out that I use none of those. I have heard that accessing the wp. global variables might be deprecated, but in this and other blocks I have written I enjoy keeping things simple and contained in a single file. It makes for a fast development feedback loop and lets me code without the normal hassle of the administrative overhead that transpilers and build steps bring.

Ways to make this better

Since I was so task oriented here I skipped lots of ancillary and polishing work. Here are some ideas on how I could improve this block.

  • Load the CodeMirror editor (or similar) to provide syntax-highlighting while editing a formula.
  • Figure out how to save the raw SVG data or make the “chtml” format work right. MathJax has done an impressive job making formulas accessible and meaningful to screen readers but I think that saving the rendered copies as data URIs destroys that.
  • Create an icon for the block that represents a formula.
  • Create an actual plugin and upload to GitHub to open up issue-tracking.
  • Provide optional visual-formula-builder similar to how hostmath does it.
  • Patch Gutenberg to allow for asynchronous updates and render large formulas in a WebWorker or anywhere off of the main thread. (The standard model’s density lagrangian formula not only failed to render but also slowed down each keystroke).

Have your own ideas? Leave them here in a comment!

The plugin’s code

const { registerBlockType } = wp.blocks;
const { TextareaControl } = wp.components;
const { createElement: el } = wp.element;
const attributes = {
formula: { type: 'string' },
rendered: {
type: 'string',
source: 'attribute',
selector: 'img',
attribute: 'src',
default: ''
}
}
const Edit = ( { attributes: { formula, rendered }, className, isSelected, setAttributes } ) =>
el( 'div', { className }, [
isSelected && el( TextareaControl, {
label: "Formula source",
value: formula,
onChange: newFormula => setAttributes( {
formula: newFormula,
rendered: 'data:image/svg+xml;base64,' + btoa( window.MathJax.tex2svg( newFormula ).innerHTML ),
} ) },
formula
),
el( 'div', {}, el( 'img', { src: rendered } ) )
]
);
const Save = ( { attributes: { rendered }, className } ) =>
el( 'div', { className }, el( 'img', { src: rendered } ) );
registerBlockType(
'dmsnell/formula',
{
title: 'Formula Entry',
icon: 'welcome-write-blog',
category: 'common',
attributes: attributes,
edit: Edit,
save: Save,
}
);
<?php
/**
* Plugin Name: Formula Block
* Plugin URI: http://wordpress.com/
* Description: Enter LaTeX-style formulas interactively
* Version: 1.0
* Author: Dennis Snell <dennis.snell@automattic.com>
* Author URI: wordpress.com
* License: GPL-2.0
**/
defined( 'ABSPATH' ) or die( 'Cannot access script directly' );
add_action( 'init', function() {
wp_register_script( 'mathjax-svg', plugins_url( 'tex-mml-svg.js', __FILE__ ), [] );
wp_enqueue_script(
'formula-block-editor',
plugins_url( 'formula-block-editor.js', __FILE__ ),
[ 'mathjax-svg', 'wp-blocks', 'wp-components', 'wp-element' ]
);
} );

Updates

After seeing my tweet for this post Adam Silverstein (@adamsilverstein WP, @roundearth) reached out to let me know that he build a similar block which renders MathML. Looks like we took different approaches but you should check out his plugin too!

Leave a Reply

%d bloggers like this:
search previous next tag category expand menu location phone mail time cart zoom edit close