Skip to content
Changelog

Changelog

All user visible changes of XTS, newest first. The version at the top may not be released yet.

0.1.1 (2026-09-22)

  • CSS floats can be placed towards or away from the binding with float: inside and float: outside.
    In HTML content, float: outside and float: inside pick the side of the float from the page it lands on: the outer edge on right pages is the right edge, on left pages the left edge. The declared margin-left and margin-right are read as written for a right page and swapped on left pages, so one rule with a negative outer margin places a margin note in the outer margin of every page. clear accepts the two values as well. A float now starts level with the block after it (the margin between the two blocks is laid out before the float), and a margin note followed by a floated figure shares its position instead of pushing the figure down.
  • Right-to-left paragraphs keep their insets and alignment on the correct side.
    In a right-to-left paragraph the lines beside a float on the right ran underneath the float, text-align end and center came out flush right, the first-line indent went to the left edge, a line ending in a forced break was flush left, and so was the last line of a justified paragraph. Hanging punctuation at the end of a right-to-left line was painted over the next glyph. All of these now follow the paragraph direction: insets are physical, indents and alignment are resolved from the line start.
  • Tables are laid out about twice as fast.
    Every paragraph in a table cell was shaped and broken into lines four times: twice to find the column widths and twice to build the row. The column widths are now measured on the shaped text without breaking it into lines, and a cell is built once. A catalog of 112 pages of nested tables takes 0.4 seconds instead of 0.9. Along the way a miscalculation was fixed: a column whose cell breaks its lines with <Br> came out too wide from the third line on, it now follows the longest line.

0.1.0 (2026-09-18)

  • height on a table row or cell sets the minimum height of the row.
    A CSS height on <Tr> or <Td>, or on tr, td and th in HTML, was ignored. It is now the minimum height of the row, as CSS 2.1 prescribes for tables: a row with less content grows to the height, a row whose cells need more room keeps its natural height, nothing is clipped. The height counts the whole cell including padding and borders, and valign places the content within the extra room. min-height and max-height have no defined meaning on table rows in CSS and are still ignored.
  • border-collapse and border-spacing work on tables, the default is separate as in CSS.
    Both properties were parsed before but had no effect: tables always drew their borders in a collapsing manner. Now a table follows CSS. The default is the separated model: every cell draws all of its borders and border-spacing (one length, or horizontal and vertical, 2pt by default) keeps the cells apart, also from the edge of the table. border-collapse: collapse draws the border between two cells once, the wider one wins and brings its color, and a border on the table itself merges into the cells on the edge instead of being drawn around them a second time. A style sheet that relied on the old behavior without saying so shows gaps and doubled borders between the cells, like a browser would; table { border-collapse: collapse } restores the old look.
  • list-style-type knows the alphabetic and roman styles.
    An ordered list can be numbered with lower-alpha, upper-alpha (or lower-latin, upper-latin), lower-roman, upper-roman, lower-greek and decimal-leading-zero; before, every style other than decimal and the three bullets fell back to a bullet. The same styles are the optional last argument of counter(), counters(), target-counter() and target-counters() in a content value, as in counter(chapter, upper-roman).
  • SVG text takes its font from an enclosing group.
    A <text> in an SVG image without font-family, font-size, font-weight, font-style or text-anchor of its own now takes them from the nearest <g> or the <svg> root that sets them, as SVG prescribes; before, such a text was not rendered at all. A text whose font family XTS does not know is still dropped, but now with a warning that names the family and the text.
  • background-color is painted on spans and other inline elements.
    A background-color on a <Span>, or on an inline element such as <span> or <code> in HTML, had no effect: only paragraphs, table cells and other block elements got a background. The color is now painted behind the text of the span, as high as the font, and follows the text across line breaks, one box per line. Nested spans paint the inner color on top of the outer one, an underline stays visible on top of the background, and a span whose color is the one of its paragraph paints nothing extra. The manual page “Text Formatting” no longer claims that a background is only painted on block elements.
  • Releases follow the 0.MINOR.PATCH scheme and the version attribute of Layout checks against it.
    Starting with this release the version number of XTS says what an update means: a patch release (0.1.0 to 0.1.1) fixes bugs only, a minor release (0.1 to 0.2) adds commands, attributes and functions, may change line and page breaks of an existing layout and may remove features that were deprecated in the previous minor release. The manual chapter “Versions and Compatibility” describes the rules. The version attribute of <Layout> takes the version the layout was written for, such as version="0.1": the parts are compared from the left, a layout that asks for a newer version than the running XTS stops with an error, an older request passes, and development builds accept every version. A two part version such as 0.1 crashed XTS before, and a development build rejected every version. The reference of the attribute still described the speedata Publisher.

0.0.30 (2026-09-17)

  • allocate=“no” no longer moves the cursor for full width objects.
    A <PlaceObject allocate="no"> whose object reached the right edge of the area still moved the cursor to the row below it, so the next object placed without coordinates ended up under a full width background instead of on top of it. The reference promises that the cursor position is not changed, and now it is not. Narrow objects were not affected.
  • xts compare can look for a reference PDF with another name.
    The new option --reference NAME makes xts compare look for NAME.pdf in every test directory instead of reference.pdf. This allows a collection of examples whose expected output is stored as result.pdf to be checked with the same command.
  • CSS @page margin boxes render headers and footers on master pages.
    A @page name { } rule in a <StyleSheet> now belongs to the <DefineMasterPage> with the same name, the generic @page rule is the base for every master page. The margin boxes (@top-left, @bottom-center, …) are rendered into the page margin when the page is shipped out, after <AtPageShipout>, so counter(page) is final and no grid cell is consumed. Static headers and footers no longer need a hook. The margin attribute of <DefineMasterPage> is now optional: when present it defines the grid and wins over the @page margins with a warning, without it the margins come from the @page rule, and without either the margin is 1cm. The selectors :first, :left and :right are not evaluated, the test attribute selects the master page.
  • StyleSheet accepts @-bag-color, spot colors get their ink name.
    Colors defined with the @-bag-color at-rule in a <StyleSheet> are registered with the document, so CSS and layout commands can use them by name. <DefineColor> with model="spotcolor" now passes the colorname attribute as the ink name and the optional c, m, y and k attributes as the fallback tint. Both were ignored before, so the Separation color space was written without a name.
  • xts watch sees subdirectories and always re-runs on a save.
    The watch mode did not notice a change to a file in a subdirectory of the layout directory, such as css/style.css, and a save of the unchanged layout afterwards was dropped as well. The watcher now observes all subdirectories, including ones created later, and a save after a run always starts a new run. Files written while a run is in progress are still ignored when their content did not change, so a Lua filter that rewrites its data file does not loop.
  • The layout schemas carry annotations for the speedata language server.
    The RELAX NG schemas layoutschema-en.rng and layoutschema-de.rng now contain lsp: annotations for the language server used by the speedata Visual Studio Code extension: links to the reference page, outline entries, definitions and references of named things such as colors and areas, conditional attributes and predefined names. The XSD schemas are unchanged.
  • Slates built before the first placement and nested slates work.
    A <Slate> created before anything was placed on page 1 got an empty grid: text blocks without an explicit width came out with zero width and objects without column and row overprinted each other. A slate nested in another slate placed the rest of the outer slate on the page instead of the outer slate, and crashed when combined with the first problem (#31, #32).

0.0.29 (2026-09-14)

  • xts compare exits with an error when a page differs from its reference.
    The compare command reported differing pages on standard output and in compare-report.html, but always exited with status 0, so a QA run could not fail. It now exits with status 1 when at least one directory does not match its reference, when the report cannot be written, and when no directory is given.
  • CSS shorthands in StyleSheet are expanded, several XPath axis fixes.
    A shorthand such as border: 1pt solid red in a layout style sheet was dropped as an unknown property, it now arrives as its longhands. The XPath engine fixes self::A (matched every element), self::A or self::B and (@A, @B)[1] (the context was not restored between operands), preceding:: and following:: (only looked at siblings) and the name tests prefix:* and *:name.
  • Floats keep the text clear of their own margins.
    A floated element in HTML content now holds the text clear of its own margins on every side instead of the default gutter alone. A percentage width on a floated image resolves against the container instead of the intrinsic size of the image, and a float stays inside its container whatever margin sits below it.
  • Text flows beside left and right floats, CSS errors name the selector.
    In HTML content and paragraphs text now flows beside elements with float: left and float: right. CSS parse errors name the selector and the line in the style sheet. A relative url() in an @page rule resolves against the style sheet that declares it instead of the document directory, and the color of text-decoration is resolved where it is declared.
  • New subcommand xts watch re-runs the publishing process on file changes.
    The new subcommand xts watch runs the publishing process once and again whenever an input file changes. It observes the current directory, the directories of the layout and data files and all extra directories. Events are debounced, files written by xts itself and editor artifacts are ignored, and errors in a run do not stop the loop (#30).

0.0.28 (2026-09-11)

  • Trace hyphenation shows the hyphenation points again.
    The hyphenation trace did not descend into the vertical lists that wrap the lines of a paragraph, so <Trace hyphenation="yes"> drew nothing.
  • Trace gains the boxmodel and hboxes overlays.
    The new attribute boxmodel on <Trace> draws the translucent CSS box model overlay known from the browser developer tools (content, padding, border and margin), hboxes outlines the line boxes.

0.0.27 (2026-09-02)

  • PositioningArea accepts a Switch to select frames per page.
    A <Switch> inside <PositioningArea> is evaluated when the page is created, so a <Case> with sd:current-page() can define different frames for even and odd pages. If no branch matches, the area does not exist on that page.
  • Column supports align, valign and background-color, Td supports align.
    The new attributes align, valign and background-color on <Column> are distributed onto the cells of the column, aware of colspan and rowspan. <Td> gets align. Settings on the cell win over settings on the column.
  • ClearPage can open the next page on the left or right side.
    The new attribute openon="right" or openon="left" on <ClearPage> inserts a blank page when the next page would open on the wrong side. Right pages have odd page numbers.
  • A slate can define its own grid and place objects on it.
    A <Grid> child of <Slate> with the attributes of <SetGrid> defines the grid of the slate, and <PlaceObject> with column and row inside a slate positions the object on that grid. A <PlaceObject> with an unknown slate name reports an error instead of crashing, as do sd:slate-width() and sd:slate-height() on an empty slate.
  • Breaking: the Lua xlsx worksheet call takes (row, column).
    The worksheet accessor of the Lua xlsx module took (column, row) while the manual documented (row, column). The code now follows the manual, Lua filters that read spreadsheets need to swap the two arguments.
  • Required attributes are enforced, CSS-like attributes use dashed names.
    Attributes the schema marks as required are now checked at run time: select on <Attribute>, <ForAll> and <Mark>, level on <Bookmark>, name on <DefineColor>, elementname on <SaveXML> and test on <Case>. CSS-like attributes use dashed names such as background-color and max-width, the old spellings keep working. <SaveXML> gains the documented attributes attribute.
  • Constructed elements are navigable with XPath, several smaller fixes.
    Structures built with <Element> and <Attribute> can be navigated with XPath. <OutputAt> with halign="right" aligns to the width of the area instead of a fixed six grid columns. sd:odd() handles negative numbers, sd:file-exists() checks the file system, the Lua filter script is looked up via the extra directories, the Lua runtime.finalizer runs after the publishing run, and the --trace help names gridallocation. The manual was audited against the implementation and examples from the speedata Publisher era were repaired.

0.0.26 (2026-09-01)

  • New command TableFoot repeats rows at the bottom of every table part.
    The new command <TableFoot> mirrors <TableHead>: its rows are set at the end of the table and, when the table is split across frames or pages, above every break as well.
  • A table with a TableHead breaks across frames and pages.
    A table taller than the frame was drawn past the bottom margin. A table placed on the grid that has a <TableHead> is now split: rows fill the frame, the table continues in the next frame of the area or on a new page, and the header rows are repeated at the top of every continuation. A lone header is never left at the bottom of a frame, frame and background are drawn per table part, and the column position is kept when the table continues. A table without a head is placed as a single object as before, as is an absolutely positioned table (#29).
  • expand-text works in mixed XHTML content of HTML.
    The attribute expand-text on <HTML> was ignored when the content mixed XHTML and XTS elements, so {$width} in an attribute value or in text reached the output with the braces intact. The setting now applies on both paths, the default stays no (#28).

0.0.25 (2026-09-01)

  • CSS white-space and text-decoration are supported, the interword space comes from the font.
    All five values of the CSS property white-space are honored, text-decoration supports overline and line-through with text-decoration-style and text-decoration-color. The width of the space between words now comes from the space glyph of the font instead of a fixed value, which changes the line breaks of existing layouts slightly. Centered and right aligned table cells no longer measure zero width, and several blocks in a cell stack instead of packing side by side.

0.0.24 (2026-09-01)

  • PlaceObject gets an id attribute for the XML dump.
    The optional attribute id on <PlaceObject> is stamped on the placed object and appears as attr-id next to the box geometry in the output of --dumpoutput, so external tools can map output boxes back to the layout element. The value goes through the usual {} interpolation. It is not a CSS id and does not create a link target (#26).
  • The subcommand list-fonts has been removed.
    The subcommand xts list-fonts, which printed @font-face rules for the fonts in the search path, is gone.
  • The XSD schemas are generated natively, the content model is permissive.
    The XSD layout schemas are now generated directly from commands.xml instead of being converted from RELAX NG with a Java tool. Since XSD cannot express the interleaved content model, the XSD allows the child elements of a command in any order and number, which keeps editor completion working. Validation with the RELAX NG schema remains exact.
  • Value keeps inline markup, imagenotfound=error no longer aborts the run.
    Mixed content in <Value> such as <Value>foo<br/>bar</Value> lost its child elements, the <br/> was dropped and the text glued together. Inline markup is now passed on to the enclosing paragraph. <Image> with imagenotfound="error" inserts the file-not-found placeholder like warning and still produces a PDF, the error only makes the process exit with a non-zero status.

0.0.23 (2026-06-29)

  • PDF/UA, PDF/A and PDF/X are selected with command line flags and configuration keys.
    The conformance classes are configured with the orthogonal flags --pdfua, --pdfa and --pdfx or the matching keys in xts.cfg. The format attribute of <PDFOptions> has been removed. <HTML> accepts <math> with MathML content, and the manual gains a chapter on accessible PDF.
  • Typed values, Template and CallTemplate, and a Programming manual.
    Every command is classified as constructor, action or control flow. A value context, that is <SetVariable> with the new as attribute and the body of <Function>, rejects layout actions such as <PlaceObject>. The as attribute takes a sequence type in the style of XSLT and XQuery. Constructors return queryable data, so count($head) and $head/@width work on a variable that holds <Column> elements. The new commands <Template> and <CallTemplate> offer reusable behavior with parameters. The manual is restructured into Layout Guide, Programming and Reference.
  • Color fonts are supported.
    Fonts with color glyphs (COLR/CPAL, sbix, CBDT and SVG-in-OpenType) such as emoji fonts render in color.
  • New output formats PDF/UA-1 and PDF/UA-2.
    XTS can write tagged, accessible PDF that conforms to PDF/UA-1 and PDF/UA-2.
  • New command AttachFile, placeholder for missing images.
    The new command <AttachFile> embeds a file in the PDF. When an image file is not found, a placeholder is inserted instead of leaving the space empty.