fix: pagination measures a text box's own spacing

The atomic leaf a text box becomes carried neither its space above nor its space
below, so the fit test ignored both and a page top never dropped the space above
the way it does for a paragraph. The box's space rides --space-top, which the
page-top decoration zeroes — the rotor centres itself in the padding box and has
to follow it.
This commit is contained in:
Steffen Becker committed 2026-08-10 19:35:09 +00:00
1 parent 71ecc17a9e
commit b134911e7b
3 files changed
+19 -9

No files matched your search

+12 -2
View File
@@ -751,13 +751,22 @@ export const PageBreaks = Extension.create({
// A text box paginates atomically: a line-split spacer can't render
// sanely inside a rotated/floated node view.
if (child.classList.contains('textbox-node')) {
// Its anchor paragraph's spacing rides the box (textBox.ts): space above
// as padding, which a page top drops, space below as the margin.
const boxCs = getComputedStyle(child);
const spaceAbove = parseFloat(boxCs.getPropertyValue('--space-before')) || 0;
const dropped = spaceAbove > 0.5 && !inTableCell && parseFloat(boxCs.paddingTop) < 0.5
? spaceAbove : 0;
leaves.push({
el: child,
kind: 'atomic',
naturalTop: naturalTopOf(child),
naturalHeight: child.offsetHeight,
naturalHeight: child.offsetHeight + dropped,
spaceAfter: inTableCell ? 0 : parseFloat(boxCs.marginBottom) || 0,
spaceAbove,
inTableCell,
});
cumulativeDropped += dropped;
continue;
}
// A multi-column section fragment: also atomic for spacer purposes (a
@@ -1316,7 +1325,8 @@ export const PageBreaks = Extension.create({
}));
}
for (const b of pageTopBlocks) {
decoArray.push(Decoration.node(b.from, b.to, { style: 'padding-top:0;margin-top:0' }));
// --space-top: a text box draws its own space above from it (textBox.ts).
decoArray.push(Decoration.node(b.from, b.to, { style: 'padding-top:0;margin-top:0;--space-top:0px' }));
}
decorations = decoArray.length > 0
+5 -5
View File
@@ -388,11 +388,11 @@ class TextBoxView {
// frame distance already is.
const before = this.node.attrs.spaceBefore as number | null;
const after = this.node.attrs.spaceAfter as number | null;
// On the rotor too: --space-before is a registered non-inheriting property, and the
// rotor centres itself in the padding box, so it needs the value to correct for it.
for (const el of [d, this.rotor]) {
if (before != null) el.style.setProperty('--space-before', `${before}pt`);
else el.style.removeProperty('--space-before');
// --space-top is the same value the rotor can inherit (--space-before is registered
// non-inheriting), and it is what pageBreaks.ts zeroes at a page top.
for (const prop of ['--space-before', '--space-top']) {
if (before != null) d.style.setProperty(prop, `${before}pt`);
else d.style.removeProperty(prop);
}
if (after != null) d.style.marginBottom = `${after}pt`;
// Set against the middle or the far end of the column, unless the file placed the
+2 -2
View File
@@ -387,8 +387,8 @@
position: absolute;
left: 50%;
/* Centred in the padding box, so half the space above has to come back to keep the
frame itself below it. */
top: calc(50% + var(--space-before) / 2);
frame itself below it — the *applied* space, which a page top drops. */
top: calc(50% + var(--space-top, 0px) / 2);
box-sizing: border-box;
transform: translate(-50%, -50%);
}