$\LaTeX$ and $\text{Ti}\textit{k}\text{Z}$ for web developers
$\LaTeX$ is a markup language for typesetting math and science. It’s used in web-dev because it’s a fast and easy way to typeset equations and other math content.
Official documentation.
Table of Contents
MathJax Support
- Enable and configure the Goldmark passthrough extension in your site configs as described here.
- Open a terminal. Go to your project directory:
cd <path>
- Run
$EDITOR layouts/partials/custom_head.html
- Append and save
<!-- MathJax Support -->
<!-- https://docs.mathjax.org/en/latest/options/input/tex.html -->
<script>
window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
displayMath: [['$$', '$$'], ['\\[', '\\]']],
processEscapes: true,
processEnvironments: true,
packages: {'[+]': ['ams', 'boldsymbol']} // 'base' + extras
},
options: {
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
ignoreHtmlClass: 'tex2jax_ignore'
},
loader: {
load: ['[tex]/ams', '[tex]/boldsymbol']
},
startup: {
ready() {
console.log('MathJax is initializing...');
MathJax.startup.defaultReady();
MathJax.startup.promise.then(() => {
console.log('MathJax typesetting complete');
}).catch((err) => {
console.error('MathJax failed:', err);
});
}
}
};
</script>
<!-- Only include polyfill if supporting older browsers -->
<script src="https://cdn.jsdelivr.net/npm/core-js-bundle@3/minified.js"></script>
<!-- https://www.mathjax.org/#gettingstarted -->
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@4.0.0/tex-mml-chtml.js"></script>
- Edit a markdown file, for example
content/en/*.md
- Insert $\LaTeX$ code inline like
$\sqrt{x^2 + y^2} = r$
or in$$
blocks. Save - Build it with
hugo server --disableFastRender
. Build details in my post How to create a Hugo-scroll website - Check locally the changes on http://localhost:1313/ or alike
- Open the browser console. Refresh the page.
- Check if any error is loaded. It should just show
MathJax is initializing...
andMathJax typesetting complete
- Run
console.log('MathJax version:', MathJax.version)
. Expected output:4.0.0
Block equations
Long one line equations, like this
($$S^{(3)} = 2\pi k\sqrt{\alpha}N_1^{TL} + N_3^{(3,1)} \left[-3k\text{arcsinh} \left(\frac{1}{\sqrt{3}\sqrt{4\alpha+1}} \righ...
),
see,
expand beyond the Hugo screen. Though they are visible.
Therefore, one needs newlines for block equations.
Hugo’s Markdown processor converts \\
to spaces.
This breaks $\LaTeX$ environments that rely on \\
for line endings.
For example
$$ \begin{matrix} a & b \\ c & d \end{matrix} $$
Gets converted to:
$$ \begin{matrix} a & b \\ c & d \end{matrix} $$The correct way is via raw HTML:
{{< rawhtml >}}
$$ \begin{matrix} a & b \\ c & d \end{matrix} $$
{{< /rawhtml >}}
Which displays as:
Raw HTML explanation and usage in my post concerning Hugo shortcodes, specially its dedicated paragraph about raw HTML.
Alternative use \newline
instead of \\
. E.g.
$$
\left(
\begin{matrix}
a & b \newline
c & d
\end{matrix}
\right)
$$
produces
$$ \left( \begin{matrix} a & b \newline c & d \end{matrix} \right) $$Error handling
Insert an invalid alignment specification to check its error.
Instead of matrix
lets use foo
Original:
$$ \begin{matrix} a & b \\ c & d \end{matrix} $$
Error:
$$ \begin{foo} a & b \\ c & d \end{foo} $$
Gets converted to:
$$ \begin{foo} a & b \\ c & d \end{foo} $$This message (Unknown environment 'foo'
) is from MathJax.
Specific from MathJax.config.tex.formatError
’s default value, as shown in its
docs.mathjax.org configuration:
MathJax = {
tex: {
formatError: // function called when TeX syntax errors occur
(jax, err) => jax.formatError(err),
}
};
In the MathJax Support section we did not change the formatError
.
So we benefit from previous default setting.
LaTeX packages
docs.mathjax.org config includes
MathJax = {
tex: {
packages: ['base'], // extensions to use
...
Include extra packages
In layouts/partials/custom_head.html
we loaded a few extra packages
<script>
window.MathJax = {
tex: {
...
packages: {'[+]': ['ams', 'boldsymbol']} // 'base' + extras
},
...
loader: {
load: ['[tex]/ams', '[tex]/boldsymbol']
},
...
};
</script>
Previous config lines use docs.mathjax.org - boldsymbol config as reference:
window.MathJax = {
loader: {load: ['[tex]/boldsymbol']},
tex: {packages: {'[+]': ['boldsymbol']}}
};
Test a LaTeX package
Check in the browser’s console if they are proper loaded with next, expected output: Array [ "[tex]/ams", "[tex]/boldsymbol" ]
.
MathJax.config.loader.load
Thus, ignore if the console shows a warning
like No version information available for component [tex]/ams
when loading.
This is might be a metadata notification.
Maybe this package doesn’t have version metadata included in its build.
Now let’s test if an AMS package, like amsmath, performs OK.
Code
\begin{equation}
\forall x \in \mathbb{R}, \quad \exists y > 0 \quad \text{such that} \quad x + y > 0
\end{equation}
Render
$$ \begin{equation} \forall x \in \mathbb{R}, \quad \exists y > 0 \quad \text{such that} \quad x + y > 0 \end{equation} $$Test if boldsymbol, notice this is a docs.mathjax.org link, succeeds.
Code
\text{x normally: } x
\newline
\backslash\text{LaTeX normally: } \LaTeX
\newline
\text{x as arg of } \texttt{boldsymbol} \text{: } \boldsymbol{x}
\newline
\backslash\text{LaTeX as arg of } \texttt{boldsymbol} \text{: } \boldsymbol{\LaTeX}
Output
$$ \text{x normally: } x \newline \backslash\text{LaTeX normally: } \LaTeX \newline \text{x as arg of } \texttt{boldsymbol} \text{: } \boldsymbol{x} \newline \backslash\text{LaTeX as arg of } \texttt{boldsymbol} \text{: } \boldsymbol{\LaTeX} $$TikZ
$\text{Ti}\textit{k}\text{Z}$ is probably the most complex and powerful tool to create graphic elements in $\LaTeX$.
Enable TikZ
Follow steps of TikZJax:
- Add to
layouts/partials/custom_head.html
:
<!-- TikZJax -->
<!-- https://github.com/kisonecat/tikzjax -->
<link rel="stylesheet" type="text/css" href="http://tikzjax.com/v1/fonts.css">
<script src="https://tikzjax.com/v1/tikzjax.js"></script>
- Try the MWE
{{< rawhtml >}}
<div class="html-content">
<script type="text/tikz">
\begin{tikzpicture}
\draw (0,0) circle (1in);
\end{tikzpicture}
</script>
</div>
{{< /rawhtml >}}
Which draws next circle. So far so good.
- I prefer a centered circle, so let’s add some more code to
layouts/partials/custom_head.html
, to its<style>
tag:
/* TikZJax centering */
/* https://github.com/kisonecat/tikzjax/issues/12 */
.tikzjax {
display: flex;
justify-content: center; /* centers horizontally */
align-items: center; /* centers vertically if needed */
}
- Now expand the classes in previous $\text{Ti}\textit{k}\text{Z}$ code,
actually in the pure raw HTML
<div>
, to<div class="html-content tikzjax">
and redraw it:
TL;DR
{{< rawhtml >}}
<div class="html-content tikzjax">
<script type="text/tikz">
\begin{tikzpicture}
\draw (0,0) circle (1in);
\end{tikzpicture}
</script>
</div>
{{< /rawhtml >}}
TikZJax issues
The most evident annoyance is the amount of messages in the console. But this is not an error nor a limitation. On the other hand, next are real problems.
To draw a 5 points star we can use
\begin{tikzpicture}
\draw[thick, fill=yellow!50, draw=orange]
(90:1in) -- (126:0.4in) -- (162:1in) -- (198:0.4in)
-- (234:1in) -- (270:0.4in) -- (306:1in)
-- (342:0.4in) -- (18:1in) -- (54:0.4in) -- cycle;
\end{tikzpicture}
Like this:
{{< rawhtml >}}
<div class="html-content tikzjax">
<script type="text/tikz">
\begin{tikzpicture}
\draw (0,0) circle (1in);
\draw[thick, fill=yellow!50, draw=orange]
(90:1in) -- (126:0.4in) -- (162:1in) -- (198:0.4in)
-- (234:1in) -- (270:0.4in) -- (306:1in)
-- (342:0.4in) -- (18:1in) -- (54:0.4in) -- cycle;
\end{tikzpicture}
</script>
</div>
{{< /rawhtml >}}
Result:
Though this does not take advantage of $\text{Ti}\textit{k}\text{Z}$’s nodes. Following is clearer and more customizable:
\begin{tikzpicture}
\draw[thick,fill=yellow!50, draw=orange]
(0,0)
node[
star,
star points=5,
star point ratio=2.5,
minimum size=2in,
draw
] {};
\end{tikzpicture}
But it has some disadvantages. Notice that fill=yellow!50
didn’t work:
The node[star, ...]
construct relies on $\text{Ti}\textit{k}\text{Z}$’s nodes shapes defined in libraries like shapes.geometric
.
TikZJax currently has limited support for some of these specialized nodes and their fill
s may not be properly rendered in the generated SVG.
The right colored star produces
<div class="html-content tikzjax">
<div style="display: flex; width: 138.267pt; height: 131.538pt;">
<div
style="position: relative; width: 100%; height: 0.3999786376953124pt;"
class="page"
>
<svg
width="138.2669pt"
height="131.5379pt"
viewBox="-72 -72 138.2669 131.5379"
style="position: absolute; top: 0pt; left: 0pt; overflow: visible;"
>
<g transform="translate(-3.1365509033203116,0.3999786376953124)">
<g stroke-miterlimit="10" transform="scale(1,-1)">
<g stroke="#000" fill="#000">
<g stroke-width="0.4">
<g stroke-width="0.8">
<g fill="#ffff80">
<g stroke="#ff8000">
<path d=" M 0.0 72.26999 L -16.99138 23.38681 L -68.73346 22.33293 L -27.49295 -8.93303 L -42.47913 -58.46793 L 0.0 -28.90755 L 42.47913 -58.46793 L 27.49295 -8.93303 L 68.73346 22.33293 L 16.99138 23.38681 Z "></path>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
</div>
</div>
</div>;
The other makes
<div class="html-content tikzjax">
<div style="display: flex; width: 138.267pt; height: 131.538pt;">
<div
style="position: relative; width: 100%; height: 0.3999786376953124pt;"
class="page"
>
<svg
width="138.2669pt"
height="131.5379pt"
viewBox="-72 -72 138.2669 131.5379"
style="position: absolute; top: 0pt; left: 0pt; overflow: visible;"
>
<g transform="translate(-3.1365509033203116,0.3999786376953124)">
<g stroke-miterlimit="10" transform="scale(1,-1)">
<g stroke="#000" fill="#000">
<g stroke-width="0.4">
<g stroke-width="0.8">
<g fill="#ffff80">
<g stroke="#ff8000">
<path d=" M 0.0 0.0 "></path>
<path
d=" M 0.0 72.26999 L -16.99527 23.39217 L -68.73346 22.33293 L -27.49925 -8.93507 L -42.47913 -58.46793 L 0.0 -28.91417 L 42.47913 -58.46793 L 27.49925 -8.93507 L 68.73346 22.33293 L 16.99527 23.39217 Z "
fill="none"
></path>
<g
stroke="none"
transform="scale(-1.00375,1.00375)translate(0.19677734374999994,0.3999786376953124)scale(-1,-1)"
>
<g fill="#000">
<g stroke="none"> </g>{" "}
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
</div>
</div>
</div>;
The differences can be checked with vim -d a.html b.html
or online with text-compare.
These are inside <g stroke="#ff8000">
.
On the not fill-colored, those lines are next. Notes:
- Mismatches in red font color
...
to hide non important parts
On the right colored star all previous HTML lines are just next <path>
,
which is like the second one of previous code but without fill="none"
.
I said like because some decimals of d
might differ, but this doesn’t matter.
Thus, inspecting this star in the browser we can force remove fill="none"
,
now the star will be right colored (as long as we don’t refresh the page obviously).
Let’s apply this fix permanently and programmatically in next section.
Workaround
Add a new class to previous $\text{Ti}\textit{k}\text{Z}$-node star:
<div class="html-content tikzjax tikzjax-node">
instead of<div class="html-content tikzjax">
.$EDITOR assets/js/tikzjax-node.js
with content
// Configuration
const TIKZJAX_FIX_CONFIG = {
TIMEOUT_MS: 3000, // 3 second timeout to ensure TikZJax is done
CONTAINER_SELECTORS: ['.tikzjax-node'] // All possible container classes
// CONTAINER_SELECTORS: ['.tikzjax', '.tikzjax-node'] // All possible container classes
};
// Main processing function
function processTikzNodes() {
// Process all TikZ containers
const containers = document.querySelectorAll(TIKZJAX_FIX_CONFIG.CONTAINER_SELECTORS.join(','));
containers.forEach(container => {
const svgs = container.querySelectorAll('svg');
svgs.forEach(svg => {
// Find all paths that have M and Z commands
const paths = Array.from(svg.querySelectorAll('path')).filter(path => {
const d = path.getAttribute('d') || '';
return d.includes('M') && d.includes('Z');
});
// Process each star path
paths.forEach(path => {
// Remove fill="none" if present
if (path.getAttribute('fill') === 'none') {
path.removeAttribute('fill');
}
// Also check parent groups
let current = path.parentElement;
while (current && current !== svg) {
if (current.getAttribute('fill') === 'none') {
current.removeAttribute('fill');
}
current = current.parentElement;
}
});
});
});
}
// Initialization
document.addEventListener('DOMContentLoaded', function() {
// Wait for TikZJax to finish rendering
setTimeout(processTikzNodes, TIKZJAX_FIX_CONFIG.TIMEOUT_MS);
});
Actually I set a intial delay bigger than 3 secs, and with retries. Check the script.
$EDITOR layouts/partials/custom_head.html
and append
<!-- TikZJax-node -->
{{ $js := resources.Get "js/tikzjax-node.js" | minify | fingerprint }}
<script src="{{ $js.RelPermalink }}" integrity="{{ $js.Data.Integrity }}" defer></script>
Result:
The star is now right colored!
If not, then the drawing took too long to be rendered, thus assets/js/tikzjax-node.js
found nothing to fix.
Just refresh the page without clearing the cache memory (F5
).
Remember this is a workaround, not an official patch.
We can even draw multiple $\text{Ti}\textit{k}\text{Z}$ nodes in the same <div>
:
Unleash your creativity
Last but not list explore the many drawing capabilities that $\text{Ti}\textit{k}\text{Z}$ offers.
Combine normal text (with emojis), HTML-tags, $\LaTeX$ and $\text{Ti}\textit{k}\text{Z}$:
Various drawings and shapes:
Do you need inspiration? Check out some $\text{Ti}\textit{k}\text{Z}$ galleries!