-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathresource.go
161 lines (139 loc) · 4.66 KB
/
resource.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// seehuhn.de/go/pdf - a library for reading and writing PDF files
// Copyright (C) 2024 Jochen Voss <[email protected]>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package pdf
import (
"errors"
"fmt"
)
// Embedder represents a PDF resource (a font, image, pattern, etc.) which has
// not yet been associated with a specific PDF file.
type Embedder[T any] interface {
// Embed converts the Go representation of the object into a PDF object,
// corresponding to the PDF version of the output file.
//
// The first return value is the PDF representation of the object.
// If the object is embedded in the PDF file, this may be a reference.
//
// The second return value is a Go representation of the embedded object.
// In most cases, this value is not used and T can be set to [Unused].
Embed(rm *ResourceManager) (Native, T, error)
}
// Unused is a placeholder type for the second return value of the
// Embedder.Embed method, for when no Go representation of the
// embedded object is required.
type Unused struct{}
// ResourceManager helps to avoid duplicate resources in a PDF file.
// It is used to embed object implementing the [Embedder] interface.
// Each such object is embedded only once.
//
// Use the [ResourceManagerEmbed] function to embed resources.
//
// The ResourceManager must be closed with the [Close] method before the PDF
// file is closed.
type ResourceManager struct {
Out *Writer
embedded map[any]embRes
finishers []Finisher
isClosed bool
}
// NewResourceManager creates a new ResourceManager.
func NewResourceManager(w *Writer) *ResourceManager {
return &ResourceManager{
Out: w,
embedded: make(map[any]embRes),
}
}
type embRes struct {
Val Object
Emb any
}
type Finisher interface {
// TODO(voss): should the ResourceManager argument be removed?
Finish(*ResourceManager) error
}
// ResourceManagerEmbed embeds a resource in the PDF file.
//
// If the resource is already present in the file, the existing resource is
// returned.
//
// If the embedded type, T, implements [Finisher], the Finish() method will be
// called when the ResourceManager is closed.
//
// Once Go supports methods with type parameters, this function can be turned
// into a method on [ResourceManager].
func ResourceManagerEmbed[T any](rm *ResourceManager, r Embedder[T]) (Object, T, error) {
var zero T
if er, ok := rm.embedded[r]; ok {
return er.Val, er.Emb.(T), nil
}
if rm.isClosed {
return nil, zero, fmt.Errorf("resource manager is already closed")
}
val, emb, err := r.Embed(rm)
if err != nil {
return nil, zero, fmt.Errorf("failed to embed resource: %w", err)
}
rm.embedded[r] = embRes{Val: val, Emb: emb}
if finisher, ok := any(emb).(Finisher); ok {
rm.finishers = append(rm.finishers, finisher)
}
return val, emb, nil
}
// Close runs the Finish methods of all embedded resources where the Go
// representation implemented the [Finisher] interface.
//
// After Close has been called, the resource manager can no longer be used.
func (rm *ResourceManager) Close() error {
if rm.isClosed {
return nil
}
for len(rm.finishers) > 0 {
r := rm.finishers[0]
k := copy(rm.finishers, rm.finishers[1:])
rm.finishers = rm.finishers[:k]
if err := r.Finish(rm); err != nil {
return err
}
}
rm.isClosed = true
return nil
}
// A CycleChecker checks for infinite recursion in PDF objects.
type CycleChecker struct {
seen map[Reference]bool
}
// NewCycleChecker creates a new CycleChecker.
func NewCycleChecker() *CycleChecker {
return &CycleChecker{seen: make(map[Reference]bool)}
}
// Check checks whether the given object is part of a recursive structure. If
// the object is not a reference, nil is returned. If the object is a reference
// which has been seen before, ErrRecursiveStructure is returned. Otherwise,
// nil is returned and the reference is marked as seen.
func (s *CycleChecker) Check(obj Object) error {
ref, ok := obj.(Reference)
if !ok {
return nil
}
if s.seen[ref] {
return &MalformedFileError{Err: ErrCycle}
}
s.seen[ref] = true
return nil
}
var ErrCycle = &MalformedFileError{
Err: errors.New("cycle in recursive structure"),
}