mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 15:06:46 +08:00
210 lines
6.1 KiB
Go
210 lines
6.1 KiB
Go
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package ofdgo
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/tdewolff/canvas"
|
|
"github.com/tdewolff/canvas/renderers/pdf"
|
|
)
|
|
|
|
// pdfPage PDF页面数据
|
|
type pdfPage struct {
|
|
Content *PageContent
|
|
Box Box
|
|
}
|
|
|
|
// pdfNavigation PDF导航信息
|
|
type pdfNavigation struct {
|
|
Anchor map[int][]pdfAnchor
|
|
Link map[int][]pdfLink
|
|
Outline map[int][]pdfOutline
|
|
nextID int
|
|
}
|
|
|
|
// pdfAnchor PDF跳转目标
|
|
type pdfAnchor struct {
|
|
Name string
|
|
Rect canvas.Rect
|
|
}
|
|
|
|
// pdfLink PDF链接
|
|
type pdfLink struct {
|
|
URI string
|
|
Rect canvas.Rect
|
|
}
|
|
|
|
// pdfOutline PDF大纲
|
|
type pdfOutline struct {
|
|
Name string
|
|
Level int
|
|
Y float64
|
|
}
|
|
|
|
// newPDFNavigation 创建PDF导航信息
|
|
// 入参: renderer 渲染器, doc 文档结构, pages 页面数据
|
|
// 返回: *pdfNavigation PDF导航信息
|
|
func newPDFNavigation(renderer *Renderer, doc *Document, pages []pdfPage) *pdfNavigation {
|
|
navigation := &pdfNavigation{
|
|
Anchor: make(map[int][]pdfAnchor),
|
|
Link: make(map[int][]pdfLink),
|
|
Outline: make(map[int][]pdfOutline),
|
|
}
|
|
pageIndex := make(map[string]int, len(pages))
|
|
for i, page := range pages {
|
|
pageIndex[page.Content.ID] = i
|
|
}
|
|
bookmarks := make(map[string]Dest)
|
|
if doc != nil {
|
|
for _, bookmark := range doc.Bookmarks.Bookmark {
|
|
bookmarks[bookmark.Name] = bookmark.Dest
|
|
}
|
|
}
|
|
for i, page := range pages {
|
|
sources := pageActionSources(page.Content, page.Box)
|
|
if renderer.RenderAnnotations {
|
|
sources = append(sources, annotationActionSources(renderer.Reader.Annots[page.Content.ID])...)
|
|
}
|
|
for _, source := range sources {
|
|
rect := pdfSourceRect(source.Box, page.Box.H)
|
|
for _, action := range source.Actions {
|
|
if action.Event != "CLICK" {
|
|
continue
|
|
}
|
|
navigation.addAction(i, rect, action, bookmarks, pageIndex, pages)
|
|
}
|
|
}
|
|
}
|
|
if doc != nil {
|
|
navigation.addOutlines(doc.Outlines.OutlineElem, 0, bookmarks, pageIndex, pages)
|
|
}
|
|
return navigation
|
|
}
|
|
|
|
// addAction 添加PDF动作
|
|
// 入参: 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 []pdfPage) {
|
|
if action.Goto != nil {
|
|
dest := gotoDest(action.Goto, bookmarks)
|
|
if dest == nil {
|
|
return
|
|
}
|
|
target, ok := pageIndex[dest.PageID]
|
|
if !ok {
|
|
return
|
|
}
|
|
targetRect, ok := pdfDestRect(*dest, pages[target].Box.H)
|
|
if !ok {
|
|
return
|
|
}
|
|
name := fmt.Sprintf("ofdgo-dest-%d", n.nextID)
|
|
n.nextID++
|
|
n.Anchor[target] = append(n.Anchor[target], pdfAnchor{Name: name, Rect: targetRect})
|
|
n.Link[page] = append(n.Link[page], pdfLink{URI: "#" + name, Rect: rect})
|
|
return
|
|
}
|
|
if action.URI != nil && action.URI.URI != "" {
|
|
n.Link[page] = append(n.Link[page], pdfLink{URI: resolveActionURI(*action.URI), Rect: rect})
|
|
}
|
|
}
|
|
|
|
// addOutlines 添加PDF大纲
|
|
// 入参: outlines 大纲节点, level 节点层级, bookmarks 书签, pageIndex 页面索引表, pages 页面数据
|
|
func (n *pdfNavigation) addOutlines(outlines []OutlineElem, level int, bookmarks map[string]Dest, pageIndex map[string]int, pages []pdfPage) {
|
|
for _, outline := range outlines {
|
|
if dest := outlineDest(outline, bookmarks); dest != nil {
|
|
if page, ok := pageIndex[dest.PageID]; ok {
|
|
n.Outline[page] = append(n.Outline[page], pdfOutline{
|
|
Name: outline.Title,
|
|
Level: level,
|
|
Y: pdfDestY(*dest, pages[page].Box.H),
|
|
})
|
|
}
|
|
}
|
|
n.addOutlines(outline.OutlineElem, level+1, bookmarks, pageIndex, pages)
|
|
}
|
|
}
|
|
|
|
// apply 应用PDF导航信息
|
|
// 入参: renderer PDF渲染器, page 页面索引
|
|
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] {
|
|
renderer.AddLink(link.URI, link.Rect)
|
|
}
|
|
for _, outline := range n.Outline[page] {
|
|
renderer.AddOutline(outline.Name, outline.Level, outline.Y)
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// pdfSourceRect 转换PDF动作区域
|
|
// 入参: box OFD区域, pageH 页面高度
|
|
// 返回: canvas.Rect PDF区域
|
|
func pdfSourceRect(box Box, pageH float64) canvas.Rect {
|
|
return canvas.RectFromSize(box.X, pageH-box.Y-box.H, box.W, box.H)
|
|
}
|
|
|
|
// pdfDestRect 转换PDF跳转目标
|
|
// 入参: dest OFD跳转目标, pageH 页面高度
|
|
// 返回: canvas.Rect PDF目标区域, bool 是否支持
|
|
func pdfDestRect(dest Dest, pageH float64) (canvas.Rect, bool) {
|
|
switch dest.Type {
|
|
case "XYZ":
|
|
return canvas.Rect{X0: dest.Left, Y0: pageH - dest.Top, X1: dest.Left, Y1: pageH - dest.Top}, true
|
|
case "Fit":
|
|
return canvas.Rect{}, true
|
|
case "FitH":
|
|
return canvas.Rect{Y0: pageH - dest.Top, Y1: pageH - dest.Top}, true
|
|
case "FitV":
|
|
return canvas.Rect{X0: dest.Left, X1: dest.Left}, true
|
|
case "FitR":
|
|
return canvas.Rect{X0: dest.Left, Y0: pageH - dest.Bottom, X1: dest.Right, Y1: pageH - dest.Top}, true
|
|
}
|
|
return canvas.Rect{}, false
|
|
}
|
|
|
|
// 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
|
|
}
|