mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
1 parent
035bbfb079
commit
73105646aa
4 files changed
+150
-95
No files matched your search
@@ -9,7 +9,7 @@ require (
|
||||
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
||||
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
||||
github.com/xiaoqidun/jbig2 v1.1.1
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928103444-c1e05032dc3a
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928111239-49265df4c04c
|
||||
golang.org/x/crypto v0.57.0
|
||||
golang.org/x/image v0.46.0
|
||||
golang.org/x/text v0.42.0
|
||||
|
||||
@@ -62,8 +62,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
|
||||
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
|
||||
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928103444-c1e05032dc3a h1:ROTJKhSKiF4LHufA5UjBWwGQimvWQV8hU9SxsrkeTWE=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928103444-c1e05032dc3a/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928111239-49265df4c04c h1:AkN9yw2b5LXinOBkWy/Wppdu4AtqQ2j6M4AYxlThL/A=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928111239-49265df4c04c/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||
|
||||
@@ -75,6 +75,7 @@ type pdfImporter struct {
|
||||
objects []GraphicObject
|
||||
pages map[pdfgo.Reference]*pdfgo.Page
|
||||
pageIDs map[pdfgo.Reference]string
|
||||
pageIndexes map[pdfgo.Reference]int
|
||||
imageIDs map[pdfImageKey]string
|
||||
maskClips map[*pdfgo.SoftMask]pdfgo.Path
|
||||
pageBox pdfgo.Rectangle
|
||||
@@ -128,6 +129,7 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
|
||||
editor.fontDirs, editor.fontFS = renderer.FontSources()
|
||||
importer := pdfImporter{ctx: ctx, reader: reader, editor: editor, renderer: renderer, rasterDPI: renderer.DPI, fontIDs: map[*pdfgo.Font]string{}, fontMetrics: map[string]FontMetrics{}, pages: map[pdfgo.Reference]*pdfgo.Page{}, pageIDs: map[pdfgo.Reference]string{}}
|
||||
importer.report.Warnings = diagnostics
|
||||
importer.pageIndexes = make(map[pdfgo.Reference]int)
|
||||
if security := reader.Encryption(); security != nil && !security.Owner && security.Permissions&0xf3c != 0xf3c {
|
||||
if options.Strict {
|
||||
return nil, PDFImportReport{}, &pdfgo.UnsupportedError{Feature: "PDF access permission conversion"}
|
||||
@@ -152,6 +154,7 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
|
||||
}
|
||||
importer.pages[page.Reference] = page
|
||||
importer.pageIDs[page.Reference] = editor.pages[index].ID
|
||||
importer.pageIndexes[page.Reference] = index
|
||||
if options.OnProgress != nil {
|
||||
return options.OnProgress("pages", index+1, 0)
|
||||
}
|
||||
|
||||
+144
-92
@@ -65,6 +65,22 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
||||
continue
|
||||
}
|
||||
dict := annotation.Dictionary
|
||||
if dict["OC"] != nil {
|
||||
if strict {
|
||||
return &pdfgo.UnsupportedError{Feature: "annotation optional content conversion"}
|
||||
}
|
||||
visible, err := p.reader.OptionalContentVisible(dict["OC"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.warning(pdfgo.Diagnostic{Page: p.page + 1, Message: "PDF annotation optional content default visibility applied; layer switching not transferred"})
|
||||
if !visible {
|
||||
continue
|
||||
}
|
||||
dict = maps.Clone(dict)
|
||||
delete(dict, "OC")
|
||||
annotation.Dictionary = dict
|
||||
}
|
||||
if annotation.Subtype == "Text" {
|
||||
value, err := p.reader.Resolve(dict["Open"])
|
||||
if err != nil {
|
||||
@@ -169,24 +185,121 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
||||
if annotation.Subtype != "Link" {
|
||||
return &pdfgo.UnsupportedError{Feature: "annotation " + string(annotation.Subtype)}
|
||||
}
|
||||
for _, key := range []pdfgo.Name{"AA", "OC"} {
|
||||
if dict[key] != nil {
|
||||
return &pdfgo.UnsupportedError{Feature: fmt.Sprintf("link annotation field %q", key)}
|
||||
if dict["AA"] != nil {
|
||||
return &pdfgo.UnsupportedError{Feature: "link annotation field \"AA\""}
|
||||
}
|
||||
}
|
||||
action := Action{Event: "CLICK"}
|
||||
target := dict["Dest"]
|
||||
if dict["A"] != nil {
|
||||
value, err := p.reader.Resolve(dict["A"])
|
||||
var actions []Action
|
||||
currentPage := p.page
|
||||
appendAction := func(dict pdfgo.Dictionary) error {
|
||||
current := annotation
|
||||
current.Dictionary = dict
|
||||
action, err := p.linkAction(current, strict, ¤tPage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if action != nil {
|
||||
actions = append(actions, *action)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if dict["A"] == nil {
|
||||
if err := appendAction(dict); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := p.reader.WalkActions(ctx, dict["A"], func(info pdfgo.ActionInfo) error {
|
||||
current := maps.Clone(dict)
|
||||
current["A"] = info.Dictionary
|
||||
return appendAction(current)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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})})
|
||||
region, err := p.linkRegion(annotation, box)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range actions {
|
||||
actions[i].Region = region
|
||||
}
|
||||
if err := p.appearanceAnnotation(ctx, page, annotation, actions...); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(actions) != 0 {
|
||||
p.report.Links++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// linkRegion 将PDF链接四边形映射为OFD动作区域,越出注解矩形时使用矩形
|
||||
// 入参: annotation PDF链接注解, box OFD对象边界
|
||||
// 返回: *Region 点击区域, error 解析错误
|
||||
func (p *pdfImporter) linkRegion(annotation pdfgo.Annotation, box Box) (*Region, error) {
|
||||
value, err := p.reader.Resolve(annotation.Dictionary["QuadPoints"])
|
||||
if err != nil || value == nil {
|
||||
return nil, err
|
||||
}
|
||||
array, ok := value.(pdfgo.Array)
|
||||
if !ok || len(array) == 0 || len(array)%8 != 0 {
|
||||
return nil, nil
|
||||
}
|
||||
region := &Region{Area: make([]RegionArea, 0, len(array)/8)}
|
||||
for start := 0; start < len(array); start += 8 {
|
||||
var points [4]pdfgo.Point
|
||||
for index := range points {
|
||||
coordinates := [2]float64{}
|
||||
for axis := range coordinates {
|
||||
resolved, err := p.reader.Resolve(array[start+index*2+axis])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch number := resolved.(type) {
|
||||
case pdfgo.Integer:
|
||||
coordinates[axis] = float64(number)
|
||||
case pdfgo.Real:
|
||||
coordinates[axis] = float64(number)
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
x, y := coordinates[0], coordinates[1]
|
||||
if !finite(x) || !finite(y) || x < annotation.Rect.XMin || x > annotation.Rect.XMax || y < annotation.Rect.YMin || y > annotation.Rect.YMax {
|
||||
return nil, nil
|
||||
}
|
||||
points[index] = p.matrix.Apply(pdfgo.Point{X: x, Y: y})
|
||||
}
|
||||
area := RegionArea{Start: pdfNumbers(points[0].X-box.X, points[0].Y-box.Y)}
|
||||
for _, point := range points[1:] {
|
||||
area.Command = append(area.Command, RegionCommand{Type: "Line", Point1: pdfNumbers(point.X-box.X, point.Y-box.Y)})
|
||||
}
|
||||
region.Area = append(region.Area, area)
|
||||
}
|
||||
return region, nil
|
||||
}
|
||||
|
||||
// linkAction 将单个链接动作转换为OFD动作,失效目标按导入策略报告
|
||||
// 入参: annotation 链接注解, strict 严格检查开关, currentPage 动作链当前页
|
||||
// 返回: *Action 动作,失效目标为空, error 转换错误
|
||||
func (p *pdfImporter) linkAction(annotation pdfgo.Annotation, strict bool, currentPage *int) (*Action, error) {
|
||||
dict := annotation.Dictionary
|
||||
action := Action{Event: "CLICK"}
|
||||
target, err := p.reader.Resolve(dict["Dest"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if target == nil && dict["A"] == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if dict["A"] != nil {
|
||||
value, err := p.reader.Resolve(dict["A"])
|
||||
if err != nil {
|
||||
return nil, 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"}
|
||||
return nil, fmt.Errorf("invalid PDF link action")
|
||||
}
|
||||
switch a["S"] {
|
||||
case pdfgo.Name("GoTo"):
|
||||
@@ -194,9 +307,9 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
||||
case pdfgo.Name("Named"):
|
||||
value, err := p.reader.Resolve(a["N"])
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
index := p.page
|
||||
index := *currentPage
|
||||
switch value {
|
||||
case pdfgo.Name("FirstPage"):
|
||||
index = 0
|
||||
@@ -207,35 +320,36 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
||||
case pdfgo.Name("PrevPage"):
|
||||
index = max(index-1, 0)
|
||||
default:
|
||||
return &pdfgo.UnsupportedError{Feature: "named link action"}
|
||||
return nil, &pdfgo.UnsupportedError{Feature: "named link action"}
|
||||
}
|
||||
action.Goto = &Goto{Dest: &Dest{Type: "XYZ", PageID: p.editor.pages[index].ID, OmitLeft: true, OmitTop: true, OmitZoom: true}}
|
||||
*currentPage = index
|
||||
case pdfgo.Name("URI"):
|
||||
value, err := p.reader.Resolve(a["URI"])
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
uri, ok := value.(pdfgo.String)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid PDF URI")
|
||||
return nil, fmt.Errorf("invalid PDF URI")
|
||||
}
|
||||
isMap, err := p.reader.Resolve(a["IsMap"])
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if isMap != nil && isMap != pdfgo.Boolean(false) && isMap != pdfgo.Boolean(true) {
|
||||
return fmt.Errorf("invalid PDF URI image map flag")
|
||||
return nil, fmt.Errorf("invalid PDF URI image map flag")
|
||||
}
|
||||
if isMap == pdfgo.Boolean(true) {
|
||||
return &pdfgo.UnsupportedError{Feature: "URI image map"}
|
||||
return nil, &pdfgo.UnsupportedError{Feature: "URI image map"}
|
||||
}
|
||||
base, err := p.reader.BaseURI()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
action.URI = &URI{URI: string(uri), Base: base}
|
||||
default:
|
||||
return &pdfgo.UnsupportedError{Feature: "link action"}
|
||||
return nil, &pdfgo.UnsupportedError{Feature: "link action"}
|
||||
}
|
||||
}
|
||||
if action.URI == nil && action.Goto == nil {
|
||||
@@ -243,23 +357,17 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
||||
if err != nil {
|
||||
if !strict && errors.Is(err, pdfgo.ErrDestinationNotFound) {
|
||||
p.report.Warnings = append(p.report.Warnings, pdfgo.Diagnostic{Page: p.page + 1, Message: fmt.Sprintf("%v; link omitted", err)})
|
||||
if err := p.appearanceAnnotation(ctx, page, annotation); err != nil {
|
||||
return err
|
||||
return nil, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
targetPage := p.pages[destination.Page]
|
||||
if targetPage == nil {
|
||||
if !strict {
|
||||
p.report.Warnings = append(p.report.Warnings, pdfgo.Diagnostic{Page: p.page + 1, Message: "PDF link targets a page outside the document page tree; link omitted"})
|
||||
if err := p.appearanceAnnotation(ctx, page, annotation); err != nil {
|
||||
return err
|
||||
return nil, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("PDF destination page missing")
|
||||
return nil, fmt.Errorf("PDF destination page missing")
|
||||
}
|
||||
matrix, _, _ := pdfPageMatrix(targetPage)
|
||||
dest := &Dest{Type: string(destination.Mode), PageID: p.pageIDs[destination.Page]}
|
||||
@@ -317,7 +425,7 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
||||
case pdfgo.Real:
|
||||
values[n] = float64(v)
|
||||
default:
|
||||
return fmt.Errorf("invalid PDF FitR destination coordinate")
|
||||
return nil, fmt.Errorf("invalid PDF FitR destination coordinate")
|
||||
}
|
||||
}
|
||||
bounds := pdfBounds([]pdfgo.Point{
|
||||
@@ -327,66 +435,10 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
||||
dest.Left, dest.Top = bounds.X, bounds.Y
|
||||
dest.Right, dest.Bottom = bounds.X+bounds.W, bounds.Y+bounds.H
|
||||
} else if destination.Mode != "Fit" {
|
||||
return &pdfgo.UnsupportedError{Feature: "destination mode " + string(destination.Mode)}
|
||||
return nil, &pdfgo.UnsupportedError{Feature: "destination mode " + string(destination.Mode)}
|
||||
}
|
||||
action.Goto = &Goto{Dest: dest}
|
||||
*currentPage = p.pageIndexes[destination.Page]
|
||||
}
|
||||
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})})
|
||||
action.Region, err = p.linkRegion(annotation, box)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.appearanceAnnotation(ctx, page, annotation, action); err != nil {
|
||||
return err
|
||||
}
|
||||
p.report.Links++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// linkRegion 将PDF链接四边形映射为OFD动作区域,越出注解矩形时使用矩形
|
||||
// 入参: annotation PDF链接注解, box OFD对象边界
|
||||
// 返回: *Region 点击区域, error 解析错误
|
||||
func (p *pdfImporter) linkRegion(annotation pdfgo.Annotation, box Box) (*Region, error) {
|
||||
value, err := p.reader.Resolve(annotation.Dictionary["QuadPoints"])
|
||||
if err != nil || value == nil {
|
||||
return nil, err
|
||||
}
|
||||
array, ok := value.(pdfgo.Array)
|
||||
if !ok || len(array) == 0 || len(array)%8 != 0 {
|
||||
return nil, nil
|
||||
}
|
||||
region := &Region{Area: make([]RegionArea, 0, len(array)/8)}
|
||||
for start := 0; start < len(array); start += 8 {
|
||||
var points [4]pdfgo.Point
|
||||
for index := range points {
|
||||
coordinates := [2]float64{}
|
||||
for axis := range coordinates {
|
||||
resolved, err := p.reader.Resolve(array[start+index*2+axis])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch number := resolved.(type) {
|
||||
case pdfgo.Integer:
|
||||
coordinates[axis] = float64(number)
|
||||
case pdfgo.Real:
|
||||
coordinates[axis] = float64(number)
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
x, y := coordinates[0], coordinates[1]
|
||||
if !finite(x) || !finite(y) || x < annotation.Rect.XMin || x > annotation.Rect.XMax || y < annotation.Rect.YMin || y > annotation.Rect.YMax {
|
||||
return nil, nil
|
||||
}
|
||||
points[index] = p.matrix.Apply(pdfgo.Point{X: x, Y: y})
|
||||
}
|
||||
area := RegionArea{Start: pdfNumbers(points[0].X-box.X, points[0].Y-box.Y)}
|
||||
for _, point := range points[1:] {
|
||||
area.Command = append(area.Command, RegionCommand{Type: "Line", Point1: pdfNumbers(point.X-box.X, point.Y-box.Y)})
|
||||
}
|
||||
region.Area = append(region.Area, area)
|
||||
}
|
||||
return region, nil
|
||||
return &action, nil
|
||||
}
|
||||
Reference in new issue
Block a user