Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 57 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,55 @@ follow semantic versioning; release dates are ISO 8601.

### Public API

- **A DOCX page zone's text stands on the page's baseline.** A page zone is written as one line
of a Word header or footer. The line was Word's single line for its face. Its top sat at the
zone content's top in a header, and its foot at the content's foot in a footer. So the line's
height, and the room a lone part's padding holds above and below its text, were Word's, and
nothing named them. In LibreOffice a header's text stood up to a point higher than on the
page. Two zones of one kind — a cover's header on the first page, the running header on the
rest — shared one distance from the edge, the last zone's, and the cover's header stood 16pt
high in both editors. A zone paragraph's anchor had no bookmark, without a note.
- **The zone's line is an exact line**, as tall as its tallest part's line on the page. It
stands as far from its page edge as puts that part's baseline where the page has it, since
both editors stand an exact line's baseline four fifths of the way down it (measured on
exact lines). A lone part's padding and margin above and below are in that distance.
- **The line is taller where it needs to be:**
- for a picture in a zone paragraph, which Word stands on the baseline, so the exact line
does not cut its top;
- for a part the page fits smaller, which Word writes at its style's size.
- **A tallest part the page sets in more lines than one** is written as as many exact lines.
Word grows a footer up from its distance, so a footer of two lines stands a line further from
the edge, its first line on the page's first baseline.
- **A zone the layout measures that shares its kind with another page zone** stands in a frame
(`w:framePr`) at its own height, as a text band does. The frame is at least its lines tall,
so a part Word sets in more lines goes on below them.
- **Measured** in Word 16.0.20430 and LibreOffice 26.8 on 8pt and 18pt Lato headers and 8pt
Lato footers. A lone part's text, and a line's tallest part's, stood within 0.1pt of the
page's baseline in each editor, the cover's header included. A smaller part beside a taller
one stays on Word's one baseline, and the note counts it.
- **Text the page sets right against the edge** stands off the page's baseline, as the line
stops at the edge: lower in a header, by what its ascent falls short of four fifths of its
line, and higher in a footer, by what its descent falls short of a fifth. In the default face
only a header's is, about 0.4pt at 18pt. Past a point and a half, the note names it.
- **The `page zone` note:**
- counts a part off the baseline Word sets the line on, and says where the parts after a
part of more lines than one stand is not measured, since Word sets them on a later line;
- names a zone paragraph's anchor, which has no bookmark, since a zone is written into a part
each kind of page repeats;
- names a picture the page sets anywhere but on the baseline;
- names a zone's lines that reach past the page margin by more than half a point. The margin
is then written negative, as for a text band, so Word holds the body at it; LibreOffice
moves the body clear. Within half a point — text under about 22pt set against the margin in
the default face — Word moves the body by as much, unnamed;
- says where the zone's text stands is not measured where the zone's content is built
otherwise for the first page it is drawn on than as written — other text, another face or
size, other pictures. The line is Word's there;
- names a zone whose content is none for no page in particular: it is not written.
- No document of the DOCX fidelity corpus has a page zone; its bytes are unchanged.
- Ledger: the `zones` option moves from a gap to `REPORTED`, and a lone or tallest page field's
padding and margin above and below are written. No entry is a gap any more, and `GAP` is no
longer a fate an entry can take.

- **A DOCX canvas holds its height.** The page gives a canvas its height whatever it holds. The
export writes what it holds one block after another, and dropped the room under it, so what
followed stood that much higher. The report named the loss only where something followed the
Expand Down Expand Up @@ -135,7 +184,9 @@ follow semantic versioning; release dates are ISO 8601.
page sets it when it meets all of these:
- its line starts within a point and a half of Word's, or, against the right margin, ends
there;
- it sits on Word's baseline: its tallest part's, with the line standing at the zone's edge;
- it sits on Word's baseline: its tallest part's, with the line standing at the zone's edge
(since placed by that part's baseline: see "A DOCX page zone's text stands on the page's
baseline");
- it is one line;
- no prefix stands before it, on the line's left side.

Expand All @@ -149,7 +200,9 @@ follow semantic versioning; release dates are ISO 8601.
zone. In `DocxNodeFieldLedgerTest` a page field's `padding` and `margin` move from a gap to
`REPORTED`, and its `align` to `INERT`: the page sets a field in a box a point wider than its
number. The `zones` option keeps a gap for the line's height and the room its parts hold above
and below, and a zone paragraph's anchor. 1 node-field gap remains, `CanvasLayerNode.height`.
and below, and a zone paragraph's anchor (since closed: see "A DOCX page zone's text stands on
the page's baseline"). 1 node-field gap remains, `CanvasLayerNode.height` (since closed: see
"A DOCX canvas holds its height").
- **A DOCX export's report names what a paragraph's own fields lose.** Several of a paragraph's
own fields were lost without a note:
- an auto-sized paragraph's text was written at its style's size, not the one the page fits it
Expand Down Expand Up @@ -359,7 +412,8 @@ follow semantic versioning; release dates are ISO 8601.
lost: 29 fields have nothing to carry, 55 node fields are gaps, and so are page zones, where
a paragraph's alignment, spacing and direction and a row's columns are not written. A node
class that is not a record, or a kind or field added to the engine, fails it until someone
decides.
decides. (The gaps have since closed, and a gap is no longer a fate: see "A DOCX page zone's
text stands on the page's baseline".)
- **A DOCX timeline's dots, and drawings centred on a line, move with their text too.** A
drawing in a table cell went into that cell only when the cell held it across, and a dot
set in a column of its own beside its entry's text — `CharcoalGold`'s timeline, a row of
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/backend-capability-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ honour an option ignores it (documented contract).
| Page backgrounds (`DocumentSession.pageBackgrounds`, `PageBackgroundFill` — full page, columns, bands) | ✅ `DocumentPageBackgrounds` adds each fill as a shape fragment under every page's content, drawn by the ordinary shape handler | ✅ the same fragments, drawn as shapes on every slide | ⚠️ `DocxPageBackgrounds` — each fill is a rectangle anchored to the page, behind the text. A section of more than one page, or with a header or footer, carries them in every header part it has (default, first page, even pages), so they are drawn on every page; a section without a header gets an empty one against the page edge to carry them, and a later section without fills gets an empty header of its own rather than inheriting them. On a page with no top margin LibreOffice still sets the first line about 3pt lower under that header. A section of one page with neither draws them from the body instead — in the first cell's paragraph when the page opens with a table — and its lines stand where the page sets them in both editors (the seven sidebar CVs stood 2.6 to 3.3pt low in LibreOffice); they are on that page alone, so a page an editor's text runs onto has none. A fill's alpha is carried as the shape's. A two-column layout still flows its columns one after the other, so a column fill can stand beside text that is not its column's |
| Watermark (front/back layers) | ✅ `PdfWatermarkRenderer` | ✅ `PptxChromeRenderer` (per-slide shape at the PDF placement math; behind-content applies before fragments, so no z-order surgery) | ❌ (not written; reported `DROPPED`, `watermark`) |
| Repeating headers / footers | ✅ `PdfHeaderFooterRenderer` — the zone's `fontName` is resolved through the document's own `FontLibrary`, so a zone draws in the family the author named; unnamed means standard-14 Helvetica, and a code point that family cannot encode is substituted with `?` exactly as body text is | ✅ `PptxChromeRenderer` (positioned per-slide text boxes; `{page}` / `{pages}` / `{date}` tokens with the numbering window rules). The named family reaches the slide run through `PptxFontMapping.familyFor`, and the same family measures the slots — a run measured against one face and typeset in another lands off-centre | ✅ `DocxSemanticBackend.writeBand` (`DocxTextBands`) — one line of a Word header or footer part: the left slot, the centre slot at a centre tab and the right slot at a right tab against the margins; `{page}` / `{pages}` as `PAGE` / `NUMPAGES` (`SECTIONPAGES` per section) fields with the roman or alphabetic switch; `{date}` as the date of the export; the separator as the paragraph's border, a translucent one flattened against white and reported (`translucency`); the header or footer distance from the band's geometry, baseline within 0.1pt in LibreOffice; a band sharing its kind with another band or a page zone stands in a frame (`w:framePr`) at its own height; `showOnFirstPage(false)` or counting from page 2 → an empty first-page part. A band starting after page 2, numbers not counting from 1 on page 1, and a band alone of its kind reaching past the page margin (written as a negative margin, so that Word holds the body at it as the page does; LibreOffice moves the body clear of it) are reported |
| Page zones (node subtree in the band) | ✅ Spliced into the layout graph by `DocumentPageZones`, so the ordinary fragment handlers draw it — no zone-specific code in the backend | ✅ Same splice, same reason: `PptxFixedLayoutBackend.renderGraph` draws every fragment of the graph | ✅ Written into a real `w:ftr` / `w:hdr` part. The band's children become runs on one Word line: a paragraph contributes its runs, a flex spacer becomes the right tab stop, and `PageContext.pageNumber()` / `pageTotal()` become live `PAGE` / `NUMPAGES` fields. Other node kinds are skipped, logged once a kind and reported once each (`DROPPED`, `page zone content`); a zone row's own fill, outline and side borders are reported as `row paint`. Word sets the line's parts one after another from the left margin and, after the first spacer, against the right margin, on one baseline, its tallest part's; the page sets each by the zone's padding, a row's columns and gap, a paragraph's alignment and a part's own sides (a page field's alignment moves nothing: its box is a point wider than its number). The `page zone` note counts the parts the page sets elsewhere — read from the layout's zone fragments: a part stands where the page sets it when its line starts, or against the right margin ends, within a point and a half of Word's, on Word's baseline with the line at the zone's edge, in one line and with no prefix before it on the left side; past a part whose width Word does not keep, or a zone whose nodes the page names or nests otherwise, where a part stands is said to be not measured — and names a zone paragraph's right-to-left direction, prefix letters, fitted size and outline entry, which the line does not carry; the line's height and the room its parts hold above and below are Word's, and a zone paragraph's anchor has no bookmark, none of these yet named. Because Word paginates, `PageContext.number()` refuses here rather than baking a number that would be wrong on every page but one. A zone's `appliesTo` predicate is asked over sample pages (`DocxPageClasses`) and, when it follows Word's first / even / other pages, becomes the matching part — `w:titlePg` for the first page, `w:evenAndOddHeaders` for even pages — with an empty part on the pages it skips; a predicate that picks pages within a kind (the last page) is written on every page and reported |
| Page zones (node subtree in the band) | ✅ Spliced into the layout graph by `DocumentPageZones`, so the ordinary fragment handlers draw it — no zone-specific code in the backend | ✅ Same splice, same reason: `PptxFixedLayoutBackend.renderGraph` draws every fragment of the graph | ✅ Written into a real `w:ftr` / `w:hdr` part. The band's children become runs on one Word line: a paragraph contributes its runs, a flex spacer becomes the right tab stop, and `PageContext.pageNumber()` / `pageTotal()` become live `PAGE` / `NUMPAGES` fields. Other node kinds are skipped, logged once a kind and reported once each (`DROPPED`, `page zone content`); a zone row's own fill, outline and side borders are reported as `row paint`. Word sets the line's parts one after another from the left margin and, after the first spacer, against the right margin, on one baseline, its tallest part's; the page sets each by the zone's padding, a row's columns and gap, a paragraph's alignment and a part's own sides (a page field's alignment moves nothing: its box is a point wider than its number). The `page zone` note counts the parts the page sets elsewhere — read from the layout's zone fragments: a part stands where the page sets it when its line starts, or against the right margin ends, within a point and a half of Word's, on Word's baseline, in one line and with no prefix before it on the left side; past a part whose width Word does not keep, or a zone whose nodes the page names or nests otherwise, where a part stands is said to be not measured — and names a zone paragraph's right-to-left direction, prefix letters, fitted size, markdown marks, outline entry, anchor (no bookmark) and a picture set off the baseline, which the line does not carry. The line is an exact line as tall as the tallest part's line on the page — taller for a picture in it or a part written at a larger size than the page's — standing as far from its edge as puts that part's baseline where the page has it (a lone or tallest part within 0.1pt in Word and LibreOffice, measured on 8pt and 18pt Lato headers and 8pt Lato footers), a lone part's padding and margin above and below included; a tallest part of more lines than one is as many exact lines, a footer's standing as many lines further from its edge; lines reaching past the page margin by more than half a point are held there with a negative margin and named; a zone the layout measures that shares its kind with another page zone stands in a frame (`w:framePr`) at its own height, at least its lines tall; a zone whose content is built otherwise for its first page — other text, face, size or pictures — is not measured, its line Word's, and a zone whose content is none for no page in particular is not written and named. Because Word paginates, `PageContext.number()` refuses here rather than baking a number that would be wrong on every page but one. A zone's `appliesTo` predicate is asked over sample pages (`DocxPageClasses`) and, when it follows Word's first / even / other pages, becomes the matching part — `w:titlePg` for the first page, `w:evenAndOddHeaders` for even pages — with an empty part on the pages it skips; a predicate that picks pages within a kind (the last page) is written on every page and reported |
| Protection / encryption | ✅ `PdfDocumentPostProcessor` | ❌ (ignored with a one-time warning — no OOXML encryption support planned) | ❌ (not written, so the file opens unprotected; reported `DROPPED`, `protection`) |
| Viewer preferences | ✅ `applyViewerPreferences` in `PdfFixedLayoutBackend` | ❌ (ignored with a one-time warning — PDF-viewer concept) | n/a (not written; reported `DROPPED`, `viewer preferences`) |
| Debug guide lines / node labels | ✅ `PdfGuideLinesRenderer`, `PdfNodeLabelRenderer` | ❌ (ignored with a one-time warning — render through the PDF backend to see overlays) | n/a |
Expand Down
Loading
Loading