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