How can I save mathjax locally as .svg (or get dvisvgm working alternatively)?

Background

I am starting to implement a few anki cards that include math equations. I used the built in mathjax for that but it sometimes takes 5 – 10 seconds to render the mathjax correctly; time, that could be invested into studying more material. It seems to be intermittent though, I cannot reproduce it reliably.

I noticed that I do not have any issues with loading pictures in Anki, even if I use multiple big pictures on the same card. So, instead, I wanted to add .svg pictures of my math equations. Since they’ll load much faster than mathjax renders for me, that would be a great step forward.

I also do not mind having a little bit of disk space used by those equations and do not sync with ankiweb either, so disk space isn’t an issue.

Things I tried

  1. I first thought that Ankis latex support could help me out here. It generates .png files though, which aren’t ideal. They have a very low resolution and don’t scale well (aside from the issue with switching themes between light and dark).
  2. Then I thought that #1 should be easily solveable. All I had to do is install livetex and dvisvgm on my system and I’m good to go. This is true, but has the unintended side effect, that the rendered equations now greatly vary in size. I do not want to set custom css logic for every .svg though – I expected this to work without much hassle from the card templates.
My tex file for #2
\documentclass[12pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{amssymb,amsmath}
\pagestyle{empty}
\setlength{\parindent}{0in}
\begin{document}
	% use \mathsf{} to use a nicer font for all math equations
	\begin{math}
		\mathsf{ \infty }
	\end{math}
\end{document}

This has been compiled with

latex Maths.tex
dvisvgm --no-fonts --exact-bbox Maths.dvi

Generated Latex_Unendlich.svg

<?xml version='1.0' encoding='UTF-8'?>
<!-- This file was generated by dvisvgm 2.11.1 -->
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='11.285679pt' height='5.415691pt' viewBox='38.854296 60.46924 11.285679 5.415691'>
<defs>
<path id='g0-49' d='M6.073225-3.239851C5.427646-4.052802 5.284184-4.23213 4.913574-4.531009C4.244085-5.068991 3.574595-5.284184 2.964882-5.284184C1.566127-5.284184 .657534-3.969116 .657534-2.570361C.657534-1.195517 1.542217 .131507 2.917061 .131507S5.284184-.956413 5.869988-1.912827C6.515567-1.099875 6.659029-.920548 7.029639-.621669C7.699128-.083686 8.368618 .131507 8.978331 .131507C10.377086 .131507 11.285679-1.183562 11.285679-2.582316C11.285679-3.957161 10.400996-5.284184 9.026152-5.284184S6.659029-4.196264 6.073225-3.239851ZM6.38406-2.833375C6.874222-3.694147 7.758904-4.901619 9.109838-4.901619C10.377086-4.901619 11.022665-3.658281 11.022665-2.582316C11.022665-1.41071 10.221669-.442341 9.169614-.442341C8.476214-.442341 7.938232-.944458 7.687173-1.195517C7.388294-1.518306 7.113325-1.888917 6.38406-2.833375ZM5.559153-2.319303C5.068991-1.458531 4.184309-.251059 2.833375-.251059C1.566127-.251059 .920548-1.494396 .920548-2.570361C.920548-3.741968 1.721544-4.710336 2.773599-4.710336C3.466999-4.710336 4.004981-4.208219 4.25604-3.957161C4.554919-3.634371 4.829888-3.263761 5.559153-2.319303Z'/>
</defs>
<g id='page1'>
<use x='38.854296' y='65.753425' xlink:href='#g0-49'/>
</g>
</svg>

The results:

Fields and templates

This is my front field (from the standard Basic card):

<b>Svg generated by dvisvgm 2.11.1 looks like this:</b><br>This is infinity&nbsp;<img class="img_math" src="Latex_Unendlich.svg">.<br><br><br><b>Latex (gen by anki)&nbsp;</b><b>looks like this</b><b>:</b><br>This is infinity [latex]<br>&nbsp;&nbsp;&nbsp; \begin{math}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \mathsf{ \infty }<br>&nbsp;&nbsp;&nbsp; \end{math}<br>[/latex].<br><br><br><b>Mathjax looks like this:</b><br>This is infinity <anki-mathjax>\infty</anki-mathjax>.

Template is default, except for the css:

.card {
    font-family: arial;
    font-size: 20px;
    text-align: center;
    color: black;
    background-color: white;
}


img.img_math {
	height: 0.55em;
}

Desired Behavior

I want to have scaleable, themable math equations that load without noticeable delay and have a height similar to my font-size (something like setting height: 1em; for the <img> tag.)

Actual Behavior

  1. With mathjax it renders very slowly.
  2. With Ankis latex it renders poorly and isn’t scale- or themable.
  3. With dvisvgm some equations are too big, some others too small.

Example from actual card

Something like this would be reasonable and desireable:


Instead, I get this:

The infinity and the thing with N = {…} are two separate .svg files, both generated by dvisvgm 2.11.1 and both .svg files get scaled by the same css, like shown above.

Question

  1. How can I get the mathjax results to safe on my disk as .svg and reuse them (not just cached but actually in the media folder)?
  2. If #1 isn’t possible, how can I make latex work so that the equations are scaled correctly (without having to add custom css to all of them)?

Anki Debug Info

Anki 24.11 (87ccd24e)  (ao)
Python 3.9.18 Qt 6.6.2 PyQt 6.6.1
Platform: Linux-6.1.0-0.deb11.21-amd64-x86_64-with-glibc2.31

===Add-ons (active)===
(add-on provided name [Add-on folder, installed at, version, is config changed])
AnkiWebView Inspector ['31746032', 2023-06-27T21:26, 'None', '']
Image Occlusion Enhanced ['1374772155', 2022-04-09T09:15, 'None', '']
Review Heatmap ['1771074083', 2022-06-30T03:43, 'None', '']
Study Time Stats ['1247171202', 2024-02-24T17:59, 'None', '']

===IDs of active AnkiWeb add-ons===
1247171202 1374772155 1771074083 31746032

===Add-ons (inactive)===
(add-on provided name [Add-on folder, installed at, version, is config changed])


I thought I could just take the html that mathjax generates and put it into my field instead. This, I thought, would remove the rendering issues. It turns out that the rendering issue isn’t resolved by that, as it renders exactly the same as the not-yet generated mathjax does.

I tried it with something insanely long and checked the performance with the dev console.

The very long mathjax code:

<anki-mathjax>S(\omega) 
= \frac{\alpha g^2}{\omega^5} e^{[ -0.74\bigl\{\frac{\omega U_\omega 19.5}{g}\bigr\}^{\!-4}\,]}
= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]
= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]= \frac{\alpha g^2}{\omega^5} \exp\Bigl[ -0.74\Bigl\{\frac{\omega U_\omega 19.5}{g}\Bigr\}^{\!-4}\,\Bigr]</anki-mathjax>

Result:

I do not blame tex-chtml-full.js to need this much time in this case – with that kind of formular it is to be expected.

Is there a way to debug intermittent issues? I don’t mind if it would be more complex or technical, as I really want to learn my cards without too big of a delay.

A couple of approaches. I’m in favor of just using mathjax. my equations doesn’t take even 1 second to render after my normal text, so that’s something you should investigate. because I know not to abuse it lol.

Custom MathJax size following parent element’s font size

mjx-container {
  font-size: 1em !important;
}

SVG for [latex] in Anki

I’m basing off my memory of my old system, because i no longer have latex. I apologize if I can’t help you further. First, you should check the box accessed from Tools > Manage Note Types > Options:

Then, make a new latex equation. The latex output in webview inspector should be SVG now. You should also check for the SVG file in your profile’s media.collection folder.

Making the SVGs look nice

I hope you know enough HTML, CSS and JS to use and adapt my suggestions. (ChatGPT can help you)

First, we want a way to rescale the SVGs after the “browser” knows its intrinsic size. Such thing is not possible to do merely with CSS, because it doesn’t know how to calculate stuff from an image’s intrinsic size. My approach is to allow you configure this in CSS via --latex-scale custom property/variable.

Use this script and make sure it’s loaded only once when rendering front and back side of your card. (otherwise, it just makes rendering a bit slower than necessary).

function scaleImage(elm, scale) {
    elm.style.width = `${elm.naturalWidth * scale}px`;
    elm.style.height = `${elm.naturalHeight * scale}px`;
}

queueMicrotask(() => {
    document.querySelectorAll('.latex').forEach((elm) => {
        const scale = parseFloat(getComputedStyle(elm).getPropertyValue('--latex-scale').trim());
        if (elm.complete) {
            scaleImage(elm, scale);
        } else {
            elm.addEventListener('load', () => scaleImage(elm, scale));
        }
    });
});

To also get proper dark mode colors right, i’m going to cheat and use CSS filters. There’s a proper way of recoloring SVGs on the fly but that’s even more work than I care to put in. Lastly, align vertically for inline latex.

.latex {
    vertical-align: middle;
    --latex-scale: 1.5; /* Eyeball the best value */
}
.nightMode .latex {
    filter: invert(100%);
}

There’s a different post talking about specific ways to style block latex: Horizontally center align block latex - #2 by jcznk. You can adapt that, if you’d like.

Configure Latex in Anki

I’m not a big fan of latex’s configuring and very uninformed, but that’s another thing to look into. i’m pretty sure you can do something with that Note Type Options above.

1 Like

It intermittently happens for every card – even those, that only render π. The insanely long test equation above was just for the sake of testing stuff.

I do not have this custom code in my template but if mathjax generates the equations the size is generally fine (unlike the size of .svg files generated outsite of anki).

I had no idea there is an option for dvisvgm in anki (and never paid attention to the options in this window)! I now turned this option on. Here’s what it looks like for me:

You’re right, it now is an svg! For reference, here’s the equation:

[latex]\begin{math}g_a(x) = 2x + a\end{math}[/latex]

And here’s the generated html:

<img class="latex" alt="$g_a(x) = 2x + a$" src="latex-b4f70b80721e55b4ec2c84414db1eae03c7f96cf.svg">

The image is in the collection folder, too.

I tried the following to compare them:

This is math [$]\mathtt{g_a(x) = 2x + a}[/$] and it is very mathematical.
<br>
This is math <anki-mathjax>g_a(x) = 2x + a</anki-mathjax> and it is very mathematical.

It looks really nice:

Changes made

  1. I had to modify the code to scale the latex .svg accordingly for my fields (some fields have smaller text).
  2. The js code is triggered twice (once on front, once on back). This is technically not necessary, especially in my case where all of my fields are on the front (most are hidden and only displayed if the back side is shown). The performance penalty is too small to really matter though, so I’ll let it the way it currently is.
  3. I didn’t use the filter: invert(100%); because anki handles it alright even without that.

Limitations

  1. The svg somehow is still a bit pixelated. This is especially visible if I zoom in (which I actually never do except for testing). But it’s also visible during normal review. It’s acceptable for now; but if you know any way to fix it that would be really great!
  2. I liked the font better that mathjax provides (mainly because it’s a little bit more bold, but less bold then e.g. \bm). But as far as my research goes it’s not possible to change it to the same font. It’s not a dealbreaker though.
  3. I wanted to remove the vertical-align: middle; alignment as this looked odd for some cards:

With alignment:
anki

Without alignment:
anki


But on other cards it looks even more odd without that property:

With alignment:
anki

Without alignment:
anki


Would be great if you have any additional input regarding the limitations.

Thanks for your help, it made it much better already according to my testing!

Weird, my previous system rendered latex with the font similar to mathjax. eh, i guess if you like it it’s fine but it’s definitely not expected behaviour.

that’s how it’s supposed to be :sweat_smile:. sorry that I worded it strangely.

SVGs are supposed to be able to render scalably as you scale it. i believe there’s something wrong with the SVG output on your end, or perhaps the font itself. SVGs i made manually with mathjax seems fine. (i still don’t wanna install latex though, it’s just so massive :sweat_smile:)

About alignment, it’s just my best guess but in reality it’s kinda bad. for instance, e^{x^{x^{x^{x^x}}}} would be strange to center align it vertically. if only the HTML side knows the right baseline…

True.

So how could this be solved? I tried transform: translateY(5px) on one of the svg – it works but would require me to align all of them manually. Is there a way to know how it’s supposed to be aligned via js maybe? I mean: How does mathjax know?

mathjax knows because it’s the one turning math into CHTML. Latex itself knows how to align things if you write an entire document in it. there’s a disconnect between Latex and the HTML side. So, Latex must provide some kind of information about the baseline for inline Latex. I don’t think this is trivial to implement.

You can try avoiding MathJax for big equations and use Latex for those instead. realistically, big equations would be in block display so the baseline stuff (mostly) doesn’t matter.

I see.

Can I convert the output from mathjax to svg instead? That way it would run the conversion once and then it just uses the saved svg (like with latex).

sure, you can use mathjax to make svgs, but having that for Anki… you would need an addon. even then, it would still have the same alignment problem except now there’s a disconnect between the SVG generated through mathjax and the HTML side. as long as the math renderer doesn’t provide baseline information, we can’t do much about alignment for SVG.

(this would only still be a problem if you’re using big math equations inline. block/display ones should be unaffected.)

1 Like