Version 5.9.4

# Version 5

## 5.9

### 5.9.4 (2026-09-04)

- The number of publishing runs can now be set in the layout file. ↗
  The new attribute `runs` on `<Options>` sets the number of publishing runs. This way a layout that needs more than one run, for example for page references or the total number of pages, works without extra settings. The command line option `--runs` takes precedence over the attribute, the attribute takes precedence over the configuration file.

### 5.9.3 (2026-09-03)

- New XPath functions for sequences and aggregates. ↗
  The XPath parser now supports more standard functions: `sum()`, `avg()`, `exists()`, `index-of()`, `subsequence()`, `insert-before()`, `remove()`, `compare()`, `deep-equal()`, `zero-or-one()`, `one-or-more()` and `exactly-one()`.
- Functions that return table rows can now call other row returning functions. ↗
  When a layout function that returned table rows called another row returning function via `<Value>` inside `<ForAll>` or `<Switch>`, all rows of the outer function disappeared from the table as soon as the inner function was called at least once. Now all rows are output.

### 5.9.2 (2026-08-21)

- The height attribute now also scales QR codes. ↗
  The attribute `height` on `<Barcode>` was ignored for QR codes, the code always filled the available width. Since QR codes are square, the smaller value of `width` and `height` now determines their size (#732).
- New setting luafile: Lua callbacks for file lookup and image conversion. ↗
  The new configuration setting `luafile` (command line `--luafile`) loads a Lua file at the beginning of the publishing run. The file can register callbacks: `lookup_file` rewrites file names before the file lookup, `image_handler` and `resize_handler` control the conversion of images with external programs. The callbacks are a more flexible replacement for the settings `pathrewrite`, `imagehandler` and `resizehandler`, which continue to work as before (#710).
- Fonts loaded with LoadFontfile override the built-in fonts serif, sans and monospace. ↗
  When a font file is loaded with `<LoadFontfile>` under one of the built-in names `serif`, `sans` or `monospace` (or one of their bold and italic variants), that font is now used in the output. Previously the built-in font was used silently instead, which could also lead to reports about missing glyphs (#718).
- New experimental Math command for mathematical formulas. ↗
  The new command `<Math>` typesets mathematical formulas that are written in MathML notation. Fractions, square roots, superscripts and subscripts are supported, both inline in the running text and in display style. A math font in OpenType format such as Latin Modern Math is required; it is loaded as a font family and selected with the attribute `fontfamily`. The command is experimental and the supported MathML subset will grow in future versions.

### 5.9.1 (2026-08-18)

- The old XPath parser (luxor) has been removed. ↗
  Layouts are always processed with the lxpath parser. The configuration setting `xpath` and `require="luxor"` on `<Layout>` now report an error. The command line options `--extra-xml` and `--prepend-xml` are also gone, use XInclude instead to split the layout into several files.
- The old fontforge font handler has been removed. ↗
  Fonts are always loaded and shaped with harfbuzz. The configuration setting `fontloader`, the attribute `mode` on `<LoadFontfile>` and `require="fontforge"` on `<Layout>` now report an error. Type 1 fonts (.pfb) are no longer supported, please convert them to OpenType.

## 5.8

### 5.8.0 (2026-08-17)

- Release version 5.8.0.

## 5.7

### 5.7.12 (2026-08-17)

- Balanced columns in Output are now split by height. ↗
  When the contents of `<Output balance="yes">` was distributed over two frames, the split position was determined by the number of lines, not by their height. Lines of different heights led to visibly unequal columns. Now the material is split by its actual height, and the second column never gets higher than the first one (#714).
- Balancing split tables now takes repeated table headers into account. ↗
  When a table with `balance="yes"` was distributed over several frames, the height of the repeated dynamic table headers (rows with `sethead="yes"`) was not part of the height calculation. With tall headers the frames became uneven or the content ran over the bottom of the frame. Now the frames end at the same height (#715).
- The letter spacing on Span now depends on the font size. ↗
  When the attribute `letter-spacing` on `<Span>` was set to `yes`, a fixed amount of space was inserted between the characters, regardless of the font size. That was far too much for small text and too little for large headings. Now the space is one tenth of the current font size, so the spacing fits all sizes (#716).
- Groups can now grow to any size. ↗
  The virtual area of a `<Group>` was internally limited to ten page heights. Objects that reached beyond that limit were placed at the top of the group again and overlapped the existing content. Now the group grows with its content (#717).

### 5.7.11 (2026-08-14)

- A wrong namespace prefix in an XPath function call now gives a clear error message. ↗
  When an XPath expression called a function with an unknown namespace prefix, for example `fn:tabhead()` with an undeclared prefix `fn`, the publisher stopped with an internal error and a long stack trace. Now the log shows a clear error message with the prefix, the function name and the position in the layout file.
- Cells that span several columns take their alignment from the first spanned column. ↗
  When a table cell with `colspan` contained a table or an image, the horizontal and vertical alignment was taken from the last spanned column. A cell that spanned up to a right aligned column was therefore output right aligned. Now such cells use the alignment of the first spanned column, as paragraphs already did.
- Objects that move to the next page keep their requested column. ↗
  When an object placed with `<PlaceObject column="...">` did not fit into the remaining space on the page and could not be split, it was output in the first grid column of the following page instead of the requested column. Now the requested column is kept.
- A warning appears when the deprecated luxor XPath parser or the fontforge font handler is used. ↗
  Both the old XPath parser (`luxor`) and the old fontforge font handler are deprecated and scheduled for removal in version 6.0. The publisher now writes a warning to the log when one of them is used, with a hint on how to switch to the defaults `lxpath` and `harfbuzz`.

### 5.7.10 (2026-08-12)

- Collecting elements in a variable with Copy-of works again. ↗
  When a variable was extended step by step with `<SetVariable>` and `<Copy-of select="$var"/>`, path expressions such as `$var/entry` only found the element added last. So for example a keyword index built this way and passed to `<Makeindex>` contained just one entry. With the old XPath parser (`luxor`) everything worked as expected. Now all collected elements are found again (#712).
- The sp doc command now shows the included manual. ↗
  The distribution ZIP files now include the HTML manual. The command `sp doc` serves it through a local web server and opens the browser; with `--no-autoopen` the browser stays closed. If no local manual is found, the command opens the online documentation as before.
- The old XPath parser (luxor) and the old fontforge font handler are deprecated. ↗
  The old XPath parser (`luxor`) and the old fontforge font handler are deprecated and scheduled for removal in version 6.0. Layouts that still use one of them should switch to the defaults `lxpath` and `harfbuzz`. The chapter on compatibility with older versions in the manual shows how to migrate.

### 5.7.9 (2026-08-10)

- Pages from SavePages and InsertPages now get trim and bleed boxes. ↗
  When all pages were output through `<SavePages>` and `<InsertPages>`, the PDF pages had no trim and bleed boxes, which for example violates the PDF/X standard (#705).

### 5.7.8 (2026-07-28)

- Functions defined with Function now return their result in all contexts. ↗
  The result of a function defined with `<Function>` only arrived in `<Value select="...">` and `<SetVariable>`. In `<Message select="...">` and in attribute expressions in curly braces, for example `file="{concat('a-', fn:add(3,4), '.pdf')}"`, the function call was silently empty, which led to broken values such as “a-.pdf” instead of “a-7.pdf” without any hint in the log. The function result is now available everywhere XPath expressions are allowed, including string functions such as `concat()` and comparisons (#704).

### 5.7.7 (2026-07-27)

- No more crash with the obsolete colorprofile attribute on Options. ↗
  When the obsolete attribute `colorprofile` on `<Options>` named an ICC file that matches one of the built-in color profiles, the publisher crashed when writing the PDF. The attribute works again; please use `<DefineColorprofile>` and `colorprofile` on `<PDFOptions>` instead.
- Loading the same font file with the harfbuzz and the fontforge font handler no longer crashes. ↗
  When the same font file was loaded once with `mode="harfbuzz"` (the default) and once with `mode="fontforge"` on `<LoadFontfile>`, the publisher stopped with the message “Perhaps you have combined harfbuzz with fallback? Please file a bug report.” and a Lua error. Both variants can now be used in the same layout.
- text-decoration: line-through and dashed work now in HTML markup. ↗
  In HTML markup, `text-decoration: line-through` rendered as an underline instead of a strike-through, and `text-decoration-style: dashed` always produced a solid line. Strike-through and dashed lines are now output correctly. The styles `dotted` and `wavy` are not implemented yet and fall back to a solid line.
- After a line break (Br) the paragraph keeps its text format. ↗
  The part of a paragraph after a `<Br/>` was formatted with a format derived from the default `text` format instead of the text format assigned to the paragraph, so settings such as alignment or indentation were lost after the break. The derived format now inherits from the assigned text format.
- A CSS ::before rule without content no longer breaks the HTML output. ↗
  A `::before` rule without a `content` property made the publisher discard parts of the HTML output and could crash it, for example with lists or tables. As CSS demands, such a rule now simply creates no pseudo-element and the rest of the output is not affected.
- The new XML parser is now also used for data from standard input and for sd:decode-html(). ↗
  When the new XML parser is active (the default), the publisher still used the old XML parser for reading data from standard input (`--data -`) and for the result of the layout function `sd:decode-html()`. Now the new parser is used in both cases. As a side effect, attributes in the result of `sd:decode-html()` used with `<HTML select="...">` are no longer lost, for example links with `href`.
- Type 1 fonts (.pfb) work again without further settings, but are deprecated. ↗
  Loading a Type 1 font (.pfb) without the setting `mode="fontforge"` on `<LoadFontfile>` stopped with the error message “lua-loaded font has no name”. The publisher now switches to the old fontforge font handler automatically and shows a warning. The fontforge font handler is deprecated and will be removed in a future version. Please convert Type 1 fonts to OpenType.

### 5.7.6 (2026-07-23)

- Warn about unknown attributes in the layout. ↗
  Attributes that a command does not know were silently ignored, for example a typo like `<Textblock widht="...">` or an attribute on an element that does not support it. The publisher now shows a warning with the name of the attribute and the element. Attributes from other namespaces (such as `xml:lang` or own annotations) are ignored as before. The check is active with the new XML parser; some old, undocumented attribute spellings such as `backgroundcolor` instead of `background-color` also get a warning now, but keep working (#702).
- No more endless loop when the text in AtPageCreation does not fit into the area. ↗
  When an `<Output>` inside `<AtPageCreation>` or `<AtPageShipout>` contained more text than fits into the frames of the area, the publisher ran into an endless loop, because no new page can be started at that point. Now the text that fits is output, the rest is discarded and an error message shows the name of the affected area (#701).
- A Mark inside a Paragraph is no longer lost. ↗
  A `<Mark>` was only processed as a direct child of `<Textblock>` or `<Td>`. Inside a `<Paragraph>` it was silently ignored. A mark can now also be placed within a paragraph, at any position in the text, and reports the page on which this position is output (#699).
- XPath union expressions such as “a | b” work now. ↗
  With the new XML parser, a union expression like `select="foo | bar"` — for example in `<ProcessNode>` — only returned the part before the `|`, without any error message. Unions now work as expected: the result contains the nodes of all parts in document order, duplicates are removed. The operators `intersect` and `except` are supported as well (#698).
- The XPath functions upper-case() and lower-case() now convert umlauts and other non-ASCII characters. ↗
  With the new XML parser, `upper-case()` and `lower-case()` only changed the letters A to Z. Umlauts and other characters outside of ASCII stayed as they were, so `upper-case('Reisebüro')` resulted in “REISEBüRO”. Both functions now use the full Unicode case rules: umlauts and accented letters are converted as expected, and `upper-case('Straße')` gives “STRASSE”. The behavior of the old XML parser (`--xpath=luxor`) is unchanged (#697).
- A Paragraph without content now creates an empty line. ↗
  A `<Paragraph>` without content was ignored in the output. It now always creates an empty line, just like a paragraph containing only a non-breaking space (`&#xa0;`). This applies to an empty `<Paragraph></Paragraph>` as well as to a paragraph whose child elements produce no content, for example an empty `<Value/>`, a `<Value select="..."/>` that selects nothing, or a `<ForAll>` or `<Switch>` without output. If no empty line is wanted in such cases, the paragraph must be created inside the `<ForAll>` or `<Switch>` instead.

### 5.7.5 (2026-07-21)

- The new attribute break-pagetype on Table inserts an extra page between the parts of a multi-page table. ↗
  When a table is split at a page break, the new attribute `break-pagetype` on `<Table>` inserts an extra page between the two parts of the table, for example for the back side of a sheet. The value is the name of a page type; the contents of the inserted page are created with `<AtPageCreation>` of that page type. The page numbering continues as usual.

### 5.7.4 (2026-07-09)

- A centered right-to-left paragraph with a line break is centered correctly again. ↗
  In a centered paragraph that runs from right to left — for example Arabic or Hebrew — and contains a line break (`<Br/>`), the line after the break was shifted slightly out of the middle. Such lines are centered correctly again.
- The characters U+0083 (no break here) and U+2060 (word joiner) now prevent a line break. ↗
  Data sometimes contains the characters U+0083 (`&[#131](https://github.com/speedata/publisher/issues/131);`, “no break here”) or U+2060 (`&[#8288](https://github.com/speedata/publisher/issues/8288);`, “word joiner”) to keep two words on the same line. Previously U+0083 raised a “glyph is missing from the font” error and U+2060 was silently dropped. Both characters now do what they are meant to do: no line break happens at their position, and an adjacent space becomes non-breaking as well (#695).

### 5.7.3 (2026-07-08)

- Allow a literal “<” such as “<5” in text passed to sd:decode-html(). ↗
  Text handed to `sd:decode-html()` that contained a `<` which does not start an HTML tag — for example a less-than sign as in “amount <5” — previously made the whole value fail and had to be escaped in the data. Such a `<` is now treated as literal text, the way web browsers do, so everyday comparisons and similar expressions work without extra escaping.

### 5.7.2 (2026-07-03)

- Keep the last glyph inside a hyperlink that ends a box (e.g. NoBreak). ↗
  When a hyperlink reached the last node of a list (`head.next == nil`), `insert_nonmoving_whatsits` inserted the `pdf_end_link` whatsit before that node, so the final glyph fell out of the clickable link area. This surfaced with `<NoBreak>` (and other tightly packed boxes), whose content is `hpack`ed with no trailing glue after the last glyph — in a normal paragraph the `parfillskip` after the text keeps the last glyph from being the final node, so the bug stayed hidden there. The `pdf_end_link` is now inserted after the node in that case, mirroring the existing endcolor handling.
- Apply HarfBuzz font kerning to NoBreak content. ↗
  The `keeptogether` strategy of `<NoBreak>` `hpack`ed its glyph list without running the HarfBuzz kern pass, so font kerning was lost and the box came out too wide — the glyphs sat at their default advance widths. The effect was most visible between the last two glyphs and varied with the language handed to HarfBuzz. `node.kerning` followed by `hbkern` now runs before the `hpack`, matching `finish_par`, so a NoBreak box is kerned identically to a normal paragraph. The three NoBreak reference PDFs are regenerated.
- Show the proper error instead of crashing when the root element matches no Record. ↗
  When a data file’s root element did not directly match a `Record` and the new XPath parser tried the pattern-matching fallback, it called a bare, unqualified `find_matching_pattern` which resolved to nil — so the run aborted with `attempt to call a nil value` instead of the intended `Can't find “Record” command for the root node` message. The call is now qualified as `M.dispatch.find_matching_pattern`, so the fallback works and, when nothing matches, the helpful diagnostic (with namespace and element name) is shown.
- Honor a boolean fourth argument to sd:merge-pagenumbers(). ↗
  The fourth argument of `sd:merge-pagenumbers()` enables hyperlinks on the merged page numbers. It was read with `string_value`, so passing `false()` produced the string `"false"` — which is truthy in Lua and switched the hyperlinks on regardless. It is now read with `boolean_value`, so an actual boolean `false` disables the hyperlinks as intended.

### 5.7.1 (2026-06-22)

- Fix false-positive “Output loop detected in vsplit” that discarded content on long documents. ↗
  The loop detection in `Output`/`Text` compared only node references (`prevobj1 == obj1`). LuaTeX recycles freed node memory, so the freshly built page box can reuse the pointer of an already-output box and compare equal even though real vertical progress was made. On a document whose single `<Output>` spans more pages than the point of the first pointer collision, this aborted output prematurely and silently discarded the remaining content. The detection now additionally requires that no vertical progress was made (the remaining material did not shrink); genuine loops — an unbreakable block taller than the area — still raise the error as before. Adds `qa/output/vsplitloop` as a regression test.
- Fix an explicit hyphen disappearing when the preceding glyph is kerned. ↗
  When `lang.hyphenate` turns an explicit hyphen into an automatic discretionary, the hyphen glyph lives in that discretionary’s `replace` list. `hbkern` overwrote the `replace` list with the pending kern node (`d_setfield(head, "replace", kern)`), so the hyphen was dropped and vanished whenever the font kerned the glyph immediately before it (e.g. with `allowbreak=" "`). The kern is now prepended to the existing `replace` list instead of replacing it. Adds `qa/harfbuzz/hyphenkern` as a regression test.

### 5.7.0 (2026-06-12)

- Accept luahbtex as a fallback backend binary. ↗
  The `sp` launcher previously looked only for `sdluatex` (in `installdir/bin` and on `PATH`) and aborted if it was missing. It now also accepts a stock `luahbtex` binary as a fallback, so installations that do not ship the speedata-patched LuaTeX (e.g. distribution packages) can still run the publisher. The `luatex` configuration option keeps overriding both. The final error message has been updated to mention both binary names.

## 5.6

### 5.6.0 (2026-06-03)

- Release version 5.6.0.

## 5.5

### 5.5.25 (2026-06-01)

- Keep rotated table cell images inside their cell. ↗
  The PDF `cm` emitted before an image whatsit no longer carries a y-shift of `-image_height`; older LuaTeX versions added one automatically. For `<Td rotate="..."/>` which pivots at the cell’s top, the image was drawn above the pivot, escaping the cell upward. The image is now wrapped in a save/restore that pre-shifts down by the content height, so rotation pivots at the image’s top edge. Text in rotated cells is unaffected because its position is set by an absolute `Tm` matrix that overrides the CTM shift.
- Don’t reverse LTR script glyphs when bidi=“no”. ↗
  With `bidi="no"` and `direction="rtl"`, the segment direction was forced to rtl and passed to HarfBuzz, which dutifully reversed Latin (or other LTR-script) glyphs — so a Lorem ipsum block read right-to-left. HarfBuzz now receives `nil` for the segment direction so it is auto-detected from the script, while the paragraph-level `maindirection` still drives line layout.
- Fix Options/startpage being overwritten by the default value. ↗
  Adding a default of `1` for `publisher.options.startpage` in a 5.5.x cleanup made a later code path always reset `current_pagenumber` to `1` — overriding whatever the `<Options startpage="..."/>` handler had just set. The handler now also writes the parsed value back into `publisher.options.startpage`, so the late check sees the user’s choice.

### 5.5.24 (2026-05-30)

- New jardir option lets OS packagers point the Publisher at system-installed Java JARs. ↗
  The Publisher bundles Saxon-HE, jing and xmlresolver as JAR files in its `lib` directory. Operating-system packagers (FreeBSD’s `textproc/saxon-he` port was the trigger, #685) need to link against the system-wide copies in `/usr/local/share/java/classes/` rather than ship a private duplicate. A new `jardir` configuration option (settable via `publisher.cfg` or `--option jardir=...`) overrides the JAR lookup location. The Saxon JAR is now matched by a `saxon-he-*.jar` glob instead of a hardcoded filename, so the system version need not match the bundled point release exactly.

### 5.5.23 (2026-05-26)

- Fix matches() XPath function rejecting a third (flags) argument with the new parser. ↗
  The XPath 2.0 `matches(input, pattern, flags)` signature was broken with the new XPath parser: a three-argument call failed with `function matches() requires 1 to 2 arguments, 3 supplied` (#692). The publisher-side wrapper in `layout_functions_lxpath.lua` overrode the lxpath-default registration with the wrong min/max arity (1/2 instead of 2/3) and silently ignored the third argument. The wrapper now accepts 2-3 arguments and forwards the flags (`i`, `m`, `s`) to Go’s `regexp` engine via an inline `(?flags)` prefix, matching the legacy parser’s behaviour. The `x` flag is still not supported and emits a warning.
- Fix HSpace width clobbered inside table cells. ↗
  `fix_justification` replaced the width of `subtype=0` glues with the surrounding font’s interword space whenever the line was being stretched. That broke explicit `<HSpace width="..."/>` inside table cells, where the left-aligned cell paragraph is almost always stretched by `parfillskip`, so the requested width was silently lost. Fixed-width `HSpace` glues are now marked with a property and skipped in `fix_justification`.

### 5.5.22 (2026-05-16)

- Strip page/line context from end-of-run summary log entries. ↗
  The end-of-run lines `Start processing`, `Stop processing data`, `N errors occurred` and `Duration: ...` were going through `main.log`, which automatically attaches the current `page`, `line_layout` and `line_data` as structured fields. Those fields are meaningful for warnings/errors emitted during processing, but on a run-summary line they just expose whatever page/line the publisher happened to stop on — misleading when the user aggregates several runs’ output. These four lines now call `splib.log` directly, so the protocol entries are bare (`<entry level="info" msg="0 errors occurred"/>`).
- Fix CJK shaping crash: publisher.prohibited_at_end is nil. ↗
  CJK text shaping (chinese, japanese) crashed with `attempt to index a nil value (field 'prohibited_at_end')` in `nodes.lua:1453`. The `prohibited_at_end` and `prohibited_at_beginning` tables in `spinit.lua` were defined as bare global variables, and `nodes.lua` accessed them as `publisher.prohibited_at_end`. That worked until the `_G.publisher` table with `__index = _G` was dropped — since then the access resolved to nil. The tables are now properly attached to the publisher namespace. A small regression test under `qa/harfbuzz/cjk/` exercises the CJK code path.

### 5.5.21 (2026-05-13)

- Fix ForAll crash on empty selection with the new XPath parser. ↗
  When the new XPath parser returned nil for a `select` expression with no matches (e.g. `<ForAll select="foo/bar/*"/>` where `foo/bar` has no children), ForAll crashed on `#selection`. The legacy-parser branch already guarded against this; the newxpath branch now does too, so an empty sequence is a silent no-op (#691).
- Harden the cgo XML-to-Lua bridge: binary-safe strings and typed key/value helpers. ↗ ↗
  Two related changes in `splib`. (1) `pushString` now uses `lua_pushlstring` with an explicit length so embedded NUL bytes survive intact; `lua_pushstring` would silently truncate at the first `\0`, which could corrupt XML CDATA or binary attribute values. The C-side malloc/free round trip via `C.CString` is gone — Lua copies the bytes internally, so the Go string can be passed directly. (2) The `any`-typed `addKeyValueToTable` helper, which boxed its arguments into interfaces and would `panic` on an unexpected concrete type, is replaced with three typed helpers (`addStringValueToTable`, `addIntValueToTable`, `addBoolValueToTable`); all call sites in the streaming parser, the tree renderer and the CSS-to-Lua renderer pick the matching one statically. No user-visible behaviour change.

### 5.5.20 (2026-05-13)

- Call lua_checkstack in splib before pushing XML data to Lua. ↗
  The streaming XML-to-Lua bridge (used for both layout and data files, including `xi:include` expansion) and the recursive tree renderer pushed values onto the Lua stack without ever calling `lua_checkstack`. Lua only guarantees `LUA_MINSTACK` (20) free slots; any further push without explicit reservation is undefined behaviour. With deeply nested or large documents this could manifest as nondeterministic `STATUS_HEAP_CORRUPTION` / `STATUS_BAD_STACK` crashes on Windows (issue #691). The bridge now reserves headroom before the document table and on every token iteration; the tree renderer reserves headroom in `RenderToLua`, in `pushElement`, and around each child push.

### 5.5.19 (2026-05-11)

- Fix tablerule misplacement inside stretched vboxes when tablerulefix=yes. ↗
  `tablerulefix="yes"` collects rule whatsits during page shipout and re-emits them at absolute `(curx, cury)` positions. The cursor update for glue nodes used the natural width only and ignored the effective width after the surrounding box stretched or shrunk its glue, so rules that sat after stretched glue in a vlist were placed too high. The effective glue width is now computed from the parent box’s `glue_sign`, `glue_set`, and `glue_order` before advancing the cursor.
- Fix lxpath misparsing $a*$b as a wildcard QName. ↗
  The lxpath QName scanner accepted `*` as a regular name character, so expressions like `$a*$b` were swallowed into a single QName instead of being parsed as a multiplication. `*` is now only accepted at the start of a QName or immediately after the namespace colon, where it is a legitimate wildcard.
- Fix crashes in the legacy XPath parser on missing attributes/elements/variables. ↗
  Issue #691 reported two crashes in the legacy XPath parser when expressions referenced unbound values: an arithmetic-on-nil in `eval_addition` (reachable via SetVariable/select) and a compare-with-nil in `eval_comparison` (reachable via Switch/Case). `get_argument_number` now treats the nilmarker sentinel like a real nil so the existing “evaluating to 0” fallback applies, and `tex.sp` is wrapped in `pcall` to fall back to 0 on inputs LuaTeX rejects. `eval_comparison` gained a post-tonumber guard mirroring the nilmarker convention: nil compared to anything is false, except `!=` which is true. lxpath remains the recommended parser; this only patches the legacy fallback.

### 5.5.18 (2026-05-09)

- Fix Lua stack overflow and major slowdown caused by Tr/Tablehead/Tablefoot origin references. ↗
  `Tr`, `Tablehead` and `Tablefoot` stored their `layoutxml` and `dataxml` directly on the row value table since 5.5.10 (commit `ea433dc7`, for the new `eval-on-split` feature). When a `SetVariable` overwrote a variable that contained such a structure, `flush_table` recursed through the entire layout and data XML trees. With deeply nested data this hit Lua’s stack limit; with shallower data it caused dramatic slowdowns (120s → 1000s in one report). The references are now kept in a weak-keyed origin map, so the value tables stay clean and `flush_table` no longer follows them (#690).

### 5.5.17 (2026-05-07)

- Fix panic on sp –help / –version and on early startup errors. ↗
  `sp --help` and `sp --version` crashed with a nil-pointer dereference because the help branch ran `exitProgram` before the configuration was loaded, and `exitProgram` calls `showDuration` which reads an option from the still-uninitialized config. The same panic could mask the real error message on early startup failures (e.g. when the bundled `sdluatex` binary cannot be executed on Windows). The help branch now exits directly, and `ConfigData.String` is safe against a nil receiver (#689).

### 5.5.16 (2026-05-05)

- Generate placeholder images via placeholder:// URL. ↗
  A new `file="placeholder://WxH"` form on `Image` renders a grey rectangle with a diagonal cross at a natural size of W × H bp, useful during layout development when the final assets are not yet available. The standard image scaling and aspect-ratio handling apply unchanged.
- Fix absolute positioning of objects inside groups. ↗
  PlaceObject with absolute `column`/`row` inside a `Group` always landed in the group’s top-left corner instead of the requested coordinates. The group branch was reading the wrong fields from the parameter table.
- Fix background-text on table cells. ↗
  background-text on a `Td` aborted the run with a Lua error since the Lua-modularization refactor in 5.5.x. Three issues were fixed: a wrong call signature for mknodelist, an erroneous %d in a format string for a float value, and the watermark geometry (the rotated bounding box is asymmetric around the rotation origin, and descenders on glyphs like ( ) g y were ignored). Existing watermarks may shift slightly because they are now correctly centered.

### 5.5.15 (2026-05-04)

- Fix wrong font in long documents that mix scripts. ↗ ↗
  In documents that switch fonts inside a paragraph — for example a non-Latin body text with Latin verse or footnote numbers — text in later `Output` blocks could end up shaped against the wrong font once it crossed a column or page break, causing characters to be replaced or to disappear from the page. An internal font-state variable was leaking between unrelated parts of the layout; both spots that exposed the leak are now properly scoped.

### 5.5.14 (2026-05-03)

- Fix crash and excessive warnings during complex-script shaping. ↗
  Avoid an indexing crash in `hbglyphlist` when a backmapped codepoint has no entry in the font’s characters table (triggered e.g. by Khmer subscript consonant stacks), skip HarfBuzz sentinel glyphs (codepoint `0xFFFF` with `x_advance=0`) emitted during complex reordering, and deduplicate “Glyph is missing from the font” warnings: each `(font, codepoint)` pair is reported only on first occurrence, with a count-based summary at the end of the run.
- sp filter scripts now run on Lua 5.4. ↗
  The Lua VM for `sp filter` scripts was switched from yuin/gopher-lua (Lua 5.1) to speedata/go-lua (Lua 5.4), bringing it closer to the LuaTeX side and consolidating Lua tooling. Includes a new in-tree `luahttp` module as a replacement for cjoudrey/gluahttp.

### 5.5.13 (2026-04-27)

- Publishing API v1. ↗
  New server API version 1 with improved endpoints for publishing runs, status queries, and PDF retrieval.
- Show progress with sp –progress. ↗
  New command line option `--progress` that displays the typesetting progress during a publishing run.

### 5.5.12 (2026-04-27)

- Update to LuaTeX version 1.24.0.
- Variable font support. ↗
  Load variable fonts and pin design axes (weight, width, optical size, etc.) to create static font instances using harfbuzz subsetting. Adds `weight` and `width` shortcut attributes and a generic `Axis` child element to `LoadFontfile`.
- Reduce memory usage for large data XML files by ~50%. ↗
  Streaming SAX parser builds Lua tables directly without an intermediate Go tree, omits unused metadata fields in data mode, handles ignoreeol during parsing, and fixes createTable size hints to avoid repeated hash table resizes.
- XPath 3.1 arrays, maps, lookup operator and string concatenation. ↗
  Add arrays (`[1,2,3]`, `array { 1 to 3 }`), maps (`map { 'key': value }`), the lookup operator (`?`), and the `||` string concatenation operator to XPath. Includes `array:` and `map:` function libraries (size, get, put, append, keys, contains, merge, etc.).

### 5.5.11 (2026-04-23)

- Implement fn:serialize() in XPath. ↗
  The XPath function `fn:serialize()` converts a node set into its XML string representation.
- Fix HTML/CSS processing of inline style attributes. ↗ ↗
  Fix incorrect quoting of HTML attributes during reconstruction (Lua’s `%q` introduced backslashes before newlines) and fix element detection in `Copy-of` and `SetVariable`.

### 5.5.10 (2026-04-22)

- Allow colspan="*" in table cells. ↗
  Use `colspan="*"` on a Td element to automatically span all remaining columns from the current position, removing the need to hard-code the column count.
- Remove Debian package generation. ↗
  It is too much work to keep the Debian packages maintained.
- Configurable list marker gap via CSS. ↗
  The gap between list marker and text is now configurable via li::marker { padding-right: … } in CSS. The default remains 5pt.
- Alternating row colors restart on each page. ↗
  Background colors in table cells are now deferred until after page splitting. The new Table attribute eval-on-split allows alternating row colors to restart correctly on each new page.
- Fix hairline artifacts between table cells. ↗
  When all cells in a row share the same background color, the background is now drawn as a single rectangle, eliminating hairline rendering artifacts between adjacent cells in some PDF viewers.
- Fix empty lines between HTML block elements. ↗
  Whitespace-only text nodes between HTML block elements (e.g. between two paragraphs) are no longer rendered as empty lines.
- Fix space rendering in some fonts. ↗
  Font backmap now prefers printable characters when multiple codepoints share a glyph ID, fixing broken space rendering in some fonts like HelveticaNeue-Bold.
- Better error handling for undefined colors. ↗
  The layout now shows a warning instead of crashing with a Lua error when encountering problems like undefined colors.
- Security: update Go dependencies. ↗
  Update golang.org/x/net and golang.org/x/image to fix CVEs (XSS, HTTP proxy bypass, OOM via crafted TIFF).
- Fix Windows DLL cleanup crash. ↗
  Work around a Go runtime issue where the shared library cleanup on Windows caused an access violation (0xc0000005) even though the PDF was generated successfully.

### 5.5.9 (2026-04-20)

- Documentation: move to Hugo static site generator, reorganize structure.
- Allow Textblock in Td for better control with rotation.
- Support css ::marker.
  CSS ::marker pseudo-element is now supported for list styling, including custom content and colors.
- Images are drawn when trace objects=“yes”.
- Bug fix HTML borders width calculation.

### 5.5.8 (2026-04-14)

- Honor box height in lines (line height).
- Box border.
- Vertical align for Box in paragraph mode.
- Images: vertical alignment in Paragraph.
- Remove action command.
- Suppressinfo for metadata.
- Show sum mismatch in sp compare.
- New attribute match on Record.

### 5.5.7 (2026-04-02)

- Bugfix table shrink algorithm.

### 5.5.6 (2026-04-02)

- Bugfix PDF copy/paste hyphen minus.

### 5.5.5 (2026-04-01)

- Fix minimum width in tables with colspan and rowspan.
- Fix CSS inheritance on td/tr.

### 5.5.4 (2026-04-01)

- HTML tables: honor rowspan and colospan, Prevent rowspan at bottom of page.

### 5.5.3 (2026-03-09)

- Server mode: add endpoint to retrieve publisher-protocol.xml.

### 5.5.2 (2026-03-06)

- Correct lots of spelling mistakes in the documentation.
- Implement accessibility for Output/Text (#614).
- PDF/UA structure tree.
- Fix PDF/UA and InsertPages / SavePages (#613).
- Add search path, don’t duplicate files.
- Better error message for function calls and old XPath parser.
- Bugfix letter-spacing and hyphenation.
- Filter: set classpath for Saxon correctly, including all required JAR files.

### 5.5.1 (2026-03-02)

- Bug fixe for image inclusion (opacity), attribute context handling and Creator version.

### 5.5.0 (2026-02-26)

- Bugfix letter-spacing in tables.

## 5.4

### 5.4.3 (2026-03-04)

- Bugfix letter-spacing and hyphenation.
- Better error message for function calls and old XPath parser.
- Filter: set classpath for Saxon correctly, including all required JAR files.

### 5.4.2 (2026-02-27)

- Fix attribute context handling.
- Fix transparent PDF inclusion.
- Fix Creator/Producer version in PDF metadata.

### 5.4.1 (2026-02-26)

- Bugfix letter-spacing in tables.

### 5.4.0 (2026-02-24)

- Release stable version 5.4.

## 5.3

### 5.3.24 (2026-02-23)

- New command Section to organize layout files (without affecting formatting).

### 5.3.23 (2026-02-19)

- Bugfix ForAll keeping the context.

### 5.3.22 (2026-02-18)

- Massive speed improvement.
- Margin protrusion setting is now in 1/1000 em.

### 5.3.21 (2026-02-05)

- Bugfix: hyphenation now works correctly with letter-spacing.

### 5.3.20 (2026-01-29)

- Bugfix: calculate remaining height in PlaceObject / absolute positioning.

### 5.3.19 (2026-01-29)

- Fix panic on sp –help / –version.

### 5.3.18 (2026-01-26)

- Add letter-spacing attribute to DefineTextformat (in 1/1000 em).
- Fix infinite loop in post_linebreak.

### 5.3.17 (2026-01-23)

- Fix infinite loop in HTML tables.
- Better handling of killing child processes in sp.

### 5.3.16 (2026-01-20)

- HTML tables with only header or footer (empty body) are now rendered correctly.

### 5.3.15 (2026-01-19)

- Bugfix for empty HTML tables.
- Implement Until for the new XPath parser.
- HTML enhancements (including documentation).

### 5.3.14 (2026-01-15)

- HTML can now wrap across multiple pages.

### 5.3.13 (2026-01-14)

- Remove infinite loop in HTML tables.

### 5.3.12 (2025-12-05)

- Add global html option in Options command and –option html=off command line flag to control HTML parsing.

### 5.3.11 (2025-12-04)

- Improve typesetting performance with early exits, cached lookups, and leaner attribute handling in mknodes.
- Modularize publisher.lua into color, links, and metadata modules to reduce file size.
- Update qrencode to the latest upstream version.

### 5.3.10 (2025-11-30)

- Fix CSS/HTML rendering slowdown.

### 5.3.9 (2025-11-25)

- Allow text formats to use the font size defined in CSS for HTML content.
- Fix handling of relative (em) font sizes based on the current font size.
- Fix HTML border rendering in regular paragraph mode.
- Update to latest Saxon HE.
- Remove Rust source code.

### 5.3.8 (2025-11-21)

- Fix HTML borders and currentcolor in table rules.

### 5.3.7 (2025-11-19)

- Rewrite HTML border.
- Allow styles in HTML tables.

### 5.3.6 (2025-11-11)

- Bugfix reset ul counter.

### 5.3.5 (2025-11-10)

- Fix @font-face in CSS (fatal crash).

### 5.3.4 (2025-11-10)

- HTML: add support for more list style types (lower-alpha, upper-alpha, etc.).
- Set exit code when error count is greater than zero.
- Compare tool: refactor for stability and improved HTML report (checksums, build errors, thumbnails, sorting).
- Refactor HTML → Lua rendering pipeline for structured output and cleaner CSS separation.

### 5.3.3 (2025-11-04)

- Reinstate old curly brace syntax for Value/select and old xpath parser (#680).
- Remove building of Rust library (#678).

### 5.3.2 (2025-11-01)

- Fix indentation after br newline.

### 5.3.1 (2025-10-21)

- Fix Windows/Rust loading.

### 5.3.0 (2025-10-21)

- Alternative Rust library for dynamic binding.

## 5.2

### 5.2.0 (2025-10-14)

- Release Version 5.2.

## 5.1

### 5.1.29 (2025-10-14)

- Rewrite parts of the Go-XML parser.

### 5.1.28 (2025-10-05)

- Fix PDF metadata / ISO dates.

### 5.1.27 (2025-10-04)

- Bugfix XPath function doc() returns root node instead of document node.

### 5.1.26 (2025-09-23)

- New XPath function translate().
- Documentation: clarified use of px (pixels).
- tabular.lua converted into a module (internal change only).
- Safeguard for missing file name in AttachFile.
- New XPath function distinct-values().

### 5.1.25 (2025-09-15)

- Publisher now continues multiple runs even if errors occur.
- Support for Japanese typesetting.
- Fixed fallback with multi-character sequences.

### 5.1.24 (2025-09-15)

- Warning when empty hyperlink.
- Improved error message for empty group/trace.
- Improved error messages for duplicate files and errors writing aux-file.
- XPath: Added format-number() and round-half-to-even().
- New attribute type “rawstring” without {} escaping.

### 5.1.23 (2025-09-10)

- Optional resizehandler for DPI setting (configuration file).
- CSS font-family can now have multiple entries.

### 5.1.22 (2025-08-25)

- More HTML/CSS features (pseudo classes, padding in tables and borders).

### 5.1.21 (2025-08-23)

- NoBreak with background color.
- NoBreak in table cells (#670).
- Bugfix image conversion with same filename and different extensions.
- Extend basic HTML features (custom fonts, border, rem size).

### 5.1.20 (2025-08-19)

- Set trapped to false in PDF for preflight check.
- Do some safeguards to prevent Go/Lua thread errors.

### 5.1.19 (2025-08-18)

- Allow paragraph shape in groups.
- Bugfix: copy files truncate itself (#668).
- Put PDF producer entry in metadata.

### 5.1.18 (2025-08-01)

- Bugfix PlaceObject/rotate and vreference.

### 5.1.17 (2025-07-30)

- Bugfix lxpath mode and new XPath parser.
- Re-implemented HTML mode.
- Set log level in server mode.

### 5.1.16 (2025-07-15)

- Fix sd:format-number (#664).

### 5.1.15 (2025-07-14)

- Proper escaping of XML metadata in ZUGFeRD attachments.

### 5.1.14 (2025-07-10)

- Bugfix HTML and uppercase tags.

### 5.1.13 (2025-07-03)

- Ignore DEL (decimal 127) control character in input (#663).

### 5.1.12 (2025-06-25)

- Fix (XPath) boolean value of attributes.

### 5.1.11 (2025-06-18)

- No fatal error in case of missing http-image.

### 5.1.10 (2025-06-06)

- Better log messages.
- New XPath function sd:symbol() to enter a glyph id.
- Remove old XPath documentation.

### 5.1.9 (2025-05-23)

- New option ‘addlocalpath’ in the configuration file for turning off recursive search of the working directory.
- New Option mpcolorwarning to show the MetaPost color warning (defaults to true).

### 5.1.8 (2025-05-22)

- Bugfix: layout function sd:current-framenumber() and new XPath parser.

### 5.1.7 (2025-05-18)

- cache=none works for SVG conversion (#622).

### 5.1.6 (2025-05-15)

- New command `sp checkversion` to see if a newer version is available (#660).

### 5.1.5 (2025-05-05)

- Update to new LuaTeX binaries (1.22.0).
- Change Windows system font directory discovery.

### 5.1.4 (2025-05-05)

- Bugfix: table split in AtPageShipout (#659).
- Absolute positioning within a group.

### 5.1.3 (2025-04-07)

- Fix spaces in imagehandler.

### 5.1.2 (2025-04-07)

- Change then width and the height for vertical rules. Internal change, should not create visible changes.

### 5.1.1 (2025-04-07)

- Lua-preprocessor: runtime.execute returns success/exit code.

### 5.1.0 (2025-04-04)

- Set background-color for each page.

## 5.0

### 5.0.2 (2025-04-04)

- Bugfix image handler automatic conversion.

### 5.0.1 (2025-04-01)

- Bugfix: rounding error in some grid configurations.

### 5.0.0 (2025-03-11)

- Release Version 5.0.

#### On this page