Skip to content

Latest commit

 

History

History
143 lines (108 loc) · 6.17 KB

File metadata and controls

143 lines (108 loc) · 6.17 KB

Roadmap

What follows is what is planned, what was decided against and why, and what has been built.

Planned

Remapping per element type

A remap gives one name to a container whatever it is: - note: mynote applies to Div.note and Span.note alike. That is fine where the two forms differ — \begin{mynote} and \mynote{...} in LaTeX — but in Typst both are now a call, so one name has to serve both positions. A function holding a block breaks a paragraph when used inline, and one built only from text works anywhere: which of the two you need depends on where the container appears, and the author knows, while the filter does not.

Written out, it would attach the name to the flag:

typst:
  - poemtitle: [/divcall=ptblock, /spancall=ptinline]

which adds a second separator to a grammar whose first one took some finding.

Not urgent, because the case has a plainer answer: use two classes. If a poem title looks different standing alone than it does inside a line, it is not the same container, and saying so in the source is more honest than remapping it away. The feature is worth having when a real document needs one name in two positions and two classes would be a lie.

Considered and rejected

Deriving TeX-safe names from hyphenated classes

A class like marginnote-open is fine in CSS and Typst but breaks the TeX formats. The filter could derive a safe name — dropping hyphens, so marginnoteopen — while HTML and Typst keep the original. It was considered and will not be done.

The failure it would fix is already loud: ConTeXt stops the build, LaTeX reports an undefined control sequence. The user learns immediately that the name is not usable. Deriving silently would trade that for a worse failure — the document compiles, an environment that was never defined is called, and the name in the error message appears nowhere in the source, because the filter invented it.

It would also let two classes collapse onto one name: note-title and notetitle both derive to notetitle, quietly merging two environments.

Remap entries already solve the case properly, per format and in the open:

container-writer:
  latex:
    - marginnote-open: marginnoteopen

Walking the tree by hand instead of using el:walk

Child processing runs in two passes: mark_children annotates matching descendants with _cw_env and _cw_acc, and wrap_marked substitutes them afterwards. The marks exist because since Pandoc 3.9 a walk handler that returns a replacement stops descending, so wrapping note.title on sight would never reach note.title.icon. Marking first breaks that dependency.

Iterating over el.content directly would remove the need for marks altogether: controlling the order means substituting from the inside out, leaving nothing behind in the AST. It will not be done for now.

el:walk handles what is easiest to get wrong — descending through blocks and inlines under different rules, into table cells, footnotes, block quotes. Reimplementing that is more code than the two functions it would replace, and every branch left out is a container that silently stops being processed. It would also merge two short functions with one job each into a single larger one, since controlling the order means doing both at once.

The gain would be dropping the attributes from the AST. They did leak into the output once — fixed in f6c7d77 — but the passthrough golden files now cover exactly that, which is most of what the rewrite would buy.

Nothing in the Pandoc API helps here: pandoc.walk_block and walk_inline behave the same way on replacement, and there is no bottom-up mode.

Done

Calling a named function per whitelist entry

Done. Six flags choose the wrapping form per entry, overriding the default of block form for a Div and inline form for a Span:

/divblock /divinline /divcall /spanblock /spaninline /spancall

latex:
  - poemtitle: [/divcall, /spanblock, ptcmd]

The leading slash tells a flag from a remap target, so both fit in one entry. It also had to survive two parsers: a backslash, as an earlier draft of this entry proposed, does not — Pandoc reads metadata values as Markdown and eats it as an escape — and @ * % # break the YAML unless quoted, while ! and & are a tag and an anchor and arrive empty.

Two things turned up while building it. A call takes its content as an argument, and a paragraph break inside a TeX argument ends it: emitting a Div as a block made \poemtitle{...} fail even with \long, because any non-long command further in stops at the \par. Single-paragraph Divs are now emitted as their inlines. And a form a format does not have — call in JATS, anywhere in DOCX — falls back to the default with a warning rather than silently.

Verified against the real verse package: a Div.poemtitle now compiles as \poemtitle{Hamlet} with the title centred, which is what the package provides as a command and no environment could give.

Refactor wrap_element as a format/mode dispatch table

The if FORMAT == 'latex' ... elseif FORMAT == 'context' chain is now a FORMATS table. Each entry declares what the format can do — block/inline templates, an attr to set, or nothing at all for passthrough — and a single engine acts on it.

It went further than planned in two ways: entries also cover formats that set an attribute instead of wrapping (DOCX, ODT) and formats that deliberately do nothing (HTML, EPUB, and the JSON/native AST passes), and writer variants resolve to their base entry, so html5 and jats_publishing need no entry of their own.

The premise held: adding JATS was four lines of data, variants included, without touching the engine.

The fields were originally named env and cmd, after LaTeX, where a Div happens to want an environment and a Span a command. That coincidence does not hold elsewhere: Typst's inline form is an anonymous group with a label, JATS's is a named-content element, and neither is a "command". They are now block and inline, which describe what the engine actually decides — which context the node is in — and that question does have the same answer in every format.

The call form it left pending is done — see above.