mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:53:44 +08:00
This commit is contained in:
1 parent
74b6b0ac93
commit
e488d25775
8 files changed
+121
-126
No files matched your search
+22
-73
@@ -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 获取页面物理区域
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
+6
-12
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ type Content struct {
|
||||
}
|
||||
|
||||
// Layer 图层
|
||||
// Objects非空时按其顺序渲染,图元修改应作用于Objects中的对象
|
||||
type Layer struct {
|
||||
ID string `xml:"ID,attr"`
|
||||
Type string `xml:"Type,attr"`
|
||||
|
||||
@@ -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 错误信息
|
||||
|
||||
+11
-18
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 错误信息
|
||||
|
||||
+4
-12
@@ -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
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user