Changelog
Changelog
All user visible changes of XTS, newest first. The version at the top may not be released yet.
0.1.10 (2026-10-13)
- New attribute column-fill on Flow: balance spreads the end of a flow over the frames of its last page (#72).
Withcolumn-fill="balance", the part of aFlowafter its last forced break that ends on a page is set in the frames of that page as high as it takes to hold it, so that a run of columns that ends part way down the page ends at about the same height in every column. The pages before fill their frames as before, andbottomand aFlowthat follows directly start below the longest frame. The defaultautofills one frame after the other, as always. - A block beside a float runs on under it, as in CSS; new display: flow-root (htmlbag v0.0.91).
A block with a border or a background beside a float was narrowed as a whole. Now only its lines keep clear of the float, while the border and the background run on under it, and a block that starts past the float is not narrowed at all. A margin, border or padding of the boxes in between takes from the room the float needs instead of adding to it, so the text no longer moves further away from the float the deeper it is nested. A float in a block with padding stands at the padding edge.display: flow-rootkeeps a block clear of a float as a whole, as before. - A paragraph’s padding-left is taken off once (htmlbag v0.0.91).
The lines of a paragraph withpadding-leftbut no border or background ended short of the right edge by the padding, and beside a float or with atext-indentthe padding was lost for the whole paragraph. The lines now run from the padding to the right edge, below a float as well, and atext-indentadds to the padding. A float without a width that has padding is as wide as its content plus its padding.
0.1.9 (2026-10-13)
- Links, backgrounds and languages stay on their text in right to left paragraphs (boxesandglue v0.2.87).
A link, an inline background, a text color, a text decoration or an element withlanginside a right to left paragraph cut the line into pieces that were reordered one by one: the words came out in logical order from left to right, and the spaces at the edges of the run were lost. They stay in place now. A link that runs from Arabic into English gets one area for each visually contiguous part, as CSS splits an inline box at a change of direction. - A left and a right float stand side by side (htmlbag v0.0.90).
A float that started while another one was still beside the text went below it, even when it floated to the other side. A left and a right float now stand side by side, each beside its own edge of the text, as long as they leave room for text between them. Two floats on the same side still stack. A page break no longer leaves the shorter of two such floats alone at the bottom of a page. - A negative margin lets the text run over a float, as in CSS (htmlbag v0.0.90).
A float’s negative margin on the side of the text fell back to-bag-float-gutter, and the text kept its distance. Now the text runs over the float by that much, or past it altogether. Only a margin of zero still gets the gutter. Where the text runs over the float, the float covers it.
0.1.8 (2026-10-09)
- An object that reaches into the rest of the grid at the right no longer warns.
When the cells do not fit evenly into the page area, the grid ends before the right margin. An object that reached past the last column into this rest gave the warning “object protrudes into the right margin”, even when it ended before the margin. The warning now compares the object’s exact right edge with the margin, as it already did at the bottom. - An image can show a part of its source with object-view-box: inset() (htmlbag v0.0.89).
The CSS propertyobject-view-boxtakesnoneorinset()on an<img>with a bitmap, PDF or SVG source. The part becomes the natural size of the image, sowidth,heightandmax-widthsize it as they size a whole image.xywh(),rect()and the round corners ofinset()are ignored with a warning. - A margin at a break inside a block split across pages or areas is truncated (htmlbag v0.0.89).
When a block with blocks inside was split and the break fell at the margin between two of them, the margin stayed at the top of the next page or area, and the text started lower there. The margin is truncated now, as a margin between two blocks of the flow itself always was. - An SVG drawn much smaller than its viewBox keeps its exact size (svgreader v0.0.6).
The transformation of an SVG was written with four decimals. It scales every coordinate drawn under it, so a largeviewBoxplaced small ended visibly short of its box: 3000 units at 85pt lost 0.14pt. It is written with six decimals now, and the drawing fills its box. - A ::before or ::after on an element with -bag-bookmark or page-break-inside no longer stops the document (htmlbag v0.0.87).
Generated content took the settings of its element along, among them the internal ones for-bag-bookmarkandpage-break-inside, and the document stopped with “Unknown setting”. The generated content of such an element is set now. - A table caption is set above its table (htmlbag v0.0.87).
The<caption>of an HTML table was dropped without a word. It is set as a block above the table and keeps with it at a page break. Under PDF/UA it is tagged as the first child of the table. - A link whose text comes from ::before or ::after is clickable (htmlbag v0.0.87).
A cross reference such as<a class="ref" href="#sec"></a>with its text fromtarget-counter()in::beforegot no link annotation. The generated content of a link carries the link now; a leader does not. - SVG text without a font-family is set (htmlbag v0.0.87).
Text in an SVG that names no font family, or one the document does not know, was dropped with a warning. An inline<svg>now takes the font of the element around it, an SVG in<img>the font family serif, as a browser does. - PDF/UA-2: links to an element get a structure destination, and a figure with a caption is tagged as one (htmlbag v0.0.87, boxes and glue v0.2.85).
Under PDF/UA-2 the action of a link to an element with an id points to the structure element of its target, as ISO 14289-2 requires. A<figure>with an image and a<figcaption>is tagged as a section holding the image as a figure with its alternative text and the caption, in reading order; before, the caption had no structure element. - MathML: the minus sign, displaystyle and mspace (boxes and glue v0.2.85).
<mo>-</mo>is set with the minus sign instead of the hyphen of the math font, and the solidus without the space of a binary operator.<mstyle displaystyle="true">, and the attribute on<math>, set their content in display style, so a fraction in a line of text can be set at full size.<mspace width>in em or as a named space adds space. - The CSS reference lists the column properties of htmlbag, which xts does not lay out (htmlbag v0.0.87).
htmlbag readscolumn-count,column-gap,column-span,column-fillandcolumn-ruleand lays out columns for glu. Text that xts sets with them stays in one column, with a warning; columns in xts are areas with several frames.
0.1.7 (2026-10-08)
- A Flow that ends in the row where the Flow before it ended no longer lets the next Flow print over it (#73).
AFlowthat follows another one starts at its exact end, which may lie partway down a grid row. When its content also ended within that row, it recorded no end, and a thirdFlowstarted where the second one had started and printed over it. The end is recorded now, so eachFlowstarts below the one before it. - -bag-text-box-trim-at-break lets the last line before a page break fit by its text (htmlbag v0.0.86).
With-bag-text-box-trim-at-break: trim-endon a block, the last line before an unforced page or frame break may fit by its text: the space below the text edge of the block’s font, such as the half-leading, is taken off that line, so a paragraph whose last line fits by its text but not by its full line height stays on the page instead of moving on. Nothing else changes: a block ending mid-page keeps its height, its first line is not trimmed, and a forced break trims nothing. A block that abreak-after: avoidmoves on with the next one stays untrimmed. The property is not inherited, and underbox-decoration-break: clonewithtext-box-trim: trim-endit changes nothing, since every part is trimmed there already. - box-decoration-break: clone repeats a block’s border and padding on every page (htmlbag v0.0.86).
The CSS propertybox-decoration-breaktakesslice, the initial value and the behavior so far, andclone. Withclone, a block with a border, padding or background that is split across pages or frames gets its padding and border on both sides of every part. Tables are not cloned yet. - Under box-decoration-break: clone, text-box-trim trims every part of a split paragraph (htmlbag v0.0.86).
As CSS has it, a paragraph withbox-decoration-break: cloneandtext-box-trimis trimmed at every break, not only at its start and end: withtrim-endthe last line before a break may fit by its text, and withtrim-startthe first line of each continued part is trimmed. A container of other blocks undercloneis still trimmed only at its start and end. - text-box-trim on a table cell trims its first and last line (htmlbag v0.0.86).
On a table cell,text-box-trimnow trims the first line of the cell’s content and its last, as on a container, so a row can end at the text of its cells. It does not reach into a table nested in the cell. - A justified line before a forced line break no longer runs past the measure (boxes and glue v0.2.84).
When none of the possible line ends before a forced line break gave an acceptable line, the line breaker took the one it had looked at last. In a justified paragraph that could set a line far looser than needed, or join the words up to the break into one line wider than the column, although a line that fits was possible. It now takes the line end with the best result for the paragraph, and one whose line fits before one whose line is too wide. - Long ragged paragraphs no longer take time and memory growing with their square (boxes and glue v0.2.83).
A paragraph set ragged, with thousands of words, took time and memory that grew with the square of its length, gigabytes for a very long one. It now takes a small part of that. The breaks stay the same, except that a paragraph whose first lines are wider than the rest no longer gets a stray line skip above its first line.
0.1.6 (2026-10-07)
- A Flow in a slate works while the body is a Flow (htmlbag v0.0.85, #71).
A slate with aFlow, built inAtPageCreationorAtPageShipoutwhile the body is itself aFlowrunning over pages, failed with the error that a flow is already running, and the slate stayed empty on the pages made during the body’s flow. The slate’s flow now runs inside the body’s, and the body goes on as without it, so a running header or footer can be aFlowon every page. - text-box-trim on a container trims its first and last line (htmlbag v0.0.85).
On a container of other blocks, such as adivaround paragraphs,text-box-trimnow trims the first line of its first block and the last line of its last block, through nested containers, unless padding or a border on that block lies between. A table cell is not trimmed yet. - A paragraph alone in a Flow keeps its widows (htmlbag v0.0.85).
A paragraph that was the only block of aFlowor of HTML text, or of the part after a forced break, broke without widow protection, so its last line could stand alone in the next frame or on the next page. It now keepswidowsat every break, and the rest that moves into a frame of another width is broken again at that width.
0.1.5 (2026-10-07)
- Table borders are painted over all cell backgrounds (htmlbag v0.0.84, boxes and glue v0.2.82).
A table painted each cell’s background and then its borders, cell by cell, so the background of the next cell came after the borders beside it. Adobe Acrobat rounds the edges of filled areas to its pixel grid, and a thin border next to a shaded cell, such as a zebra stripe or a header row, then vanished partly or wholly. A table now paints its borders after all of its cell backgrounds, as CSS 2.1 asks, but still before anything placed over it later. This holds forTableand for tables in HTML text. In PDF/UA the borders are layout artifacts instead of content of their cells. - Tables with cell spacing pass PDF/UA, and PDF/A-3 with PDF/UA declares its schema (boxes and glue v0.2.82).
The space between table cells, as fromborder-spacing, left an empty text operator outside of tagged content, which veraPDF and pdfa11y reject in PDF/UA. It is gone. A document that is PDF/A-3 and PDF/UA at once now declares the PDF/UA identification schema in its XMP metadata, as PDF/A-3 requires; veraPDF rejected the PDF/A part without it. - An SVG image floats like any other image (htmlbag v0.0.84).
Animgwith an SVG file andfloat: leftorfloat: rightin HTML text was set inline, at the start of a paragraph and as a block before one alike. It now floats as a raster image does. - Fonts with CFF outlines no longer break in some subsets (textshape v0.0.18).
The subset of an OpenType font with CFF outlines, such as TeX Gyre Heros, could come out with every offset in its Top DICT one byte short, depending on the glyphs a document uses. FreeType then rejected the font as a broken table and viewers showed a system font instead. - CSS text-box-trim trims a block to its text (htmlbag v0.0.83).
The CSS propertiestext-box-trim(trim-start,trim-end,trim-both),text-box-edgeand the shorthandtext-boxtake the space above a block’s first line and below its last line, such as the half-leading, off down to the text edges of the block’s font, so the block ends at its text. They are not inherited. Split across pages, only the start of the first part and the end of the last part are trimmed. A container of other blocks and a table cell are not trimmed yet. - -bag-trace: boxmodel shows the box model of chosen elements.
The CSS property-bag-trace: boxmodelpaints the box model overlay (margin, border, padding and content) on the elements a rule matches, as the global box model trace does on every block. It is not inherited, andnoneturns it off again in a more specific rule. - Floats in HTML text shrink to fit, float at the start of a paragraph and are painted over the blocks beside them (htmlbag v0.0.83, boxes and glue v0.2.81).
A float without awidthshrinks to its longest line instead of taking the full measure, and where it leaves no room beside it the text goes below it. A float at the start of a paragraph, such as an image withfloat: leftbefore the paragraph’s text, now floats; before, it was set inline. A paragraph with a border or padding still sets it inline. A float is painted over the background and border of the blocks beside it, as in CSS, where those used to cover it. - A list item with lang no longer stops the run.
An HTMLliwith alangattribute,-bag-bookmarkorpage-break-insidestopped the run with an “Unknown setting” error, because its marker took over settings only htmlbag knows (htmlbag v0.0.83). - A Flow in a slate stacks its blocks as one object.
AFlowin aSlatewas an error. It now stacks its blocks as one object, as wide as the slate’s frame, with the same children, rules and warnings as on a page: the blocks keep their exact heights and their margins collapse, where aPlaceObjectper block rounds each one up to whole rows, but nothing goes on to a next frame or page.bottomandsd:slate-height()give its exact end, and aPlaceObjectafter it starts on the next whole row. A forced break withpage,column,leftorrighthas nothing to break to there and is ignored with one warning per flow;areais an error in a slate. AMarkorBookmarkbetween the blocks takes the page the slate is placed on (#59). - A Go program can choose how paragraphs break.
A program that runs XTS can register breakers inXTSConfig.Breakers, by name, as it registers line models inXTSConfig.LineModels. The CSS property-bag-line-breakerselects one for a paragraph, which then chooses the paragraph’s breaks among its legal breakpoints in place of Knuth-Plass; the lines are measured and set as before.auto, the default, keeps Knuth-Plass. - A link target in a line sits at the line’s top, and an inline background is one band (boxes and glue v0.2.80).
With boxes and glue v0.2.80 and baseline-pdf v1.1.26: a named destination inside a line, from aMarkwithpdftarget, aBookmarkin aParagraphor anidon an element in HTML text, was placed the line’s depth above the line, so a viewer jumping there showed a sliver of the line before it; it is now at the top of the line. Abackground-coloron an inline element takes its height from that element’s own font, as in a browser, so text inside it in another font or size no longer makes the band step up and down. A PDF no longer claims a Unicode mapping for a glyph that is only part of another, such as the dieresis of an “ä” in a TrueType font. - A margin no longer starts a page on its own, and a CSS height splits at the page end (htmlbag v0.0.81).
With htmlbag v0.0.81 and boxes and glue v0.2.79: when a page ended exactly full, the bottom margin of its last block went to the next page on its own, and a block taller than a page then moved one page further and left a page with nothing but that margin; a margin at the top of a page after an automatic break is now dropped, as in CSS. The space a CSSheightadds below a block’s content is split at the page end like the content, rather than going whole to the next page and running below its content area. - A Mark in an Action inside a Paragraph takes the page of its line.
AMarkin anActioninside aParagraph, or insideB,Spanand the other text elements, was dropped, sosd:page-number()never found it. It now goes into the line it is in, as an invisible character, and takes that line’s page; withpdftargetit is a named destination there. ABookmarkworks the same way and is now allowed in the text of aParagraph. In aFlowboth are possible inside a paragraph, also when the flow breaks it across pages; elsewhere inside a block they remain an error (#67). - A Mark in a slate takes the page the slate is placed on.
AMarkin aSlategot the page that was current when the slate was composed, sosd:page-number()gave that page even when the slate was placed on a later one. A mark now takes the page it is shipped out on; a slate placed more than once leaves the mark on the last of its pages, as a secondMarkof the same name does (#68).
0.1.4 (2026-10-05)
- Floats get -bag-float-gutter, and a table moves with position: relative (htmlbag v0.0.80).
With htmlbag v0.0.80 and boxes and glue v0.2.78: the new CSS property-bag-float-guttersets the space between a float and the text beside it where the float declares no margin on that side (9pt by default,0gives a browser’s spacing); a table withposition: relativeis moved byleftorright, as other blocks are; a float taller than a page no longer leaves empty space on the next page, or a blank page at the end; and a line model fromXTSConfig.LineModelsgets the synthetic oblique inLineModelStyles.Fontwherefont-synthesis-styleslants the upright. - A Flow runs on to the bottom margin below the grid’s last row.
In a frame that reaches the grid’s last row, aFlow’s band ended at that row’s bottom, so the rest of the page area below it, when its height is not a whole number of rows, was never used. The band now runs on to the bottom margin there; a frame that ends higher, or rows taken by an object such as a footer, keep the row edge. A line that ends between the last row and the bottom margin gives no “protrudes into the bottom margin” warning (#58). - Text and HTML follow the browser more closely in margins, backgrounds and link targets (htmlbag v0.0.79).
With htmlbag v0.0.79 and boxes and glue v0.2.77:margin: autocenters a block with a width (both sides) or moves it to the right (margin-left: auto); a block image is placed by its margins, applied once, and no longer by thetext-alignit inherits; an inline background such asbackground-coloron aspancovers the font’s ascent and descent, as in a browser, rather than the em box; a block withbreak-inside: avoidstays whole; and every element with anid, also adivor aspan, is a named PDF destination that a link can point to. - Mark no longer documents append and shiftup, which had no effect.
The reference and the schema listed the attributesappendandshiftupofMark, but XTS read them and did nothing with them: a mark never collected several pages and its anchor was never raised. Both are removed until they are implemented, and a layout that sets them gets the warning about an unknown attribute (#56). - ProcessNode passes on what its records return, and their errors.
The blocks made in aRecordcalled throughProcessNodewere thrown away, so aFlowoverProcessNodecame out empty without a warning, and the same held in aParagraph, a table cell or a variable around it.ProcessNodenow returns what its records return, in order, asForAlldoes with its body, so a record per chapter can fill a flow, itsMarkincluded. An error in such a record, such as an unknown area, was logged and the run went on; it now ends the run as in aForAll. The schema allowsProcessNodeandCallTemplateinside aFlow(#64). - sd:slate-height while a slate is composed no longer draws objects twice.
Measuring a slate withsd:slate-height()while itsContentswere still being placed kept links from that measurement, so the next object on the slate brought an earlier one back: it was drawn twice, or, with the objects one below the other, the run did not end (#62). - A Flow below content on the page moves a block that does not fit on.
AFlowthat starts below what the page already holds, such as a secondFlowor aPlaceObject, set a block that did not fit in the space left there anyway, past the bottom of the area with a warning. The block now moves on to the next frame or page, as it does after another block of the same flow (#53, #61). - XTS warns about attributes that a command does not know.
An attribute that XTS does not read, such as a misspelled<PlaceObject colum="3">, was ignored without a word. XTS now writes a warning with the line, the command and, when one is close, the attribute that was probably meant:Layout line 12: unknown attribute "colum" on PlaceObject, did you mean "column"?Attributes in a namespace, such asxml:lang, are not checked. Attribute names are now matched exactly, so the spelling without the hyphen that XTS used to accept, such asbackgroundcolorforbackground-color, is ignored with this warning; layouts that use it need the hyphen (#61). - break-inside: avoid keeps a paragraph of a Flow in one frame.
AParagraph(or a bordered block) directly in aFlowsplit between two frames although it hadbreak-inside: avoid; the property worked only on a block inside a container. Such a block now moves to the next frame whole when it fits into an empty one (htmlbag#75). - In a Flow, break-before and break-after with recto and verso pick the page side.
A forced break withrectoorversoin aFlowstarted a new page without looking at its side, unlikerightandleft.rectonow goes on to the next right (odd) page andversoto the next left (even) one (#60). - Objects are placed on the cells of a grid with gaps.
With gaps between the grid cells (dxanddyofSetGridorGrid), an object in column or row n was placed one gap too far left or up, and in a positioning frame that does not start in the first column or row, the gaps before the frame were not counted at all. Objects, frames and flows now start on their cells as the grid trace draws them. Layouts withdxordychange. - The grid trace draws every frame of an area where it is.
With<Trace grid="yes"/>, all frames of an area were outlined at the place of the area’s current frame, so an area with two columns showed one of them twice and the other not at all. Every frame is now drawn at its own position, and the frames are drawn in a fixed order, so two runs with the trace on give the same PDF. - An object without coordinates no longer lands on a cell that is taken in its last column.
When XTS looked for a free place for an object withoutrowandcolumn, it checked every column the object covers but the last one, and none at all for an object one column wide. Such an object could be put over something placed before. Every column is checked now, so the object moves down to the next row where it is free. - An object can be placed in the last row of a frame.
The search for a free row never looked at the last row of a frame, so an object withoutrowandcolumnthat fits exactly into it, as well as the cursor ofNextRow, went on to the next frame or page instead. The last row is now used like every other, which changes layouts that relied on the extra break: such an object stays on the page. - Loop passes on what its children make.
ALoopdropped the result of its children, so rows made in aLoopinside aTable, or text inside aParagraph, were lost without a warning and the table stayed empty. ALoopnow returns what its children return in every round, asForAllandWhiledo. - Mark and Bookmark work between the blocks of a Flow.
AMarkbetween the blocks of a<Flow>, also in anAction, marks the page of the block that follows it, wherever the flow puts that block, also when it moves on to the next frame or page; after the last block, it marks the page where the flow ends. Withpdftargetit is a named destination at the top of that block. ABookmarkbetween the blocks points to the top of the block that follows it, where before it was left out. Inside a block of the flow, such as aParagraph, both are an error for now (#54). - A registered line model gets the paragraph’s font.
A line model registered throughXTSConfig.LineModelsgets the paragraph’s own font inhtmlbag.LineModelStyles.Font, with the face, size and vertical metrics its glyphs are set in, so it can give a line without glyphs, such as the one between twoBr, the height of the paragraph’s font (CSS 2.1 §10.8.1). - PlaceObject keeps a table whole; a table that breaks goes into a Flow.
A table placed withPlaceObjectwas split across frames and pages. It is now kept whole, as any other object: when it does not fit below what is in the frame, it moves to the next frame or page as one piece, and a table taller than every frame of its area is an error that names thePlaceObjectand points toFlow. A table that should break goes into a<Flow>, which breaks it between rows with its head and foot repeated and keeps the rows arowspanjoins together. This changes layouts that relied on the split (#47). - New command Flow pours paragraphs and tables into an area, page after page.
The children of a<Flow>, such asParagraph,Table,HTML,UlandOl, are laid out as one sequence of blocks into the frames of an area, starting new pages as needed. Paragraphs break between lines withwidowsandorphans, also inside a container such as a list item, adivor a bordered box, tables between rows with their head and foot repeated,break-after: avoidkeeps a block with the next, andbreak-beforeorbreak-aftergo to the next frame (column) or page (page,left,right), also on a block inside a container. Rows that hold objects are passed over, and the blocks are not rounded to the grid: a flow starts at the exact bottom of the objects above it, and aFlowthat follows another directly continues exactly where it ended, in every frame of its area that is free from there. A child with anidshows up in--dumpoutputonce per part.PlaceObject,ClearPage,NextFrameandNextRoware an error inside a flow. The attributebottomsets a variable to where the flow ends (#47). - A run no longer uses the Records of an earlier run in the same process.
TheRecordcommands of every layout were kept for the lifetime of the process, so a secondRunXTSwith a different layout, as inxts watchor a program that calls XTS several times, fell back to a Record of an earlier layout when its own layout had no match, and kept the earlier layout trees in memory. The Records, the cache of files found on disk and the numbering of destinations now belong to the run, and every run starts as a fresh process does (#51). - Ragged text is set at its natural width.
Withtext-alignleft, right, center, start or end (the default), the word spaces of a line could shrink by up to a third and the glyphs were condensed by the font expansion, so a line kept a word that does not fit at its natural width. Ragged lines are now set at their natural width and such a word moves to the next line, so line breaks in ragged text change. Justified text is unchanged, and-bag-font-expansiononly acts on justified text. - letter-spacing also widens the spaces.
A space now gets theletter-spacinglike any other character, so a letter-spaced line no longer sets its words closer than its letters. This holds for preserved spaces underwhite-space: preandpre-wrapas well. - New CSS property -bag-horizontal-scale.
Draws the glyphs narrower or wider by a fixed factor, a percentage or a number (90%or0.9). It is inherited and does not compound, so100%inside a scaled element returns to normal width. The line breaker sees the scaled width. - New CSS property -bag-fixed-height for table rows.
On aTr,-bag-fixed-heighttakes a length and makes the row exactly that high: content that does not fit draws past the row, and the row never breaks inside. Unlikeheight, which stays a minimum, it also limits the row. It is not inherited and has no effect on aTd.
0.1.3 (2026-10-02)
- font-variant-caps: small-caps sets small capitals.
A text withfont-variant-caps: small-capsuses the small capitals of the font (OpenType featuresmcp) when it has them. Otherwise the small capitals are synthesized: every letter that changes when uppercased becomes its capital at 70% of the size, with the case mapping of the language, so ß becomes SS.font-synthesis-small-caps: noneswitches the synthesis off, and thefontshorthand reads and resetssmall-capsas well. - A paragraph in a table cell draws its background and border.
Abackground-colororborderon aParagraphinside aTdhad no effect, only the cell itself drew one. The paragraph’s box is now drawn as on the page. - A background-color on a span that holds only spaces is painted.
ASpanwith abackground-colorwhose text is only spaces painted nothing. The background now covers the space, as high as the font, like on a span with text. - A collapsed column border is drawn in one piece.
Withborder-collapse: collapse, a vertical border between two columns was drawn as one segment per row, which some PDF viewers show with fine gaps between the rows. Where the rows agree on the border’s width and color, it is now drawn as one line from top to bottom. - Hyphenation is two to three times faster.
The hyphenation patterns are now matched once per word and language, so a word that appears again is hyphenated from a cache. The hyphenation points do not change. - A table is moved by its margin-left.
Amargin-lefton aTablehad no effect. The table is now moved to the right by it, with its borders, also when it is split across pages and its header is repeated. - A negative margin-left moves a paragraph in a table cell to the left.
AParagraphin aTdwith a negativemargin-leftwas widened but stayed at the left edge of the cell, so its text ran out on the right. It now starts left of the cell’s content edge by that amount, as in CSS. - New command Tab inserts a tab.
A<Tab/>in aParagraphor another text command moves the text after it to the next stop of-bag-tab-stops, and without stops it advances bytab-size. Unlike a tab character in the text, whichwhite-space: normalcollapses to a space where no stops are set, it always stays a tab. The new manual page on tab stops shows a table of contents and a price list (#39). - A Slate draws its objects in the order they were placed.
ASlatesorted its objects by their vertical position before drawing them, so an object placed later but higher up, such as a background withallocate="no", was drawn under the objects below its top edge. A slate now draws its objects in the order they were placed, as a page does, andlayer="behind"still puts an object under the others. The positions of the objects and the size of the slate do not change (#49). - Go callers can register line models for -bag-leading-model.
The newXTSConfig.LineModelsfield maps-bag-leading-modelnames tohtmlbag.LineModelFuncvalues, which XTS registers with its CSS builder throughCSSBuilder.RegisterLineModelbefore the layout is read. A reserved or empty name makesRunXTSreturn an error. Layouts that do not use a registered name are not affected. - A Span with position: relative moves by top or bottom.
ASpanwithposition: relativeis moved down bytopor up bybottomafter the lines are set, and the lines and the neighboring text stay where they are.topwins overbottom, nested offsets add up, and a percentage has no effect. Before, the offsets were ignored.leftandrightstill have no effect on inline text. - Raised text keeps its position across a color change.
Inside aSpanwithvertical-align, text after a color change, such as a nestedSpanwithcolor: red, was drawn on the baseline instead of raised. The same happened to the horizontal scaling of font expansion. Both now stay in effect. - New PlaceObject attribute layer draws an object behind the others.
Withlayer="behind", an object is drawn under everything placed on the page since itsAtPageCreationran, but over what that drew, and in aSlateunder the slate’s other objects. It is meant for decoration such as panels, tints and rules.layer="front"is the default. - In a Slate, allocate=“no” makes an object take no room.
An object placed withallocate="no"in aSlate, at lengths or in the grid, is drawn where it is placed, negative offsets included, without making the slate larger or moving the objects placed after it. Before, it still counted towards the size of the slate. - An object placed at lengths in a Slate is placed in the slate.
APlaceObjectwithcolumnandrowgiven as lengths inside aSlatewas drawn on the current page, measured from the page’s top left corner. It now goes into the slate, measured from the slate’s top left corner as grid positions already are, and the slate grows to hold it. - A tab after a Br is kept where tab stops apply.
With-bag-tab-stopsin force, a tab right after aBrwas dropped with the white space that starts the new line, so the text after it began at the margin instead of at the first stop. The tab is now kept. - Nested tables keep their place among the paragraphs of a cell.
A table inside aTdwas set above all of the cell’s paragraphs, and a second nested table in the same cell replaced the first. Each nested table now appears where it stands in the cell, so text and tables can alternate. - sd:current-row(), sd:number-of-columns() and sd:number-of-rows() work before the first page.
Before anything had created a page, these functions read a grid that did not exist yet and XTS stopped with a panic. Likesd:current-page()and the other grid functions, they now set up the page first and report its grid. A page type test that cannot be evaluated, such as one that does not give true or false, or one that asks for the grid while the first page is being set up, is reported as an error and does not match, instead of stopping XTS. - xts compare catches a missing page and a page of a different size.
xts compare only looked at as many pages as the new PDF has, so a reference with more pages passed, and with ImageMagick 6, as on Ubuntu, two pages of different sizes counted as equal. A page missing on either side and a page whose size differs from the reference now count as bad pages. Rendering errors of the new PDF are reported instead of being ignored. - A tab stop’s leader is drawn in a Paragraph.
With-bag-tab-stopsandleader(), the leader showed only in<HTML>text and came out blank in aParagraph, because the tab lost its leader when the lines were copied for placing (#38). - margin-left and margin-right of a paragraph work in a table cell.
A paragraph with a side margin was indented on the page but not in aTd. It is now indented in a cell too, and the height of the cell does not change. - The right border of a table cell stands at the edge of the cell.
Content narrower than the cell, such as a fixed-width box or a paragraph with a side margin, put the right padding and border just after the content instead of at the edge of the cell. - New CSS property font-synthesis-style slants the upright for a missing italic.
Withfont-synthesis-style: auto(orfont-synthesis: style) a font family without an italic sets italic text in its upright slanted by 12 degrees, as browsers do. Unlike CSS, the default isnone, so existing layouts keep the upright. - A missing font style is reported once, not for every run of text.
A family without an italic logged “Style italic not found” for each italic run of text. The warning now comes once for each missing weight and style. - A span that holds only white space no longer breaks the line in two.
A paragraph whose only content was white space inside a span, such as a colored space or a lone tab, could break at the invisible start and end of the span and set as two lines. - A table with fixed columns no longer collapses on x86 computers.
When a line or row was one scaled point too narrow or too wide for its glue, the arithmetic produced a value that is 0 on ARM and a huge negative number on x86. On x86 a table with fixed column widths could set every column to nothing. The glue now keeps its natural width in that case, as in TeX. - Text selection and extraction get the right line height for fonts whose units per em are not 1000.
The ascent, descent and font bounding box in the PDF font descriptor were written in the units of the font instead of 1/1000 of the em, so viewers took each line of a 2048-unit TrueType font for twice its height and could merge table rows when copying text. Nothing drawn changes. - position() and last() in ForAll and ProcessNode count the right items.
A path such as../xorcount(../rec)leftposition()at 1 andlast()at the number of nodes of its last step, for the rest of the expression and the rest of the loop body. goxpath 1.0.21 fixes this. ForAll and ProcessNode now setlast()to the number of items they go through and give the outerposition()andlast()back when they end. Also, an expression that sets up the first page in its middle, such asconcat(sd:current-page(), sd:current-page()), no longer loses its namespaces and fails with “Could not find namespace for prefix”. - Trace gridallocation gives the same PDF on every run.
The allocated grid cells were drawn in a random order, so a layout with<Trace gridallocation="yes"/>gave a different PDF on every run, even with--suppressinfo. They are now drawn in the order of the cells. - A finished run no longer keeps its document in memory.
Every page started a goroutine that handed out its marker ids and never ended. It held the page, and the page the whole document, so a process that renders more than once, such asxts watchor a run with--runs, grew by every document it had made. The marker ids now come from a counter on the page.
0.1.2 (2026-09-25)
- Justified lines end at the right edge of the text instead of running past it.
Glyph widths were measured with the font size divided by the units per em rounded down, so every glyph was measured slightly narrower than it is drawn: 0.3% for a font with 2048 units per em, 0.05% for one with 1000. Justified lines printed past their measure by that amount. Glyphs are now measured at their exact width, so text comes out slightly wider and a few lines break differently. Also, an OpenType font with CFF outlines whose units per em are not 1000 was drawn at the wrong size and spacing, four times too large at 4000 units per em. - New CSS property -bag-tab-stops sets tab stops in HTML text.
A comma separated list of stops, each a length or a percentage of the line width, optionally followed bystart,end,center,decimalordecimal(",")and aleader(), for example-bag-tab-stops: 12mm, 100% end leader(dotted)for a contents line. A tab in the text advances to the next stop. With stops set a tab survives anywhite-spacemode, whitespace with a newline in it stays source formatting. Theleader()function incontentnow also accepts the keywordsdotted,solidandspace. - A table does not break between rows a cell spans.
When a table was split across frames or pages, the break could fall between rows joined by a cell withrowspan. The spanning cell is drawn whole with its first row, so it hung past the bottom of the frame and left a gap beside the rows continued in the next one. The rows a rowspan joins now go to the next frame together; a group taller than a frame is still broken inside, so none of its rows are lost. - Text that exactly fills its line is no longer pushed 1pt to the right.
A line whose contents were exactly as wide as the measure had its glue stretched fully instead of not at all. In tables that hit the widest cell of a column, the one the column width is taken from: its text started 1pt further right than the other cells and ran 1pt past the cell. Such lines are now set at their natural width. Also, a line breaking at a soft hyphen right before a space was chosen as if the hyphen were set and the space kept on the next line; both lines are now measured the way they are set, without the hyphen and the space. - An object that no longer fits continues where there is room: in the next frame of its area, or below what a new page already carries.
When an object placed in an area with several frames did not fit the rest of a frame, it went to row 1 of the next frame without checking that row was free, so a frame that starts beside content placed earlier (a full-width box above two columns) was written over. It now moves frame by frame to the first that has room; an object taller than every frame takes the next free one. On a new page the same search runs over its frames, so the first object no longer covers what<AtPageCreation>placed; a table continuing into the next frame or onto a new page does the same. This changes documents with a single area too: after a page break, placement used the previous page’s area, so the objects after the first on a new page could be set in the wrong frame or column; they now follow the first as they did on page 1.<NextRow>had the same page-break problem and is fixed too. - A CSS float stays with its text at a page break.
A float reserved no height of its own, so at a page break a margin note or a floated figure could stay at the bottom of the page while the paragraph beside it moved to the next one. The band a figure leaves beside itself also ran across the break and narrowed the first lines on the next page next to nothing. A float now stays on the same page as the block beside it, or both move to the next page together, and the band ends at the page break: the lines of a split paragraph on the next page run at full width again. Also, theidattribute of a paragraph, table row or table cell in HTML content is now carried onto the box it produces and shows up asattr-idin the dump output. - A table without a TableHead breaks across frames and pages too.
A table taller than the frame that had no<TableHead>was placed as a single object and drawn past the bottom margin. It is now split row by row like a table with a head, continuing in the next frame of the area or on a new page; there are just no header rows to repeat. A table with only a<TableFoot>is split as well, with its footer repeated. This also changes a short table without a head that does not fit the space left in the frame: it starts where it is and breaks, as a table with a head already did, where before it moved whole to the next frame (#29).
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.