From 7b6c8ef90922573b6013b6a2992ac29db6a9fe0c Mon Sep 17 00:00:00 2001 From: Local Dev Date: Tue, 22 Sep 2026 23:09:58 +0200 Subject: [PATCH] fix(pdf-editor): a replaced run is text, so let it be edited like text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Testing 0.3.0 on a real install showed the gap immediately: select a run you had replaced and the selection bar offered duplicate and delete and nothing else. The one mark made entirely of words was the one with no way to change them, double-clicking it did nothing, and the size stepper and the bold and italic buttons all stayed hidden. Everything else that holds text could be reopened; this could not. The cause was three places testing `kind === "text"` where the question was really "does this mark hold words". A replacement holds words. Reusing the dialog exposed a second, quieter fault. A run lifted out of a document is whatever size the document set — 11 pt, 9.5 pt — while the size picker lists round numbers. Selecting a value the list does not contain leaves the select empty, and the size on the way out fell back to 12. Editing the wording of an 11 pt line would silently have resized it. The dialog now adds the document's own size as an option for as long as it is open, and falls back to the size it started with rather than to a guess. --- bundled-addons/pdf-editor/editor.js | 29 +++++++++++++++++++------- bundled-addons/pdf-editor/lib/tools.js | 2 +- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/bundled-addons/pdf-editor/editor.js b/bundled-addons/pdf-editor/editor.js index 6f258e05..2e2818ac 100644 --- a/bundled-addons/pdf-editor/editor.js +++ b/bundled-addons/pdf-editor/editor.js @@ -560,7 +560,9 @@ const selbar = $("selbar"); function paintSelectionBar(id) { const a = id && session.model ? session.model.annot(id) : null; if (!a) { selbar.hidden = true; return; } - const isText = a.kind === "text"; + // A replaced run is text too. Leaving it out meant the one mark made of + // words offered no way to change them — the exact gap this bar exists for. + const isText = a.kind === "text" || a.kind === "textedit"; const isShape = a.kind === "rect" || a.kind === "ellipse"; $("sb-edit").hidden = !isText; $("sb-textsize").hidden = !isText; @@ -633,13 +635,13 @@ $("sb-bigger").addEventListener("click", () => stepTextSize(1)); function toggleTextStyle(which) { const a = session.model?.annot(session.tools?.selected); - if (!a || a.kind !== "text") return; + if (!a || (a.kind !== "text" && a.kind !== "textedit")) return; delete a._w; // the glyphs change width; re-measure session.tools.patchSelected({ [which]: !a[which] }); } function stepTextSize(dir) { const a = session.model?.annot(session.tools?.selected); - if (!a || a.kind !== "text") return; + if (!a || (a.kind !== "text" && a.kind !== "textedit")) return; const steps = [6, 8, 9, 10, 11, 12, 14, 16, 18, 24, 30, 36, 48, 60, 72, 96]; const at = a.size || 12; const next = dir > 0 ? steps.find((v) => v > at + 0.01) : [...steps].reverse().find((v) => v < at - 0.01); @@ -657,20 +659,33 @@ async function onTextModal(id, at) { const existing = id ? session.model?.annot(id) : null; $("modal-text-title").textContent = existing ? "Edit text" : "Text stamp"; $("text-body").value = existing ? existing.text : ""; - $("text-size").value = String(existing ? existing.size || 12 : 12); + // A run lifted out of a document is whatever size the document set — 11 pt, + // 9.5 pt — and the picker only lists round numbers. Without this the select + // falls back to empty and the size silently becomes 12 on the way out. + const wanted = existing ? existing.size || 12 : 12; + const sizeSel = $("text-size"); + if (![...sizeSel.options].some((o) => Number(o.value) === Number(wanted))) { + const opt = document.createElement("option"); + opt.value = String(wanted); + opt.textContent = String(Math.round(wanted * 10) / 10); + opt.dataset.adhoc = "1"; + sizeSel.append(opt); + } + sizeSel.value = String(wanted); $("text-bold").classList.toggle("active", !!existing?.bold); $("text-italic").classList.toggle("active", !!existing?.italic); $("text-warn").hidden = true; const p = openModal("modal-text"); setTimeout(() => { $("text-body").focus(); $("text-body").select(); }, 30); const ok = await p; - if (!ok) return null; const props = { text: $("text-body").value, - size: parseFloat($("text-size").value) || 12, + size: parseFloat($("text-size").value) || wanted, bold: $("text-bold").classList.contains("active"), italic: $("text-italic").classList.contains("active"), }; + for (const o of [...$("text-size").options]) if (o.dataset.adhoc) o.remove(); + if (!ok) return null; return existing ? session.tools.updateText(id, props) : session.tools.placeText(at, props); } for (const b of [$("text-bold"), $("text-italic")]) { @@ -1066,7 +1081,7 @@ window.addEventListener("keydown", (e) => { if (e.key === "Enter") { const sel = session.tools?.selected; const a = sel && session.model?.annot(sel); - if (a?.kind === "text") { onTextModal(sel, null); e.preventDefault(); } + if (a?.kind === "text" || a?.kind === "textedit") { onTextModal(sel, null); e.preventDefault(); } return; } if (e.key === "+" || e.key === "=") { session.strip?.stepScale(1); syncZoomSelect(); e.preventDefault(); return; } diff --git a/bundled-addons/pdf-editor/lib/tools.js b/bundled-addons/pdf-editor/lib/tools.js index 2decfe93..c77edf0e 100644 --- a/bundled-addons/pdf-editor/lib/tools.js +++ b/bundled-addons/pdf-editor/lib/tools.js @@ -206,7 +206,7 @@ export class Tools { e.preventDefault(); if (double) { const a = this.model.annot(id); - if (a?.kind === "text") { this.host.editText?.(id, null); return; } + if (a?.kind === "text" || a?.kind === "textedit") { this.host.editText?.(id, null); return; } } this.drag = { kind: "move", uid: hit.uid, id, x0: x, y0: y, orig: structuredClone(this.model.annot(id)), cx: e.clientX, cy: e.clientY, moved: false };