fix: keep the text area at the page margins under a page border
ODF lays a page border and its padding out inside the margin, so the bordered ODT rendered every line narrower than the editor and Word (offsetFrom="text") and paginated a page long. The export shrinks the fo:margin-* by border + padding, the import grows them back.
This commit is contained in:
1 parent
d9496abe4f
commit
6d85a88907
3 files changed
+27
-3
No files matched your search
@@ -3058,6 +3058,16 @@ function rewriteStylesXml(odtBytes: Uint8Array, lang: { language: string; countr
|
||||
styles = styles.replace(/<style:page-layout-properties /, '<style:page-layout-properties style:writing-mode="rl-tb" ');
|
||||
}
|
||||
|
||||
// ODF lays border and padding out *inside* the page margin, shrinking the text area;
|
||||
// the editor and Word (w:pgBorders offsetFrom="text") keep the text at the margins and
|
||||
// push the border out. Shrink the margins by border + padding so the text stays put.
|
||||
if (decor.border) {
|
||||
const inset = decor.border.paddingCm + (decor.border.widthPt * 2.54) / 72;
|
||||
styles = styles.replace(/<style:page-layout-properties [^>]*/, (m) =>
|
||||
m.replace(/fo:margin-(top|bottom|left|right)="([\d.]+)cm"/g, (_a, side, v) =>
|
||||
`fo:margin-${side}="${Math.max(0, round3(Number(v) - inset))}cm"`));
|
||||
}
|
||||
|
||||
// Page background and page border ride the page layout, exactly where LibreOffice
|
||||
// keeps them (probed). The border is offset from the text area by fo:padding.
|
||||
const decorProps = [
|
||||
|
||||
@@ -737,10 +737,14 @@ export class StyleResolver {
|
||||
if (!props) return null;
|
||||
|
||||
const hf = this.masterPageHF(pageName);
|
||||
// A page border + its fo:padding sit inside the margin and push the text in; the
|
||||
// editor keeps text at the margin (like w:pgBorders offsetFrom="text"), so the
|
||||
// shrunken ODF margins are read back grown by that inset (inverse of the export).
|
||||
const inset = this.decorInsetCm(props);
|
||||
const cm = (attr: string, fallback: number, extra = 0) => {
|
||||
const v = lengthToCm(props.getAttributeNS(NS.fo, attr));
|
||||
if (v == null) return fallback;
|
||||
return Math.min(10, Math.max(0, Math.round((v + extra) * 100) / 100));
|
||||
return Math.min(10, Math.max(0, Math.round((v + extra + inset) * 100) / 100));
|
||||
};
|
||||
const margins: PageMargins = {
|
||||
top: cm('margin-top', 2.54, hf.header ? hf.headerExtraCm : 0),
|
||||
@@ -825,10 +829,20 @@ export class StyleResolver {
|
||||
edgeDistancesCm(): { top: number; bottom: number } | null {
|
||||
const props = this.pageLayoutEl()?.getElementsByTagNameNS(NS.style, 'page-layout-properties')[0] ?? null;
|
||||
if (!props) return null;
|
||||
const inset = this.decorInsetCm(props);
|
||||
const cm = (attr: string) => {
|
||||
const v = lengthToCm(props.getAttributeNS(NS.fo, attr));
|
||||
return v == null ? null : Math.min(10, Math.max(0, Math.round(v * 100) / 100));
|
||||
return v == null ? null : Math.min(10, Math.max(0, Math.round((v + inset) * 100) / 100));
|
||||
};
|
||||
return { top: cm('margin-top') ?? 1.25, bottom: cm('margin-bottom') ?? 1.25 };
|
||||
}
|
||||
|
||||
// Border width + fo:padding of the page's own border, 0 without one — the amount ODF
|
||||
// pushed the text in past the declared margin (pageGeometry adds it back).
|
||||
private decorInsetCm(props: Element): number {
|
||||
const border = props.getAttributeNS(NS.fo, 'border') ?? props.getAttributeNS(NS.fo, 'border-top');
|
||||
if (!border || border === 'none') return 0;
|
||||
const widthPt = lengthToPt(border.trim().split(/\s+/)[0]) ?? 0;
|
||||
return (lengthToCm(props.getAttributeNS(NS.fo, 'padding')) ?? 0) + (widthPt * 2.54) / 72;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
- **`wordCompletion.ts`** / **`.svelte.ts`** — the word-completion settings *and* the collected words (`edentext-word-completion`), LibreOffice's defaults: on, no appended space, words from eight letters, 1000 of them. The list outlives the document that filled it, as LibreOffice's profile copy does; a reactive singleton, since the extension both reads it and adds to it on every keystroke while the AutoCorrect dialog edits it.
|
||||
- **`autoText.ts`** / **`.svelte.ts`** — the AutoText library (`edentext-autotext`, the key removed when empty): name, shortcut and the entry's HTML. App-level, not document-level, so no exporter looks at it.
|
||||
- **`tableOptions.svelte.ts`** — table number recognition (`edentext-number-recognition`, the key removed when off, as it is by default in LibreOffice). A reactive singleton like `trackChanges.svelte.ts`: the extension reads it on every transaction, both table surfaces flip it. It also holds the **number locale** — the document's language, which is what LibreOffice reads and writes a cell's number in — pushed by `App.svelte`'s language effect; the exporters take that locale from their own `language` argument instead, so they stay store-free. See `docs/architecture/tables.md`.
|
||||
- **`pageDecor.ts`** — the page's own decoration (`edentext-page-decor`, the key removed when all three are off): background colour, a border around the text area at `paddingCm` from it, and a text watermark (LibreOffice's Format ▸ Watermark, field for field). All three ride the ODF page layout — the watermark as a `fontwork-plain-text` `<draw:custom-shape>` named `PowerPlusWaterMarkObject` in the **master page's header**, the only place LibreOffice reads one from (probed: hung off the master page directly it is dropped, and an undeclared `xmlns:draw` in styles.xml drops it too). Word keeps the background on `w:document` (plus `w:displayBackgroundShape`), the border in `w:sectPr` **after `w:pgMar`** — the child order is fixed, and ahead of the header references Word's reader loses those — and the watermark as the VML `_x0000_t136` shape, whose definition has to travel or LibreOffice cannot resolve it. `PageDecorLayer.svelte` draws border and watermark per page; the background is a `--color-page-bg` override on `.paper`.
|
||||
- **`pageDecor.ts`** — the page's own decoration (`edentext-page-decor`, the key removed when all three are off): background colour, a border around the text area at `paddingCm` from it, and a text watermark (LibreOffice's Format ▸ Watermark, field for field). All three ride the ODF page layout — the watermark as a `fontwork-plain-text` `<draw:custom-shape>` named `PowerPlusWaterMarkObject` in the **master page's header**, the only place LibreOffice reads one from (probed: hung off the master page directly it is dropped, and an undeclared `xmlns:draw` in styles.xml drops it too). Word keeps the background on `w:document` (plus `w:displayBackgroundShape`), the border in `w:sectPr` **after `w:pgMar`** — the child order is fixed, and ahead of the header references Word's reader loses those — and the watermark as the VML `_x0000_t136` shape, whose definition has to travel or LibreOffice cannot resolve it. `PageDecorLayer.svelte` draws border and watermark per page; the background is a `--color-page-bg` override on `.paper`. ODF lays border + `fo:padding` out *inside* the page margin, shrinking the text area — the editor and Word (`offsetFrom="text"`) keep text at the margin — so the ODT export shrinks every `fo:margin-*` by border + padding and the import grows them back (`decorInsetCm`).
|
||||
- **`trackChanges.svelte.ts`** — whether the **document** records revisions (`edentext-record-changes`, the key removed when off, as it is by default in both word processors). It travels with the file — ODF `text:track-changes` on the change registry, Word `w:trackRevisions` in settings.xml (both probed against soffice) — so an import adopts the file's flag and a new document starts off; only the open document's state lives under the key. A reactive singleton because the extension reads it on every transaction while the Review tab flips it; the recorded changes themselves live in the document, on the marks.
|
||||
- **`lineNumbering.ts`** — numbers in the left margin (`edentext-line-numbering`, the key removed when off, as it is by default in both word processors). ODF keeps one `<text:linenumbering-configuration>` in `office:styles`, Word a `<w:lnNumType>` per `w:sectPr` (probed). `LineNumberLayer.svelte` measures the lines: CSS exposes no line boxes, so a Range per block is taken and its distinct client-rect tops are the lines.
|
||||
- **`foldMarks.ts`** — fold + punch marks in the left margin (`edentext-fold-marks`, the key removed when off; neither word processor prints any by default). The DIN 5008 positions (87/192mm folds, 148.5mm punch) are this module's constants; `FoldMarkLayer.svelte` draws them per page — the folds on **both** edges (both hands find the crease), the punch mark only left, where the pages are filed. They export as named lines (`EdenFoldMark*`) in the master page's header — the one place that repeats on every page, where LibreOffice's own DIN templates keep them — ODF as page-relative `<draw:line>`s, DOCX as VML `<v:line>`s; both importers read the names back into the flag and never into the zone's content.
|
||||
|
||||
Reference in new issue
Block a user