From e488d25775db6ac6fd43d3cd641f40513da0974a Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Sun, 13 Sep 2026 21:26:52 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E9=87=8D=E6=9E=84=E6=8F=90=E5=8D=87):?= =?UTF-8?q?=20=E7=BB=9F=E4=B8=80=E5=8F=96=E9=A1=B5=E4=B8=8E=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=AF=BC=E5=87=BA=E6=8E=A5=E5=8F=A3=EF=BC=8C=E7=B2=BE?= =?UTF-8?q?=E7=AE=80WebUI=E9=87=8D=E5=A4=8D=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/webui/webui.go | 95 ++++++++++------------------------------ ofdgo.go | 13 +----- ofdgo_font_loader.go | 18 +++----- ofdgo_page.go | 1 + ofdgo_reader.go | 26 +++++++++++ ofdgo_renderer.go | 29 +++++------- ofdgo_renderer_output.go | 49 +++++++++++++++++++++ ofdgo_text.go | 16 ++----- 8 files changed, 121 insertions(+), 126 deletions(-) diff --git a/internal/webui/webui.go b/internal/webui/webui.go index acabd39..e99c110 100644 --- a/internal/webui/webui.go +++ b/internal/webui/webui.go @@ -17,10 +17,7 @@ package webui import ( "bytes" "fmt" - "image" - "image/draw" "image/jpeg" - "image/png" "path" "strings" "time" @@ -279,7 +276,7 @@ func (s *Session) SetFonts(fonts []FontFile) error { func (s *Session) PageText(index int) (*ofdgo.PageText, error) { text := s.textCache[index] if text == nil { - _, page, err := s.pageContent(index) + page, err := s.pageContent(index) if err != nil { return nil, err } @@ -389,7 +386,7 @@ func (s *Session) Info() DocumentInfo { pages := make([]*ofdgo.PageContent, 0, len(s.doc.Pages.Page)) for index, pageRef := range s.doc.Pages.Page { pageInfo := PageInfo{Index: index, ID: pageRef.ID} - if _, page, err := s.pageContent(index); err == nil { + if page, err := s.pageContent(index); err == nil { pages = append(pages, page) if box, err := s.pageBox(index, page); err == nil { pageInfo.Width = box.W @@ -409,7 +406,7 @@ func (s *Session) Info() DocumentInfo { // 入参: index 页面索引 // 返回: PageSVG 页面SVG结果, error 错误信息 func (s *Session) RenderPageSVG(index int) (PageSVG, error) { - pageRef, page, err := s.pageContent(index) + page, err := s.pageContent(index) if err != nil { return PageSVG{}, err } @@ -436,7 +433,7 @@ func (s *Session) RenderPageSVG(index int) (PageSVG, error) { s.svgFonts[font.Name] = font.Data fonts[i].Data = nil } - return PageSVG{Index: index, Number: index + 1, ID: pageRef.ID, Width: box.W, Height: box.H, SVG: buf.String(), Links: links, Fonts: fonts}, nil + return PageSVG{Index: index, Number: index + 1, ID: page.ID, Width: box.W, Height: box.H, SVG: buf.String(), Links: links, Fonts: fonts}, nil } // SVGFontData 获取已渲染页面引用的字体数据 @@ -458,30 +455,26 @@ func (s *Session) ExportPage(index int, value string, dpi float64) ([]byte, Expo if !ok { return nil, ExportFormat{}, fmt.Errorf("unsupported export format %s", value) } - _, page, err := s.pageContent(index) + page, err := s.pageContent(index) if err != nil { return nil, ExportFormat{}, err } var buf bytes.Buffer + renderer := *s.Renderer + if dpi > 0 && (format.Value == "png" || format.Value == "jpg") { + renderer.DPI = dpi + } switch format.Value { case "svg": - err = s.Renderer.RenderToSVG(page, &buf) + err = renderer.RenderToSVG(page, &buf) case "pdf": - err = s.Renderer.RenderToPDF(page, &buf) + err = renderer.RenderToPDF(page, &buf) case "eps": - err = s.Renderer.RenderToEPS(page, &buf) + err = renderer.RenderToEPS(page, &buf) case "png": - img, renderErr := s.renderPageImage(page, dpi) - if renderErr != nil { - return nil, ExportFormat{}, renderErr - } - err = png.Encode(&buf, img) + err = renderer.RenderToPNG(page, &buf) case "jpg": - img, renderErr := s.renderPageImage(page, dpi) - if renderErr != nil { - return nil, ExportFormat{}, renderErr - } - err = jpeg.Encode(&buf, fillWhiteBackground(img), &jpeg.Options{Quality: 95}) + err = renderer.RenderToJPEG(page, &buf, &jpeg.Options{Quality: 95}) } if err != nil { return nil, ExportFormat{}, err @@ -489,21 +482,6 @@ func (s *Session) ExportPage(index int, value string, dpi float64) ([]byte, Expo return buf.Bytes(), format, nil } -// renderPageImage 渲染页面图片 -// 入参: page 页面内容, dpi 图片DPI -// 返回: image.Image 图像对象, error 错误信息 -func (s *Session) renderPageImage(page *ofdgo.PageContent, dpi float64) (image.Image, error) { - if dpi <= 0 { - return s.Renderer.RenderToImage(page) - } - oldDPI := s.Renderer.DPI - s.Renderer.DPI = dpi - defer func() { - s.Renderer.DPI = oldDPI - }() - return s.Renderer.RenderToImage(page) -} - // signatureInfos 获取签名验证信息 // 返回: []SignatureInfo 签名验证信息, error 错误信息 func (s *Session) signatureInfos() ([]SignatureInfo, error) { @@ -660,7 +638,7 @@ func (s *Session) ExportPDF() ([]byte, error) { var buf bytes.Buffer pages := make([]*ofdgo.PageContent, len(s.doc.Pages.Page)) for i := range pages { - _, page, err := s.pageContent(i) + page, err := s.pageContent(i) if err != nil { return nil, err } @@ -672,51 +650,22 @@ func (s *Session) ExportPDF() ([]byte, error) { return buf.Bytes(), nil } -// fillWhiteBackground 填充白色背景,复用当前导出图片的RGBA像素 -// 入参: img 图片对象 -// 返回: image.Image 图片对象 -func fillWhiteBackground(img image.Image) image.Image { - bounds := img.Bounds() - if rgba, ok := img.(*image.RGBA); ok { - for y := 0; y < bounds.Dy(); y++ { - row := rgba.Pix[y*rgba.Stride : y*rgba.Stride+bounds.Dx()*4] - for i := 0; i < len(row); i += 4 { - if alpha := 255 - row[i+3]; alpha != 0 { - row[i] += alpha - row[i+1] += alpha - row[i+2] += alpha - row[i+3] = 255 - } - } - } - return rgba - } - dst := image.NewRGBA(bounds) - draw.Draw(dst, bounds, image.White, image.Point{}, draw.Src) - draw.Draw(dst, bounds, img, bounds.Min, draw.Over) - return dst -} - // pageContent 获取页面内容 // 入参: index 页面索引 -// 返回: ofdgo.Page 页面引用, *ofdgo.PageContent 页面内容, error 错误信息 -func (s *Session) pageContent(index int) (ofdgo.Page, *ofdgo.PageContent, error) { +// 返回: *ofdgo.PageContent 页面内容, error 错误信息 +func (s *Session) pageContent(index int) (*ofdgo.PageContent, error) { if s == nil || s.Reader == nil || s.Renderer == nil || s.doc == nil { - return ofdgo.Page{}, nil, fmt.Errorf("ofd document is not opened") + return nil, fmt.Errorf("ofd document is not opened") } - if index < 0 || index >= len(s.doc.Pages.Page) { - return ofdgo.Page{}, nil, fmt.Errorf("page index %d out of range", index) - } - pageRef := s.doc.Pages.Page[index] if page, ok := s.pageCache[index]; ok { - return pageRef, page, nil + return page, nil } - page, err := s.Reader.PageContent(pageRef) + page, err := s.Reader.PageContentByIndex(index) if err != nil { - return ofdgo.Page{}, nil, err + return nil, err } s.pageCache[index] = page - return pageRef, page, nil + return page, nil } // pageBox 获取页面物理区域 diff --git a/ofdgo.go b/ofdgo.go index eec37e0..afd814f 100644 --- a/ofdgo.go +++ b/ofdgo.go @@ -23,6 +23,7 @@ import ( ) // Open 打开OFD文件 +// 返回的阅读器持有文件,使用完毕后需调用Close // 入参: path 文件路径 // 返回: *Reader 阅读器实例, error 错误信息 func Open(path string) (*Reader, error) { @@ -43,6 +44,7 @@ func Open(path string) (*Reader, error) { } // NewReader 从IO读取器创建OFD阅读器 +// 读取器的生命周期由调用方管理,Reader.Close不会关闭它 // 入参: r IO读取器, size 数据大小 // 返回: *Reader 阅读器实例, error 错误信息 func NewReader(r io.ReaderAt, size int64) (*Reader, error) { @@ -115,14 +117,3 @@ func WithFontFS(fs ...fs.FS) RendererOption { r.fontFS = append(r.fontFS, fs...) } } - -// PageCount 获取文档总页数 -// 入参: reader 阅读器 -// 返回: int 页数 -func PageCount(reader *Reader) int { - doc, _ := reader.Doc() - if doc == nil { - return 0 - } - return len(doc.Pages.Page) -} diff --git a/ofdgo_font_loader.go b/ofdgo_font_loader.go index 82057c8..e29131a 100644 --- a/ofdgo_font_loader.go +++ b/ofdgo_font_loader.go @@ -62,7 +62,7 @@ type fontCacheKey struct { // 入参: fontID 字体ID // 返回: *canvas.FontFamily 字体族 func (r *Renderer) loadFont(fontID string) *canvas.FontFamily { - if ff, ok := r.FontMap[fontID]; ok { + if ff, ok := r.fontMap[fontID]; ok { return ff } var defaultFont *canvas.FontFamily @@ -78,17 +78,11 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily { if of.FontFile != "" { if fontData, err := r.Reader.ResData(of.FontFile); err == nil { if cidMap := getCFFCIDRuneMap(fontData); len(cidMap) > 0 { - if r.FontCIDMap == nil { - r.FontCIDMap = make(map[string]map[uint16]rune) - } - r.FontCIDMap[fontID] = cidMap + r.fontCIDMap[fontID] = cidMap } if _, fixedData, mapping, _, err := FixFontDataAggressive(fontData, true, true); err == nil { fontData = fixedData if mapping != nil { - if r.FontGIDMap == nil { - r.FontGIDMap = make(map[string]map[uint16]rune) - } inv := make(map[uint16]rune) for k, v := range mapping { if k == packedGlyphRune(v) { @@ -100,11 +94,11 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily { inv[v] = k } } - r.FontGIDMap[fontID] = inv + r.fontGIDMap[fontID] = inv } } if err := ff.LoadFont(fontData, 0, fontStyle); err == nil { - r.FontMap[fontID] = ff + r.fontMap[fontID] = ff return ff } return nil @@ -113,12 +107,12 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily { } for _, source := range r.fontSources(fontID, of, fontStyle) { if loaded := r.loadFontSource(ff, source, fontStyle); loaded != nil { - r.FontMap[fontID] = loaded + r.fontMap[fontID] = loaded r.fontSourceUsed[fontID] = source return loaded } } - r.FontMap[fontID] = defaultFont + r.fontMap[fontID] = defaultFont return defaultFont } diff --git a/ofdgo_page.go b/ofdgo_page.go index 94c6f21..a2969e1 100644 --- a/ofdgo_page.go +++ b/ofdgo_page.go @@ -39,6 +39,7 @@ type Content struct { } // Layer 图层 +// Objects非空时按其顺序渲染,图元修改应作用于Objects中的对象 type Layer struct { ID string `xml:"ID,attr"` Type string `xml:"Type,attr"` diff --git a/ofdgo_reader.go b/ofdgo_reader.go index 9e5208b..4bb034e 100644 --- a/ofdgo_reader.go +++ b/ofdgo_reader.go @@ -24,6 +24,7 @@ import ( ) // Reader OFD文件阅读器 +// Reader及共享它的Renderer需串行调用 type Reader struct { Path string Zip *zip.Reader @@ -249,6 +250,7 @@ func resolveResourcePath(resPath, baseLoc, filePath string) string { } // PageContent 获取页面内容 +// 每次返回独立的页面内容,修改后可传入Renderer重绘,不会写回原文件 // 入参: page 页面对象 // 返回: *PageContent 页面内容, error 错误信息 func (r *Reader) PageContent(page Page) (*PageContent, error) { @@ -272,6 +274,30 @@ func (r *Reader) PageContent(page Page) (*PageContent, error) { return &content, nil } +// PageCount 获取当前文档总页数 +// 返回: int 页数, error 错误信息 +func (r *Reader) PageCount() (int, error) { + doc, err := r.Doc() + if err != nil { + return 0, err + } + return len(doc.Pages.Page), nil +} + +// PageContentByIndex 按当前文档页面顺序读取页面内容 +// 入参: index 页面索引,从0开始 +// 返回: *PageContent 页面内容, error 错误信息 +func (r *Reader) PageContentByIndex(index int) (*PageContent, error) { + doc, err := r.Doc() + if err != nil { + return nil, err + } + if index < 0 || index >= len(doc.Pages.Page) { + return nil, fmt.Errorf("page index %d out of range", index) + } + return r.PageContent(doc.Pages.Page[index]) +} + // PageArea 读取页面区域,不解析页面图元 // 入参: page 页面对象 // 返回: PageArea 页面区域, error 错误信息 diff --git a/ofdgo_renderer.go b/ofdgo_renderer.go index c5c6c05..e661e1f 100644 --- a/ofdgo_renderer.go +++ b/ofdgo_renderer.go @@ -15,7 +15,6 @@ package ofdgo import ( - "fmt" "image" "io/fs" @@ -23,19 +22,20 @@ import ( ) // Renderer 渲染器实现 +// 通过NewRenderer创建,实例及共享的Reader需串行使用 // OnPageText可选,接收页面绘制时同步提取的文字,不包含图案和签名外观 type Renderer struct { Reader *Reader DPI float64 RenderAnnotations bool OnPageText func(*PageContent, *PageText) - fontFamily *canvas.FontFamily - defaultFontLoaded bool DrawParams map[string]*DrawParam CompositeGraphicUnits map[string]*CompositeGraphicUnit - FontMap map[string]*canvas.FontFamily - FontGIDMap map[string]map[uint16]rune - FontCIDMap map[string]map[uint16]rune + fontFamily *canvas.FontFamily + defaultFontLoaded bool + fontMap map[string]*canvas.FontFamily + fontGIDMap map[string]map[uint16]rune + fontCIDMap map[string]map[uint16]rune fontCache map[fontCacheKey]*canvas.FontFamily svgFontCache map[*canvas.Font]SVGFont fontSourceCache map[string][]fontSource @@ -64,9 +64,9 @@ func (r *Renderer) SetFontFS(fsys ...fs.FS) { // resetFontCache 重置字体匹配、加载和字形缓存 func (r *Renderer) resetFontCache() { - r.FontMap = make(map[string]*canvas.FontFamily) - r.FontGIDMap = make(map[string]map[uint16]rune) - r.FontCIDMap = make(map[string]map[uint16]rune) + r.fontMap = make(map[string]*canvas.FontFamily) + r.fontGIDMap = make(map[string]map[uint16]rune) + r.fontCIDMap = make(map[string]map[uint16]rune) r.fontCache = make(map[fontCacheKey]*canvas.FontFamily) r.svgFontCache = make(map[*canvas.Font]SVGFont) r.fontSourceCache = make(map[string][]fontSource) @@ -205,17 +205,10 @@ func (r *Renderer) PageLinks(page *PageContent) ([]PageLink, error) { } // RenderPageByIndex 按索引渲染页面 -// 入参: index 页面索引 +// 入参: index 页面索引,从0开始 // 返回: *canvas.Canvas 画布实例, error 错误信息 func (r *Renderer) RenderPageByIndex(index int) (*canvas.Canvas, error) { - doc, err := r.Reader.Doc() - if err != nil { - return nil, err - } - if index < 0 || index >= len(doc.Pages.Page) { - return nil, fmt.Errorf("page index %d out of range", index) - } - page, err := r.Reader.PageContent(doc.Pages.Page[index]) + page, err := r.Reader.PageContentByIndex(index) if err != nil { return nil, err } diff --git a/ofdgo_renderer_output.go b/ofdgo_renderer_output.go index 12d8e2a..e4167fb 100644 --- a/ofdgo_renderer_output.go +++ b/ofdgo_renderer_output.go @@ -19,6 +19,8 @@ import ( "bytes" "fmt" "image" + "image/jpeg" + "image/png" "io" "github.com/tdewolff/canvas" @@ -82,6 +84,53 @@ func (r *Renderer) RenderToImage(page *PageContent) (image.Image, error) { return raster.Image, nil } +// RenderToPNG 渲染为PNG,像素尺寸由DPI决定 +// 入参: page 页面内容, writer 输出流 +// 返回: error 错误信息 +func (r *Renderer) RenderToPNG(page *PageContent, writer io.Writer) error { + img, err := r.RenderToImage(page) + if err != nil { + return err + } + return png.Encode(writer, img) +} + +// RenderToJPEG 渲染为白底JPEG,像素尺寸由DPI决定 +// 入参: page 页面内容, writer 输出流, options 编码选项,nil使用标准库默认值 +// 返回: error 错误信息 +func (r *Renderer) RenderToJPEG(page *PageContent, writer io.Writer, options *jpeg.Options) error { + img, err := r.RenderToImage(page) + if err != nil { + return err + } + return jpeg.Encode(writer, fillWhiteBackground(img), options) +} + +// fillWhiteBackground 填充白色背景,复用当前导出图片的RGBA像素 +// 入参: img 图片对象 +// 返回: image.Image 图片对象 +func fillWhiteBackground(img image.Image) image.Image { + bounds := img.Bounds() + if rgba, ok := img.(*image.RGBA); ok { + for y := 0; y < bounds.Dy(); y++ { + row := rgba.Pix[y*rgba.Stride : y*rgba.Stride+bounds.Dx()*4] + for i := 0; i < len(row); i += 4 { + if alpha := 255 - row[i+3]; alpha != 0 { + row[i] += alpha + row[i+1] += alpha + row[i+2] += alpha + row[i+3] = 255 + } + } + } + return rgba + } + dst := image.NewRGBA(bounds) + draw.Draw(dst, bounds, image.White, image.Point{}, draw.Src) + draw.Draw(dst, bounds, img, bounds.Min, draw.Over) + return dst +} + // RenderToSVG 渲染为SVG // 入参: page 页面内容, writer 输出流 // 返回: error 错误信息 diff --git a/ofdgo_text.go b/ofdgo_text.go index 72ac273..edce6d6 100644 --- a/ofdgo_text.go +++ b/ofdgo_text.go @@ -232,19 +232,11 @@ func (r *Renderer) fontGlyphRune(fontID string, glyphID int) (rune, bool) { return 0, false } id := uint16(glyphID) - if r.FontCIDMap != nil { - if mapping := r.FontCIDMap[fontID]; mapping != nil { - if mapped, ok := mapping[id]; ok { - return mapped, true - } - } + if mapped, ok := r.fontCIDMap[fontID][id]; ok { + return mapped, true } - if r.FontGIDMap != nil { - if mapping := r.FontGIDMap[fontID]; mapping != nil { - if mapped, ok := mapping[id]; ok { - return mapped, true - } - } + if mapped, ok := r.fontGIDMap[fontID][id]; ok { + return mapped, true } return 0, false }