feat: an ODF paragraph reads the contextual spacing its style declares
style:contextual-spacing drops a paragraph's own spacing towards a neighbour of the same style; LibreOffice's List styles carry it, and only the docx path read its w:contextualSpacing counterpart. Unread, every list point kept the 8pt its style declares and a long document accumulated a page every dozen items. The editor has no such mode, so the suppressed side becomes an explicit 0. In a list each item wraps its own paragraph, so the neighbour is the next item's.
This commit is contained in:
1 parent
b65920f7b7
commit
b3c52e53fc
1 file changed
+25
@@ -1568,6 +1568,7 @@ function convertParaLike(el: Element, ctx: Ctx, kind: BlockKind, boldByDefault =
|
||||
if (kind === 'cell' && !isHeading) { defaults.marginTopPt = 0; defaults.marginBottomPt = 0; }
|
||||
|
||||
const attrs = blockAttrs(paraProps, baseTextProps, defaults, kind);
|
||||
if (paraProps['style:contextual-spacing'] === 'true') applyContextualSpacing(el, styleName, attrs);
|
||||
// A style:master-page-name switches the page master, which is how ODF gives a section
|
||||
// its own header/footer; the block that does it opens that section.
|
||||
const master = kind === 'body' ? resolver.masterPageOf(styleName) : null;
|
||||
@@ -1720,6 +1721,30 @@ function blockAttrs(paraProps: PropMap, textProps: PropMap, defaults: BlockDefau
|
||||
return attrs;
|
||||
}
|
||||
|
||||
// style:contextual-spacing drops a paragraph's own spacing towards a neighbour of the
|
||||
// same style — LibreOffice's List styles carry it. The editor has no such mode, so the
|
||||
// suppressed side becomes an explicit 0, as the docx path does for w:contextualSpacing.
|
||||
function applyContextualSpacing(el: Element, styleName: string | null, attrs: Record<string, unknown>): void {
|
||||
const same = (sib: Element | null) => !!sib && sib.getAttributeNS(NS.text, 'style-name') === styleName;
|
||||
if (same(flowSibling(el, 'previous'))) attrs.spaceBefore = 0;
|
||||
if (same(flowSibling(el, 'next'))) attrs.spaceAfter = 0;
|
||||
}
|
||||
|
||||
// The paragraph before or after this one in the flow: its own sibling, or — in a list,
|
||||
// where each item wraps its paragraphs — the neighbouring item's nearest one.
|
||||
function flowSibling(el: Element, dir: 'previous' | 'next'): Element | null {
|
||||
const isPara = (e: Element) => e.namespaceURI === NS.text && (e.localName === 'p' || e.localName === 'h');
|
||||
const step = (e: Element) => (dir === 'previous' ? e.previousElementSibling : e.nextElementSibling);
|
||||
for (let s = step(el); s; s = step(s)) if (isPara(s)) return s;
|
||||
const item = el.parentElement;
|
||||
if (!item || item.namespaceURI !== NS.text || item.localName !== 'list-item') return null;
|
||||
for (let s = step(item); s; s = step(s)) {
|
||||
const kids = Array.from(s.children).filter(isPara);
|
||||
if (kids.length) return dir === 'previous' ? kids[kids.length - 1] : kids[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Paragraph background + per-side border attrs (paragraphBox.ts) from resolved paraProps.
|
||||
// Each key is omitted when absent so a paragraph with no box props stays clean. Borders:
|
||||
// a per-side fo:border-* overrides the all-sides fo:border; 'none' ⇒ no border (undefined).
|
||||
|
||||
Reference in new issue
Block a user