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 |
|
|
Wraps or separates |
|---|---|---|---|
|
|
|
The start of a math group |
|
|
|
The end of a math group |
|
|
|
Rows within a group |
|
|
(empty) |
Before and after each group |
|
|
(empty) |
The start of each prose paragraph |
|
|
|
A |
|
a |
not used |
Around an |
|
not used |
|
Each line of an |
|
|
not used ( |
Headings by level |
|
|
inherited |
Around an inline comment |
|
|
adds |
The symbol for each operator node type |
|
|
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
\[
\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
$$\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’sassign), the same way as in the HTML example.The LaTeX layout recognises an inline comment by its
COMMENT_OPENprefix, and moves the comment of a long calculation onto its own row. Becausecolor_commentchanges how comments start, the subclass setsCOMMENT_OPENto match. If you changeCOMMENT_COLORin a further subclass, setCOMMENT_OPENagain.
Example: a different heading scheme in LaTeX#
class ChapterLatexRenderer(LatexRenderer):
HEADINGS = ("chapter*", "section*", "subsection*", "subsubsection*")
Next: Symbols and functions.