fix(pdf-editor): a replaced run is text, so let it be edited like text
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.
This commit is contained in:
parent
9a59bf1025
commit
c4dbdb25fb
2 changed files with 23 additions and 8 deletions
|
|
@ -560,7 +560,9 @@ const selbar = $("selbar");
|
||||||
function paintSelectionBar(id) {
|
function paintSelectionBar(id) {
|
||||||
const a = id && session.model ? session.model.annot(id) : null;
|
const a = id && session.model ? session.model.annot(id) : null;
|
||||||
if (!a) { selbar.hidden = true; return; }
|
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";
|
const isShape = a.kind === "rect" || a.kind === "ellipse";
|
||||||
$("sb-edit").hidden = !isText;
|
$("sb-edit").hidden = !isText;
|
||||||
$("sb-textsize").hidden = !isText;
|
$("sb-textsize").hidden = !isText;
|
||||||
|
|
@ -633,13 +635,13 @@ $("sb-bigger").addEventListener("click", () => stepTextSize(1));
|
||||||
|
|
||||||
function toggleTextStyle(which) {
|
function toggleTextStyle(which) {
|
||||||
const a = session.model?.annot(session.tools?.selected);
|
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
|
delete a._w; // the glyphs change width; re-measure
|
||||||
session.tools.patchSelected({ [which]: !a[which] });
|
session.tools.patchSelected({ [which]: !a[which] });
|
||||||
}
|
}
|
||||||
function stepTextSize(dir) {
|
function stepTextSize(dir) {
|
||||||
const a = session.model?.annot(session.tools?.selected);
|
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 steps = [6, 8, 9, 10, 11, 12, 14, 16, 18, 24, 30, 36, 48, 60, 72, 96];
|
||||||
const at = a.size || 12;
|
const at = a.size || 12;
|
||||||
const next = dir > 0 ? steps.find((v) => v > at + 0.01) : [...steps].reverse().find((v) => v < at - 0.01);
|
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;
|
const existing = id ? session.model?.annot(id) : null;
|
||||||
$("modal-text-title").textContent = existing ? "Edit text" : "Text stamp";
|
$("modal-text-title").textContent = existing ? "Edit text" : "Text stamp";
|
||||||
$("text-body").value = existing ? existing.text : "";
|
$("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-bold").classList.toggle("active", !!existing?.bold);
|
||||||
$("text-italic").classList.toggle("active", !!existing?.italic);
|
$("text-italic").classList.toggle("active", !!existing?.italic);
|
||||||
$("text-warn").hidden = true;
|
$("text-warn").hidden = true;
|
||||||
const p = openModal("modal-text");
|
const p = openModal("modal-text");
|
||||||
setTimeout(() => { $("text-body").focus(); $("text-body").select(); }, 30);
|
setTimeout(() => { $("text-body").focus(); $("text-body").select(); }, 30);
|
||||||
const ok = await p;
|
const ok = await p;
|
||||||
if (!ok) return null;
|
|
||||||
const props = {
|
const props = {
|
||||||
text: $("text-body").value,
|
text: $("text-body").value,
|
||||||
size: parseFloat($("text-size").value) || 12,
|
size: parseFloat($("text-size").value) || wanted,
|
||||||
bold: $("text-bold").classList.contains("active"),
|
bold: $("text-bold").classList.contains("active"),
|
||||||
italic: $("text-italic").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);
|
return existing ? session.tools.updateText(id, props) : session.tools.placeText(at, props);
|
||||||
}
|
}
|
||||||
for (const b of [$("text-bold"), $("text-italic")]) {
|
for (const b of [$("text-bold"), $("text-italic")]) {
|
||||||
|
|
@ -1066,7 +1081,7 @@ window.addEventListener("keydown", (e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
const sel = session.tools?.selected;
|
const sel = session.tools?.selected;
|
||||||
const a = sel && session.model?.annot(sel);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (e.key === "+" || e.key === "=") { session.strip?.stepScale(1); syncZoomSelect(); e.preventDefault(); return; }
|
if (e.key === "+" || e.key === "=") { session.strip?.stepScale(1); syncZoomSelect(); e.preventDefault(); return; }
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ export class Tools {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (double) {
|
if (double) {
|
||||||
const a = this.model.annot(id);
|
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,
|
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 };
|
orig: structuredClone(this.model.annot(id)), cx: e.clientX, cy: e.clientY, moved: false };
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue