From 04783725da493b9f8fda10d00c74cd89d5446e91 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Sat, 10 Oct 2026 14:12:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=96=87=E6=9C=AC=E5=A4=84=E7=90=86):=20?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=8D=A2=E8=A1=8C=E5=88=A4=E6=96=AD=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ofdgo_text.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/ofdgo_text.go b/ofdgo_text.go index 575c215..c245767 100644 --- a/ofdgo_text.go +++ b/ofdgo_text.go @@ -19,6 +19,7 @@ import ( "strconv" "strings" "unicode/utf16" + "unicode/utf8" ) // textGlyph 绘制字形 @@ -63,7 +64,7 @@ func (tc *TextCode) GetDeltaY() []float64 { return parseFloats(tc.DeltaY) } -// textCodeLineBreak 判断横向文字的行首回退,或有足够行距且水平范围重叠的对齐段落 +// textCodeLineBreak 根据行首、行距和字形位置推断横向文本换行 // 入参: index 文本编码索引 // 返回: bool 是否换行 func (obj TextObject) textCodeLineBreak(index int) bool { @@ -106,14 +107,90 @@ func (obj TextObject) textCodeLineBreak(index int) bool { if err != nil || obj.Size <= 0 || y-prev < obj.Size/2 || previous.DeltaY != "" || code.DeltaY != "" { return false } + count := obj.textCodeGlyphCount(index - 1) + if count == 0 { + return false + } right := left + obj.Size - deltas := textPositionValues{data: previous.DeltaX, compressed: strings.Contains(previous.DeltaX, "g")} - for dx, ok := deltas.next(); ok; dx, ok = deltas.next() { - right += dx + positioner := NewTextPositioner(previous) + for range count { + right = math.Max(right, positioner.Next().X+obj.Size) } return x <= right } +// textCodeGlyphCount 统计文本段实际绘制的字形数量,计入字符到字形的变换 +// 入参: index 文本段索引 +// 返回: int 字形数量 +func (obj TextObject) textCodeGlyphCount(index int) int { + code := obj.TextCode[index] + if code.Index != "" { + count := 0 + for _, field := range strings.Fields(code.Index) { + first, last, interval := strings.Cut(field, "-") + start, err := strconv.ParseUint(first, 10, 16) + if err != nil { + continue + } + if !interval { + count++ + continue + } + end, err := strconv.ParseUint(last, 10, 16) + if err == nil && end >= start { + count += int(end-start) + 1 + } + } + return count + } + count := textCodeRuneCount(code.Value) + if len(obj.CGTransform) == 0 || count == 0 { + return count + } + offset := 0 + for _, previous := range obj.TextCode[:index] { + offset += textCodeRuneCount(previous.Value) + } + transforms := make(map[int][2]int) + for _, transform := range obj.CGTransform { + start, codes := transform.CodePosition-offset, max(1, transform.CodeCount) + if start < 0 || start >= count { + continue + } + glyphs := len(parseInts(transform.Glyphs)) + if transform.GlyphCount > 0 { + glyphs = min(glyphs, transform.GlyphCount) + } + if glyphs > 0 { + transforms[start] = [2]int{codes, glyphs} + } + } + glyphs := 0 + for i := 0; i < count; { + if transform, ok := transforms[i]; ok && transform[0] <= count-i { + i += transform[0] + glyphs += transform[1] + } else { + i++ + glyphs++ + } + } + return glyphs +} + +// textCodeRuneCount 统计文本编码中的字符数,识别转义及XML排版空白 +// 入参: value 文本编码内容 +// 返回: int 字符数量 +func textCodeRuneCount(value string) int { + if strings.Contains(value, "\\") { + return len(textCodeRunes(value)) + } + if strings.ContainsAny(value, "\r\n") { + value = strings.TrimSpace(value) + } + return utf8.RuneCountInString(value) +} + // textObjectFontID 获取文本对象字体ID // 入参: text 文本对象 // 返回: string 字体ID