origamindee 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +89 -0
- data/COPYING.LESSER +165 -0
- data/README.md +131 -0
- data/bin/config/pdfcop.conf.yml +236 -0
- data/bin/pdf2pdfa +87 -0
- data/bin/pdf2ruby +333 -0
- data/bin/pdfcop +476 -0
- data/bin/pdfdecompress +97 -0
- data/bin/pdfdecrypt +91 -0
- data/bin/pdfencrypt +113 -0
- data/bin/pdfexplode +223 -0
- data/bin/pdfextract +277 -0
- data/bin/pdfmetadata +143 -0
- data/bin/pdfsh +12 -0
- data/bin/shell/console.rb +128 -0
- data/bin/shell/hexdump.rb +59 -0
- data/bin/shell/irbrc +69 -0
- data/examples/README.md +34 -0
- data/examples/attachments/attachment.rb +38 -0
- data/examples/attachments/nested_document.rb +51 -0
- data/examples/encryption/encryption.rb +28 -0
- data/examples/events/events.rb +72 -0
- data/examples/flash/flash.rb +37 -0
- data/examples/flash/helloworld.swf +0 -0
- data/examples/forms/javascript.rb +54 -0
- data/examples/forms/xfa.rb +115 -0
- data/examples/javascript/hello_world.rb +22 -0
- data/examples/javascript/js_emulation.rb +54 -0
- data/examples/loop/goto.rb +32 -0
- data/examples/loop/named.rb +33 -0
- data/examples/signature/signature.rb +65 -0
- data/examples/uri/javascript.rb +56 -0
- data/examples/uri/open-uri.rb +21 -0
- data/examples/uri/submitform.rb +47 -0
- data/lib/origami/3d.rb +364 -0
- data/lib/origami/acroform.rb +321 -0
- data/lib/origami/actions.rb +318 -0
- data/lib/origami/annotations.rb +711 -0
- data/lib/origami/array.rb +242 -0
- data/lib/origami/boolean.rb +90 -0
- data/lib/origami/catalog.rb +418 -0
- data/lib/origami/collections.rb +144 -0
- data/lib/origami/compound.rb +161 -0
- data/lib/origami/destinations.rb +252 -0
- data/lib/origami/dictionary.rb +192 -0
- data/lib/origami/encryption.rb +1084 -0
- data/lib/origami/extensions/fdf.rb +347 -0
- data/lib/origami/extensions/ppklite.rb +422 -0
- data/lib/origami/filespec.rb +197 -0
- data/lib/origami/filters/ascii.rb +211 -0
- data/lib/origami/filters/ccitt/tables.rb +267 -0
- data/lib/origami/filters/ccitt.rb +357 -0
- data/lib/origami/filters/crypt.rb +38 -0
- data/lib/origami/filters/dct.rb +54 -0
- data/lib/origami/filters/flate.rb +69 -0
- data/lib/origami/filters/jbig2.rb +57 -0
- data/lib/origami/filters/jpx.rb +47 -0
- data/lib/origami/filters/lzw.rb +170 -0
- data/lib/origami/filters/predictors.rb +292 -0
- data/lib/origami/filters/runlength.rb +129 -0
- data/lib/origami/filters.rb +364 -0
- data/lib/origami/font.rb +196 -0
- data/lib/origami/functions.rb +79 -0
- data/lib/origami/graphics/colors.rb +230 -0
- data/lib/origami/graphics/instruction.rb +98 -0
- data/lib/origami/graphics/path.rb +182 -0
- data/lib/origami/graphics/patterns.rb +174 -0
- data/lib/origami/graphics/render.rb +62 -0
- data/lib/origami/graphics/state.rb +149 -0
- data/lib/origami/graphics/text.rb +225 -0
- data/lib/origami/graphics/xobject.rb +918 -0
- data/lib/origami/graphics.rb +38 -0
- data/lib/origami/header.rb +75 -0
- data/lib/origami/javascript.rb +713 -0
- data/lib/origami/linearization.rb +330 -0
- data/lib/origami/metadata.rb +172 -0
- data/lib/origami/name.rb +135 -0
- data/lib/origami/null.rb +65 -0
- data/lib/origami/numeric.rb +181 -0
- data/lib/origami/obfuscation.rb +245 -0
- data/lib/origami/object.rb +760 -0
- data/lib/origami/optionalcontent.rb +183 -0
- data/lib/origami/outline.rb +54 -0
- data/lib/origami/outputintents.rb +85 -0
- data/lib/origami/page.rb +722 -0
- data/lib/origami/parser.rb +269 -0
- data/lib/origami/parsers/fdf.rb +56 -0
- data/lib/origami/parsers/pdf/lazy.rb +176 -0
- data/lib/origami/parsers/pdf/linear.rb +122 -0
- data/lib/origami/parsers/pdf.rb +118 -0
- data/lib/origami/parsers/ppklite.rb +57 -0
- data/lib/origami/pdf.rb +1108 -0
- data/lib/origami/reference.rb +134 -0
- data/lib/origami/signature.rb +702 -0
- data/lib/origami/stream.rb +705 -0
- data/lib/origami/string.rb +444 -0
- data/lib/origami/template/patterns.rb +56 -0
- data/lib/origami/template/widgets.rb +151 -0
- data/lib/origami/trailer.rb +190 -0
- data/lib/origami/tree.rb +62 -0
- data/lib/origami/version.rb +23 -0
- data/lib/origami/webcapture.rb +100 -0
- data/lib/origami/xfa/config.rb +453 -0
- data/lib/origami/xfa/connectionset.rb +146 -0
- data/lib/origami/xfa/datasets.rb +49 -0
- data/lib/origami/xfa/localeset.rb +42 -0
- data/lib/origami/xfa/package.rb +59 -0
- data/lib/origami/xfa/pdf.rb +73 -0
- data/lib/origami/xfa/signature.rb +42 -0
- data/lib/origami/xfa/sourceset.rb +43 -0
- data/lib/origami/xfa/stylesheet.rb +44 -0
- data/lib/origami/xfa/template.rb +1691 -0
- data/lib/origami/xfa/xdc.rb +42 -0
- data/lib/origami/xfa/xfa.rb +146 -0
- data/lib/origami/xfa/xfdf.rb +43 -0
- data/lib/origami/xfa/xmpmeta.rb +43 -0
- data/lib/origami/xfa.rb +62 -0
- data/lib/origami/xreftable.rb +557 -0
- data/lib/origami.rb +47 -0
- data/test/dataset/calc.pdf +85 -0
- data/test/dataset/crypto.pdf +36 -0
- data/test/dataset/empty.pdf +49 -0
- data/test/test_actions.rb +27 -0
- data/test/test_annotations.rb +68 -0
- data/test/test_forms.rb +30 -0
- data/test/test_native_types.rb +83 -0
- data/test/test_object_tree.rb +33 -0
- data/test/test_pages.rb +60 -0
- data/test/test_pdf.rb +20 -0
- data/test/test_pdf_attachment.rb +34 -0
- data/test/test_pdf_create.rb +24 -0
- data/test/test_pdf_encrypt.rb +102 -0
- data/test/test_pdf_parse.rb +134 -0
- data/test/test_pdf_parse_lazy.rb +69 -0
- data/test/test_pdf_sign.rb +97 -0
- data/test/test_streams.rb +184 -0
- data/test/test_xrefs.rb +67 -0
- metadata +280 -0
data/lib/origami/page.rb
ADDED
@@ -0,0 +1,722 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
4
|
+
Copyright (C) 2016 Guillaume Delugré.
|
5
|
+
|
6
|
+
Origami is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU Lesser General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
Origami is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU Lesser General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
17
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
module Origami
|
22
|
+
|
23
|
+
class PDF
|
24
|
+
|
25
|
+
#
|
26
|
+
# Appends a page or list of pages to the end of the page tree.
|
27
|
+
# _page_:: The page to append to the document. Creates a new Page if not specified.
|
28
|
+
#
|
29
|
+
# Pass the Page object if a block is present.
|
30
|
+
#
|
31
|
+
def append_page(page = Page.new)
|
32
|
+
init_page_tree
|
33
|
+
|
34
|
+
self.Catalog.Pages.append_page(page)
|
35
|
+
yield(page) if block_given?
|
36
|
+
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Inserts a page at position _index_ into the document.
|
42
|
+
# _index_:: Page index (starting from one).
|
43
|
+
# _page_:: The page to insert into the document. Creates a new one if none given.
|
44
|
+
#
|
45
|
+
# Pass the Page object if a block is present.
|
46
|
+
#
|
47
|
+
def insert_page(index, page = Page.new)
|
48
|
+
init_page_tree
|
49
|
+
|
50
|
+
# Page from another document must be exported.
|
51
|
+
page = page.export if page.document and page.document != self
|
52
|
+
|
53
|
+
self.Catalog.Pages.insert_page(index, page)
|
54
|
+
|
55
|
+
yield(page) if block_given?
|
56
|
+
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Returns an Enumerator of Page
|
62
|
+
#
|
63
|
+
def pages
|
64
|
+
init_page_tree
|
65
|
+
|
66
|
+
self.Catalog.Pages.pages
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Iterate through each page, returns self.
|
71
|
+
#
|
72
|
+
def each_page(&b)
|
73
|
+
init_page_tree
|
74
|
+
|
75
|
+
self.Catalog.Pages.each_page(&b)
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Get the n-th Page object.
|
80
|
+
#
|
81
|
+
def get_page(n)
|
82
|
+
init_page_tree
|
83
|
+
|
84
|
+
self.Catalog.Pages.get_page(n)
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Lookup page in the page name directory.
|
89
|
+
#
|
90
|
+
def get_page_by_name(name)
|
91
|
+
resolve_name Names::PAGES, name
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Calls block for each named page.
|
96
|
+
#
|
97
|
+
def each_named_page(&b)
|
98
|
+
each_name(Names::PAGES, &b)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def init_page_tree #:nodoc:
|
104
|
+
unless self.Catalog.key?(:Pages)
|
105
|
+
self.Catalog.Pages = PageTreeNode.new
|
106
|
+
return
|
107
|
+
end
|
108
|
+
|
109
|
+
unless self.Catalog.Pages.is_a?(PageTreeNode)
|
110
|
+
raise InvalidPageTreeError, "Root page node is not a PageTreeNode"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
module ResourcesHolder
|
116
|
+
|
117
|
+
def add_extgstate(extgstate, name = nil)
|
118
|
+
add_resource(Resources::EXTGSTATE, extgstate, name)
|
119
|
+
end
|
120
|
+
def add_colorspace(colorspace, name = nil)
|
121
|
+
add_resource(Resources::COLORSPACE, colorspace, name)
|
122
|
+
end
|
123
|
+
|
124
|
+
def add_pattern(pattern, name = nil)
|
125
|
+
add_resource(Resources::PATTERN, pattern, name)
|
126
|
+
end
|
127
|
+
|
128
|
+
def add_shading(shading, name = nil)
|
129
|
+
add_resource(Resources::SHADING, shading, name)
|
130
|
+
end
|
131
|
+
|
132
|
+
def add_xobject(xobject, name = nil)
|
133
|
+
add_resource(Resources::XOBJECT, xobject, name)
|
134
|
+
end
|
135
|
+
|
136
|
+
def add_font(font, name = nil)
|
137
|
+
add_resource(Resources::FONT, font, name)
|
138
|
+
end
|
139
|
+
|
140
|
+
def add_properties(properties, name = nil)
|
141
|
+
add_resource(Resources::PROPERTIES, properties, name)
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Adds a resource of the specified _type_ in the current object.
|
146
|
+
# If _name_ is not specified, a new name will be automatically generated.
|
147
|
+
#
|
148
|
+
def add_resource(type, rsrc, name = nil)
|
149
|
+
if name.nil?
|
150
|
+
rsrc_name = self.resources(type).key(rsrc)
|
151
|
+
return rsrc_name if rsrc_name
|
152
|
+
end
|
153
|
+
|
154
|
+
name ||= new_id(type)
|
155
|
+
target = self.is_a?(Resources) ? self : (self.Resources ||= Resources.new)
|
156
|
+
|
157
|
+
rsrc_dict = (target[type] and target[type].solve) || (target[type] = Dictionary.new)
|
158
|
+
rsrc_dict[name.to_sym] = rsrc
|
159
|
+
|
160
|
+
name
|
161
|
+
end
|
162
|
+
|
163
|
+
#
|
164
|
+
# Iterates over the resources by _type_.
|
165
|
+
#
|
166
|
+
def each_resource(type)
|
167
|
+
target = self.is_a?(Resources) ? self : (self.Resources ||= Resources.new)
|
168
|
+
|
169
|
+
rsrc = (target[type] and target[type].solve)
|
170
|
+
|
171
|
+
return enum_for(__method__, type) { rsrc.is_a?(Dictionary) ? rsrc.length : 0 } unless block_given?
|
172
|
+
return unless rsrc.is_a?(Dictionary)
|
173
|
+
|
174
|
+
rsrc.each_pair do |name, obj|
|
175
|
+
yield(name.value, obj.solve)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def each_colorspace(&block); each_resource(Resources::COLORSPACE, &block) end
|
180
|
+
def each_extgstate(&block); each_resource(Resources::EXTGSTATE, &block) end
|
181
|
+
def each_pattern(&block); each_resource(Resources::PATTERN, &block) end
|
182
|
+
def each_shading(&block); each_resource(Resources::SHADING, &block) end
|
183
|
+
def each_xobject(&block); each_resource(Resources::XOBJECT, &block) end
|
184
|
+
def each_font(&block); each_resource(Resources::FONT, &block) end
|
185
|
+
def each_property(&block); each_resource(Resources::PROPERTIES, &block) end
|
186
|
+
|
187
|
+
def extgstates; each_extgstate.to_h end
|
188
|
+
def colorspaces; each_colorspace.to_h end
|
189
|
+
def patterns; each_pattern.to_h end
|
190
|
+
def shadings; each_shading.to_h end
|
191
|
+
def xobjects; each_xobject.to_h end
|
192
|
+
def fonts; each_font.to_h end
|
193
|
+
def properties; each_property.to_h end
|
194
|
+
|
195
|
+
#
|
196
|
+
# Returns a Hash of all resources in the object or only the specified _type_.
|
197
|
+
#
|
198
|
+
def resources(type = nil)
|
199
|
+
if type.nil?
|
200
|
+
self.extgstates
|
201
|
+
.merge self.colorspaces
|
202
|
+
.merge self.patterns
|
203
|
+
.merge self.shadings
|
204
|
+
.merge self.xobjects
|
205
|
+
.merge self.fonts
|
206
|
+
.merge self.properties
|
207
|
+
else
|
208
|
+
self.each_resource(type).to_h
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
private
|
213
|
+
|
214
|
+
def new_id(type, prefix = nil) #:nodoc:
|
215
|
+
prefix ||=
|
216
|
+
{
|
217
|
+
Resources::EXTGSTATE => 'ExtG',
|
218
|
+
Resources::COLORSPACE => 'CS',
|
219
|
+
Resources::PATTERN => 'P',
|
220
|
+
Resources::SHADING => 'Sh',
|
221
|
+
Resources::XOBJECT => 'Im',
|
222
|
+
Resources::FONT => 'F',
|
223
|
+
Resources::PROPERTIES => 'Pr'
|
224
|
+
}[type]
|
225
|
+
|
226
|
+
rsrc = self.resources(type)
|
227
|
+
n = '1'
|
228
|
+
|
229
|
+
n.next! while rsrc.include?((prefix + n).to_sym)
|
230
|
+
|
231
|
+
Name.new(prefix + n)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
#
|
236
|
+
# Class representing a Resources Dictionary for a Page.
|
237
|
+
#
|
238
|
+
class Resources < Dictionary
|
239
|
+
include StandardObject
|
240
|
+
include ResourcesHolder
|
241
|
+
|
242
|
+
EXTGSTATE = :ExtGState
|
243
|
+
COLORSPACE = :ColorSpace
|
244
|
+
PATTERN = :Pattern
|
245
|
+
SHADING = :Shading
|
246
|
+
XOBJECT = :XObject
|
247
|
+
FONT = :Font
|
248
|
+
PROPERTIES = :Properties
|
249
|
+
|
250
|
+
field EXTGSTATE, :Type => Dictionary
|
251
|
+
field COLORSPACE, :Type => Dictionary
|
252
|
+
field PATTERN, :Type => Dictionary
|
253
|
+
field SHADING, :Type => Dictionary, :Version => "1.3"
|
254
|
+
field XOBJECT, :Type => Dictionary
|
255
|
+
field FONT, :Type => Dictionary
|
256
|
+
field :ProcSet, :Type => Array.of(Name)
|
257
|
+
field PROPERTIES, :Type => Dictionary, :Version => "1.2"
|
258
|
+
|
259
|
+
def pre_build
|
260
|
+
add_font(Font::Type1::Standard::Helvetica.new.pre_build) unless self.Font
|
261
|
+
|
262
|
+
super
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
class InvalidPageTreeError < Error #:nodoc:
|
267
|
+
end
|
268
|
+
|
269
|
+
#
|
270
|
+
# Class representing a node in a Page tree.
|
271
|
+
#
|
272
|
+
class PageTreeNode < Dictionary
|
273
|
+
include StandardObject
|
274
|
+
|
275
|
+
field :Type, :Type => Name, :Default => :Pages, :Required => true
|
276
|
+
field :Parent, :Type => PageTreeNode
|
277
|
+
field :Kids, :Type => Array, :Default => [], :Required => true
|
278
|
+
field :Count, :Type => Integer, :Default => 0, :Required => true
|
279
|
+
|
280
|
+
def initialize(hash = {}, parser = nil)
|
281
|
+
super
|
282
|
+
|
283
|
+
set_default_values # Ensure that basic tree fields are present.
|
284
|
+
set_indirect(true)
|
285
|
+
end
|
286
|
+
|
287
|
+
def pre_build #:nodoc:
|
288
|
+
self.Count = self.pages.count
|
289
|
+
|
290
|
+
super
|
291
|
+
end
|
292
|
+
|
293
|
+
#
|
294
|
+
# Inserts a page into the node at a specified position (starting from 1).
|
295
|
+
#
|
296
|
+
def insert_page(n, page)
|
297
|
+
raise IndexError, "Page numbers are referenced starting from 1" if n < 1
|
298
|
+
|
299
|
+
kids = self.Kids
|
300
|
+
unless kids.is_a?(Array)
|
301
|
+
raise InvalidPageTreeError, "Kids must be an Array"
|
302
|
+
end
|
303
|
+
|
304
|
+
count = 0
|
305
|
+
kids.each_with_index do |kid, index|
|
306
|
+
node = kid.solve
|
307
|
+
|
308
|
+
case node
|
309
|
+
when Page
|
310
|
+
count = count + 1
|
311
|
+
if count == n
|
312
|
+
kids.insert(index, page)
|
313
|
+
page.Parent = self
|
314
|
+
self.Count += 1
|
315
|
+
return self
|
316
|
+
end
|
317
|
+
|
318
|
+
when PageTreeNode
|
319
|
+
count = count + node.Count
|
320
|
+
if count >= n
|
321
|
+
node.insert_page(n - count + node.Count, page)
|
322
|
+
self.Count += 1
|
323
|
+
return self
|
324
|
+
end
|
325
|
+
else
|
326
|
+
raise InvalidPageTreeError, "not a Page or PageTreeNode"
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
raise IndexError, "Out of order page index" unless count + 1 == n
|
331
|
+
|
332
|
+
self.append_page(page)
|
333
|
+
end
|
334
|
+
|
335
|
+
#
|
336
|
+
# Returns an Array of Pages inheriting this tree node.
|
337
|
+
#
|
338
|
+
def pages
|
339
|
+
self.each_page.to_a
|
340
|
+
end
|
341
|
+
|
342
|
+
#
|
343
|
+
# Iterate through each page of that node.
|
344
|
+
#
|
345
|
+
def each_page(browsed_nodes: [], &block)
|
346
|
+
return enum_for(__method__) { self.Count.to_i } unless block_given?
|
347
|
+
|
348
|
+
if browsed_nodes.any?{|node| node.equal?(self)}
|
349
|
+
raise InvalidPageTreeError, "Cyclic tree graph detected"
|
350
|
+
end
|
351
|
+
|
352
|
+
unless self.Kids.is_a?(Array)
|
353
|
+
raise InvalidPageTreeError, "Kids must be an Array"
|
354
|
+
end
|
355
|
+
|
356
|
+
browsed_nodes.push(self)
|
357
|
+
|
358
|
+
unless self.Count.nil?
|
359
|
+
[ self.Count.value, self.Kids.length ].min.times do |n|
|
360
|
+
node = self.Kids[n].solve
|
361
|
+
|
362
|
+
case node
|
363
|
+
when PageTreeNode then node.each_page(browsed_nodes: browsed_nodes, &block)
|
364
|
+
when Page then yield(node)
|
365
|
+
else
|
366
|
+
raise InvalidPageTreeError, "not a Page or PageTreeNode"
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
self
|
372
|
+
end
|
373
|
+
|
374
|
+
#
|
375
|
+
# Get the n-th Page object in this node, starting from 1.
|
376
|
+
#
|
377
|
+
def get_page(n)
|
378
|
+
raise IndexError, "Page numbers are referenced starting from 1" if n < 1
|
379
|
+
raise IndexError, "Page not found" if n > self.Count.to_i
|
380
|
+
|
381
|
+
self.each_page.lazy.drop(n - 1).first or raise IndexError, "Page not found"
|
382
|
+
end
|
383
|
+
|
384
|
+
#
|
385
|
+
# Removes all pages in the node.
|
386
|
+
#
|
387
|
+
def clear_pages
|
388
|
+
self.Count = 0
|
389
|
+
self.Kids = []
|
390
|
+
end
|
391
|
+
|
392
|
+
#
|
393
|
+
# Returns true unless the node is empty.
|
394
|
+
#
|
395
|
+
def pages?
|
396
|
+
self.each_page.size > 0
|
397
|
+
end
|
398
|
+
|
399
|
+
#
|
400
|
+
# Append a page at the end of this node.
|
401
|
+
#
|
402
|
+
def append_page(page)
|
403
|
+
self.Kids ||= []
|
404
|
+
self.Kids.push(page)
|
405
|
+
self.Count += 1
|
406
|
+
|
407
|
+
page.Parent = self
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
class PageLabel < Dictionary
|
412
|
+
include StandardObject
|
413
|
+
|
414
|
+
module Style
|
415
|
+
DECIMAL = :D
|
416
|
+
UPPER_ROMAN = :R
|
417
|
+
LOWER_ROMAN = :r
|
418
|
+
UPPER_ALPHA = :A
|
419
|
+
LOWER_ALPHA = :a
|
420
|
+
end
|
421
|
+
|
422
|
+
field :Type, :Type => Name, :Default => :PageLabel
|
423
|
+
field :S, :Type => Name
|
424
|
+
field :P, :Type => String
|
425
|
+
field :St, :Type => Integer
|
426
|
+
end
|
427
|
+
|
428
|
+
# Forward declarations.
|
429
|
+
class ContentStream < Stream; end
|
430
|
+
class Annotation < Dictionary; end
|
431
|
+
module Graphics; class ImageXObject < Stream; end end
|
432
|
+
|
433
|
+
#
|
434
|
+
# Class representing a Page in the PDF document.
|
435
|
+
#
|
436
|
+
class Page < Dictionary
|
437
|
+
include StandardObject
|
438
|
+
include ResourcesHolder
|
439
|
+
|
440
|
+
class BoxStyle < Dictionary
|
441
|
+
include StandardObject
|
442
|
+
|
443
|
+
SOLID = :S
|
444
|
+
DASH = :D
|
445
|
+
|
446
|
+
field :C, :Type => Array.of(Number), :Default => [0.0, 0.0, 0.0]
|
447
|
+
field :W, :Type => Number, :Default => 1
|
448
|
+
field :S, :Type => Name, :Default => SOLID
|
449
|
+
field :D, :Type => Array.of(Integer)
|
450
|
+
end
|
451
|
+
|
452
|
+
#
|
453
|
+
# Box color information dictionary associated to a Page.
|
454
|
+
#
|
455
|
+
class BoxColorInformation < Dictionary
|
456
|
+
include StandardObject
|
457
|
+
|
458
|
+
field :CropBox, :Type => BoxStyle
|
459
|
+
field :BleedBox, :Type => BoxStyle
|
460
|
+
field :TrimBox, :Type => BoxStyle
|
461
|
+
field :ArtBox, :Type => BoxStyle
|
462
|
+
end
|
463
|
+
|
464
|
+
#
|
465
|
+
# Class representing a navigation node associated to a Page.
|
466
|
+
#
|
467
|
+
class NavigationNode < Dictionary
|
468
|
+
include StandardObject
|
469
|
+
|
470
|
+
field :Type, :Type => Name, :Default => :NavNode
|
471
|
+
field :NA, :Type => Dictionary # Next action
|
472
|
+
field :PA, :Type => Dictionary # Prev action
|
473
|
+
field :Next, :Type => NavigationNode
|
474
|
+
field :Prev, :Type => NavigationNode
|
475
|
+
field :Dur, :Type => Number
|
476
|
+
end
|
477
|
+
|
478
|
+
#
|
479
|
+
# Class representing additional actions which can be associated to a Page.
|
480
|
+
#
|
481
|
+
class AdditionalActions < Dictionary
|
482
|
+
include StandardObject
|
483
|
+
|
484
|
+
field :O, :Type => Dictionary, :Version => "1.2" # Page Open
|
485
|
+
field :C, :Type => Dictionary, :Version => "1.2" # Page Close
|
486
|
+
end
|
487
|
+
|
488
|
+
module Format
|
489
|
+
A0 = Rectangle[width: 2384, height: 3370]
|
490
|
+
A1 = Rectangle[width: 1684, height: 2384]
|
491
|
+
A2 = Rectangle[width: 1191, height: 1684]
|
492
|
+
A3 = Rectangle[width: 842, height: 1191]
|
493
|
+
A4 = Rectangle[width: 595, height: 842]
|
494
|
+
A5 = Rectangle[width: 420, height: 595]
|
495
|
+
A6 = Rectangle[width: 298, height: 420]
|
496
|
+
A7 = Rectangle[width: 210, height: 298]
|
497
|
+
A8 = Rectangle[width: 147, height: 210]
|
498
|
+
A9 = Rectangle[width: 105, height: 147]
|
499
|
+
A10 = Rectangle[width: 74, height: 105]
|
500
|
+
|
501
|
+
B0 = Rectangle[width: 2836, height: 4008]
|
502
|
+
B1 = Rectangle[width: 2004, height: 2835]
|
503
|
+
B2 = Rectangle[width: 1417, height: 2004]
|
504
|
+
B3 = Rectangle[width: 1001, height: 1417]
|
505
|
+
B4 = Rectangle[width: 709, height: 1001]
|
506
|
+
B5 = Rectangle[width: 499, height: 709]
|
507
|
+
B6 = Rectangle[width: 354, height: 499]
|
508
|
+
B7 = Rectangle[width: 249, height: 354]
|
509
|
+
B8 = Rectangle[width: 176, height: 249]
|
510
|
+
B9 = Rectangle[width: 125, height: 176]
|
511
|
+
B10 = Rectangle[width: 88, height: 125]
|
512
|
+
end
|
513
|
+
|
514
|
+
field :Type, :Type => Name, :Default => :Page, :Required => true
|
515
|
+
field :Parent, :Type => PageTreeNode, :Required => true
|
516
|
+
field :LastModified, :Type => String, :Version => "1.3"
|
517
|
+
field :Resources, :Type => Resources, :Required => true
|
518
|
+
field :MediaBox, :Type => Rectangle, :Default => Format::A4, :Required => true
|
519
|
+
field :CropBox, :Type => Rectangle
|
520
|
+
field :BleedBox, :Type => Rectangle, :Version => "1.3"
|
521
|
+
field :TrimBox, :Type => Rectangle, :Version => "1.3"
|
522
|
+
field :ArtBox, :Type => Rectangle, :Version => "1.3"
|
523
|
+
field :BoxColorInfo, :Type => BoxColorInformation, :Version => "1.4"
|
524
|
+
field :Contents, :Type => [ ContentStream, Array.of(ContentStream) ]
|
525
|
+
field :Rotate, :Type => Integer, :Default => 0
|
526
|
+
field :Group, :Type => Dictionary, :Version => "1.4"
|
527
|
+
field :Thumb, :Type => Graphics::ImageXObject
|
528
|
+
field :B, :Type => Array, :Version => "1.1"
|
529
|
+
field :Dur, :Type => Integer, :Version => "1.1"
|
530
|
+
field :Trans, :Type => Dictionary, :Version => "1.1"
|
531
|
+
field :Annots, :Type => Array.of(Annotation)
|
532
|
+
field :AA, :Type => AdditionalActions, :Version => "1.2"
|
533
|
+
field :Metadata, :Type => MetadataStream, :Version => "1.4"
|
534
|
+
field :PieceInfo, :Type => Dictionary, :Version => "1.2"
|
535
|
+
field :StructParents, :Type => Integer, :Version => "1.3"
|
536
|
+
field :ID, :Type => String
|
537
|
+
field :PZ, :Type => Number
|
538
|
+
field :SeparationInfo, :Type => Dictionary, :Version => "1.3"
|
539
|
+
field :Tabs, :Type => Name, :Version => "1.5"
|
540
|
+
field :TemplateAssociated, :Type => Name, :Version => "1.5"
|
541
|
+
field :PresSteps, :Type => NavigationNode, :Version => "1.5"
|
542
|
+
field :UserUnit, :Type => Number, :Default => 1.0, :Version => "1.6"
|
543
|
+
field :VP, :Type => Dictionary, :Version => "1.6"
|
544
|
+
|
545
|
+
def initialize(hash = {}, parser = nil)
|
546
|
+
super(hash, parser)
|
547
|
+
|
548
|
+
set_indirect(true)
|
549
|
+
end
|
550
|
+
|
551
|
+
def pre_build
|
552
|
+
self.Resources = Resources.new.pre_build unless self.has_key?(:Resources)
|
553
|
+
|
554
|
+
super
|
555
|
+
end
|
556
|
+
|
557
|
+
#
|
558
|
+
# Iterates over all the ContentStreams of the Page.
|
559
|
+
#
|
560
|
+
def each_content_stream
|
561
|
+
contents = self.Contents
|
562
|
+
|
563
|
+
return enum_for(__method__) do
|
564
|
+
case contents
|
565
|
+
when Array then contents.length
|
566
|
+
when Stream then 1
|
567
|
+
else
|
568
|
+
0
|
569
|
+
end
|
570
|
+
end unless block_given?
|
571
|
+
|
572
|
+
case contents
|
573
|
+
when Stream then yield(contents)
|
574
|
+
when Array then contents.each { |stm| yield(stm.solve) }
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
#
|
579
|
+
# Returns an Array of ContentStreams for the Page.
|
580
|
+
#
|
581
|
+
def content_streams
|
582
|
+
self.each_content_stream.to_a
|
583
|
+
end
|
584
|
+
|
585
|
+
#
|
586
|
+
# Add an Annotation to the Page.
|
587
|
+
#
|
588
|
+
def add_annotation(*annotations)
|
589
|
+
self.Annots ||= []
|
590
|
+
|
591
|
+
annotations.each do |annot|
|
592
|
+
annot.solve[:P] = self if self.indirect?
|
593
|
+
self.Annots << annot
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
#
|
598
|
+
# Iterate through each Annotation of the Page.
|
599
|
+
#
|
600
|
+
def each_annotation
|
601
|
+
annots = self.Annots
|
602
|
+
|
603
|
+
return enum_for(__method__) { annots.is_a?(Array) ? annots.length : 0 } unless block_given?
|
604
|
+
return unless annots.is_a?(Array)
|
605
|
+
|
606
|
+
annots.each do |annot|
|
607
|
+
yield(annot.solve)
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
#
|
612
|
+
# Returns the array of Annotation objects of the Page.
|
613
|
+
#
|
614
|
+
def annotations
|
615
|
+
self.each_annotation.to_a
|
616
|
+
end
|
617
|
+
|
618
|
+
#
|
619
|
+
# Embed a SWF Flash application in the page.
|
620
|
+
#
|
621
|
+
def add_flash_application(swfspec, params = {})
|
622
|
+
options =
|
623
|
+
{
|
624
|
+
windowed: false,
|
625
|
+
transparent: false,
|
626
|
+
navigation_pane: false,
|
627
|
+
toolbar: false,
|
628
|
+
pass_context_click: false,
|
629
|
+
activation: Annotation::RichMedia::Activation::PAGE_OPEN,
|
630
|
+
deactivation: Annotation::RichMedia::Deactivation::PAGE_CLOSE,
|
631
|
+
flash_vars: nil
|
632
|
+
}
|
633
|
+
options.update(params)
|
634
|
+
|
635
|
+
annot = create_richmedia(:Flash, swfspec, options)
|
636
|
+
add_annotation(annot)
|
637
|
+
|
638
|
+
annot
|
639
|
+
end
|
640
|
+
|
641
|
+
#
|
642
|
+
# Will execute an action when the page is opened.
|
643
|
+
#
|
644
|
+
def onOpen(action)
|
645
|
+
self.AA ||= Page::AdditionalActions.new
|
646
|
+
self.AA.O = action
|
647
|
+
|
648
|
+
self
|
649
|
+
end
|
650
|
+
|
651
|
+
#
|
652
|
+
# Will execute an action when the page is closed.
|
653
|
+
#
|
654
|
+
def onClose(action)
|
655
|
+
self.AA ||= Page::AdditionalActions.new
|
656
|
+
self.AA.C = action
|
657
|
+
|
658
|
+
self
|
659
|
+
end
|
660
|
+
|
661
|
+
#
|
662
|
+
# Will execute an action when navigating forward from this page.
|
663
|
+
#
|
664
|
+
def onNavigateForward(action) #:nodoc:
|
665
|
+
self.PresSteps ||= NavigationNode.new
|
666
|
+
self.PresSteps.NA = action
|
667
|
+
|
668
|
+
self
|
669
|
+
end
|
670
|
+
|
671
|
+
#
|
672
|
+
# Will execute an action when navigating backward from this page.
|
673
|
+
#
|
674
|
+
def onNavigateBackward(action) #:nodoc:
|
675
|
+
self.PresSteps ||= NavigationNode.new
|
676
|
+
self.PresSteps.PA = action
|
677
|
+
|
678
|
+
self
|
679
|
+
end
|
680
|
+
|
681
|
+
private
|
682
|
+
|
683
|
+
def create_richmedia(type, content, params) #:nodoc:
|
684
|
+
content.set_indirect(true)
|
685
|
+
richmedia = Annotation::RichMedia.new.set_indirect(true)
|
686
|
+
|
687
|
+
rminstance = Annotation::RichMedia::Instance.new.set_indirect(true)
|
688
|
+
rmparams = rminstance.Params = Annotation::RichMedia::Parameters.new
|
689
|
+
rmparams.Binding = Annotation::RichMedia::Parameters::Binding::BACKGROUND
|
690
|
+
rmparams.FlashVars = params[:flash_vars]
|
691
|
+
rminstance.Asset = content
|
692
|
+
|
693
|
+
rmconfig = Annotation::RichMedia::Configuration.new.set_indirect(true)
|
694
|
+
rmconfig.Instances = [ rminstance ]
|
695
|
+
rmconfig.Subtype = type
|
696
|
+
|
697
|
+
rmcontent = richmedia.RichMediaContent = Annotation::RichMedia::Content.new.set_indirect(true)
|
698
|
+
rmcontent.Assets = NameTreeNode.new
|
699
|
+
rmcontent.Assets.Names = NameLeaf.new(content.F.value => content)
|
700
|
+
|
701
|
+
rmcontent.Configurations = [ rmconfig ]
|
702
|
+
|
703
|
+
rmsettings = richmedia.RichMediaSettings = Annotation::RichMedia::Settings.new
|
704
|
+
rmactivation = rmsettings.Activation = Annotation::RichMedia::Activation.new
|
705
|
+
rmactivation.Condition = params[:activation]
|
706
|
+
rmactivation.Configuration = rmconfig
|
707
|
+
rmactivation.Animation = Annotation::RichMedia::Animation.new(:PlayCount => -1, :Subtype => :Linear, :Speed => 1.0)
|
708
|
+
rmpres = rmactivation.Presentation = Annotation::RichMedia::Presentation.new
|
709
|
+
rmpres.Style = Annotation::RichMedia::Presentation::WINDOWED if params[:windowed]
|
710
|
+
rmpres.Transparent = params[:transparent]
|
711
|
+
rmpres.NavigationPane = params[:navigation_pane]
|
712
|
+
rmpres.Toolbar = params[:toolbar]
|
713
|
+
rmpres.PassContextClick = params[:pass_context_click]
|
714
|
+
|
715
|
+
rmdeactivation = rmsettings.Deactivation = Annotation::RichMedia::Deactivation.new
|
716
|
+
rmdeactivation.Condition = params[:deactivation]
|
717
|
+
|
718
|
+
richmedia
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
end
|