From de229c163b0bb5a44eb3bce941a29e06ec4e6bc3 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Wed, 14 Oct 2026 15:12:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=BC=96=E8=BE=91=E8=83=BD=E5=8A=9B):=20?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=96=87=E6=9C=AC=E9=80=89=E6=A1=86=E4=B8=8E?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E6=A0=8F=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/webui/ofdgo.css | 26 +++++++++++++++++++++++--- assets/webui/ofdgo_edit.js | 12 +++++++----- internal/webui/wasm.go | 8 ++++++-- ofdgo_rich_layout.go | 14 +++++++++++--- 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/assets/webui/ofdgo.css b/assets/webui/ofdgo.css index 198d03f..64a23e2 100644 --- a/assets/webui/ofdgo.css +++ b/assets/webui/ofdgo.css @@ -3806,9 +3806,16 @@ audio.page-media { } } -@media (min-width: 901px) and (max-width: 1600px) and (pointer: fine) { +@media (min-width: 901px) { .editor-toolbar { - --editor-control-width: 52px; + flex-wrap: wrap; + row-gap: 8px; + } +} + +@media (min-width: 901px) and (max-width: 1600px) { + .editor-toolbar { + --editor-control-width: 44px; } .editor-group { @@ -3820,12 +3827,25 @@ audio.page-media { padding-inline: 6px; } + .editor-group > select { + appearance: none; + padding-inline: 2px 10px; + background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%); + background-position: right 5px center, right 2px center; + background-size: 3px 3px; + background-repeat: no-repeat; + } + .editor-group:has(#textFont) { grid-template-columns: repeat(7, 44px); } .editor-paragraph { - grid-template-columns: var(--editor-control-width) 108px; + grid-template-columns: var(--editor-control-width) 100px; + } + + .editor-group:has(#shapeFill) { + grid-template-columns: repeat(2, 76px); } .toolbar { diff --git a/assets/webui/ofdgo_edit.js b/assets/webui/ofdgo_edit.js index d15979c..49cf6d6 100644 --- a/assets/webui/ofdgo_edit.js +++ b/assets/webui/ofdgo_edit.js @@ -689,11 +689,12 @@ export class CanvasEditor { if (item.contour) item.contour.svg.setAttribute("viewBox", `${item.x} ${item.y} ${item.width} ${item.height}`); if (item.textFrame) { const { matrix: [a, b, c, d, e, f], width, height } = item.textFrame; + const box = item.textFrame.content || { x: 0, y: 0, width, height }; for (const handle of item.node.children) { if (handle.dataset.corner !== "w" && handle.dataset.corner !== "e") continue; - const x = handle.dataset.corner === "e" ? width : 0; - handle.style.left = `${(a * x + c * height / 2 + e - item.x) * change.scale * PX_PER_MM}px`; - handle.style.top = `${(b * x + d * height / 2 + f - item.y) * change.scale * PX_PER_MM}px`; + const x = box.x + (handle.dataset.corner === "e" ? box.width : 0), y = box.y + box.height / 2; + handle.style.left = `${(a * x + c * y + e - item.x) * change.scale * PX_PER_MM}px`; + handle.style.top = `${(b * x + d * y + f - item.y) * change.scale * PX_PER_MM}px`; handle.style.cursor = a === 0 ? "var(--resize-vertical, ns-resize)" : "var(--resize-horizontal, ew-resize)"; } } @@ -969,7 +970,7 @@ export class CanvasEditor { if (drag.item.textFrame) { const frame = drag.item.textFrame, [a, b, c, d] = frame.matrix; const dx = (d * (to.x - from.x) - c * (to.y - from.y)) / (a * d - b * c); - const box = reshapeBox({ geometry: { x: 0, y: 0, width: frame.width, height: frame.height } }, dx, 0, drag.corner, false); + const box = reshapeBox({ geometry: frame.content || { x: 0, y: 0, width: frame.width, height: frame.height } }, dx, 0, drag.corner, false); drag.box = { x: drag.item.x + box.x, width: box.width }; const bounds = transformedBox(box, frame.matrix); Object.assign(drag.item.node.style, { left: `${bounds.x * PX_PER_MM}px`, top: `${bounds.y * PX_PER_MM}px`, @@ -1130,7 +1131,8 @@ export class CanvasEditor { if (drag.corner && drag.box) { this.drag = null; this.viewer.releasePointerCapture(drag.pointerID); - const geometry = drag.item.oriented?.box || drag.item.geometry || (drag.item.textFrame ? { x: drag.item.x, width: drag.item.textFrame.width } : drag.item); + const frame = drag.item.textFrame; + const geometry = drag.item.oriented?.box || drag.item.geometry || (frame ? { x: drag.item.x + (frame.content?.x || 0), width: frame.content?.width ?? frame.width } : drag.item); const changed = Object.keys(drag.box).some((key) => drag.box[key] !== geometry[key]); const apply = drag.item.shape || drag.item.oriented ? this.options.onReshape : canEditObject(drag.item, "stretch") ? this.options.onResize : this.options.onTextWidth; Promise.resolve(changed && apply(drag.item, drag.box)).finally(() => { diff --git a/internal/webui/wasm.go b/internal/webui/wasm.go index 5e9d969..1ae5376 100644 --- a/internal/webui/wasm.go +++ b/internal/webui/wasm.go @@ -2774,7 +2774,11 @@ func editorRichText(page int, item map[string]any, object ofdgo.GraphicObject, v return } width, height := local.W, local.H - frame := matrix.TransformBox(local) + _, contentBounds, err := currentEditor.LayoutRichText(value, width) + if err != nil { + return + } + frame := matrix.TransformBox(contentBounds) format, layout := value.Runs[0].Format, value.Layout var content strings.Builder for _, run := range value.Runs { @@ -2792,7 +2796,7 @@ func editorRichText(page int, item map[string]any, object ofdgo.GraphicObject, v item["letterSpacing"], item["leftIndent"], item["rightIndent"], item["firstLineIndent"] = layout.LetterSpacing, layout.LeftIndent, layout.RightIndent, layout.FirstLineIndent item["paragraphWidth"], item["bounds"] = width, editorBox(frame) item["x"], item["y"], item["width"], item["height"] = frame.X, frame.Y, frame.W, frame.H - item["textFrame"] = map[string]any{"width": width, "height": height, "matrix": editorMatrix(matrix)} + item["textFrame"] = map[string]any{"width": width, "height": height, "matrix": editorMatrix(matrix), "content": editorBox(contentBounds)} capabilities := item["capabilities"].(map[string]any) for _, key := range []string{"textContent", "rewriteText", "replaceFont", "reflow", "layoutKnown", "paint"} { capabilities[key] = true diff --git a/ofdgo_rich_layout.go b/ofdgo_rich_layout.go index 9872976..933d410 100644 --- a/ofdgo_rich_layout.go +++ b/ofdgo_rich_layout.go @@ -61,7 +61,7 @@ type richContextShaper struct { } // LayoutRichText 生成局部坐标的标准文字和路径,不修改页面或编辑历史。 -// 边界包含排版高度和溢出墨迹,可能有负的X或Y,不截断文字。 +// 边界包含内容的排版高度和溢出墨迹,不包含空余段落宽度,可能有负的X或Y。 // 非标准RGB文档按需复用或注册RGB颜色空间,返回对象可直接用于当前文档。 // 注册不产生修订或撤销记录;失败时不保留新增资源和资源编号。 // 提交对象后应创建新的Reader;已有Reader是独立快照,不包含新注册的资源。 @@ -100,7 +100,7 @@ func (e *Editor) layoutRichText(value RichText, width float64) (richLayout, erro return richLayout{}, err } metrics := make([]richMetrics, len(value.Runs)) - result := richLayout{anchors: make([]int, len(value.Runs)), bounds: Box{W: width}} + result := richLayout{anchors: make([]int, len(value.Runs))} owners := make([]int, 0, value.Len()) for i, run := range value.Runs { font, err := e.editorFont(run.Format.Font) @@ -205,7 +205,12 @@ func (e *Editor) layoutRichText(value RichText, width float64) (richLayout, erro if err := richPositionSpans(spans, steps, local, x); err != nil { return richLayout{}, err } - result.bounds = richUnion(result.bounds, Box{X: x, Y: y, H: height}) + lineBounds := Box{X: x, Y: y, H: height} + if result.bounds.H == 0 { + result.bounds = lineBounds + } else { + result.bounds = richUnion(result.bounds, lineBounds) + } for _, span := range spans { format := value.Runs[span.owner].Format for _, glyph := range span.glyphs { @@ -291,6 +296,9 @@ func (e *Editor) layoutRichText(value RichText, width float64) (richLayout, erro if !finite(result.bounds.X) || !finite(result.bounds.Y) || !finite(result.bounds.W) || !finite(result.bounds.H) || result.bounds.H <= 0 { return richLayout{}, fmt.Errorf("rich text layout overflow") } + if result.bounds.W == 0 { + result.bounds.W = min(width, value.Runs[0].Format.Size) + } for i, anchor := range result.anchors { if anchor >= 0 { continue