feat(编辑能力): 修正文本选框与工具栏布局
build.yaml / build (push) Successful in 54s

This commit is contained in:
xiaoqidun committed 2026-10-14 15:12:01 +08:00
1 parent 6299c0f8f9
commit de229c163b
4 files changed
+47 -13

No files matched your search

+23 -3
View File
@@ -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 {
+7 -5
View File
@@ -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(() => {
+6 -2
View File
@@ -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
+11 -3
View File
@@ -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