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: outsideandfloat: insidepick 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 declaredmargin-leftandmargin-rightare 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.clearaccepts 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-alignendandcentercame 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 CSSheighton<Tr>or<Td>, or ontr,tdandthin 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, andvalignplaces the content within the extra room.min-heightandmax-heighthave 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 andborder-spacing(one length, or horizontal and vertical, 2pt by default) keeps the cells apart, also from the edge of the table.border-collapse: collapsedraws the border between two cells once, the wider one wins and brings its color, and aborderon 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 withlower-alpha,upper-alpha(orlower-latin,upper-latin),lower-roman,upper-roman,lower-greekanddecimal-leading-zero; before, every style other thandecimaland the three bullets fell back to a bullet. The same styles are the optional last argument ofcounter(),counters(),target-counter()andtarget-counters()in acontentvalue, as incounter(chapter, upper-roman). - SVG text takes its font from an enclosing group.
A<text>in an SVG image withoutfont-family,font-size,font-weight,font-styleortext-anchorof 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.
Abackground-coloron 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. Theversionattribute of<Layout>takes the version the layout was written for, such asversion="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 as0.1crashed 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 NAMEmakesxts comparelook forNAME.pdfin every test directory instead ofreference.pdf. This allows a collection of examples whose expected output is stored asresult.pdfto be checked with the same command. - CSS
@pagemargin 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@pagerule 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>, socounter(page)is final and no grid cell is consumed. Static headers and footers no longer need a hook. Themarginattribute of<DefineMasterPage>is now optional: when present it defines the grid and wins over the@pagemargins with a warning, without it the margins come from the@pagerule, and without either the margin is 1cm. The selectors:first,:leftand:rightare not evaluated, thetestattribute selects the master page. - StyleSheet accepts
@-bag-color, spot colors get their ink name. ↗
Colors defined with the@-bag-colorat-rule in a<StyleSheet>are registered with the document, so CSS and layout commands can use them by name.<DefineColor>withmodel="spotcolor"now passes thecolornameattribute as the ink name and the optionalc,m,yandkattributes 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 ascss/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 schemaslayoutschema-en.rngandlayoutschema-de.rngnow containlsp: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 withoutcolumnandrowoverprinted 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 incompare-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 asborder: 1pt solid redin a layout style sheet was dropped as an unknown property, it now arrives as its longhands. The XPath engine fixesself::A(matched every element),self::A or self::Band(@A, @B)[1](the context was not restored between operands),preceding::andfollowing::(only looked at siblings) and the name testsprefix:*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 withfloat: leftandfloat: right. CSS parse errors name the selector and the line in the style sheet. A relativeurl()in an@pagerule resolves against the style sheet that declares it instead of the document directory, and the color oftext-decorationis resolved where it is declared. - New subcommand xts watch re-runs the publishing process on file changes. ↗
The new subcommandxts watchruns 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 attributeboxmodelon<Trace>draws the translucent CSS box model overlay known from the browser developer tools (content, padding, border and margin),hboxesoutlines 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>withsd: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 attributesalign,valignandbackground-coloron<Column>are distributed onto the cells of the column, aware ofcolspanandrowspan.<Td>getsalign. Settings on the cell win over settings on the column. - ClearPage can open the next page on the left or right side. ↗
The new attributeopenon="right"oropenon="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>withcolumnandrowinside a slate positions the object on that grid. A<PlaceObject>with an unknown slate name reports an error instead of crashing, as dosd:slate-width()andsd: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:selecton<Attribute>,<ForAll>and<Mark>,levelon<Bookmark>,nameon<DefineColor>,elementnameon<SaveXML>andteston<Case>. CSS-like attributes use dashed names such asbackground-colorandmax-width, the old spellings keep working.<SaveXML>gains the documentedattributesattribute. - Constructed elements are navigable with XPath, several smaller fixes. ↗
Structures built with<Element>and<Attribute>can be navigated with XPath.<OutputAt>withhalign="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 Luaruntime.finalizerruns after the publishing run, and the--tracehelp namesgridallocation. 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 attributeexpand-texton<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 staysno(#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 propertywhite-spaceare honored,text-decorationsupports overline and line-through withtext-decoration-styleandtext-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 attributeidon<PlaceObject>is stamped on the placed object and appears asattr-idnext 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 subcommandxts list-fonts, which printed@font-facerules 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 fromcommands.xmlinstead 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>withimagenotfound="error"inserts the file-not-found placeholder likewarningand 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,--pdfaand--pdfxor the matching keys inxts.cfg. Theformatattribute 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 newasattribute and the body of<Function>, rejects layout actions such as<PlaceObject>. Theasattribute takes a sequence type in the style of XSLT and XQuery. Constructors return queryable data, socount($head)and$head/@widthwork 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.