From 8d9175294307e65d6597a57dc74bc1c5acb75da9 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Sun, 27 Sep 2026 21:04:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96):=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=96=87=E6=9C=AC=E6=90=9C=E7=B4=A2=E4=B8=8E?= =?UTF-8?q?=E6=B5=81=E5=BC=8F=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- go.sum | 4 ++-- ofdgo_render_output.go | 16 +++++--------- ofdgo_search.go | 50 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 52 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index b851c06..9cf7af6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6 github.com/xiaoqidun/jbig2 v1.1.1 - github.com/xiaoqidun/pdfgo v0.0.0-20260927123406-320d8b5546b5 + github.com/xiaoqidun/pdfgo v0.0.0-20260927125911-04f5476a9051 golang.org/x/crypto v0.57.0 golang.org/x/image v0.46.0 golang.org/x/text v0.42.0 diff --git a/go.sum b/go.sum index 863fc81..588be39 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk= github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI= github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ= -github.com/xiaoqidun/pdfgo v0.0.0-20260927123406-320d8b5546b5 h1:FyW2HSyxcrf33xo3F1eKVZmlizgpxML2H+qn63yXcDI= -github.com/xiaoqidun/pdfgo v0.0.0-20260927123406-320d8b5546b5/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ= +github.com/xiaoqidun/pdfgo v0.0.0-20260927125911-04f5476a9051 h1:VhoxfD1yFJMBcgThFZCo480VVBEU5dB8qAZXWCUSeqI= +github.com/xiaoqidun/pdfgo v0.0.0-20260927125911-04f5476a9051/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= diff --git a/ofdgo_render_output.go b/ofdgo_render_output.go index 37b8ea6..21e8d0a 100644 --- a/ofdgo_render_output.go +++ b/ofdgo_render_output.go @@ -166,7 +166,7 @@ func (r *Renderer) RenderToText(page *PageContent, writer io.Writer) error { if err != nil { return err } - _, err = io.WriteString(writer, text.String()) + _, err = text.WriteTo(writer) return err } @@ -191,17 +191,11 @@ func (r *Renderer) RenderToMultiPageText(writer io.Writer, indices ...int) error if err != nil { return fmt.Errorf("failed to extract page %d text: %w", index+1, err) } - if value := text.String(); value != "" { - if written { - if _, err := io.WriteString(writer, "\n"); err != nil { - return err - } - } - if _, err := io.WriteString(writer, value); err != nil { - return err - } - written = true + n, err := text.writeTo(writer, written) + if err != nil { + return err } + written = written || n > 0 if err := r.exportProgress(i+1, len(indices)); err != nil { return err } diff --git a/ofdgo_search.go b/ofdgo_search.go index b41757f..dbeaa22 100644 --- a/ofdgo_search.go +++ b/ofdgo_search.go @@ -17,6 +17,7 @@ package ofdgo import ( "encoding/xml" "fmt" + "io" "math" "strings" "unicode" @@ -62,7 +63,7 @@ type PageText struct { } // TextRun 文本对象原文及逐字符区域,坐标以页面左上角为原点,单位为毫米 -// Boxes与Text的Unicode字符一一对应,缺少字体时使用文本对象区域 +// Boxes与Text的Unicode字符一一对应,缺少字体时使用文本对象区域,缺省区域不影响文字搜索 type TextRun struct { ID string `json:"id"` Text string `json:"text"` @@ -122,16 +123,47 @@ func (r *Renderer) PageTextByIndex(index int) (*PageText, error) { // 返回: string 页面原文 func (p *PageText) String() string { var text strings.Builder + _, _ = p.WriteTo(&text) + return text.String() +} + +// WriteTo 按文本对象顺序输出UTF-8原文,以换行分隔非空对象,不拼接整页字符串 +// 入参: writer 输出流 +// 返回: int64 已写字节数, error 写入错误,出错时可能已有部分输出 +func (p *PageText) WriteTo(writer io.Writer) (int64, error) { + return p.writeTo(writer, false) +} + +// writeTo 输出非空文本对象,可在首个对象前分隔上一页 +// 入参: writer 输出流, separator 是否需要前导换行 +// 返回: int64 已写字节数, error 写入错误 +func (p *PageText) writeTo(writer io.Writer, separator bool) (int64, error) { + var written int64 for _, run := range p.Runs { if run.Text == "" { continue } - if text.Len() > 0 { - text.WriteByte('\n') + if separator { + n, err := io.WriteString(writer, "\n") + written += int64(n) + if err != nil { + return written, err + } + if n != 1 { + return written, io.ErrShortWrite + } } - text.WriteString(run.Text) + n, err := io.WriteString(writer, run.Text) + written += int64(n) + if err != nil { + return written, err + } + if n != len(run.Text) { + return written, io.ErrShortWrite + } + separator = true } - return text.String() + return written, nil } // Search 按原文进行忽略大小写的字面匹配,不跨文本对象拼接或识别图像 @@ -166,7 +198,7 @@ func (p *PageText) Search(query string) []TextMatch { Text: string(runes[start:end]), After: string(runes[end:min(len(runes), end+16)]), } - for _, box := range run.Boxes[start:end] { + for _, box := range run.Boxes[min(start, len(run.Boxes)):min(end, len(run.Boxes))] { if box.W > 0 && box.H > 0 && (len(match.Boxes) == 0 || match.Boxes[len(match.Boxes)-1] != box) { match.Boxes = append(match.Boxes, box) } @@ -184,6 +216,12 @@ func (p *PageText) Search(query string) []TextMatch { // 返回: string 匹配用文字 func foldText(text string) string { return strings.Map(func(char rune) rune { + if char < utf8.RuneSelf { + if char >= 'a' && char <= 'z' { + return char - ('a' - 'A') + } + return char + } folded := char for next := unicode.SimpleFold(char); next != char; next = unicode.SimpleFold(next) { folded = min(folded, next)