Fixed
- (pdf): a two-column page is no longer split down the middle of a column because one line
crosses its gutter. The corridor search treated whitespace as "no span's bbox", so a single
centred footer, caption, or heading set across both columns closed the page's real gutter for
every line on the page. The per-line median then placed the split inside the left column, every
line it crossed became a band boundary, and the reordered text interleaved the two columns
mid-word — producing spliced tokens such asactivamodelanddemandviating. A split that six or
more lines run through now also considers corridors at most one line crosses, still bounded by the
same distance cap and still refused when either side does not read as a column. Measured over 490
corpus PDFs: 7 documents change, all of them repairing cross-column splices, and none loses
vocabulary. Reported and fixed by Data Polder. (GH#1619) - (pdf): a wrapped numbered heading now closes above the run-in beneath it. The GH#1634 fix's
closing term also required a right-edge test that only applies to the un-indented case. A hanging
indent heading's wrap and a run-in sub-heading below it are both short lines by definition, so
their right edges land within tolerance of each other by coincidence, which overrode the correct
left-edge answer and held the heading open across the run-in and the body under it. (GH#1637) - (ppt): a legacy
.pptslide's title is read from the file rather than guessed. The title came
from the first line of the slide's merged text, gated on a second line existing and the first being
at most 80 characters — so a title-only slide had no title, a two-line title was truncated to its
first line, and a longer title was dropped. The outline collection's ownTextHeaderAtomstates
which text is the title; that is now read, with the first-line rule kept only for decks that state
no title. (GH#1635) - (ppt): the live save's slides are read, not the oldest save's. The persist chain resolved the
current revision correctly and then scanned the stream for the firstSlideListWithText, which on
a deck saved more than once belongs to the earliest save — so edited titles and outline text
reverted to a superseded revision. (GH#1639) - (ppt): speaker notes attach to the slide that owns them, and the notes master is no longer one.
The notes master's placeholder text ("Click to edit Master text styles…") was emitted as the first
slide's speaker note, and the remaining notes were zipped to slides by position, so every note
after a slide without one landed on the wrong slide. Notes are now keyed by theNotesAtom's own
slideIdRef. (GH#1640) - (release): the
xberg-cliarchives are attached again. v1.2.0 published 12 assets and
v1.2.1 published 54, both with none of the ninexberg-cli-*archives, somise,cargo binstall
and every direct download had nothing to fetch. One matrix leg (the aarch64-musl link) failed, and
the upload job required all three CLI build jobs to succeed, so a single leg zeroed the whole asset
class — as a skipped job, which reports no failure and left both releases looking green. The
upload now attaches whatever built, and a following step fails the release when any of the nine is
missing. (GH#1638) - (pdf render):
s(close-and-stroke) is no longer dropped, and no longer floods the next fill.
The object-level content parser had arms for every path-painting operator in ISO 32000-1 Table 60
excepts, which fell through to an operator every consumer ignores. Two things followed: the
stroke was never painted, and — because only a painting arm resets the path builder — the
abandoned geometry stayed in the builder and was painted by the next fill. A stroked frame
followed by an ordinary white label fill therefore flooded the frame's whole interior, wiping
anything already drawn inside it. Text extraction was unaffected: the byte-level fast path always
decomposedscorrectly. (GH#1633) - (pdf render): a glyph is no longer discarded because its inferred Unicode is whitespace.
The byte-indexed rasterizer decided whether to paint by asking what character a code represents
rather than whether there was an outline to draw. For a simple TrueType font with a byte-indexed
cmap and no/Encodingthat character is the glyph id reverse-mapped through the font's own
(1, 0)subtable and read as ASCII or Mac Roman — private glyph ordering decoded as an encoding
it never expressed. Six byte values were affected (0x09–0x0D,0x20, and0xCA→ U+00A0);
re-indexed subsets numbering their glyphs from0x01upward walked through a whole run unpainted
while the advance still applied, leaving gaps that read as spaces. (GH#1632) - (pdf): a hanging-number heading no longer swallows body text indented to its title's edge.
A line was treated as a numbered heading's continuation whenever the heading had a hanging indent
and the line began at the title's left edge — which is equally the geometry of any layout that
indents the whole clause, as contracts, tenders and many installation manuals are set. Wrapping
now also requires that the preceding line actually ran out of room. Separately, a heading that had
absorbed a genuine wrap could never be closed, so the sub-heading and entire body below were
pulled in after it. (GH#1634) - (cli): the Linux musl CLI binaries are published again.
Since 1.2.0 theaarch64-unknown-linux-muslCLI build has been killed mid-link, and because
the upload job requires every musl leg to succeed, no CLI assets were attached to the 1.2.0
or 1.2.1 releases. The cause was the switch to fat LTO: over theallfeature set the final
whole-program link exceeded what the arm64 runner could complete. That build now uses a
release-muslprofile with thin LTO; every other target keeps fat LTO. - (php): setting
outputFormatno longer throws. Every call that passed an output format —
ExtractionConfig::from_json(json_encode(["outputFormat" => "markdown"])), and any
extract/extractBatchcarrying a per-input config — raised
invalid type: string "markdown", expected struct OutputFormat. The generatedXberg\OutputFormat
class carried a derived serde implementation that read and wrote its own storage shape,
{"type":"markdown"}, while the core enum is externally tagged and writes a bare string, so no
real value ever deserialized.EntityCategoryandPiiCategorycarried the same defect.
Introduced in 1.2.0 alongside the flat-class change and shipped in three releases. - (wasm):
metadata.formatfields are readable again.metadata.format.titlereturned''and
metadata.format.sheetCountreturnedNaNfor every document, because the value reached
JavaScript as aMaprather than the plain objectindex.d.tsdeclares —serde-wasm-bindgen
renders every serde map that way, and serde's internally tagged enum representation is a map.
Property access on aMapisundefined. Introduced in 1.2.0 with theFormatMetadataflatten.
Changed
extraction::ppt::PptSlideTextcarries the slide's stated title and notes. The struct gains
title: Option<String>andnotes: Option<String>, so Rust callers constructing it with an
exhaustive struct literal must add the two fields. The type is not exposed through any binding.- Breaking (PHP binding):
OutputFormat,EntityCategoryandPiiCategorynow serialize to the
wire their Rust counterparts emit —"markdown"for a fieldless variant and{"custom":"latex"}
for one carrying a label — instead of the flat{"type":"markdown"}object.from_jsonstill
accepts the object form, so existing input keeps working; output moves. - Breaking (WebAssembly binding):
metadata.format— and any other field carrying a flattened,
camelCased enum — is now a plain object rather than aMap. This is the shapeindex.d.tshas
always declared; code that worked around the discrepancy with.get()must switch to property
access. Fields carrying an enum that is not flattened, such aschunking.sizing, keep the
serde-wasm-bindgenbridge and are unchanged. - Breaking (C#, Dart, Go, Java, Kotlin, Python, Swift bindings): an enum variant whose name ends
in a multi-letter acronym followed by a single lowercase letter is spelled correctly now.
StructuredDataType.RdFabecomesRdfa(C#, Dart, Go, Swift), Kotlin'sR_D_FAbecomesRDFA
andPADDLE_O_C_RbecomesPADDLE_OCR, and Python's stub and runtime agree onRDFA. Wire
values are unchanged — only the identifiers move. Names without that shape (IOError,
HTMLParser,JSONLD) are unaffected.