From a6e5f8874ef33a63d7ad02696d58543f65803dd9 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Tue, 22 Sep 2026 21:49:37 +0200 Subject: [PATCH] feat(pdf-editor): replace the document's own text, on its own baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now "editing" a PDF here meant laying things over it. You could put a word on top of a word, but the document underneath never changed, and the result read like a sticker because it was one. This adds the thing the word Edit actually promises: click a line of the document's text, type different words, and they land where the old ones were, in the old size and the old colour. The position and size come from pdf.js's text layer, which has already placed a span over every run and carries that run's size in unscaled PDF points — so the size is right whatever the zoom, which reading it off the rendered box would not be. The colours come from the rendered page, because nothing in the text API reports them: the background is the average of the most common colour bucket in the run's box, since type is a minority of the pixels even when it is dense, and the ink is whatever sits furthest from that background. On the test fixture it recovers the marker's red exactly. Two things that look like details and are not. The bucket only chooses WHICH pixels are background; the colour itself is their average, because rebuilding it from the bucket index rounds white down to #f8f8f8 and a not-quite-white patch on a white page is a visible seam. And the cover reaches below the baseline by a quarter of the font size, because pdf.js sizes its spans to the em box: cut the cover to the span and every descender in the original line survives as a little hook under the replacement. A replacement is a cover plus text, so it is a mark like any other — movable, resizable, undoable, and rendered on screen from the same numbers the writer uses, which is what makes the preview trustworthy. Said plainly in the dialog and again in the save summary: this hides the original, it does not remove it. The old glyphs are still in the content stream underneath. Redact is the tool that takes text away, and it says so too. --- bundled-addons/pdf-editor/editor.css | 13 ++++ bundled-addons/pdf-editor/editor.html | 6 +- bundled-addons/pdf-editor/editor.js | 99 +++++++++++++++++++++++- bundled-addons/pdf-editor/lib/overlay.js | 25 +++++- bundled-addons/pdf-editor/lib/save.js | 26 ++++++- bundled-addons/pdf-editor/lib/shape.js | 26 ++++++- bundled-addons/pdf-editor/lib/tools.js | 77 +++++++++++++++++- 7 files changed, 265 insertions(+), 7 deletions(-) diff --git a/bundled-addons/pdf-editor/editor.css b/bundled-addons/pdf-editor/editor.css index 8e65d2fb..79e1e570 100644 --- a/bundled-addons/pdf-editor/editor.css +++ b/bundled-addons/pdf-editor/editor.css @@ -271,3 +271,16 @@ body[data-dragging="1"] .selbar, `visibleStroke`: the copy is transparent on purpose. */ body[data-mode="select"] .smOverlay .mark .hit { pointer-events: stroke; } .smOverlay .mark .hit { pointer-events: none; } + +/* Picking a run of the document's own text to replace. The text layer stays + live so a click can land on a run; the form layer steps aside so a widget + does not swallow it. The hover outline is the only cue that the invisible + text layer is a target at all. */ +body[data-mode="pickrun"] .annotationLayer { pointer-events: none !important; } +body[data-mode="pickrun"] .pdfViewer .page { cursor: text; } +body[data-mode="pickrun"] .textLayer span:hover { + outline: 2px solid rgb(from var(--acid) r g b / .9); + outline-offset: 1px; + background: rgb(from var(--acid) r g b / .16); + border-radius: 2px; +} diff --git a/bundled-addons/pdf-editor/editor.html b/bundled-addons/pdf-editor/editor.html index 7e23eb66..c0d5074a 100644 --- a/bundled-addons/pdf-editor/editor.html +++ b/bundled-addons/pdf-editor/editor.html @@ -62,6 +62,9 @@ + @@ -191,7 +194,7 @@
-