mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 08:46:55 +08:00
1 parent
73472c5d4b
commit
77ffbe6a72
56 files changed
+2420
-1635
No files matched your search
@@ -0,0 +1,214 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// pdfRenderer PDF渲染器
|
||||
type pdfRenderer struct {
|
||||
*pdf.PDF
|
||||
glyphPaths map[*canvas.Path]*canvas.Path
|
||||
}
|
||||
|
||||
// glyphPath 复用相似变换下的PDF字形圆弧转换
|
||||
// 入参: path 缓存字形路径, matrix 字形变换
|
||||
// 返回: *canvas.Path PDF字形路径
|
||||
func (r *pdfRenderer) glyphPath(path *canvas.Path, matrix canvas.Matrix) *canvas.Path {
|
||||
if !matrix.IsSimilarity() {
|
||||
return path
|
||||
}
|
||||
if cached, ok := r.glyphPaths[path]; ok {
|
||||
return cached
|
||||
}
|
||||
converted := path.ReplaceArcs()
|
||||
r.glyphPaths[path] = converted
|
||||
return converted
|
||||
}
|
||||
|
||||
// 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导航信息, error 几何错误
|
||||
func newPDFNavigation(renderer *Renderer, doc *Document, pages []RenderDocumentPage) (*pdfNavigation, error) {
|
||||
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 := renderer.pageActionSources(page.Content, page.Box)
|
||||
if renderer.RenderAnnotations {
|
||||
sources = append(sources, renderer.annotationActionSources(renderer.Reader.Annots[page.Content.ID])...)
|
||||
}
|
||||
for _, source := range sources {
|
||||
for _, action := range source.Actions {
|
||||
if action.Event != "CLICK" {
|
||||
continue
|
||||
}
|
||||
box, _, err := renderer.actionLinkRegion(source, action)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if box.W <= 0 || box.H <= 0 {
|
||||
continue
|
||||
}
|
||||
rect := pdfSourceRect(box, page.Box.H)
|
||||
navigation.addAction(i, rect, action, bookmarks, pageIndex, pages)
|
||||
}
|
||||
}
|
||||
}
|
||||
if doc != nil {
|
||||
navigation.addOutlines(doc.Outlines.OutlineElem, 0, bookmarks, pageIndex, pages)
|
||||
}
|
||||
return navigation, nil
|
||||
}
|
||||
|
||||
// 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 []RenderDocumentPage) {
|
||||
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 []RenderDocumentPage) {
|
||||
for _, outline := range outlines {
|
||||
nextLevel := level
|
||||
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),
|
||||
})
|
||||
nextLevel++
|
||||
}
|
||||
}
|
||||
n.addOutlines(outline.OutlineElem, nextLevel, 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in new issue
Block a user