mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
1 parent
32ab7fe732
commit
60b8b44eca
6 files changed
+206
-133
No files matched your search
@@ -10,7 +10,7 @@ require (
|
|||||||
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
||||||
github.com/xiaoqidun/j2kgo v0.0.0-20261009063910-4865a21ef893
|
github.com/xiaoqidun/j2kgo v0.0.0-20261009063910-4865a21ef893
|
||||||
github.com/xiaoqidun/jbig2 v1.1.3
|
github.com/xiaoqidun/jbig2 v1.1.3
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261009094649-718bb9e8a149
|
github.com/xiaoqidun/pdfgo v0.0.0-20261009121346-abd17a2aa440
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.57.0
|
||||||
golang.org/x/image v0.46.0
|
golang.org/x/image v0.46.0
|
||||||
golang.org/x/text v0.42.0
|
golang.org/x/text v0.42.0
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ github.com/xiaoqidun/j2kgo v0.0.0-20261009063910-4865a21ef893 h1:jmQlBO7R+bcimOM
|
|||||||
github.com/xiaoqidun/j2kgo v0.0.0-20261009063910-4865a21ef893/go.mod h1:DCwAfuDzCDYqUiAlZ7BDwigTjaXw7o9L1FtEma/D1aA=
|
github.com/xiaoqidun/j2kgo v0.0.0-20261009063910-4865a21ef893/go.mod h1:DCwAfuDzCDYqUiAlZ7BDwigTjaXw7o9L1FtEma/D1aA=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.3 h1:p0d8FMLiLfzQuX278T4VTfTz8tIJEw87u4v63lH4CBc=
|
github.com/xiaoqidun/jbig2 v1.1.3 h1:p0d8FMLiLfzQuX278T4VTfTz8tIJEw87u4v63lH4CBc=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.3/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
github.com/xiaoqidun/jbig2 v1.1.3/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261009094649-718bb9e8a149 h1:E5g5tl7ms0DTBYnccc33VoQOpGQUnQQPKd4UlIh0LCM=
|
github.com/xiaoqidun/pdfgo v0.0.0-20261009121346-abd17a2aa440 h1:auHwW058jOQGHVB6FXNfT6kdLh5DU6KByu4DGDSLJ7U=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261009094649-718bb9e8a149/go.mod h1:swzoqA5+wSHsRPesJS1VOMLhSyWkLCSQ+IgHNYXbcKo=
|
github.com/xiaoqidun/pdfgo v0.0.0-20261009121346-abd17a2aa440/go.mod h1:swzoqA5+wSHsRPesJS1VOMLhSyWkLCSQ+IgHNYXbcKo=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||||
|
|||||||
@@ -444,22 +444,3 @@ func resolveActionURI(action URI) string {
|
|||||||
}
|
}
|
||||||
return base.ResolveReference(target).String()
|
return base.ResolveReference(target).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// outlineDest 获取大纲跳转目标
|
|
||||||
// 入参: outline 大纲节点, bookmarks 书签
|
|
||||||
// 返回: *Dest 跳转目标
|
|
||||||
func outlineDest(outline OutlineElem, bookmarks map[string]Dest) *Dest {
|
|
||||||
for _, action := range outline.Actions {
|
|
||||||
if action.Goto != nil {
|
|
||||||
if dest := gotoDest(action.Goto, bookmarks); dest != nil {
|
|
||||||
return dest
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, child := range outline.OutlineElem {
|
|
||||||
if dest := outlineDest(child, bookmarks); dest != nil {
|
|
||||||
return dest
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -165,38 +165,30 @@ func (CanvasBackend) RenderPDF(r *Renderer, pages []RenderDocumentPage, writer i
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data := replacePDFProducer(buf.Bytes()[start:])
|
data := replacePDFProducer(buf.Bytes()[start:])
|
||||||
data, err = renderer.preserveImages(r.outputContext(), data)
|
options := pdfgo.OptimizeOptions{Compression: r.Compression}
|
||||||
if err != nil {
|
if r.Compression.Mode != CompressionUnchanged && progress != nil {
|
||||||
buf.Truncate(start)
|
options.OnProgress = func(_ string, _, _ int) error {
|
||||||
return err
|
return progress(len(pages), len(pages))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if direct && (renderer.exactImages || navigation.exactLinks) {
|
if direct {
|
||||||
|
if !renderer.exactImages && !navigation.exactLinks {
|
||||||
|
if err := r.outputContext().Err(); err != nil {
|
||||||
|
buf.Truncate(start)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var result bytes.Buffer
|
||||||
|
err := renderer.writePDF(r.outputContext(), data, &result, options)
|
||||||
buf.Truncate(start)
|
buf.Truncate(start)
|
||||||
_, err = buf.Write(data)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if r.Compression.Mode != CompressionUnchanged {
|
|
||||||
reader, err := pdfgo.NewReader(bytes.NewReader(data), int64(len(data)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
_, err = buf.Write(result.Bytes())
|
||||||
_, err = reader.OptimizeTo(r.outputContext(), writer, pdfgo.OptimizeOptions{Compression: r.Compression, OnProgress: func(_ string, _, _ int) error {
|
|
||||||
if progress != nil {
|
|
||||||
return progress(len(pages), len(pages))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}})
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if direct {
|
return renderer.writePDF(r.outputContext(), data, writer, options)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
n, err := writer.Write(data)
|
|
||||||
if err == nil && n != len(data) {
|
|
||||||
err = io.ErrShortWrite
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// replacePDFProducer 替换PDF的Producer属性
|
// replacePDFProducer 替换PDF的Producer属性
|
||||||
|
|||||||
+151
-68
@@ -15,9 +15,11 @@
|
|||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/tdewolff/canvas"
|
"github.com/tdewolff/canvas"
|
||||||
cimage "github.com/tdewolff/canvas/image"
|
cimage "github.com/tdewolff/canvas/image"
|
||||||
@@ -37,31 +39,36 @@ type pdfRenderer struct {
|
|||||||
|
|
||||||
// pdfNavigation PDF导航信息
|
// pdfNavigation PDF导航信息
|
||||||
type pdfNavigation struct {
|
type pdfNavigation struct {
|
||||||
Anchor map[int][]pdfAnchor
|
|
||||||
Link map[int][]pdfLink
|
Link map[int][]pdfLink
|
||||||
Outline map[int][]pdfOutline
|
Outline []pdfOutline
|
||||||
nextID int
|
|
||||||
exactLinks bool
|
exactLinks bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfAnchor PDF跳转目标
|
|
||||||
type pdfAnchor struct {
|
|
||||||
Name string
|
|
||||||
Rect canvas.Rect
|
|
||||||
}
|
|
||||||
|
|
||||||
// pdfLink PDF链接
|
// pdfLink PDF链接
|
||||||
type pdfLink struct {
|
type pdfLink struct {
|
||||||
URI string
|
URI string
|
||||||
Rect canvas.Rect
|
Rect canvas.Rect
|
||||||
Region *pdfgo.LinkRegion
|
Region *pdfgo.LinkRegion
|
||||||
|
Target *pdfTarget
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfTarget 保存输出页索引及默认用户空间中的跳转参数
|
||||||
|
type pdfTarget struct {
|
||||||
|
Page int
|
||||||
|
Destination pdfgo.Destination
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfOutline PDF大纲
|
// pdfOutline PDF大纲
|
||||||
type pdfOutline struct {
|
type pdfOutline struct {
|
||||||
Name string
|
Name string
|
||||||
Level int
|
Level int
|
||||||
Y float64
|
Actions []pdfOutlineAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfOutlineAction 保存目录的导航动作,输出时关联实际页面引用
|
||||||
|
type pdfOutlineAction struct {
|
||||||
|
URI string
|
||||||
|
Target *pdfTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderImage 校验惰性图片并同步不透明画笔,保留解码错误及后端透明度状态
|
// RenderImage 校验惰性图片并同步不透明画笔,保留解码错误及后端透明度状态
|
||||||
@@ -126,12 +133,10 @@ func (r *pdfRenderer) glyphPath(path *canvas.Path, matrix canvas.Matrix) *canvas
|
|||||||
|
|
||||||
// newPDFNavigation 创建PDF导航信息
|
// newPDFNavigation 创建PDF导航信息
|
||||||
// 入参: renderer 渲染器, doc 文档结构, pages 页面数据
|
// 入参: renderer 渲染器, doc 文档结构, pages 页面数据
|
||||||
// 返回: *pdfNavigation PDF导航信息, error 几何错误
|
// 返回: *pdfNavigation PDF导航信息, error 区域或目标错误
|
||||||
func newPDFNavigation(renderer *Renderer, doc *Document, pages []RenderDocumentPage) (*pdfNavigation, error) {
|
func newPDFNavigation(renderer *Renderer, doc *Document, pages []RenderDocumentPage) (*pdfNavigation, error) {
|
||||||
navigation := &pdfNavigation{
|
navigation := &pdfNavigation{
|
||||||
Anchor: make(map[int][]pdfAnchor),
|
Link: make(map[int][]pdfLink),
|
||||||
Link: make(map[int][]pdfLink),
|
|
||||||
Outline: make(map[int][]pdfOutline),
|
|
||||||
}
|
}
|
||||||
pageIndex := make(map[string]int, len(pages))
|
pageIndex := make(map[string]int, len(pages))
|
||||||
for i, page := range pages {
|
for i, page := range pages {
|
||||||
@@ -162,7 +167,9 @@ func newPDFNavigation(renderer *Renderer, doc *Document, pages []RenderDocumentP
|
|||||||
}
|
}
|
||||||
rect := pdfSourceRect(box, page.Box.H)
|
rect := pdfSourceRect(box, page.Box.H)
|
||||||
count := len(navigation.Link[i])
|
count := len(navigation.Link[i])
|
||||||
navigation.addAction(i, rect, action, bookmarks, pageIndex, pages)
|
if err := navigation.addAction(i, rect, action, bookmarks, pageIndex, pages); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if len(navigation.Link[i]) == count || outline == "" {
|
if len(navigation.Link[i]) == count || outline == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -182,71 +189,154 @@ func newPDFNavigation(renderer *Renderer, doc *Document, pages []RenderDocumentP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if doc != nil {
|
if doc != nil {
|
||||||
navigation.addOutlines(doc.Outlines.OutlineElem, 0, bookmarks, pageIndex, pages)
|
if err := navigation.addOutlines(doc.Outlines.OutlineElem, 0, bookmarks, pageIndex, pages); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return navigation, nil
|
return navigation, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addAction 添加PDF动作
|
// addAction 添加PDF动作
|
||||||
// 入参: page 页面索引, rect 动作区域, action 动作, bookmarks 书签, pageIndex 页面索引表, pages 页面数据
|
// 入参: page 页面索引, rect 动作区域, action 动作, bookmarks 书签, pageIndex 页面索引表, pages 页面数据
|
||||||
func (n *pdfNavigation) addAction(page int, rect canvas.Rect, action Action, bookmarks map[string]Dest, pageIndex map[string]int, pages []RenderDocumentPage) {
|
// 返回: error 目标参数错误
|
||||||
|
func (n *pdfNavigation) addAction(page int, rect canvas.Rect, action Action, bookmarks map[string]Dest, pageIndex map[string]int, pages []RenderDocumentPage) error {
|
||||||
if action.Goto != nil {
|
if action.Goto != nil {
|
||||||
dest := gotoDest(action.Goto, bookmarks)
|
dest := gotoDest(action.Goto, bookmarks)
|
||||||
if dest == nil {
|
if dest == nil {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
target, ok := pageIndex[dest.PageID]
|
target, ok := pageIndex[dest.PageID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
targetRect, ok := pdfDestRect(*dest, pages[target].Box.H)
|
destination, err := pdfDestination(*dest, pages[target].Box.H)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
name := fmt.Sprintf("ofdgo-dest-%d", n.nextID)
|
n.Link[page] = append(n.Link[page], pdfLink{Rect: rect, Target: &pdfTarget{Page: target, Destination: destination}})
|
||||||
n.nextID++
|
n.exactLinks = true
|
||||||
n.Anchor[target] = append(n.Anchor[target], pdfAnchor{Name: name, Rect: targetRect})
|
return nil
|
||||||
n.Link[page] = append(n.Link[page], pdfLink{URI: "#" + name, Rect: rect})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if action.URI != nil && action.URI.URI != "" {
|
if action.URI != nil && action.URI.URI != "" {
|
||||||
n.Link[page] = append(n.Link[page], pdfLink{URI: resolveActionURI(*action.URI), Rect: rect})
|
n.Link[page] = append(n.Link[page], pdfLink{URI: resolveActionURI(*action.URI), Rect: rect})
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addOutlines 添加PDF大纲
|
// addOutlines 保留目录层级及节点自身的导航动作,选页时移除无保留子项的失效跳转节点
|
||||||
// 入参: outlines 大纲节点, level 节点层级, bookmarks 书签, pageIndex 页面索引表, pages 页面数据
|
// 入参: outlines 大纲节点, level 节点层级, bookmarks 书签, pageIndex 页面索引表, pages 页面数据
|
||||||
func (n *pdfNavigation) addOutlines(outlines []OutlineElem, level int, bookmarks map[string]Dest, pageIndex map[string]int, pages []RenderDocumentPage) {
|
// 返回: error 目标参数错误
|
||||||
|
func (n *pdfNavigation) addOutlines(outlines []OutlineElem, level int, bookmarks map[string]Dest, pageIndex map[string]int, pages []RenderDocumentPage) error {
|
||||||
for _, outline := range outlines {
|
for _, outline := range outlines {
|
||||||
nextLevel := level
|
entry := pdfOutline{Name: outline.Title, Level: level}
|
||||||
if dest := outlineDest(outline, bookmarks); dest != nil {
|
onlyMissingTargets := len(outline.Actions) != 0
|
||||||
if page, ok := pageIndex[dest.PageID]; ok {
|
for _, action := range outline.Actions {
|
||||||
n.Outline[page] = append(n.Outline[page], pdfOutline{
|
if action.Goto != nil {
|
||||||
Name: outline.Title,
|
dest := gotoDest(action.Goto, bookmarks)
|
||||||
Level: level,
|
if dest == nil {
|
||||||
Y: pdfDestY(*dest, pages[page].Box.H),
|
continue
|
||||||
})
|
}
|
||||||
nextLevel++
|
page, ok := pageIndex[dest.PageID]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
destination, err := pdfDestination(*dest, pages[page].Box.H)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
entry.Actions = append(entry.Actions, pdfOutlineAction{Target: &pdfTarget{Page: page, Destination: destination}})
|
||||||
|
} else {
|
||||||
|
onlyMissingTargets = false
|
||||||
|
if action.URI != nil && action.URI.URI != "" {
|
||||||
|
entry.Actions = append(entry.Actions, pdfOutlineAction{URI: resolveActionURI(*action.URI)})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n.addOutlines(outline.OutlineElem, nextLevel, bookmarks, pageIndex, pages)
|
index := len(n.Outline)
|
||||||
|
n.Outline = append(n.Outline, entry)
|
||||||
|
if err := n.addOutlines(outline.OutlineElem, level+1, bookmarks, pageIndex, pages); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if onlyMissingTargets && len(entry.Actions) == 0 && len(n.Outline) == index+1 {
|
||||||
|
n.Outline = n.Outline[:index]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
n.exactLinks = n.exactLinks || len(n.Outline) != 0
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply 应用PDF导航信息
|
// apply 生成链接及大纲结构,内部目标在原生写入时关联
|
||||||
// 入参: renderer PDF渲染器, page 页面索引
|
// 入参: renderer PDF渲染器, page 页面索引
|
||||||
func (n *pdfNavigation) apply(renderer *pdf.PDF, page int) {
|
func (n *pdfNavigation) apply(renderer *pdf.PDF, page int) {
|
||||||
for _, anchor := range n.Anchor[page] {
|
|
||||||
renderer.AddAnchor(anchor.Name, anchor.Rect)
|
|
||||||
}
|
|
||||||
for _, link := range n.Link[page] {
|
for _, link := range n.Link[page] {
|
||||||
renderer.AddLink(link.URI, link.Rect)
|
renderer.AddLink(link.URI, link.Rect)
|
||||||
}
|
}
|
||||||
for _, outline := range n.Outline[page] {
|
if page == 0 {
|
||||||
renderer.AddOutline(outline.Name, outline.Level, outline.Y)
|
for index, outline := range n.Outline {
|
||||||
|
renderer.AddOutline(strconv.Itoa(index), outline.Level, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// destinations 将输出页引用关联到原有链接和先序大纲,不按目标页重新排序
|
||||||
|
// 入参: ctx 取消上下文, reader 输出PDF, pages 输出页引用, options 替换配置
|
||||||
|
// 返回: error 目录结构或取消错误
|
||||||
|
func (n *pdfNavigation) destinations(ctx context.Context, reader *pdfgo.Reader, pages []pdfgo.Reference, options *pdfgo.RewriteOptions) error {
|
||||||
|
options.LinkDestinations = make(map[pdfgo.AnnotationLocation]pdfgo.Destination)
|
||||||
|
for page, links := range n.Link {
|
||||||
|
for index, link := range links {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if link.Target != nil {
|
||||||
|
dest := link.Target.Destination
|
||||||
|
dest.Page = pages[link.Target.Page]
|
||||||
|
options.LinkDestinations[pdfgo.AnnotationLocation{Page: pages[page], Index: index}] = dest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(n.Outline) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
options.OutlineDestinations = make(map[pdfgo.Reference]pdfgo.Destination, len(n.Outline))
|
||||||
|
options.OutlineTitles = make(map[pdfgo.Reference]string, len(n.Outline))
|
||||||
|
options.OutlineActions = make(map[pdfgo.Reference][]pdfgo.NavigationAction)
|
||||||
|
count := 0
|
||||||
|
if err := reader.WalkOutlines(ctx, func(level int, item pdfgo.OutlineItem) error {
|
||||||
|
if count >= len(n.Outline) || n.Outline[count].Level != level || strconv.Itoa(count) != item.Title {
|
||||||
|
return fmt.Errorf("PDF output outline differs")
|
||||||
|
}
|
||||||
|
actions := n.Outline[count].Actions
|
||||||
|
if len(actions) == 1 && actions[0].Target != nil {
|
||||||
|
target := actions[0].Target
|
||||||
|
dest := target.Destination
|
||||||
|
dest.Page = pages[target.Page]
|
||||||
|
options.OutlineDestinations[item.Reference] = dest
|
||||||
|
} else {
|
||||||
|
converted := make([]pdfgo.NavigationAction, 0, len(actions))
|
||||||
|
for _, action := range actions {
|
||||||
|
value := pdfgo.NavigationAction{URI: action.URI}
|
||||||
|
if action.Target != nil {
|
||||||
|
dest := action.Target.Destination
|
||||||
|
dest.Page = pages[action.Target.Page]
|
||||||
|
value.Destination = &dest
|
||||||
|
}
|
||||||
|
converted = append(converted, value)
|
||||||
|
}
|
||||||
|
options.OutlineActions[item.Reference] = converted
|
||||||
|
}
|
||||||
|
options.OutlineTitles[item.Reference] = n.Outline[count].Name
|
||||||
|
count++
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count != len(n.Outline) {
|
||||||
|
return fmt.Errorf("PDF output outline missing")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// pdfSourceRect 转换PDF动作区域
|
// pdfSourceRect 转换PDF动作区域
|
||||||
// 入参: box OFD区域, pageH 页面高度
|
// 入参: box OFD区域, pageH 页面高度
|
||||||
// 返回: canvas.Rect PDF区域
|
// 返回: canvas.Rect PDF区域
|
||||||
@@ -254,32 +344,25 @@ func pdfSourceRect(box Box, pageH float64) canvas.Rect {
|
|||||||
return canvas.RectFromSize(box.X, pageH-box.Y-box.H, box.W, box.H)
|
return canvas.RectFromSize(box.X, pageH-box.Y-box.H, box.W, box.H)
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfDestRect 转换PDF跳转目标
|
// pdfDestination 按OFD目标语义转换PDF坐标、显示模式及缩放比例
|
||||||
// 入参: dest OFD跳转目标, pageH 页面高度
|
// 入参: dest OFD跳转目标, pageH 页面高度
|
||||||
// 返回: canvas.Rect PDF目标区域, bool 是否支持
|
// 返回: pdfgo.Destination 待关联输出页的目标, error 目标参数错误
|
||||||
func pdfDestRect(dest Dest, pageH float64) (canvas.Rect, bool) {
|
func pdfDestination(dest Dest, pageH float64) (pdfgo.Destination, error) {
|
||||||
|
dest = dest.effective()
|
||||||
|
if _, err := dest.attributes(); err != nil {
|
||||||
|
return pdfgo.Destination{}, err
|
||||||
|
}
|
||||||
|
const scale = 72.0 / 25.4
|
||||||
|
result := pdfgo.Destination{Mode: pdfgo.Name(dest.Type)}
|
||||||
switch dest.Type {
|
switch dest.Type {
|
||||||
case "XYZ":
|
case "XYZ":
|
||||||
return canvas.Rect{X0: dest.Left, Y0: pageH - dest.Top, X1: dest.Left, Y1: pageH - dest.Top}, true
|
result.Parameters = pdfgo.Array{pdfgo.Real(dest.Left * scale), pdfgo.Real((pageH - dest.Top) * scale), pdfgo.Real(dest.Zoom)}
|
||||||
case "Fit":
|
|
||||||
return canvas.Rect{}, true
|
|
||||||
case "FitH":
|
case "FitH":
|
||||||
return canvas.Rect{Y0: pageH - dest.Top, Y1: pageH - dest.Top}, true
|
result.Parameters = pdfgo.Array{pdfgo.Real((pageH - dest.Top) * scale)}
|
||||||
case "FitV":
|
case "FitV":
|
||||||
return canvas.Rect{X0: dest.Left, X1: dest.Left}, true
|
result.Parameters = pdfgo.Array{pdfgo.Real(dest.Left * scale)}
|
||||||
case "FitR":
|
case "FitR":
|
||||||
return canvas.Rect{X0: dest.Left, Y0: pageH - dest.Bottom, X1: dest.Right, Y1: pageH - dest.Top}, true
|
result.Parameters = pdfgo.Array{pdfgo.Real(dest.Left * scale), pdfgo.Real((pageH - dest.Bottom) * scale), pdfgo.Real(dest.Right * scale), pdfgo.Real((pageH - dest.Top) * scale)}
|
||||||
}
|
}
|
||||||
return canvas.Rect{}, false
|
return result, nil
|
||||||
}
|
|
||||||
|
|
||||||
// pdfDestY 获取PDF大纲目标位置
|
|
||||||
// 入参: dest OFD跳转目标, pageH 页面高度
|
|
||||||
// 返回: float64 PDF纵坐标
|
|
||||||
func pdfDestY(dest Dest, pageH float64) float64 {
|
|
||||||
switch dest.Type {
|
|
||||||
case "XYZ", "FitH", "FitR":
|
|
||||||
return pageH - dest.Top
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
@@ -19,34 +19,48 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/xiaoqidun/pdfgo"
|
"github.com/xiaoqidun/pdfgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// preserveImages 一次恢复图像精度和链接区域,二值透明采用标准Matte预混合
|
// writePDF 合并图像、导航修正及压缩,直接写出最终PDF
|
||||||
// 入参: ctx 取消上下文, data 后端PDF数据
|
// 入参: ctx 取消上下文, data 后端PDF数据, writer 输出流, optimization 优化配置
|
||||||
// 返回: []byte 保留图像及导航信息的PDF数据, error 资源或写入错误
|
// 返回: error 资源、压缩或写入错误
|
||||||
func (r *pdfRenderer) preserveImages(ctx context.Context, data []byte) ([]byte, error) {
|
func (r *pdfRenderer) writePDF(ctx context.Context, data []byte, writer io.Writer, optimization pdfgo.OptimizeOptions) error {
|
||||||
if r.imageError != nil {
|
if r.imageError != nil {
|
||||||
return nil, r.imageError
|
return r.imageError
|
||||||
}
|
}
|
||||||
exactLinks := r.navigation != nil && r.navigation.exactLinks
|
exactLinks := r.navigation != nil && r.navigation.exactLinks
|
||||||
if !r.exactImages && !exactLinks {
|
if !r.exactImages && !exactLinks && optimization.Compression.Mode == CompressionUnchanged {
|
||||||
return data, nil
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n, err := writer.Write(data)
|
||||||
|
if err == nil && n != len(data) {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
reader, err := pdfgo.NewReader(bytes.NewReader(data), int64(len(data)))
|
reader, err := pdfgo.NewReader(bytes.NewReader(data), int64(len(data)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
if !r.exactImages && !exactLinks {
|
||||||
|
_, err := reader.OptimizeTo(ctx, writer, optimization)
|
||||||
|
return err
|
||||||
|
}
|
||||||
images := make(map[pdfgo.Reference]image.Image)
|
images := make(map[pdfgo.Reference]image.Image)
|
||||||
links := make(map[pdfgo.AnnotationLocation]pdfgo.LinkRegion)
|
links := make(map[pdfgo.AnnotationLocation]pdfgo.LinkRegion)
|
||||||
|
references := make([]pdfgo.Reference, len(r.images))
|
||||||
pages := 0
|
pages := 0
|
||||||
err = reader.WalkPages(ctx, func(index int, page *pdfgo.Page) error {
|
err = reader.WalkPages(ctx, func(index int, page *pdfgo.Page) error {
|
||||||
if index >= len(r.images) {
|
if index >= len(r.images) {
|
||||||
return fmt.Errorf("unexpected PDF output page")
|
return fmt.Errorf("unexpected PDF output page")
|
||||||
}
|
}
|
||||||
pages++
|
pages++
|
||||||
|
references[index] = page.Reference
|
||||||
if exactLinks {
|
if exactLinks {
|
||||||
annotations, err := page.AnnotationsContext(ctx)
|
annotations, err := page.AnnotationsContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -119,27 +133,30 @@ func (r *pdfRenderer) preserveImages(ctx context.Context, data []byte) ([]byte,
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
if pages != len(r.images) {
|
if pages != len(r.images) {
|
||||||
return nil, fmt.Errorf("PDF output page missing")
|
return fmt.Errorf("PDF output page missing")
|
||||||
|
}
|
||||||
|
options := pdfgo.RewriteOptions{Images: images, ImageOptions: pdfgo.ImageWriteOptions{PreblendBinary: true}, LinkRegions: links, Optimization: optimization}
|
||||||
|
if exactLinks {
|
||||||
|
if err := r.navigation.destinations(ctx, reader, references, &options); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for ref, original := range images {
|
for ref, original := range images {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
pixels, err := imagePixelData(original)
|
pixels, err := imagePixelData(original)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
if pixels == nil || pixels.Bounds() != original.Bounds() {
|
if pixels == nil || pixels.Bounds() != original.Bounds() {
|
||||||
return nil, fmt.Errorf("PDF output image pixel bounds differ")
|
return fmt.Errorf("PDF output image pixel bounds differ")
|
||||||
}
|
}
|
||||||
images[ref] = pixels
|
images[ref] = pixels
|
||||||
}
|
}
|
||||||
var result bytes.Buffer
|
_, err = reader.RewriteTo(ctx, writer, options)
|
||||||
if _, err := reader.RewriteTo(ctx, &result, pdfgo.RewriteOptions{Images: images, ImageOptions: pdfgo.ImageWriteOptions{PreblendBinary: true}, LinkRegions: links}); err != nil {
|
return err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user