Skip to content

Honour \definecolor - #31

Merged
pyramation merged 2 commits into
mainfrom
feat/definecolor
Aug 17, 2026
Merged

Honour \definecolor#31
pyramation merged 2 commits into
mainfrom
feat/definecolor

Conversation

@pyramation

Copy link
Copy Markdown
Collaborator

xcolor lets a document name its own colours. Without that, a page wanting a shade xcolor does not define had no valid way to ask for it.

Why this came up

The pages on latex2js.com ask for lightblue. Real LaTeX rejects it:

! Package xcolor Error: Undefined color `lightblue'.

So those pages could not compile at all, and rendered here only because unknown names were handed to the browser, where CSS happens to know that one. \definecolor{lightblue}{RGB}{173,216,230} says exactly which colour is meant, in a line LaTeX and this agree on.

What it does

  • All five xcolor models: rgb and cmyk as fractions, RGB as 0-255, gray as one fraction, HTML as six hex digits.
  • A definition may shadow a built-in of the same name, as it may in xcolor.
  • A model this cannot read is reported against the declaration rather than silently ignored.
  • Definitions are cleared at the start of each parse, beside the counters, so a reused parser instance does not inherit the previous document's palette.

Intercepted where \psset is rather than in the command walk: the grammar delivers a command inside the line that holds it, not as a node of its own, so a walk-level handler never fires and the declaration would be rendered as text.

Verified against real LaTeX

The snippet the site now uses compiles with zero errors through latex → dvips → ps2pdf → gs, and renders the intended colours.

10 new tests; 568 across the repo.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf

xcolor lets a document name its own colours. Without that, a page wanting a
shade xcolor does not define had no valid way to ask for it — and the pages on
latex2js.com ask for `lightblue`, which real LaTeX rejects outright:

    ! Package xcolor Error: Undefined color `lightblue'.

So those pages could not compile at all, and only rendered here because this
handed unknown names to the browser, where CSS happens to know that one. That
is the gap this closes: `\definecolor{lightblue}{RGB}{173,216,230}` says
exactly which colour is meant, in a line LaTeX and this agree on.

All five xcolor models are read — rgb and cmyk as fractions, RGB as 0-255,
gray as one fraction, HTML as six hex digits — and a definition may shadow a
built-in of the same name, as it may in xcolor. A model this cannot read is
reported against the declaration rather than silently ignored.

Definitions belong to a document, not to the parser: they are cleared at the
start of each parse, next to the counters, so a reused instance does not
inherit the previous document's palette.

Intercepted where \psset is rather than in the command walk. The grammar
delivers a command inside the line that holds it, not as a node of its own, so
a walk-level handler never fires and the declaration would be rendered as text.
A blank line became a `<br>`, one per blank line, so the gap between two
paragraphs was however many times the author happened to press return, and no
stylesheet could reach it. That is not what a blank line means in TeX.

In TeX a run of blank lines — of any length — is a single \par, and the space
between paragraphs comes from \parskip: a document style, set once. Two blank
lines and one are the same input.

So text is grouped into real paragraphs now. A run of breaks ends one, however
long the run is, and the gap is the paragraph's own margin, named in the
stylesheet as --latex2js-parskip. That is the whole point of the change: the
spacing became a style, which is both what TeX means and the only version a
theme can restyle. A picture takes the same value, so a figure and a paragraph
sit apart by the same amount.

Only the plain text environment is grouped. A list keeps its \item lines for
its own component to turn into <li>, verbatim is literal, a picture is
commands, and a nicebox is one inline run — wrapping any of those in
paragraphs breaks the element that consumes them.

The text container is a div rather than a span, because a <p> inside a <span>
is invalid nesting that a browser silently hoists out, taking the text with it.

This supersedes the blank-line collapsing added a few commits ago. That change
was written for a real problem — a break beside a heading stacks against the
heading's own margin — but it also folded runs of breaks between ordinary
paragraphs into one, which halved the gap on every page. The problem was
adjacency, not repetition, and modelling paragraphs properly removes both.
@pyramation
pyramation merged commit b7d0051 into main Aug 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant