Mathematical expressions

Use a fenced code block or shortcode to embed an SVG image of a LaTeX mathematical expression or equation in your Hugo site using the free Math API service. Unlike JavaScript solutions such as KaTeX or MathJax, this approach embeds an SVG image in your page.

Fenced code block

Include a LaTeX expression or equation in your Markdown using a fenced code block.

Examples

```math
$$
\begin{aligned}
KL(\hat{y} || y) &= \sum_{c=1}^{M}\hat{y}_c \log{\frac{\hat{y}_c}{y_c}} \\
JS(\hat{y} || y) &= \frac{1}{2}(KL(y||\frac{y+\hat{y}}{2}) + KL(\hat{y}||\frac{y+\hat{y}}{2}))
\end{aligned}
$$
```

Hugo renders this to:

mathematical expression or equation

You may also:

  1. Change the color of the rendered equation by providing a color Markdown attribute using named CSS colors or hex values.
  2. Add global HTML attributes such as class and id to the div element that wraps the SVG image.
```math {.class-1 #id-1 color=darkred}
$$
E=mc^2
$$
```

Hugo renders this to:

mathematical expression or equation

A more complex example:

```math
$$
\begin{array} {lcl}
  L(p,w_i) &=& \dfrac{1}{N}\Sigma_{i=1}^N(\underbrace{f_r(x_2
  \rightarrow x_1
  \rightarrow x_0)G(x_1
  \longleftrightarrow x_2)f_r(x_3
  \rightarrow x_2
  \rightarrow x_1)}_{sample\, radiance\, evaluation\, in\, stage2}
  \\\\\\ &=&
  \prod_{i=3}^{k-1}(\underbrace{\dfrac{f_r(x_{i+1}
  \rightarrow x_i
  \rightarrow x_{i-1})G(x_i
  \longleftrightarrow x_{i-1})}{p_a(x_{i-1})}}_{stored\,in\,vertex\, during\,light\, path\, tracing\, in\, stage1})\dfrac{G(x_k
  \longleftrightarrow x_{k-1})L_e(x_k
  \rightarrow x_{k-1})}{p_a(x_{k-1})p_a(x_k)})
\end{array}
$$
```

Hugo renders this to:

mathematical expression or equation

Source code

layouts/_default/_markup/render-codeblock-math.html
{{- /* Last modified: 2024-02-11T14:33:41-08:00 */}}

{{- /*
Copyright 2023 Veriphor LLC

Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
*/}}

{{- /*
Renders an SVG image of a mathematical expression or equation from LaTeX using the Math API service.

References:

  - https://math.vercel.app/
  - https://github.com/uetchy/math-api
  - https://docs.mathjax.org/

@context {map} Attributes The Markdown attributes from the info string.
@context {string} Inner The content between the leading and trailing code fences, excluding the info string.
@context {map} Options The highlighting options from the info string.
@context {int} Ordinal The zero-based ordinal of the code block on the page.
@context {page} Page A reference to the page containing the code block.
@context {text.Position} Position The position of the code block within the page content.
@context {string} Type The first word of the info string.

@param {string} [Attributes.color=black] The foreground color specified as a named CSS color or hex value.

@returns {template.html}
*/}}

{{- /* Initialize. */}}
{{- $renderHookName := "math" }}

{{- /* Verify minimum required version. */}}
{{- $minHugoVersion := "0.114.0" }}
{{- if lt hugo.Version $minHugoVersion }}
  {{- errorf "The %q code block render hook requires Hugo v%s or later." $renderHookName $minHugoVersion }}
{{- end }}

{{- /* Get context. */}}
{{- $attrs := .Attributes }}
{{- $inner := trim .Inner "\n\r" }}
{{- $options := .Options }}
{{- $ordinal := .Ordinal }}
{{- $page := .Page }}
{{- $position := .Position }}
{{- $type := .Type }}

{{- /* Initialize. */}}
{{- $apiEndpoint := "https://math.vercel.app/" }}
{{- $color := $attrs.color | default "" }}

{{- /* Determine display mode. */}}
{{- $displayMode := true }}
{{- if or (strings.HasPrefix $inner `$$`) (strings.HasPrefix $inner `\[`) }}
  {{- $displayMode = true }}
{{- else if or (strings.HasPrefix $inner `$`) (strings.HasPrefix $inner `\(`) }}
  {{- $displayMode = false }}
{{- end }}

{{- /* Strip display mode indicators. */}}
{{- $inner = trim $inner `$` }}
{{- $inner = $inner | strings.TrimPrefix `\[` }}
{{- $inner = $inner | strings.TrimPrefix `\(` }}
{{- $inner = $inner | strings.TrimSuffix `\]` }}
{{- $inner = $inner | strings.TrimSuffix `\)` }}

{{- /* Determine class attribute. */}}
{{- $class := "math math-inline" }}
{{- if $displayMode }}
  {{- $class = "math math-block" }}
{{- end }}
{{- with $attrs.class }}
  {{- $class = printf "%s %s" $class . }}
{{- end }}

{{- /* Determine id attribute. */}}
{{- $id := printf "h-rh-cb-math-%d" $ordinal }}
{{- with $attrs.id }}
  {{- $id = . }}
{{- end }}

{{- /* Merge class and id attributes. */}}
{{- $attrs = merge $attrs (dict "class" $class "id" $id) }}

{{- /* Create query string. */}}
{{- $mode := "inline" }}
{{- if $displayMode }}
  {{- $mode = "from" }}
{{- end }}
{{- $qs := querify $mode $inner "color" $color }}

{{- /* Get image. */}}
{{- $url := printf "%s?%s" $apiEndpoint $qs }}
{{- with resources.GetRemote $url }}
  {{- with .Err }}
    {{- errorf "The %q code block render hook was unable to get the remote image. See %s. %s" $renderHookName $position . }}
  {{- else }}
    {{- $url = .RelPermalink }}
  {{- end }}
{{- else }}
  {{- errorf "The %q code block render hook was unable to get the remote image. See %s" $renderHookName $position }}
{{- end }}

{{- /* Render. */}}
<span
{{- range $k, $v := $attrs }}
  {{- if not (eq $k "color") }}
    {{- if $v }}
      {{- printf " %s=%q" $k (string $v) | safeHTMLAttr }}
    {{- end }}
  {{- end }}
{{- end -}}
>
  <img src="{{ $url }}" alt="mathematical expression or equation">
</span>
{{- /**/ -}}

Shortcode

Include a LaTeX expression or equation in your Markdown using a shortcode, either as a block or inline.

Arguments

class
(string) Optional. A class name to add to the class attribute of the wrapping span element.
color
(string) Optional. The foreground color specified as a named CSS color or hex value.
id
(string) Optional. The id attribute of the wrapping span element.

Examples

Block

{{< math class=class-2 id=id-2 color=darkred >}}
$$
x^n + y^n = z^n
$$
{{< /math >}}

Hugo renders this to:

mathematical expression or equation

Inline

An inline {{< math >}}${(x+y)}^2${{< /math >}} expression.

Hugo renders this to:

An inline mathematical expression or equation expression.

Source code

layouts/shortcodes/math.html
{{- /* Last modified: 2023-06-30T12:24:14-07:00 */}}

{{- /*
Copyright 2023 Veriphor LLC

Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
*/}}

{{- /*
Renders an SVG image of a mathematical expression or equation from LaTeX using the Math API service.

References:

  - https://math.vercel.app/
  - https://github.com/uetchy/math-api/
  - https://docs.mathjax.org/

@context {string} Inner The content between the opening and closing shortcode tags.
@context {string} InnerDeindent The content between the opening and closing shortcode tags with indentation removed.
@context {string} Name The file name of the shortcode template, excluding the extension.
@context {int} Ordinal The zero-based ordinal of the shortcode on the page, or within its parent shortcode.
@context {page} Page A reference to the page containing the shortcode.
@context {map} Params The parameters specified in the opening shortcode tag.
@context {hugolib.ShortcodeWithPage} Parent The context of the parent shortcode.
@context {text.Position} Position The position of the shortcode within the page content.

@method {any} Get Returns the parameter value for the given key (for named parameters) or position (for positional parameters).
@mathod {bool} IsNamedParams Returns true if the shortcode is called with named instead of positional parameters.
@method {maps.Scratch) Scratch Returns a writable Scratch to store and manipulate data.

@param {string} [Params.class] A class name to add to the class attribute of the wrapping span element.
@param {string} [Params.color=black] The foreground color specified as a named CSS color or hex value.
@param {string} [Params.id] The id attribute of the wrapping span element.

@returns {template.html}

@example {{< math >}}${(x+y)}^2${{< /math >}}
*/}}

{{- /* Verify minimum required version. */}}
{{- $minHugoVersion := "0.114.0" }}
{{- if lt hugo.Version $minHugoVersion }}
  {{- errorf "The %s shortcode requires Hugo v%s or later." .Name $minHugoVersion }}
{{- end }}

{{- /* Get context. */}}
{{- $inner := trim .Inner "\n\r" }}
{{- $name := .Name }}
{{- $ordinal := .Ordinal }}
{{- $position := .Position }}

{{- /* Initialize. */}}
{{- $apiEndpoint := "https://math.vercel.app/" }}
{{- $color := .Get "color" | default "" }}

{{- /* Determine display mode. */}}
{{- $displayMode := true }}
{{- if or (strings.HasPrefix $inner `$$`) (strings.HasPrefix $inner `\[`) }}
  {{- $displayMode = true }}
{{- else if or (strings.HasPrefix $inner `$`) (strings.HasPrefix $inner `\(`) }}
  {{- $displayMode = false }}
{{- end }}

{{- /* Strip display mode indicators. */}}
{{- $inner = trim $inner `$` }}
{{- $inner = $inner | strings.TrimPrefix `\[` }}
{{- $inner = $inner | strings.TrimPrefix `\(` }}
{{- $inner = $inner | strings.TrimSuffix `\]` }}
{{- $inner = $inner | strings.TrimSuffix `\)` }}

{{- /* Determine class attribute. */}}
{{- $class := "math math-inline" }}
{{- if $displayMode }}
  {{- $class = "math math-block" }}
{{- end }}
{{- with .Get "class" }}
  {{- $class = printf "%s %s" $class . }}
{{- end }}

{{- /* Get id attribute. */}}
{{- $id := or (.Get "id") (printf "h-sc-%s-%d" $name $ordinal) }}

{{- /* Define attributes map. */}}
{{- $attrs := dict "class" $class "id" $id }}

{{- /* Create query string. */}}
{{- $mode := "inline" }}
{{- if $displayMode }}
  {{- $mode = "from" }}
{{- end }}
{{- $qs := querify $mode $inner "color" $color }}

{{- /* Get image. */}}
{{- $url := printf "%s?%s" $apiEndpoint $qs }}
{{- with resources.GetRemote $url }}
  {{- with .Err }}
    {{- errorf "The %q shortcode was unable to get the remote image. See %s. %s" $name $position . }}
  {{- else }}
    {{- $url = .RelPermalink }}
  {{- end }}
{{- else }}
  {{- errorf "The %q shortcode was unable to get the remote image. See %s" $name $position }}
{{- end }}

{{- /* Render. */}}
<span
{{- range $k, $v := $attrs }}
  {{- if not (eq $k "color") }}
    {{- if $v }}
      {{- printf " %s=%q" $k (string $v) | safeHTMLAttr }}
    {{- end }}
  {{- end }}
{{- end -}}
>
  <img src="{{ $url }}" alt="mathematical expression or equation">
</span>
{{- /**/ -}}

Performance

The render hook and shortcode above call Hugo’s resources.GetRemote function to request the SVG image from Math API. Hugo caches the result, and invalidates the cache when (a) you edit the LaTex markup, or (b) the cache expires.

To optimize performance in a CI/CD environment such as Cloudflare Pages, GitHub Pages, or Netlify, you should:

  1. Edit your site configuration to store the getresource cache in the project’s resources directory, setting the cache to never expire:

    [getresource]
    dir = ':resourceDir/_gen'
    maxAge = -1
  2. Check the resources directory into source control.

In this configuration, Hugo will use the cached resources when building your site locally and remotely, invalidating the cache when you change the LaTeX markup.

Last modified: