From 444c01b9765bccc9047fd16385198dc9a54dc5a9 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Fri, 9 Oct 2026 21:45:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B8=B2=E6=9F=93=E7=BC=96=E8=BE=91):=20?= =?UTF-8?q?=E8=A7=84=E8=8C=83=E6=96=87=E5=AD=97=E5=AE=9A=E4=BD=8D=E4=B8=8E?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E5=BA=A6=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/webui/ofdgo_edit.js | 2 +- ofdgo_editor_text.go | 2 +- ofdgo_render_bounds.go | 34 ++++++++++++++++++++++++++++++---- ofdgo_render_text.go | 8 ++------ ofdgo_renderer_text_canvas.go | 9 ++------- ofdgo_text_position.go | 29 ++++++----------------------- 6 files changed, 42 insertions(+), 42 deletions(-) diff --git a/assets/webui/ofdgo_edit.js b/assets/webui/ofdgo_edit.js index dad9ab8..c0aad6a 100644 --- a/assets/webui/ofdgo_edit.js +++ b/assets/webui/ofdgo_edit.js @@ -1391,7 +1391,7 @@ export class CanvasEditor { right = Math.max(right, x + advance * source.scale); bottom = Math.max(bottom, y + size - baseline); naturalX += advance; - x += run.dx?.length ? run.dx[Math.min(index, run.dx.length - 1)] * PX_PER_MM : run.dy?.length ? 0 : advance * source.scale; + x += run.dx?.length ? run.dx[Math.min(index, run.dx.length - 1)] * PX_PER_MM : 0; y += run.dy?.length ? run.dy[Math.min(index, run.dy.length - 1)] * PX_PER_MM : 0; index++; } diff --git a/ofdgo_editor_text.go b/ofdgo_editor_text.go index d59ce78..1fc36f9 100644 --- a/ofdgo_editor_text.go +++ b/ofdgo_editor_text.go @@ -229,7 +229,7 @@ func (e *Editor) RewriteText(obj *TextObject, value string) error { unit *= obj.HScale } advance := func(char rune) float64 { return float64(sfnt.GlyphAdvance(sfnt.GlyphIndex(char))) * unit } - if codes[i].DeltaX != "" || codes[i].DeltaY == "" { + if codes[i].DeltaX != "" { codes[i].DeltaX = rewriteTextDeltas(codes[i].DeltaX, runes, replacement, from, to, advance) } if codes[i].DeltaY != "" { diff --git a/ofdgo_render_bounds.go b/ofdgo_render_bounds.go index d7622c7..4dc3559 100644 --- a/ofdgo_render_bounds.go +++ b/ofdgo_render_bounds.go @@ -22,6 +22,12 @@ type ObjectContour struct { EvenOdd bool `json:"evenOdd,omitempty"` } +// annotationMeasurement 按注解绘制顺序收集对象范围和轮廓 +type annotationMeasurement struct { + renderer *Renderer + result ObjectMeasurement +} + // ObjectBounds 获取文字、路径、图片或复合对象在页面坐标中的轴对齐范围,不修改对象 // 文字采用字形范围,路径包含描边与裁剪,图片采用裁剪后的几何范围,不解码像素或排除透明像素 // 底纹按填充或描边轮廓度量,不展开图案单元 @@ -38,11 +44,11 @@ func (r *Renderer) ObjectBounds(object GraphicObject, drawParam string) (Box, er // 入参: annotation 注解 // 返回: Box 页面范围, []ObjectContour 命中轮廓, error 错误信息 func (r *Renderer) AnnotationGeometry(annotation Annotation) (Box, []ObjectContour, error) { - object := GraphicObject{Type: "CompositeObject", CompositeGraphicUnit: CompositeGraphicUnit{Boundary: annotation.Appearance.Boundary, Objects: annotation.Appearance.Objects}} - box, contours, err := r.ObjectGeometry(object, "") - if err != nil { - return box, contours, err + measurement := &annotationMeasurement{renderer: r} + if err := r.walkAnnotation(annotation, measurement); err != nil { + return Box{}, nil, err } + box, contours := measurement.result.Bounds, measurement.result.Contours for _, source := range r.annotationActionSources([]Annotation{annotation}) { for _, action := range source.Actions { if action.Event != "CLICK" { @@ -65,6 +71,26 @@ func (r *Renderer) AnnotationGeometry(annotation Annotation) (Box, []ObjectConto return box, contours, nil } +// DrawObject 使用对象实际继承状态度量注解内容,不附加容器裁剪 +// 入参: object 图形对象, state 绘制状态 +// 返回: error 度量错误 +func (m *annotationMeasurement) DrawObject(object *GraphicObject, state RenderState) error { + result, err := m.renderer.MeasureObject(*object, MeasureOptions{Defaults: state.Defaults, Parent: state.Parent, BoundaryInCTM: state.BoundaryInCTM, Clip: state.Clip, Contours: true}) + if err != nil { + return err + } + m.result.Bounds = unionTextBox(m.result.Bounds, result.Bounds) + m.result.Contours = append(m.result.Contours, result.Contours...) + return nil +} + +// DrawStamp 满足页面访问接口,注解外观遍历不包含数字签章 +// 入参: stamp 数字签章 +// 返回: error 始终为空 +func (m *annotationMeasurement) DrawStamp(stamp Stamp) error { + return nil +} + // ObjectContours 获取路径、图片或复合对象的实际绘制区域,包含描边、虚线、变换及对象裁剪 // 图片不解码像素,底纹不展开图案单元;不包含页面边界或父级变换 // 入参: object 路径、图片或复合对象, drawParam 图层绘制参数标识 diff --git a/ofdgo_render_text.go b/ofdgo_render_text.go index b2b7fd4..c5ad2ce 100644 --- a/ofdgo_render_text.go +++ b/ofdgo_render_text.go @@ -171,15 +171,11 @@ func (r *Renderer) PositionText(object TextObject, state RenderState) (*Position if err != nil { return nil, err } - positioner := NewTextPositioner(code, object.ReadDirection) + positioner := NewTextPositioner(code) for i, gid := range ids { path := paths[i] width := float64(metrics.GlyphAdvance(gid)) * unit - advance := width * horizontal - if (object.ReadDirection-object.CharDirection)%180 != 0 { - advance = size - } - origin := positioner.Next(advance) + origin := positioner.Next() scaleX := horizontal if bold && len(path) > 0 { outline, err := geometry.Stroke(path, StrokeOptions{Width: size * 0.04, Join: "Round", Cap: "Round"}) diff --git a/ofdgo_renderer_text_canvas.go b/ofdgo_renderer_text_canvas.go index 5795bbf..c417224 100644 --- a/ofdgo_renderer_text_canvas.go +++ b/ofdgo_renderer_text_canvas.go @@ -203,7 +203,6 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 glyphTransforms := r.textObjectGlyphTransforms(fontID, obj) hasUnderline := strings.Contains(obj.Decoration, "Underline") _, shadedFill := fillPaint.(canvas.Gradient) - verticalAdvance := (obj.ReadDirection-obj.CharDirection)%180 != 0 codePos := 0 textPos := 0 for index, tc := range obj.TextCode { @@ -223,7 +222,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 if textRun != nil && tc.Index == "" { spans = textGlyphSpans(runes, glyphTransforms, codePos) } - positioner := NewTextPositioner(tc, obj.ReadDirection) + positioner := NewTextPositioner(tc) xs, ys := positioner.xs, positioner.ys drawAsPath := embeddedFont || face.FauxBold > 0 || textCodePositioned(tc, xs, ys) || fillClip != nil || shadedFill || fillPattern != nil || shouldStroke for i, glyph := range glyphs { @@ -244,11 +243,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 } else { glyphWidth = textGlyphWidth(face, glyph) } - advance := glyphWidth * hScale - if verticalAdvance { - advance = sizeMM - } - position := positioner.Next(advance) + position := positioner.Next() cx, cy := position.X, position.Y var canvasX, canvasY float64 if boundaryInCTM && parentCTM != nil { diff --git a/ofdgo_text_position.go b/ofdgo_text_position.go index 2e6dc41..99ff150 100644 --- a/ofdgo_text_position.go +++ b/ofdgo_text_position.go @@ -19,9 +19,7 @@ import "slices" // TextPositioner 按OFD坐标及增量定位连续字形,不依赖字体或绘图库 type TextPositioner struct { xs, ys, dxs, dys []float64 - direction Point current Point - previous float64 index int } @@ -54,34 +52,22 @@ func textCodeOrigins(codes []TextCode, ascent float64) []TextCode { return codes } -// NewTextPositioner 创建单个TextCode的定位器,方向采用OFD角度 -// 入参: code 文本定位数据, direction 阅读方向 +// NewTextPositioner 创建单个TextCode的定位器,缺省位移不改变字形原点 +// 入参: code 文本定位数据 // 返回: TextPositioner 字形定位器 -func NewTextPositioner(code TextCode, direction int) TextPositioner { - result := TextPositioner{xs: parseFloats(code.X), ys: parseFloats(code.Y), dxs: parseFloats(code.DeltaX), dys: parseFloats(code.DeltaY), direction: Point{X: 1}} - switch direction { - case 90: - result.direction = Point{Y: 1} - case 180: - result.direction = Point{X: -1} - case 270: - result.direction = Point{Y: -1} - } - return result +func NewTextPositioner(code TextCode) TextPositioner { + return TextPositioner{xs: parseFloats(code.X), ys: parseFloats(code.Y), dxs: parseFloats(code.DeltaX), dys: parseFloats(code.DeltaY)} } -// Next 返回下一个字形位置并保存其推进长度,显式坐标和增量优先 -// 入参: advance 当前字形推进长度,单位为毫米 +// Next 根据坐标与位移返回下一个字形原点 // 返回: Point 字形原点 -func (p *TextPositioner) Next(advance float64) Point { +func (p *TextPositioner) Next() Point { i := p.index if i < len(p.xs) { p.current.X = p.xs[i] } else if i > 0 { if dx, ok := textDelta(p.dxs, i-1); ok { p.current.X += dx - } else if len(p.dys) == 0 { - p.current.X += p.previous * p.direction.X } } if i < len(p.ys) { @@ -89,11 +75,8 @@ func (p *TextPositioner) Next(advance float64) Point { } else if i > 0 { if dy, ok := textDelta(p.dys, i-1); ok { p.current.Y += dy - } else if len(p.dxs) == 0 { - p.current.Y += p.previous * p.direction.Y } } - p.previous = advance p.index++ return p.current }