From 00212288989e3a07d3aaa12530ceb9f9696cbde9 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Sat, 24 Oct 2026 00:05:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E7=BC=96=E8=BE=91=E8=83=BD=E5=8A=9B):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=87=E5=AD=97=E7=BC=96=E8=BE=91=E4=B8=8E?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/webui/ofdgo.css | 14 ++++++++++ assets/webui/ofdgo.js | 8 +++--- assets/webui/ofdgo_edit.js | 53 +++++++++++++++++++++++++++++--------- 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/assets/webui/ofdgo.css b/assets/webui/ofdgo.css index f7a2ded..d2b7b39 100644 --- a/assets/webui/ofdgo.css +++ b/assets/webui/ofdgo.css @@ -3081,6 +3081,20 @@ button.outline-link:hover { -webkit-text-fill-color: transparent; } +.edit-text span[data-glyph]::selection { + background: transparent; + color: transparent; + -webkit-text-fill-color: transparent; +} + +.edit-text span[data-glyph][data-selected] { + background: Highlight; +} + +.edit-text span[data-glyph][data-selected]::after { + color: HighlightText; +} + .edit-text span[data-glyph]::after { content: attr(data-glyph); position: absolute; diff --git a/assets/webui/ofdgo.js b/assets/webui/ofdgo.js index 45226d1..071dbb4 100644 --- a/assets/webui/ofdgo.js +++ b/assets/webui/ofdgo.js @@ -3407,10 +3407,10 @@ async function toggleEditor() { const callWASM = (name, ...args) => callTabWASM(tab, name, ...args); if (!state.doc || !state.ready || document.body.hasAttribute("data-busy") || formDialogOpen()) return; if (state.editorInfo) { - const openSeq = state.openSeq; - if (!await canvasEditor.commitText() || !isForegroundDocument(tab) || openSeq !== state.openSeq) return; - if (!await canvasEditor.commitCrop()) return; - if (!isForegroundDocument(tab) || openSeq !== state.openSeq) return; + const wasmSeq = state.wasmSeq; + const current = () => !tab.closed && tab.state === state && isForegroundDocument(tab) && wasmSeq === state.wasmSeq; + if (!await canvasEditor.commitText() || !current()) return; + if (!await canvasEditor.commitCrop() || !current()) return; const anchor = scaleAnchor(0); state.editing = !state.editing; renderDocumentFonts(); diff --git a/assets/webui/ofdgo_edit.js b/assets/webui/ofdgo_edit.js index aac7f17..61005be 100644 --- a/assets/webui/ofdgo_edit.js +++ b/assets/webui/ofdgo_edit.js @@ -5,6 +5,12 @@ const FORMAT_KEYS = ["font", "size", "color", "bold", "italic", "underline", "st const RICH_CLIPBOARD = "application/x-ofdgo-rich-text"; const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" }); +function sourceTextScale(editing) { + if (!editing.source) return 1; + const [a, b, c, d] = editing.frame.matrix; + return Math.max(Math.hypot(a, b), Math.hypot(c, d)) || 1; +} + export function fontFaceStyle(data) { const style = { weight: "400", style: "normal" }; const bytes = ArrayBuffer.isView(data) ? new DataView(data.buffer, data.byteOffset, data.byteLength) : new DataView(data); @@ -1533,6 +1539,10 @@ export class CanvasEditor { this.input = { input, item, face, faces, frame, source: Boolean(source), fontChoice: item.fontChoice }; const editing = this.input; if (rich) editing.rich = new RichTextSession(this, rich.model, rich.faces, rich.fonts); + if (source) { + editing.selectionListener = () => this.paintSourceSelection(); + document.addEventListener("selectionchange", editing.selectionListener); + } if (rich && !rich.model.layout.lineHeight) input.style.lineHeight = "normal"; if (source) editing.history = { entries: [{ value: item.text, face, fontChoice: item.fontChoice }], index: 0 }; item.artwork?.classList.add("edit-text-source"); @@ -1635,6 +1645,7 @@ export class CanvasEditor { input.focus({ preventScroll: true }); if (!item.draft) document.getSelection().selectAllChildren(input); if (editing.rich) editing.rich.selection(); + if (source) this.paintSourceSelection(); this.options.onTextChange(); } @@ -1701,8 +1712,11 @@ export class CanvasEditor { Object.assign(session, { entries, index: entries.length - 1, model: current.model, typing: format, caret }); editing.rich = session; editing.source = false; + document.removeEventListener("selectionchange", editing.selectionListener); editing.history = null; editing.range = null; + const [a, b, c, d, e, f] = editing.frame.matrix, left = editing.item.leftIndent || 0; + editing.input.style.transform = `matrix(${a},${b},${c},${d},${(e + a * left) * PX_PER_MM},${(f + b * left) * PX_PER_MM})`; if (!rich.model.layout.lineHeight) editing.input.style.lineHeight = "normal"; session.render(); session.preview(); @@ -1737,7 +1751,7 @@ export class CanvasEditor { document.fonts.add(entry.face); Object.assign(editing, { face: entry.face, fontData: entry.fontData, fontChoice: entry.fontChoice, style: entry.style, reflow: entry.reflow }); editing.input.style.fontFamily = `"${entry.face.family}"`; - editing.input.style.fontSize = `${(entry.style?.size || editing.item.size) * PX_PER_MM}px`; + editing.input.style.fontSize = `${(entry.style?.size || editing.item.size) * PX_PER_MM * sourceTextScale(editing)}px`; editing.input.style.color = entry.style?.color || editing.item.color; } let position = 0; @@ -1783,6 +1797,7 @@ export class CanvasEditor { paintSourceDraft() { const editing = this.input, input = editing.input; + editing.glyphPositions = null; for (const span of input.querySelectorAll("[data-glyph]")) span.removeAttribute("data-glyph"); if (editing.composing) return; const value = editTextValue(input), caret = editTextSelection(input, editing.range); @@ -1797,23 +1812,24 @@ export class CanvasEditor { position += text.length + 1; } input.replaceChildren(fragment); - Object.assign(input.style, { width: "max-content", height: "auto", minWidth: `${(editing.style?.size || editing.item.size) * PX_PER_MM + 4}px` }); + Object.assign(input.style, { width: "max-content", height: "auto", minWidth: `${(editing.style?.size || editing.item.size) * PX_PER_MM * sourceTextScale(editing) + 4}px` }); restoreTextSelection(positions, caret, editing.range); } paintSourceText(source) { const editing = this.input, { input, item, face, frame } = editing; const caret = editTextSelection(input, editing.range); - const size = (editing.style?.size || item.size) * PX_PER_MM; + const displayScale = sourceTextScale(editing), unit = PX_PER_MM * displayScale; + const size = (editing.style?.size || item.size) * unit; const context = document.createElement("canvas").getContext("2d"); context.font = `${source.italic ? "italic" : "normal"} ${source.weight || 400} ${size}px "${face.family}"`; - Object.assign(input.style, { lineHeight: `${size}px`, fontWeight: String(source.weight || 400), fontStyle: source.italic ? "italic" : "normal" }); - const fragment = document.createDocumentFragment(), positions = []; + Object.assign(input.style, { fontSize: `${size}px`, lineHeight: `${size}px`, fontWeight: String(source.weight || 400), fontStyle: source.italic ? "italic" : "normal" }); + const fragment = document.createDocumentFragment(), positions = [], glyphPositions = []; let line, lineIndex = -1, naturalX = 0, position = 0, glyphIndex = 0; let left = Infinity, top = Infinity, right = -Infinity, bottom = -Infinity; for (const run of source.runs) { const runFace = editing.fontData ? face : editing.faces?.get(run.font) || face; - const runSize = (editing.style?.size ?? run.size ?? editing.item.size) * PX_PER_MM; + const runSize = (editing.style?.size ?? run.size ?? editing.item.size) * unit; const weight = (run.weight ?? source.weight) || 400, italic = run.italic ?? source.italic; const scale = (run.scale ?? source.scale) || 1, vscale = (run.vscale ?? source.vscale) || 1; const rotation = run.rotation ?? source.rotation ?? 0, angle = rotation * Math.PI / 180; @@ -1828,7 +1844,7 @@ export class CanvasEditor { lineIndex++; naturalX = 0; } - let x = run.x * PX_PER_MM, y = run.y * PX_PER_MM, index = 0; + let x = run.x * unit, y = run.y * unit, index = 0; if (!run.text) { left = Math.min(left, x); top = Math.min(top, y - baseline); @@ -1836,12 +1852,15 @@ export class CanvasEditor { bottom = Math.max(bottom, y + runSize - baseline); } for (const char of run.text) { - x = run.positions[index][0] * PX_PER_MM; - y = run.positions[index][1] * PX_PER_MM; + x = run.positions[index][0] * unit; + y = run.positions[index][1] * unit; const span = document.createElement("span"), node = document.createTextNode(char); span.append(node); const glyph = source.glyphs?.[glyphIndex++]; - if (glyph) span.dataset.glyph = glyph; + if (glyph) { + span.dataset.glyph = glyph; + glyphPositions.push({ span, start: position, end: position + char.length }); + } Object.assign(span.style, { display: "inline-block", verticalAlign: "top", fontFamily: `"${runFace.family}"`, fontSize: `${runSize}px`, lineHeight: `${runSize}px`, fontWeight: String(weight), fontStyle: italic ? "italic" : "normal", color: editing.style?.color ?? run.color ?? item.color, opacity: String((run.alpha ?? item.alpha ?? 255) / 255), transformOrigin: `left ${baseline}px`, @@ -1872,13 +1891,22 @@ export class CanvasEditor { if (!Number.isFinite(left)) { left = top = 0; right = bottom = size; } for (const line of fragment.children) line.style.transform = `translate(${-left}px,${-top}px)`; input.replaceChildren(fragment); - const [a, b, c, d, e, f] = frame.matrix; + const [a, b, c, d, e, f] = frame.matrix.map((value, index) => index < 4 ? value / displayScale : value); Object.assign(input.style, { width: `${Math.max(size, right - left) + 4}px`, minHeight: "0px", height: `${bottom - top + 4}px`, transform: `matrix(${a},${b},${c},${d},${e * PX_PER_MM + a * left + c * top},${f * PX_PER_MM + b * left + d * top})`, }); restoreTextSelection(positions, caret, editing.range); + editing.glyphPositions = glyphPositions; + this.paintSourceSelection(); + } + + paintSourceSelection() { + const editing = this.input; + if (!editing?.glyphPositions) return; + const caret = editTextSelection(editing.input, editing.range), start = caret ? Math.min(...caret) : 0, end = caret ? Math.max(...caret) : 0; + for (const glyph of editing.glyphPositions) glyph.span.toggleAttribute("data-selected", glyph.start < end && glyph.end > start); } setTextFont(face, data, key) { @@ -1902,7 +1930,7 @@ export class CanvasEditor { for (const key of Object.keys(style)) if (!editing.item.selectionIDs && style[key] === editing.item[key]) delete style[key]; editing.style = Object.keys(style).length ? style : null; const item = { ...editing.item, ...editing.style }; - editing.input.style.fontSize = `${item.size * PX_PER_MM}px`; + editing.input.style.fontSize = `${item.size * PX_PER_MM * sourceTextScale(editing)}px`; editing.input.style.color = item.color; this.recordSourceText(); this.fitTextInput(); @@ -1961,6 +1989,7 @@ export class CanvasEditor { this.setTextError(null); this.input = null; if (editing) { + if (editing.selectionListener) document.removeEventListener("selectionchange", editing.selectionListener); editing.rich?.destroy(); editing.input.remove(); editing.item.artwork?.classList.remove("edit-text-source");