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

### Public API

- **A DOCX export keeps a page break the layout makes by itself after a card where the page
makes it.**
- A block kept together or kept with the next in a table cell — a row's column, a panel's cell —
keeps its lines together (`w:keepLines`) and no longer keeps with the next (`w:keepNext`).
Word reads keep-with-next on the paragraph a cell opens with as keeping the cell's whole row
with what follows its table, and holds no keep across a break inside the row, so in a cell
those keeps did nothing else; a row of columns, which may not split, keeps the block whole.
The separator between two tables in a cell keeps with the next table as before: measured, Word
does not read it as the paragraph the cell opens with. Measured in Word 16 on `ObsidianInvoice`
with its line items repeated 12 times: its info cards, kept together in a row's two cells, went
to page 5 with the footer divider under them, 17 lines below the page's, where the layout sets
them at the foot of page 4.
- A rule the layout moves to a new page in the flow holds the space the layout leaves above it
there in an empty line kept with it, as a paragraph, a list and a picture do: Word and
LibreOffice drop a paragraph's space above at the top of a page they break themselves, and
a rule's is its paragraph's. The same invoice's footer divider stood its 13pt margin high on
page 5 in both.
- A table, a row, a painted row or a panel the layout moves to a new page holds there, with its
own top edge, the gap above it the layout carries onto that page where the gap does not fit at
the foot of the page above. It left the gap on the page above: `MeteredInvoice` with its line
items repeated 9 times opens page 4 with its closing row 12.4pt below the margin, the gap, and
Word set the page 13.0pt high and LibreOffice 12.8pt. One the layout runs on past the page it
opens has no place of its own there to measure, and holds its own top edge alone, as before.
- After: `ObsidianInvoice` ×12 sets every line on its page in Word and LibreOffice 24.2, page 5
within 0.1pt; `MeteredInvoice` ×9's page 4 within 0.7pt in both. Of 35 lengths of 7 invoice
templates whose layout opens a page with a block of their body, those two move, and none
other.
- In the corpus, 15 documents lose keep-with-next on paragraphs in table cells, and nothing else;
no page in it opens with a rule or a gap the layout carries there. Word and LibreOffice 24.2
set every one of their lines where they did.

- **A DOCX export writes the margins of a page that starts at a panel's cut as a Word section
there.**
- A page that `session.pageMargins(...)` sets against other top and bottom margins than Word
Expand Down Expand Up @@ -39,11 +70,9 @@ follow semantic versioning; release dates are ISO 8601.
Word and LibreOffice 24.2, as it did. Repeated 12 times, its page 5 starts between its info
cards and its footer divider. With page 1's margins in force on pages 2 to 4, page 5 opened a
Word section of its own, which fixed that break, and Word held the cards on page 4; page 5's
margins are now page 2's, so the editors place that break themselves: Word sets the cards on
page 5 with the divider, because `keepNext` on
the paragraphs inside the cards' cell keeps their table with the paragraph after it, and both
editors drop the divider's 13pt of space above it at a page they break themselves. The same
invoice with one set of margins on every page is set the same way in both editors.
margins are now page 2's, so the editors place that break themselves, as they do the same
invoice with one set of margins on every page — where the page places it, since the entry
above.
- Where a page the panel runs onto is not cut and a later one is, LibreOffice 24.2 sets the part
after that cut with the margins of the section before: it reads no section's margins after a
table it broke over a page itself. Word reads them.
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 @@ -122,7 +122,7 @@ honour an option ignores it (documented contract).
| 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 |
| Keep a block on one page (`keepTogether()`, `keepWithNext()`) | ✅ resolved by `LayoutCompiler` before any backend runs | ✅ same — the slides are the laid-out pages | ✅ `DocxSemanticBackend.keepOnOnePage` — Word re-paginates, so a block the layout placed on one page is told to stay there: `w:keepLines` on each of its paragraphs and `w:keepNext` on every one but the last (on the last too for `keepWithNext`), a table inside it chained row by row. A layer stack or a shape container in the flow is kept so too, as the layout moves it to a new page whole, a band of layers included; the first block of a band the layout moves to a new page — a paragraph, a picture, a list, a title and its dates set as one line or a spacer, a backdrop drawn before it or not — holds the space above it there, as one in the flow does: in a line kept with it, or in a spacer's own line; a table, a row or a panel opening it holds the band's top edge so with its own, the gap before the band left at the foot of the page above. A list or such a line the layout moves to a new page in the flow holds its space above there as a paragraph does. A block that ran over a page break in the layout is taller than a page and is left to flow, as the layout left it. A body table of one row kept with what follows, its cells holding paragraphs alone, is written with a row that may break (`letTheOneRowTablesKeptWithTheNextBreak`): LibreOffice reads a one-row table whose row may not break as one that may not split, and keeps such a table with nothing. A panel in the body the layout breaks over pages inside a table it holds is a table a page, cut at the row each page starts with (`cutThePanelsAtTheirPages`): Word repeats no header row of a table nested in a cell, so each part after a cut opens with copies of the table's header rows, where it has them, and the page breaks between the parts are fixed; where the page after a cut has other top and bottom margins, and the page above has room for it, the paragraph between the parts ends a Word section with them (per-page margins, below) |
| Keep a block on one page (`keepTogether()`, `keepWithNext()`) | ✅ resolved by `LayoutCompiler` before any backend runs | ✅ same — the slides are the laid-out pages | ✅ `DocxSemanticBackend.keepOnOnePage` — Word re-paginates, so a block the layout placed on one page is told to stay there: `w:keepLines` on each of its paragraphs and `w:keepNext` on every one but the last (on the last too for `keepWithNext`), a table inside it chained row by row; in a table cell, `w:keepLines` alone, as Word reads `w:keepNext` on the paragraph a cell opens with as keeping the row with what follows its table. A layer stack or a shape container in the flow is kept so too, as the layout moves it to a new page whole, a band of layers included; the first block of a band the layout moves to a new page — a paragraph, a picture, a list, a title and its dates set as one line or a spacer, a backdrop drawn before it or not — holds the space above it there, as one in the flow does: in a line kept with it, or in a spacer's own line; a table, a row or a panel opening it holds the band's top edge so with its own, the gap before the band left at the foot of the page above. A list, such a line or a rule the layout moves to a new page in the flow holds its space above there as a paragraph does, and a table, a row or a panel on that one page holds the gap the layout carries there with its own top edge. A block that ran over a page break in the layout is taller than a page and is left to flow, as the layout left it. A body table of one row kept with what follows, its cells holding paragraphs alone, is written with a row that may break (`letTheOneRowTablesKeptWithTheNextBreak`): LibreOffice reads a one-row table whose row may not break as one that may not split, and keeps such a table with nothing. A panel in the body the layout breaks over pages inside a table it holds is a table a page, cut at the row each page starts with (`cutThePanelsAtTheirPages`): Word repeats no header row of a table nested in a cell, so each part after a cut opens with copies of the table's header rows, where it has them, and the page breaks between the parts are fixed; where the page after a cut has other top and bottom margins, and the page above has room for it, the paragraph between the parts ends a Word section with them (per-page margins, below) |
| Layer stack — layers drawn over one another (`addLayerStack`) | ✅ each layer's fragments at the place the layout gave it | ✅ same | ⚠️ `DocxSemanticBackend` — Word has no layers, so a stack's children are written one after the other, without their positions. The exception is a stack whose layers are side-by-side columns (`DocxLayerColumns`): every layer a plain container at the stack's top-left corner, the bands their padding leaves either the same or apart. That is written as one table row, a cell per band, the way a two-column CV lays its columns out as layers to draw the name first. Layers sharing a band follow one another in its cell. A spacer the layout placed level with content of another layer of the same band only keeps that content's place, and is not written. The space above a later layer's first block is the gap the page shows below the content before it, to the block's content: what the block holds above that content past the gap — its margin, its padding, a contained picture's room — comes out of its own edge (`DocxLayerColumns.resume`) where it is a paragraph, a picture, a list or a table in its layer's own flow with its edge above at zero or more, and a list keeps its own edges after the gap, as a table does. Each layer is held to its box across, past the stack's edge where the page sets it past it, by an indent below zero; in a table cell no further left than the indents round it give, the rest named. The one layer of a stack of one layer is held the same way, its offset across kept and its measure with it; in a table cell, composed in a table's cell with no place of its own, and for an offset down or up, which moves it without making room for it, it is written where the stack starts and named — not where a band or a column's layer sets it at the page's distance below what is above it, which holds the offset. A shape container written as a panel in a table cell keeps its padding round its outline, as the page does — the table's indent and the space above and below it; where its table ends its cell, Word sets the cell's content at its middle or foot without the space below, which a centred cell takes off the space above, the rest named — sets what its cell holds at the top, middle or foot as its first written layer is set, holds a paragraph across to its box, and names a written layer after the first (written under the ones before it), an offset down or up, a box past the shape's side, and a layer set across by a box the layout does not give (`layer offset`). A layer whose content the page starts above where those before it end — a caption over its photo, a label in a card's corner, not a block giving back its edge whose padding or contained picture's room alone reaches there — is written under them, in a column's cell as in a band, and the report names it; a stack composed in a table cell, or exported with no layout, has no place of its own, and one writing more than one of its layers is named with them not measured. What the page draws inside another layer's fill comes after that fill. Drawings stack as the page paints them, by their z-index, each given its place (`wp:anchor/@relativeHeight`) from where the page paints its fragment, whatever order the layers are written in — among the drawings behind the text, and among those in front of it, as Word draws every one behind under every one in front. Measured in LibreOffice: `CharcoalGold`, `SidebarPortrait` and `SlateOrange` fit on one page, as on the page; `NavySidebar` runs one line onto a second page. A stack or shape container the page gives no room — its margins taking back its whole height, as `LumaStudioInvoice`'s sidebar over the page's top margin — is laid over the flow (`laidOverTheFlow`): its drawings where the page draws them, each paragraph in a text box in front of the text where the page sets it, nothing in the flow. Not in a table cell or a panel, and not when it holds anything but drawing and plain paragraphs — a link, an anchor, a picture, a list or a table keeps it in the flow as above. A canvas is written as its contents — what it writes one block after another, its drawings where it places them — and holds its height: the room under what it writes is the space below it, and one that only draws holds its whole box where nothing round it — a band before its layer's first block, drawings held whole, an overlay it is a layer of — measures that room already. The report names the places and the width it loses, as it does a column's fixed width narrower than its band, and what it writes running past its height, a drawing in what it writes that takes no room in Word, or a height not measured |

## Output surface and lifecycle
Expand Down
Loading
Loading