$\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

  1. Enable and configure the Goldmark passthrough extension in your site configs as described here.
  2. Open a terminal. Go to your project directory: cd <path>
  3. Run $EDITOR layouts/partials/custom_head.html
  4. 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>
  1. Edit a markdown file, for example content/en/*.md
  2. Insert $\LaTeX$ code inline like $\sqrt{x^2 + y^2} = r$ or in $$ blocks. Save
  3. Build it with hugo server --disableFastRender. Build details in my post How to create a Hugo-scroll website
  4. Check locally the changes on http://localhost:1313/ or alike
  5. Open the browser console. Refresh the page.
  6. Check if any error is loaded. It should just show MathJax is initializing... and MathJax typesetting complete
  7. 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:

$$ \begin{matrix} a & b \\ c & d \end{matrix} $$

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:

  1. 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>
  1. 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.

  1. 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 */
}
  1. 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 fills 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
<path d=" M 0.0 0.0 "></path> <path" d=" M 0.0 72.26999 ... Z " fill="none" >/path" <g stroke="none" transform="scale(...)translate(...)scale(-1,-1)" > <g fill="#000"> <g stroke="none"> </g>{" "} </g> </g>

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.

<path d=" M 0.0 72.26999 ... Z " ></path>

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

  1. 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">.

  2. $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.

  1. $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}$:

5 points star ⭐ with $\,Ti\textit{k}Z \quad\Rightarrow\qquad$

Various drawings and shapes:

Do you need inspiration? Check out some $\text{Ti}\textit{k}\text{Z}$ galleries!