forked from cucy/unipdf1
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoutline.go
300 lines (254 loc) · 8.16 KB
/
outline.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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package model
import (
"errors"
"fmt"
"github.com/unidoc/unipdf/v3/common"
"github.com/unidoc/unipdf/v3/core"
)
// OutlineDest represents the destination of an outline item.
// It holds the page and the position on the page an outline item points to.
type OutlineDest struct {
Page int64 `json:"page"`
Mode string `json:"mode"`
X float64 `json:"x"`
Y float64 `json:"y"`
Zoom float64 `json:"zoom"`
}
// NewOutlineDest returns a new outline destination which can be used
// with outline items.
func NewOutlineDest(page int64, x, y float64) OutlineDest {
return OutlineDest{
Page: page,
Mode: "XYZ",
X: x,
Y: y,
}
}
// newOutlineDestFromPdfObject creates a new outline destination from the
// specified PDF object.
func newOutlineDestFromPdfObject(o core.PdfObject, r *PdfReader) (*OutlineDest, error) {
// Validate input PDF object.
destArr, ok := core.GetArray(o)
if !ok {
return nil, errors.New("outline destination object must be an array")
}
destArrLen := destArr.Len()
if destArrLen < 2 {
return nil, fmt.Errorf("invalid outline destination array length: %d", destArrLen)
}
// Extract page number.
dest := &OutlineDest{Mode: "Fit"}
pageObj := destArr.Get(0)
if pageInd, ok := core.GetIndirect(pageObj); ok {
// Page object is provided. Identify page number using the reader.
if _, pageNum, err := r.PageFromIndirectObject(pageInd); err == nil {
dest.Page = int64(pageNum - 1)
}
} else if pageNum, ok := core.GetIntVal(pageObj); ok {
// Page number is provided.
dest.Page = int64(pageNum)
} else {
return nil, fmt.Errorf("invalid outline destination page: %T", pageObj)
}
// Extract magnification mode.
mode, ok := core.GetNameVal(destArr.Get(1))
if !ok {
common.Log.Debug("invalid outline destination magnification mode: %v", destArr.Get(1))
return dest, nil
}
// Parse magnification mode parameters.
// See section 12.3.2.2 "Explicit Destinations" (page 374).
switch mode {
// [pageObj|pageNum /Fit]
// [pageObj|pageNum /FitB]
case "Fit", "FitB":
// [pageObj|pageNum /FitH top]
// [pageObj|pageNum /FitBH top]
case "FitH", "FitBH":
if destArrLen > 2 {
dest.Y, _ = core.GetNumberAsFloat(core.TraceToDirectObject(destArr.Get(2)))
}
// [pageObj|pageNum /FitV left]
// [pageObj|pageNum /FitBV left]
case "FitV", "FitBV":
if destArrLen > 2 {
dest.X, _ = core.GetNumberAsFloat(core.TraceToDirectObject(destArr.Get(2)))
}
// [pageObj|pageNum /XYZ x y zoom]
case "XYZ":
if destArrLen > 4 {
dest.X, _ = core.GetNumberAsFloat(core.TraceToDirectObject(destArr.Get(2)))
dest.Y, _ = core.GetNumberAsFloat(core.TraceToDirectObject(destArr.Get(3)))
dest.Zoom, _ = core.GetNumberAsFloat(core.TraceToDirectObject(destArr.Get(4)))
}
default:
mode = "Fit"
}
dest.Mode = mode
return dest, nil
}
// ToPdfObject returns a PDF object representation of the outline destination.
func (od OutlineDest) ToPdfObject() core.PdfObject {
if od.Page < 0 || od.Mode == "" {
return core.MakeNull()
}
dest := core.MakeArray(
core.MakeInteger(od.Page),
core.MakeName(od.Mode),
)
// See section 12.3.2.2 "Explicit Destinations" (page 374).
switch od.Mode {
// [pageObj|pageNum /Fit]
// [pageObj|pageNum /FitB]
case "Fit", "FitB":
// [pageObj|pageNum /FitH top]
// [pageObj|pageNum /FitBH top]
case "FitH", "FitBH":
dest.Append(core.MakeFloat(od.Y))
// [pageObj|pageNum /FitV left]
// [pageObj|pageNum /FitBV left]
case "FitV", "FitBV":
dest.Append(core.MakeFloat(od.X))
// [pageObj|pageNum /XYZ x y zoom]
case "XYZ":
dest.Append(core.MakeFloat(od.X))
dest.Append(core.MakeFloat(od.Y))
dest.Append(core.MakeFloat(od.Zoom))
default:
dest.Set(1, core.MakeName("Fit"))
}
return dest
}
// Outline represents a PDF outline dictionary (Table 152 - p. 376).
// Currently, the Outline object can only be used to construct PDF outlines.
type Outline struct {
Entries []*OutlineItem `json:"entries,omitempty"`
}
// NewOutline returns a new outline instance.
func NewOutline() *Outline {
return &Outline{}
}
// Add appends a top level outline item to the outline.
func (o *Outline) Add(item *OutlineItem) {
o.Entries = append(o.Entries, item)
}
// Insert adds a top level outline item in the outline,
// at the specified index.
func (o *Outline) Insert(index uint, item *OutlineItem) {
l := uint(len(o.Entries))
if index > l {
index = l
}
o.Entries = append(o.Entries[:index], append([]*OutlineItem{item}, o.Entries[index:]...)...)
}
// Items returns all children outline items.
func (o *Outline) Items() []*OutlineItem {
return o.Entries
}
// ToPdfOutline returns a low level PdfOutline object, based on the current
// instance.
func (o *Outline) ToPdfOutline() *PdfOutline {
// Create outline.
outline := NewPdfOutline()
// Create outline items.
var outlineItems []*PdfOutlineItem
var prev *PdfOutlineItem
for _, item := range o.Entries {
outlineItem, _ := item.ToPdfOutlineItem()
outlineItem.Parent = &outline.PdfOutlineTreeNode
if prev != nil {
prev.Next = &outlineItem.PdfOutlineTreeNode
outlineItem.Prev = &prev.PdfOutlineTreeNode
}
outlineItems = append(outlineItems, outlineItem)
prev = outlineItem
}
// Add outline linked list properties.
lenOutlineItems := int64(len(outlineItems))
if lenOutlineItems > 0 {
outline.First = &outlineItems[0].PdfOutlineTreeNode
outline.Last = &outlineItems[lenOutlineItems-1].PdfOutlineTreeNode
outline.Count = &lenOutlineItems
}
return outline
}
// ToOutlineTree returns a low level PdfOutlineTreeNode object, based on
// the current instance.
func (o *Outline) ToOutlineTree() *PdfOutlineTreeNode {
return &o.ToPdfOutline().PdfOutlineTreeNode
}
// ToPdfObject returns a PDF object representation of the outline.
func (o *Outline) ToPdfObject() core.PdfObject {
return o.ToPdfOutline().ToPdfObject()
}
// OutlineItem represents a PDF outline item dictionary (Table 153 - pp. 376 - 377).
type OutlineItem struct {
Title string `json:"title"`
Dest OutlineDest `json:"dest"`
Entries []*OutlineItem `json:"entries,omitempty"`
}
// NewOutlineItem returns a new outline item instance.
func NewOutlineItem(title string, dest OutlineDest) *OutlineItem {
return &OutlineItem{
Title: title,
Dest: dest,
}
}
// Add appends an outline item as a child of the current outline item.
func (oi *OutlineItem) Add(item *OutlineItem) {
oi.Entries = append(oi.Entries, item)
}
// Insert adds an outline item as a child of the current outline item,
// at the specified index.
func (oi *OutlineItem) Insert(index uint, item *OutlineItem) {
l := uint(len(oi.Entries))
if index > l {
index = l
}
oi.Entries = append(oi.Entries[:index], append([]*OutlineItem{item}, oi.Entries[index:]...)...)
}
// Items returns all children outline items.
func (oi *OutlineItem) Items() []*OutlineItem {
return oi.Entries
}
// ToPdfOutlineItem returns a low level PdfOutlineItem object,
// based on the current instance.
func (oi *OutlineItem) ToPdfOutlineItem() (*PdfOutlineItem, int64) {
// Create outline item.
currItem := NewPdfOutlineItem()
currItem.Title = core.MakeEncodedString(oi.Title, true)
currItem.Dest = oi.Dest.ToPdfObject()
// Create outline items.
var outlineItems []*PdfOutlineItem
var lenDescendants int64
var prev *PdfOutlineItem
for _, item := range oi.Entries {
outlineItem, lenChildren := item.ToPdfOutlineItem()
outlineItem.Parent = &currItem.PdfOutlineTreeNode
if prev != nil {
prev.Next = &outlineItem.PdfOutlineTreeNode
outlineItem.Prev = &prev.PdfOutlineTreeNode
}
outlineItems = append(outlineItems, outlineItem)
lenDescendants += lenChildren
prev = outlineItem
}
// Add outline item linked list properties.
lenOutlineItems := len(outlineItems)
lenDescendants += int64(lenOutlineItems)
if lenOutlineItems > 0 {
currItem.First = &outlineItems[0].PdfOutlineTreeNode
currItem.Last = &outlineItems[lenOutlineItems-1].PdfOutlineTreeNode
currItem.Count = &lenDescendants
}
return currItem, lenDescendants
}
// ToPdfObject returns a PDF object representation of the outline item.
func (oi *OutlineItem) ToPdfObject() core.PdfObject {
outlineItem, _ := oi.ToPdfOutlineItem()
return outlineItem.ToPdfObject()
}