diff --git a/src/App.svelte b/src/App.svelte index c56efdc..723d1e9 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -45,6 +45,7 @@ import { loadPageDecor, savePageDecor, EMPTY_PAGE_DECOR, type PageDecor } from './lib/storage/pageDecor'; import { loadLineNumbering, saveLineNumbering, DEFAULT_LINE_NUMBERING, type LineNumbering } from './lib/storage/lineNumbering'; import { loadFoldMarks, saveFoldMarks } from './lib/storage/foldMarks'; + import { printMarkup } from './lib/storage/printMarkup.svelte'; import { loadDocumentLanguage, saveDocumentLanguage, odfFromLanguage, type DocumentLanguage } from './lib/storage/documentLanguage'; import { setTableLanguage } from './lib/storage/tableOptions.svelte'; import { spellController } from './lib/spell/controller'; @@ -942,6 +943,7 @@ pageFormat, numPages, commentsHeading: t().comments.title, + printMarkup: printMarkup(), }); } catch (err) { console.error('[pdf] Export failed:', err); @@ -968,6 +970,7 @@ pageFormat, numPages, commentsHeading: t().comments.title, + printMarkup: printMarkup(), }); } catch (err) { console.error('[pdf] Print failed:', err); @@ -999,6 +1002,7 @@ footerEvenDoc, differentOddEven, commentsHeading: t().comments.title, + printMarkup: printMarkup(), }); } catch (err) { console.error('[pdf] Print failed:', err); diff --git a/src/lib/components/ribbon/tabs/ReviewTab.svelte b/src/lib/components/ribbon/tabs/ReviewTab.svelte index 1f16d8d..ac799b7 100644 --- a/src/lib/components/ribbon/tabs/ReviewTab.svelte +++ b/src/lib/components/ribbon/tabs/ReviewTab.svelte @@ -7,6 +7,7 @@ import { countText, type TextStats } from '../../../utils/wordCount'; import type { DocumentLanguage } from '../../../storage/documentLanguage'; import { recordChanges, setRecordChanges } from '../../../storage/trackChanges.svelte'; + import { printMarkup, setPrintMarkup } from '../../../storage/printMarkup.svelte'; import { revisions } from '../../../editor/extensions/trackChanges'; import { OPEN_THESAURUS_EVENT } from '../../../spell/thesaurus'; import { shortcutHint } from '../../../editor/shortcuts'; @@ -165,6 +166,14 @@ onclick={() => editor?.chain().focus().rejectRevisions(true).run()} /> + setPrintMarkup(!printMarkup())} + />
diff --git a/src/lib/export/CLAUDE.md b/src/lib/export/CLAUDE.md index 167ddff..c41cee9 100644 --- a/src/lib/export/CLAUDE.md +++ b/src/lib/export/CLAUDE.md @@ -66,7 +66,9 @@ A printed document says what the screen says: a bar in the margin beside every c commented block, and the comment bodies as a list after the document. The list is not a nicety — a comment's text is nowhere on the page, so a bar alone could only say that one exists. A resolved comment prints nowhere, and a document with no markup prints exactly as -before (no list, no CSS, no margin shift). There is no on/off switch; both products have one. +before (no list, no CSS, no margin shift). The Review tab's **Print markup** toggle +(`storage/printMarkup.svelte.ts`) turns the whole thing off, as both products offer; +`printPdf` rebuilds its options field by field, so a new one has to be copied there too. - **The margin strip is paid for.** The print engine clips to the page area, so a bar at a negative offset is silently dropped — measured, not assumed. `printPdf` takes diff --git a/src/lib/export/pdf.ts b/src/lib/export/pdf.ts index 440d808..fd347ce 100644 --- a/src/lib/export/pdf.ts +++ b/src/lib/export/pdf.ts @@ -31,6 +31,8 @@ export interface PdfOptions { numPages?: number; /** Heading of the printed comment list; no comments, no extra pages. */ commentsHeading?: string; + /** Whether the review markup prints at all (`storage/printMarkup`); default on. */ + printMarkup?: boolean; } type Run = { str: string; x: number; y: number; h: number }; // doc px, relative to .paper top-left @@ -42,13 +44,14 @@ function pageDims(format: PageFormat, orientation: Orientation): { pageW: number // Off-screen, scale-1, theme-neutral copy of the live .paper. Rendered identically to // the editor (no print re-layout), so the raster + measured text positions agree. -function buildClone(paper: HTMLElement, pageW: number): { holder: HTMLElement; clone: HTMLElement; style: HTMLStyleElement } { +function buildClone(paper: HTMLElement, pageW: number, markup: boolean): { holder: HTMLElement; clone: HTMLElement; style: HTMLStyleElement } { const clone = paper.cloneNode(true) as HTMLElement; clone.style.transform = 'none'; clone.style.width = `${pageW}px`; // pin width so capture geometry can't drift on var inheritance clone.classList.remove('show-formatting-marks', 'hf-editing'); // The margin bar prints at one weight: which one the caret is on is an editing state. - clone.querySelectorAll('.change-bar.active').forEach((el) => el.classList.remove('active')); + if (markup) clone.querySelectorAll('.change-bar.active').forEach((el) => el.classList.remove('active')); + else clone.querySelectorAll('.change-bar-layer').forEach((el) => el.remove()); // html2canvas paints an inline background from one bounding rect, so a wrapped // highlight covers the bold term preceding it on the first line. Lift bold-and- @@ -223,7 +226,7 @@ export async function renderPaperToCanvas(opts: PdfOptions, scale = 2): Promise< const { pageW, pageH } = pageDims(opts.pageFormat ?? 'A4', orientation); const cycle = pageH + PAGE_GAP; - const { holder, clone, style } = buildClone(paper, pageW); + const { holder, clone, style } = buildClone(paper, pageW, opts.printMarkup !== false); document.head.appendChild(style); document.body.appendChild(holder); const cleanup = () => { holder.remove(); style.remove(); }; @@ -324,7 +327,7 @@ export async function exportPdf(opts: PdfOptions): Promise { const landscape = (opts.orientation ?? 'portrait') === 'landscape'; try { const runs = collectRuns(clone); - const comments = printedComments(opts.source); + const comments = opts.printMarkup === false ? [] : printedComments(opts.source); const extra = comments.length ? await renderCommentPages(opts, comments, pageW, pageH, scale) : { images: [], runs: [] }; @@ -380,7 +383,7 @@ export async function printRaster(opts: PdfOptions): Promise { } finally { cleanup(); } - const comments = printedComments(opts.source); + const comments = opts.printMarkup === false ? [] : printedComments(opts.source); if (comments.length) imgs = imgs.concat((await renderCommentPages(opts, comments, pageW, pageH, scale)).images); const title = (opts.fileName ?? deriveFilename(opts.json)).replace(/\.(odt|pdf)$/i, ''); @@ -445,6 +448,8 @@ export interface PrintPdfOptions { differentOddEven?: boolean; /** Heading of the printed comment list; no comments, no list. */ commentsHeading?: string; + /** Whether the review markup prints at all (`storage/printMarkup`); default on. */ + printMarkup?: boolean; } // First-row column weights from table JSON (honours colspan); mirrors tableView. @@ -617,14 +622,17 @@ export function printPdf(opts: PrintPdfOptions): void { headerEvenDoc: opts.headerEvenDoc ?? null, footerEvenDoc: opts.footerEvenDoc ?? null, differentOddEven: opts.differentOddEven ?? false, + commentsHeading: opts.commentsHeading, + printMarkup: opts.printMarkup, }; const title = (o.fileName ?? deriveFilename(o.json)).replace(/\.(odt|pdf)$/i, ''); // The layers are Svelte DOM and generateHTML knows nothing of them, so this path // rebuilds the review markup from the marks the document itself carries. const host = document.createElement('div'); host.innerHTML = buildBodyHtml(o.json); - const comments = printedComments(host); - const review = markReviewBlocks(host); + const markup = o.printMarkup !== false; + const comments = markup ? printedComments(host) : []; + const review = markup && markReviewBlocks(host); const list = comments.length ? commentListHtml(comments, o.commentsHeading ?? 'Comments') : ''; const iframe = document.createElement('iframe'); diff --git a/src/lib/i18n/locales/de.ts b/src/lib/i18n/locales/de.ts index d366124..3020a60 100644 --- a/src/lib/i18n/locales/de.ts +++ b/src/lib/i18n/locales/de.ts @@ -827,6 +827,8 @@ const de: Messages = { }, revisions: { record: 'Änderungen verfolgen', + printMarkup: 'Markup drucken', + printMarkupHint: 'Randbalken und Kommentarliste mitdrucken', recordHint: 'Jede Bearbeitung als Änderung aufzeichnen', accept: 'Annehmen', acceptHint: 'Die Änderung an der Einfügemarke annehmen', diff --git a/src/lib/i18n/locales/en.ts b/src/lib/i18n/locales/en.ts index b1ad250..6388636 100644 --- a/src/lib/i18n/locales/en.ts +++ b/src/lib/i18n/locales/en.ts @@ -833,6 +833,8 @@ const en = { }, revisions: { record: 'Track changes', + printMarkup: 'Print markup', + printMarkupHint: 'Print the margin bar and the comment list', recordHint: 'Record every edit as a revision', accept: 'Accept', acceptHint: 'Accept the change at the cursor', diff --git a/src/lib/storage/CLAUDE.md b/src/lib/storage/CLAUDE.md index 3d551a8..574cffc 100644 --- a/src/lib/storage/CLAUDE.md +++ b/src/lib/storage/CLAUDE.md @@ -19,6 +19,7 @@ - **`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` `` 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 `` in `office:styles`, Word a `` 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. +- **`printMarkup.svelte.ts`** — whether a printout carries the review markup (`edentext-print-markup`, the key removed at its **on** default, so it is the off state that is stored — the switch both word processors offer). A reactive singleton like `trackChanges.svelte.ts`: the Review tab flips it, all three export paths read it. What it governs lives in `export/reviewPrint.ts`. - **`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 ``s, DOCX as VML ``s; both importers read the names back into the flag and never into the zone's content. - **`pageOrientation.ts`** — `'portrait' | 'landscape'`. `applyOrientationVars` sets `--user-page-{width,height}`; landscape swaps the A4 dimensions (matching odf-kit's automatic swap). diff --git a/src/lib/storage/printMarkup.svelte.ts b/src/lib/storage/printMarkup.svelte.ts new file mode 100644 index 0000000..e45219d --- /dev/null +++ b/src/lib/storage/printMarkup.svelte.ts @@ -0,0 +1,18 @@ +// Whether a printout carries the review markup — the margin bar beside a changed or +// commented block, and the comment list after the document (`export/reviewPrint.ts`). +// A reactive singleton like trackChanges.svelte.ts: the Review tab flips it, the three +// export paths read it. On by default; both word processors offer the same switch. + +const KEY = 'edentext-print-markup'; + +let current = $state(localStorage.getItem(KEY) !== 'false'); + +export function printMarkup(): boolean { + return current; +} + +export function setPrintMarkup(on: boolean): void { + current = on; + if (on) localStorage.removeItem(KEY); + else localStorage.setItem(KEY, 'false'); +}