mathtext

package
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package mathtext typesets mathematical notation for chart labels.

An axis reading "energy (eV)" is a label. One reading "E = mc²" or "σ²/√n" is notation, and notation is not a string with some characters in it: a superscript is smaller and raised, a fraction is two things stacked with a rule between them, a radical has a bar over what it covers. Writing those in Unicode gets a few of them approximately right and the rest not at all.

Pluggable

Typesetter is the seam. figure calls it with a label and gets back the runs and rules that draw the label; what happens in between is the typesetter's business. TeX is the one that ships — a small, deliberately bounded subset of TeX's notation — and a caller with a real typesetting engine, or with notation of their own, implements the interface instead of arguing with this one.

Nothing here is on by default. A chart typesets its labels when it is given a typesetter, and draws them as plain text when it is not:

p := figure.New(figure.Math(mathtext.TeX()), figure.YTitle(`$\sigma^2$`))

Where the work happens

A typesetter places; it does not shape. It measures through Measurer, which is the one method of ir.Backend it is given, and returns positions — so the backend that will draw the label is the one that measured it, which is the same bargain the rest of figure's text handling makes (docs/adr/0003).

A layout is relative to an origin on the baseline at the start of the expression, x rightwards and y downwards, which is the coordinate system everything else in figure uses. Placing that origin — anchoring, alignment, rotation — is the caller's job, and package render does it.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Draw

func Draw(b ir.Backend, l Layout, at, offset ir.Point, color ir.Color, rotation float64)

Draw emits a layout into a backend.

at is the anchor — the point the label was positioned at, and the point a rotated one turns about. offset moves the layout's own origin relative to that anchor, which is where alignment lives: a centred label is offset by half its width, and only the caller knows it wanted one. color paints every piece, text and rule alike, and rotation turns the whole expression.

It is the other half of Typesetter: a typesetter says where the pieces go and this puts them there, so that every caller draws notation the same way and no caller has to know that a fraction bar is a filled rectangle.

func PlainOf

func PlainOf(ts Typesetter, src string) string

PlainOf writes src as readable text using ts, falling back to src for a typesetter that cannot — including no typesetter at all, which is a chart whose labels were never notation.

func RegisterSymbol

func RegisterSymbol(name string, r rune)

RegisterSymbol adds a command to the symbol table, or replaces one: after RegisterSymbol("degree", '°'), a label containing `\degree` sets the degree sign. The name is written without its backslash. A caller may register from an init function or at any later time; the table is guarded, so a registration and a render on another goroutine do not race.

A name this package defines may be replaced, which is how a caller with an opinion about `\epsilon` gets the variant they want; there is no way to remove one, because a label written against the built-in table has to keep meaning what it meant.

func Symbol

func Symbol(name string) (rune, bool)

Symbol reports the character a command sets, and whether the command is known. It is the read side of RegisterSymbol.

Types

type Layout

type Layout struct {
	// Runs are the pieces of text, each with its own font and position. There
	// is one per run of characters at one size; a superscript is a run of its
	// own because it is set smaller.
	Runs []Run
	// Rules are the filled rectangles notation is made of: a fraction bar, the
	// bar over a radical. They are rectangles rather than strokes because
	// that is what a rule is — a thin filled box — and it needs no line width,
	// cap or join to be decided somewhere else.
	Rules []ir.Rect

	Width           float32
	Ascent, Descent float32
}

Layout is a typeset label: the pieces of text to draw and the rules to fill, with the box they occupy.

Positions are relative to an origin on the baseline of the outermost row. Width, Ascent and Descent describe the whole expression, so that a caller can centre it, right-align it or stack it exactly as it would a text run — ir.TextMetrics carries the same three, and means the same thing by them.

func (Layout) Empty

func (l Layout) Empty() bool

Empty reports whether l draws nothing.

func (Layout) Metrics

func (l Layout) Metrics() ir.TextMetrics

Metrics reports the layout as text metrics, so that a caller measuring a label does not care whether it turned out to be notation.

Ink is the full box rather than a tight outline: an expression's extent is what it occupies, and the pieces that stick out of a font box — a raised superscript, a fraction's numerator — are exactly the ones a caller sizing a margin must not clip.

type Measurer

type Measurer interface {
	// Measure reports the metrics of a run. See [ir.Backend.Measure].
	Measure(run ir.TextRun) ir.TextMetrics
}

Measurer is the part of ir.Backend a typesetter is given: the ability to ask the font stack that will draw a run how wide it is.

type Plainer

type Plainer interface {
	// Plain returns src with its notation written as readable text. ok is
	// false when there was no notation in it, in which case src is already
	// what to say.
	Plain(src string) (string, bool)
}

Plainer is implemented by a typesetter that can also write its notation as readable text.

A description that will be read aloud cannot contain markup: a screen reader asked to announce `$\frac{\sigma^2}{\sqrt{n}}$` says "dollar backslash frac". So a chart being described asks its typesetter for the same expression in words-and-characters, and falls back to the label as written when the typesetter has no opinion.

It is optional, like every other interface figure extends a type through: a typesetter that does not implement it is still a typesetter.

type Request

type Request struct {
	// Src is the label as the caller wrote it, delimiters and all.
	Src string
	// Font is the font the surrounding text is set in. A typesetter picks its
	// own sizes and styles relative to it.
	Font ir.FontRef
	// Measurer measures a run in that font. It is the backend, so a
	// typesetter measures in the same place the text will be drawn.
	Measurer Measurer
}

Request is what Typesetter.Typeset is asked to set.

It is a struct rather than three parameters because a Typesetter is implemented outside this module and so never gains one, and because setting notation has more inputs than these three the day it needs them: a writing direction, a language, a size the caller wants the result to fit. ADR 0060 is the record.

type Run

type Run struct {
	Text string
	Font ir.FontRef
	At   ir.Point
}

Run is one piece of text in a layout: what to draw, in what font, with its baseline start at At.

type TeXOption

type TeXOption func(*tex)

TeXOption configures the built-in typesetter.

func Italic

func Italic(on bool) TeXOption

Italic controls whether single-letter variables are set italic. It is on by default; turn it off for a chart whose font has no italic face and whose backend synthesises an unconvincing one.

func ScriptScale

func ScriptScale(f float64) TeXOption

ScriptScale sets how much smaller a superscript or subscript is than what it is attached to. The default is 0.7, which is TeX's own first script size.

type Typesetter

type Typesetter interface {
	Typeset(req Request) (l Layout, ok bool)
}

Typesetter lays out a label that may contain notation.

It is given the whole label, not the notation inside it, so that a typesetter decides for itself what its delimiters are — TeX reads TeX's dollar signs, and another may read something else. A label with nothing to typeset in it returns ok false, and the caller draws it as ordinary text: that is the common case, and it must stay free.

A typesetter never fails a render. Notation it cannot parse comes back as ok false too, so the label is drawn exactly as it was written — which is what a reader needs in order to see what is wrong with it.

A Typesetter is implemented outside this module, so it never gains a method.

func TeX

func TeX(opts ...TeXOption) Typesetter

TeX returns a typesetter for a bounded subset of TeX's notation.

It reads what a chart label actually contains, and stops there. What it knows:

  • `$...$` delimits notation; everything outside is drawn as it was typed, so "peak power $P_\mathrm{max}$ (W)" is one label with one expression in it. A `$$` is a literal dollar sign.
  • `x^2` and `x_i` raise and lower, `x^{n+1}` groups, and the two combine: `x_i^2` sets both about one base.
  • `\frac{a}{b}` stacks two expressions with a rule between them.
  • `\sqrt{x}` sets a radical with a bar over its argument.
  • `\alpha`, `\Omega`, `\times`, `\leq`, `\infty` and the rest of the symbol table name characters that are hard to type; RegisterSymbol adds to it.
  • `\mathrm{...}` and `\text{...}` set their argument upright, which is how a unit or a word inside notation is kept from looking like a product of variables.
  • `\,` `\;` `\quad` and `~` are spaces of the usual widths.

What it does not know is everything else: matrices, integrals with limits, alignment, \left\right delimiters that grow, and the hundreds of macros a document class defines. A label needing those wants a typesetting engine, and Typesetter is where one plugs in.

Italics

A single letter is a variable and is set italic, the way TeX sets one; a run of letters is a name and is set upright, so that "max" does not read as m times a times x. `\mathrm` forces upright and `\mathit` forces italic. A backend with no italic face draws the upright one, which is a legible chart rather than a failed render.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL