mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
012569dbc0
commit
1945ea355a
7 files changed
+128
-8
No files matched your search
@@ -10,7 +10,7 @@ require (
|
|||||||
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
||||||
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1
|
github.com/xiaoqidun/jbig2 v1.1.1
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261002153346-125ae06f23bf
|
github.com/xiaoqidun/pdfgo v0.0.0-20261002164637-41541220b1c3
|
||||||
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/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
|
|||||||
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
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 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261002153346-125ae06f23bf h1:AjfoqpGGjeyWFH1ikxmZJ9n+hG1Qaqolaqlv9kGX91w=
|
github.com/xiaoqidun/pdfgo v0.0.0-20261002164637-41541220b1c3 h1:mrWCBsxLuBf1sjeEK9r+1KZa5WpER5J3sDPVicajM1E=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261002153346-125ae06f23bf/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
github.com/xiaoqidun/pdfgo v0.0.0-20261002164637-41541220b1c3/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
||||||
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=
|
||||||
|
|||||||
+13
-2
@@ -35,7 +35,8 @@ var ErrPDFPassword = pdfgo.ErrPassword
|
|||||||
// RendererOptions复用渲染器字体来源和后端配置,Backends优先于其中的后端设置
|
// RendererOptions复用渲染器字体来源和后端配置,Backends优先于其中的后端设置
|
||||||
// RasterDPI指定局部透明效果合成精度,0沿用渲染器DPI,默认300dpi,Strict禁用局部合成
|
// RasterDPI指定局部透明效果合成精度,0沿用渲染器DPI,默认300dpi,Strict禁用局部合成
|
||||||
// OnProgress按open、pages、convert及write.*阶段报告进度,total为0表示总量未知
|
// OnProgress按open、pages、convert及write.*阶段报告进度,total为0表示总量未知
|
||||||
// ResolveFile读取PDF引用的外部媒体和附件,默认不访问网络或本地路径
|
// ResolveFile读取PDF引用的外部媒体、附件和页面,默认不访问网络或本地路径
|
||||||
|
// ResolveReference可提供已解密的引用页面,目标阅读器由调用方维护
|
||||||
type PDFImportOptions struct {
|
type PDFImportOptions struct {
|
||||||
Password []byte
|
Password []byte
|
||||||
PasswordUTF8 bool
|
PasswordUTF8 bool
|
||||||
@@ -46,6 +47,7 @@ type PDFImportOptions struct {
|
|||||||
Strict bool
|
Strict bool
|
||||||
RasterDPI float64
|
RasterDPI float64
|
||||||
ResolveFile pdfgo.FileResolver
|
ResolveFile pdfgo.FileResolver
|
||||||
|
ResolveReference pdfgo.ReferenceResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
// PDFImportReport 汇总转换页数、对象数、链接数和转换警告
|
// PDFImportReport 汇总转换页数、对象数、链接数和转换警告
|
||||||
@@ -91,6 +93,10 @@ type pdfImporter struct {
|
|||||||
compositeSpace *pdfgo.ColorSpace
|
compositeSpace *pdfgo.ColorSpace
|
||||||
annotationData []byte
|
annotationData []byte
|
||||||
resolveFile pdfgo.FileResolver
|
resolveFile pdfgo.FileResolver
|
||||||
|
resolveReference pdfgo.ReferenceResolver
|
||||||
|
referenceReaders map[[32]byte]*pdfgo.Reader
|
||||||
|
referenceStreams map[*pdfgo.Stream]*pdfgo.Reader
|
||||||
|
referencePages map[pdfReferencePageKey]*pdfgo.Page
|
||||||
halftoneWarnings map[*pdfgo.Halftone]bool
|
halftoneWarnings map[*pdfgo.Halftone]bool
|
||||||
transferBackdrop bool
|
transferBackdrop bool
|
||||||
}
|
}
|
||||||
@@ -137,6 +143,11 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
|
|||||||
importer.report.Warnings = diagnostics
|
importer.report.Warnings = diagnostics
|
||||||
importer.pageIndexes = make(map[pdfgo.Reference]int)
|
importer.pageIndexes = make(map[pdfgo.Reference]int)
|
||||||
importer.resolveFile = options.ResolveFile
|
importer.resolveFile = options.ResolveFile
|
||||||
|
importer.resolveReference = options.ResolveReference
|
||||||
|
importer.referenceReaders = make(map[[32]byte]*pdfgo.Reader)
|
||||||
|
importer.referenceStreams = make(map[*pdfgo.Stream]*pdfgo.Reader)
|
||||||
|
importer.referencePages = make(map[pdfReferencePageKey]*pdfgo.Page)
|
||||||
|
defer importer.closeReferenceReaders()
|
||||||
importer.halftoneWarnings = make(map[*pdfgo.Halftone]bool)
|
importer.halftoneWarnings = make(map[*pdfgo.Halftone]bool)
|
||||||
if security := reader.Encryption(); security != nil && !security.Owner && security.Permissions&0xf3c != 0xf3c {
|
if security := reader.Encryption(); security != nil && !security.Owner && security.Permissions&0xf3c != 0xf3c {
|
||||||
if options.Strict {
|
if options.Strict {
|
||||||
@@ -657,7 +668,7 @@ func (p *pdfImporter) tilingPattern(source *pdfgo.TilingPattern, base pdfgo.Pain
|
|||||||
cell.matrix = pdfgo.Matrix{unit, 0, 0, -unit, -box.XMin * unit, box.YMax * unit}
|
cell.matrix = pdfgo.Matrix{unit, 0, 0, -unit, -box.XMin * unit, box.YMax * unit}
|
||||||
cell.pageWidth, cell.pageHeight = width*unit, height*unit
|
cell.pageWidth, cell.pageHeight = width*unit, height*unit
|
||||||
cell.objects, cell.pendingPath = nil, nil
|
cell.objects, cell.pendingPath = nil, nil
|
||||||
visitor := pdfgo.Visitor{Path: cell.path, Text: cell.text, Image: cell.image}
|
visitor := pdfgo.Visitor{Path: cell.path, Text: cell.text, Image: cell.image, Reference: p.referencePage}
|
||||||
visitor.Group = func(mark pdfgo.GroupMark, walk func(pdfgo.Visitor) error) error {
|
visitor.Group = func(mark pdfgo.GroupMark, walk func(pdfgo.Visitor) error) error {
|
||||||
return cell.group(mark, walk, visitor)
|
return cell.group(mark, walk, visitor)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ func (p *pdfImporter) collectCompositeNodes(walk func(pdfgo.Visitor) error) ([]p
|
|||||||
var collect func(func(pdfgo.Visitor) error) ([]pdfCompositeNode, error)
|
var collect func(func(pdfgo.Visitor) error) ([]pdfCompositeNode, error)
|
||||||
collect = func(walk func(pdfgo.Visitor) error) ([]pdfCompositeNode, error) {
|
collect = func(walk func(pdfgo.Visitor) error) ([]pdfCompositeNode, error) {
|
||||||
var nodes []pdfCompositeNode
|
var nodes []pdfCompositeNode
|
||||||
v := pdfgo.Visitor{Warning: p.warning}
|
v := pdfgo.Visitor{Warning: p.warning, Reference: p.referencePage}
|
||||||
v.Path = func(mark pdfgo.PathMark) error {
|
v.Path = func(mark pdfgo.PathMark) error {
|
||||||
if err := p.halftone(mark.Style); err != nil {
|
if err := p.halftone(mark.Style); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+5
-1
@@ -93,6 +93,8 @@ func pdfOpaqueVisitor(visitor pdfgo.Visitor) pdfgo.Visitor {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
v := pdfgo.Visitor{
|
v := pdfgo.Visitor{
|
||||||
|
Reference: visitor.Reference,
|
||||||
|
OptionalContent: visitor.OptionalContent,
|
||||||
Path: func(m pdfgo.PathMark) error { return check(m.Style, m.Fill, m.Stroke) },
|
Path: func(m pdfgo.PathMark) error { return check(m.Style, m.Fill, m.Stroke) },
|
||||||
Text: func(m pdfgo.TextMark) error {
|
Text: func(m pdfgo.TextMark) error {
|
||||||
return check(m.Style, m.Mode%4 == 0 || m.Mode%4 == 2, m.Mode%4 == 1 || m.Mode%4 == 2)
|
return check(m.Style, m.Mode%4 == 0 || m.Mode%4 == 2, m.Mode%4 == 1 || m.Mode%4 == 2)
|
||||||
@@ -134,6 +136,8 @@ func (p *pdfImporter) groupPaths(mark pdfgo.GroupMark, walk func(pdfgo.Visitor)
|
|||||||
opaque := true
|
opaque := true
|
||||||
scale := math.Hypot(p.matrix[0], p.matrix[1])
|
scale := math.Hypot(p.matrix[0], p.matrix[1])
|
||||||
collect := pdfgo.Visitor{
|
collect := pdfgo.Visitor{
|
||||||
|
Reference: visitor.Reference,
|
||||||
|
OptionalContent: visitor.OptionalContent,
|
||||||
Warning: visitor.Warning,
|
Warning: visitor.Warning,
|
||||||
Text: func(pdfgo.TextMark) error { return errPDFGroupRaster },
|
Text: func(pdfgo.TextMark) error { return errPDFGroupRaster },
|
||||||
Image: func(pdfgo.ImageMark) error { return errPDFGroupRaster },
|
Image: func(pdfgo.ImageMark) error { return errPDFGroupRaster },
|
||||||
@@ -297,7 +301,7 @@ func (p *pdfImporter) rasterGroup(mark pdfgo.GroupMark, walk func(pdfgo.Visitor)
|
|||||||
// visitor 绑定当前导入器的图元与透明组访问函数
|
// visitor 绑定当前导入器的图元与透明组访问函数
|
||||||
// 返回: pdfgo.Visitor 页面访问器
|
// 返回: pdfgo.Visitor 页面访问器
|
||||||
func (p *pdfImporter) visitor() pdfgo.Visitor {
|
func (p *pdfImporter) visitor() pdfgo.Visitor {
|
||||||
v := pdfgo.Visitor{Path: p.path, Text: p.text, Image: p.image, Warning: p.warning}
|
v := pdfgo.Visitor{Path: p.path, Text: p.text, Image: p.image, Warning: p.warning, Reference: p.referencePage}
|
||||||
v.Group = func(mark pdfgo.GroupMark, walk func(pdfgo.Visitor) error) error { return p.group(mark, walk, v) }
|
v.Group = func(mark pdfgo.GroupMark, walk func(pdfgo.Visitor) error) error { return p.group(mark, walk, v) }
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -188,7 +188,7 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
|||||||
if err := p.flushPath(); err != nil {
|
if err := p.flushPath(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
visitor := pdfgo.Visitor{Path: p.path, Image: p.image, Warning: p.warning}
|
visitor := pdfgo.Visitor{Path: p.path, Image: p.image, Warning: p.warning, Reference: p.referencePage}
|
||||||
visitor.Group = func(group pdfgo.GroupMark, walk func(pdfgo.Visitor) error) error {
|
visitor.Group = func(group pdfgo.GroupMark, walk func(pdfgo.Visitor) error) error {
|
||||||
return p.group(group, walk, visitor)
|
return p.group(group, walk, visitor)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/xiaoqidun/pdfgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pdfReferencePageKey 区分目标文档、索引和文本标签,避免重复遍历页面树
|
||||||
|
type pdfReferencePageKey struct {
|
||||||
|
reader *pdfgo.Reader
|
||||||
|
index int64
|
||||||
|
label string
|
||||||
|
labeled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// referencePage 解析引用目标,目标不可用时按PDF标准保留代理内容
|
||||||
|
// 入参: ctx 取消上下文, source 来源文档, reference 引用目标
|
||||||
|
// 返回: *pdfgo.Page 目标页面,不可用时为空, error 取消或调用方解析错误
|
||||||
|
func (p *pdfImporter) referencePage(ctx context.Context, source *pdfgo.Reader, reference pdfgo.ReferenceXObject) (*pdfgo.Page, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if p.resolveReference != nil {
|
||||||
|
return p.resolveReference(ctx, source, reference)
|
||||||
|
}
|
||||||
|
reader := p.referenceStreams[reference.File.Embedded]
|
||||||
|
if reader == nil {
|
||||||
|
if reference.File.Embedded == nil && p.resolveFile == nil {
|
||||||
|
return p.referenceProxy(ctx, fmt.Errorf("external reference file unavailable"))
|
||||||
|
}
|
||||||
|
data, err := source.ReadFileData(ctx, reference.File, p.resolveFile)
|
||||||
|
if err != nil {
|
||||||
|
return p.referenceProxy(ctx, err)
|
||||||
|
}
|
||||||
|
key := sha256.Sum256(data)
|
||||||
|
reader = p.referenceReaders[key]
|
||||||
|
if reader == nil {
|
||||||
|
reader, err = pdfgo.NewReaderWithOptions(bytes.NewReader(data), int64(len(data)), pdfgo.ReaderOptions{Warning: p.warning})
|
||||||
|
if err != nil {
|
||||||
|
return p.referenceProxy(ctx, err)
|
||||||
|
}
|
||||||
|
p.referenceReaders[key] = reader
|
||||||
|
}
|
||||||
|
if reference.File.Embedded != nil {
|
||||||
|
p.referenceStreams[reference.File.Embedded] = reader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key := pdfReferencePageKey{reader: reader, index: reference.PageIndex}
|
||||||
|
if reference.PageLabel != nil {
|
||||||
|
key.index, key.label, key.labeled = 0, *reference.PageLabel, true
|
||||||
|
}
|
||||||
|
if page := p.referencePages[key]; page != nil {
|
||||||
|
return page, ctx.Err()
|
||||||
|
}
|
||||||
|
page, err := reader.ResolveReferencePage(ctx, reference)
|
||||||
|
if err != nil {
|
||||||
|
return p.referenceProxy(ctx, err)
|
||||||
|
}
|
||||||
|
p.referencePages[key] = page
|
||||||
|
return page, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// referenceProxy 记录目标不可用原因,不将合法的代理绘制视为转换失败
|
||||||
|
// 入参: ctx 取消上下文, cause 目标不可用原因
|
||||||
|
// 返回: *pdfgo.Page 空目标, error 取消错误
|
||||||
|
func (p *pdfImporter) referenceProxy(ctx context.Context, cause error) (*pdfgo.Page, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if errors.Is(cause, context.Canceled) || errors.Is(cause, context.DeadlineExceeded) {
|
||||||
|
return nil, cause
|
||||||
|
}
|
||||||
|
if p.warning != nil {
|
||||||
|
p.warning(pdfgo.Diagnostic{Message: "reference XObject proxy retained: " + cause.Error()})
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeReferenceReaders 释放本次转换打开的引用文档,不关闭调用方提供的阅读器
|
||||||
|
func (p *pdfImporter) closeReferenceReaders() {
|
||||||
|
for _, reader := range p.referenceReaders {
|
||||||
|
reader.Close()
|
||||||
|
}
|
||||||
|
p.referenceReaders, p.referenceStreams = nil, nil
|
||||||
|
p.referencePages = nil
|
||||||
|
}
|
||||||
Reference in new issue
Block a user