LaTeX and Markdown tokens#

LatexRenderer builds its output from a set of tokens: class attributes holding the strings it wraps math groups, paragraphs and blocks in. MarkdownRenderer is a subclass of LatexRenderer that overrides some of them. Change a token by overriding it in your own subclass.

The tokens#

Attribute

LatexRenderer

MarkdownRenderer

Wraps or separates

MATH_OPEN

\noindent$\displaystyle + newline + \begin{aligned}[t]

$$\begin{aligned}

The start of a math group

MATH_CLOSE

\end{aligned}$

\end{aligned}$$

The end of a math group

ROW_SEP

\\ + newline

\\

Rows within a group

MATH_SKIP

\medskip

(empty)

Before and after each group

PARAGRAPH

\noindent

(empty)

The start of each prose paragraph

LINE_BREAK

\medskip

 

A # hc: -b break

BLOCK_OPEN, BLOCK_CLOSE

a list environment

not used

Around an if/for body

BLOCK_PREFIX

not used

>

Each line of an if/for body (blockquote)

HEADINGS

section*, subsection*, subsubsection*, paragraph*

not used (# headings)

Headings by level

COMMENT_OPEN, COMMENT_CLOSE

\quad\text{(, )}

inherited

Around an inline comment

OPERATOR_SYMBOLS

{"mult_op": "\\cdot", ...}

adds \lt, \gt

The symbol for each operator node type

MATH_FUNCS

{"sin": "\\sin", ...}

inherited

Function names typeset as operators

The values are in src/handcalcs/renderers/latex.py and src/handcalcs/renderers/markdown.py.

Important

The Markdown renderer keeps each math group on one physical line. If you override ROW_SEP or MATH_OPEN in a Markdown subclass, don’t add newlines; Jupyter would carry the blockquote > markers of an if body into the math.

Example: centred LaTeX displays with \times#

"""A LatexRenderer that sets each calculation group as a centred display."""
from handcalcs.renderers import LatexRenderer


class DisplayLatexRenderer(LatexRenderer):
    name = "display_latex"

    MATH_OPEN = "\\[\n\\begin{aligned}"
    MATH_CLOSE = "\\end{aligned}\n\\]"
    MATH_SKIP = ""
    OPERATOR_SYMBOLS = LatexRenderer.OPERATOR_SYMBOLS | {"mult_op": "\\times"}
a = 2
b = a * 3
Output (DisplayLatexRenderer)
\[\begin{aligned} a &= 2 \\ b &= a \times 3 = 2 \times 3 = 6 \end{aligned}\]
\[
\begin{aligned}
a &= 2 \\
b &= a \times 3 = 2 \times 3 = 6
\end{aligned}
\]

Example: colors in Markdown math#

MathJax and KaTeX both support \color{name}{...}, so a Markdown renderer can color parts of the math. This subclass colors calculated results and inline comments:

"""A MarkdownRenderer that colors results and comments."""
from handcalcs.renderers import MarkdownRenderer


class ColorMarkdownRenderer(MarkdownRenderer):
    name = "color_markdown"

    RESULT_COLOR = "teal"
    COMMENT_COLOR = "gray"
    # The LaTeX layout recognises an inline comment by this prefix (it moves the
    # comment of a long calculation onto its own row), so it must match the
    # output of color_comment below.
    COMMENT_OPEN = f"\\quad\\color{{{COMMENT_COLOR}}}{{\\text{{("


@ColorMarkdownRenderer.register("calc_line:post")
def color_result(renderer, rendered, node, base_context):
    """Color the result of a calculation with \\color (MathJax and KaTeX)."""
    if not isinstance(rendered, list):
        return rendered
    assign = base_context.current.assign
    positions = [i for i, part in enumerate(rendered) if part == assign]
    if len(positions) < 2:
        return rendered  # a parameter (target = value): leave inputs plain
    result = positions[-1] + 1
    rendered[result] = f"\\color{{{renderer.RESULT_COLOR}}}{{{rendered[result]}}}"
    return rendered


@ColorMarkdownRenderer.register("inline_comment:post")
def color_comment(renderer, rendered, node, base_context):
    """Color inline comments. The output starts with COMMENT_OPEN."""
    body = rendered.removeprefix("\\quad")
    return f"\\quad\\color{{{renderer.COMMENT_COLOR}}}{{{body}}}"
w = 5  # kN/m
L = 6  # m
M = w * L**2 / 8  # kN m
Output (ColorMarkdownRenderer)
\[\begin{aligned} w &= 5 \quad\color{gray}{\text{(kN/m)}} \\ L &= 6 \quad\color{gray}{\text{(m)}} \\ M &= \frac{w \cdot L^{2}}{8} = \frac{5 \cdot 6^{2}}{8} = \color{teal}{22.5} \\ &\quad\color{gray}{\text{(kN m)}} \end{aligned}\]
$$\begin{aligned} w &= 5 \quad\color{gray}{\text{(kN/m)}} \\ L &= 6 \quad\color{gray}{\text{(m)}} \\ M &= \frac{w \cdot L^{2}}{8} = \frac{5 \cdot 6^{2}}{8} = \color{teal}{22.5} \\ &\quad\color{gray}{\text{(kN m)}} \end{aligned}$$

Two details in this example come from how the LaTeX layout works:

  • The result is found as the part after the last = of the line (the context’s assign), the same way as in the HTML example.

  • The LaTeX layout recognises an inline comment by its COMMENT_OPEN prefix, and moves the comment of a long calculation onto its own row. Because color_comment changes how comments start, the subclass sets COMMENT_OPEN to match. If you change COMMENT_COLOR in a further subclass, set COMMENT_OPEN again.

Example: a different heading scheme in LaTeX#

class ChapterLatexRenderer(LatexRenderer):
    HEADINGS = ("chapter*", "section*", "subsection*", "subsubsection*")

Next: Symbols and functions.