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
52 changes: 48 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,45 @@ follow semantic versioning; release dates are ISO 8601.

### Public API

- **A DOCX page zone writes its picture — a logo — in its line, where the page draws it.** A
page zone is written as one line of a Word header or footer. An `ImageNode` in it was not
written, and was named `DROPPED`, `page zone content`.
- **The picture is an inline picture in the line**, at the size the page draws it: fitted
inside its box where it is contained, the box's size and cropped where it covers it. Its link
is its run's.
- **Word stands it on the line's baseline.** Where it is the line's tallest part, the line is
placed by its foot, which then stands where the page draws it, and is tall enough to hold it:
a 24pt logo makes a 30pt exact line, its baseline four fifths down.
- **A one-line part the page sets on a baseline of its own is raised or lowered to it**
(`w:position`, in half points), in a zone of one line, and the exact line grows to hold
what is raised. The text beside a logo, set from the logo's top or its middle, would
otherwise stand on the logo's foot, 9 to 18pt low. A smaller text beside a larger one is
raised too, where it stood on the larger one's baseline. A part is lowered only as far as
the line holds below Word's baseline — a fifth of it, or as far as its tallest part reaches;
one set lower stands on Word's baseline, and the note counts it. A picture that is not the
line's tallest part is raised as a text is. A line that stops at the page's edge raises its
tallest part back onto the page's baseline too: an 80pt title against the top edge stood
1.8pt low before, and was named. Text the page sets past the edge itself is left off its
baseline, and named.
- **What a picture loses on the line is named** in the `page zone` note: its transform, its
outline entry and its anchor's bookmark. A picture the zone lays out on the first page it
is drawn on in another box — another size, fit or insets — or, where its own proportions
set what is drawn (a side left unstated, or a picture contained in its box), another
picture, is written, and where it stands is named not measured.
- **Measured** in Word 16 and LibreOffice on a 48 by 24pt logo in a header and in a footer:
alone, beside 8pt text set from its top and from its middle, after 18pt text, and beside a
page number. Each logo stood where the page draws it, to a tenth of a point, in both editors.
In Word each text's baseline stood within half a point of the page's: a run is raised by
whole half points, and to the next one towards the line's baseline where the nearest would
pass the line's edge. LibreOffice raises a run about a seventh further than `w:position`
says, does not raise a page field, which stands on the logo's foot there, and stands every
picture on the line's baseline, a raised one included.
- Anything else in a page zone — a shape, a barcode, a container — is still `DROPPED`, `page
zone content`.
- No document of the DOCX fidelity corpus has a page zone; its bytes are unchanged. In
`DocxNodeFieldLedgerTest` the `zones` entry writes each part's own baseline and the zone's
pictures, and names a part set lower than the line holds or past the page's edge.

- **A DOCX export writes a row's fill, outline and side borders, as a panel holding its
columns.** A row paints its box as a container does — `RowBuilder.fillColor`, `stroke`,
`borders` — and the export wrote its columns as a table with no shading or borders, naming the
Expand Down Expand Up @@ -206,11 +245,14 @@ follow semantic versioning; release dates are ISO 8601.
- **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.
one stays on Word's one baseline, and the note counts it (since raised to its own: see "A
DOCX page zone writes its picture — a logo — in its line, where the page draws 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.
only a header's is, about 0.4pt at 18pt. Past a point and a half, the note names it (since
raised back onto it: see "A DOCX page zone writes its picture — a logo — in its line, where
the page draws 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;
Expand Down Expand Up @@ -362,7 +404,8 @@ follow semantic versioning; release dates are ISO 8601.
there;
- 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");
baseline"; and each part since raised to its own: see "A DOCX page zone writes its picture
— a logo — in its line, where the page draws it");
- it is one line;
- no prefix stands before it, on the line's left side.

Expand Down Expand Up @@ -574,7 +617,8 @@ follow semantic versioning; release dates are ISO 8601.
- anything in a page zone but paragraphs, page fields and spacers, such as a logo: now
`DROPPED`, `page zone content`, once for each, though a zone is written into each kind of
header or footer it is given — two logos of no name are two notes, and in a file of several
sections each names its section;
sections each names its section (a picture since written: see "A DOCX page zone writes its
picture — a logo — in its line, where the page draws it");
- a watermark, a protection and viewer preferences: now `DROPPED`, `watermark` (once per
section that sets one), `protection` and `viewer preferences` (once for the file) — a file
asked to be protected opens and edits without a password, and the report says so;
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, 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 where it is not measured, markdown marks written as letters, a markdown heading taller than its line, 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 |
| 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, an `ImageNode` an inline picture at the size the page draws it — contained or cropped to cover its box as the page draws it, its link its run's — 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, in a zone of one line a one-line part the page sets on a baseline of its own raised or lowered to it by `w:position`, in whole half points — lowered only as far as the line holds below its baseline, a fifth of it or as far as its tallest part reaches; LibreOffice raises a run about a seventh further than Word, does not raise a page field and stands every picture on the baseline —; 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 the baseline Word sets it on, 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 where it is not measured, markdown marks written as letters, a markdown heading taller than its line, outline entry, anchor (no bookmark) and a picture set off the baseline, which the line does not carry, and a picture part's transform, outline entry and anchor. The line is an exact line as tall as the tallest part's line on the page — taller for a picture in it, a part written at a larger size than the page's or a part raised above it — standing as far from its edge as puts that part's baseline — a picture's foot — 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