Files
ofdgo/ofdgo_pdf_link.go
T

158 lines
4.7 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 (
"context"
"fmt"
"github.com/xiaoqidun/pdfgo"
)
// annotations 将链接和签名外观按页面顺序转换为OFD对象
// 入参: ctx 取消上下文, page PDF页面
// 返回: error 错误信息
func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page) error {
annotations, err := page.Annotations()
if err != nil {
return err
}
for _, annotation := range annotations {
dict := annotation.Dictionary
if annotation.Subtype == "Widget" {
if err := p.signatureWidget(ctx, page, annotation); err != nil {
return err
}
continue
}
if annotation.Subtype != "Link" {
return &pdfgo.UnsupportedError{Feature: "annotation " + string(annotation.Subtype)}
}
for _, key := range []pdfgo.Name{"AP", "AA", "BS", "QuadPoints", "OC"} {
if dict[key] != nil {
return &pdfgo.UnsupportedError{Feature: fmt.Sprintf("link annotation field %q", key)}
}
}
if value, err := p.reader.Resolve(dict["F"]); err != nil {
return err
} else if value != nil {
flags, ok := value.(pdfgo.Integer)
if !ok {
return fmt.Errorf("invalid PDF annotation flags")
}
if flags != 0 && flags != 4 {
return &pdfgo.UnsupportedError{Feature: "link annotation flags"}
}
}
border, err := p.reader.Resolve(dict["Border"])
if err != nil {
return err
}
array, ok := border.(pdfgo.Array)
if !ok || len(array) != 3 || (array[2] != pdfgo.Integer(0) && array[2] != pdfgo.Real(0)) {
return &pdfgo.UnsupportedError{Feature: "visible link border"}
}
action := Action{Event: "CLICK"}
target := dict["Dest"]
if dict["A"] != nil {
value, err := p.reader.Resolve(dict["A"])
if err != nil {
return err
}
a, ok := value.(pdfgo.Dictionary)
if !ok {
return fmt.Errorf("invalid PDF link action")
}
if a["Next"] != nil {
return &pdfgo.UnsupportedError{Feature: "chained link actions"}
}
switch a["S"] {
case pdfgo.Name("GoTo"):
target = a["D"]
case pdfgo.Name("URI"):
value, err := p.reader.Resolve(a["URI"])
if err != nil {
return err
}
uri, ok := value.(pdfgo.String)
if !ok {
return fmt.Errorf("invalid PDF URI")
}
isMap, err := p.reader.Resolve(a["IsMap"])
if err != nil {
return err
}
if isMap != nil && isMap != pdfgo.Boolean(false) && isMap != pdfgo.Boolean(true) {
return fmt.Errorf("invalid PDF URI image map flag")
}
if isMap == pdfgo.Boolean(true) {
return &pdfgo.UnsupportedError{Feature: "URI image map"}
}
base, err := p.reader.BaseURI()
if err != nil {
return err
}
action.URI = &URI{URI: string(uri), Base: base}
default:
return &pdfgo.UnsupportedError{Feature: "link action"}
}
}
if action.URI == nil {
destination, err := p.reader.ReadDestination(target)
if err != nil {
return err
}
targetPage := p.pages[destination.Page]
if targetPage == nil {
return fmt.Errorf("PDF destination page missing")
}
matrix, _, _ := pdfPageMatrix(targetPage)
dest := &Dest{Type: string(destination.Mode), PageID: p.pageIDs[destination.Page]}
if destination.Mode == "XYZ" {
values := [3]float64{}
for n, v := range destination.Parameters {
switch v := v.(type) {
case pdfgo.Integer:
values[n] = float64(v)
case pdfgo.Real:
values[n] = float64(v)
case nil:
if n < 2 {
return &pdfgo.UnsupportedError{Feature: "retained destination coordinates"}
}
}
}
point := matrix.Apply(pdfgo.Point{X: values[0], Y: values[1]})
dest.Left, dest.Top, dest.Zoom = point.X, point.Y, values[2]
} else if destination.Mode != "Fit" {
return &pdfgo.UnsupportedError{Feature: "destination mode " + string(destination.Mode)}
}
action.Goto = &Goto{Dest: dest}
}
b := annotation.Rect
box := pdfBounds([]pdfgo.Point{p.matrix.Apply(pdfgo.Point{X: b.XMin, Y: b.YMin}), p.matrix.Apply(pdfgo.Point{X: b.XMax, Y: b.YMax})})
object, err := NewShape(ShapeRectangle, box)
if err != nil {
return err
}
no := false
object.Fill, object.Stroke = &no, &no
object.Actions = []Action{action}
p.objects = append(p.objects, GraphicObject{Type: "PathObject", PathObject: object})
p.report.Links++
}
return nil
}