origami 1.2.7 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/README.md +112 -0
  4. data/bin/config/pdfcop.conf.yml +232 -233
  5. data/bin/gui/about.rb +27 -37
  6. data/bin/gui/config.rb +108 -117
  7. data/bin/gui/file.rb +416 -365
  8. data/bin/gui/gtkhex.rb +1138 -1153
  9. data/bin/gui/hexview.rb +55 -57
  10. data/bin/gui/imgview.rb +48 -51
  11. data/bin/gui/menu.rb +388 -386
  12. data/bin/gui/properties.rb +114 -130
  13. data/bin/gui/signing.rb +571 -617
  14. data/bin/gui/textview.rb +77 -95
  15. data/bin/gui/treeview.rb +382 -387
  16. data/bin/gui/walker.rb +227 -232
  17. data/bin/gui/xrefs.rb +56 -60
  18. data/bin/pdf2pdfa +53 -57
  19. data/bin/pdf2ruby +212 -228
  20. data/bin/pdfcop +338 -348
  21. data/bin/pdfdecompress +58 -65
  22. data/bin/pdfdecrypt +56 -60
  23. data/bin/pdfencrypt +75 -80
  24. data/bin/pdfexplode +185 -182
  25. data/bin/pdfextract +201 -218
  26. data/bin/pdfmetadata +83 -82
  27. data/bin/pdfsh +4 -5
  28. data/bin/pdfwalker +1 -2
  29. data/bin/shell/.irbrc +45 -82
  30. data/bin/shell/console.rb +105 -130
  31. data/bin/shell/hexdump.rb +40 -64
  32. data/examples/README.md +34 -0
  33. data/examples/attachments/attachment.rb +38 -0
  34. data/examples/attachments/nested_document.rb +51 -0
  35. data/examples/encryption/encryption.rb +28 -0
  36. data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
  37. data/examples/flash/flash.rb +37 -0
  38. data/{samples → examples}/flash/helloworld.swf +0 -0
  39. data/examples/forms/javascript.rb +54 -0
  40. data/examples/forms/xfa.rb +115 -0
  41. data/examples/javascript/hello_world.rb +22 -0
  42. data/examples/javascript/js_emulation.rb +54 -0
  43. data/examples/loop/goto.rb +32 -0
  44. data/examples/loop/named.rb +33 -0
  45. data/examples/signature/signature.rb +65 -0
  46. data/examples/uri/javascript.rb +56 -0
  47. data/examples/uri/open-uri.rb +21 -0
  48. data/examples/uri/submitform.rb +47 -0
  49. data/lib/origami.rb +29 -42
  50. data/lib/origami/3d.rb +350 -225
  51. data/lib/origami/acroform.rb +262 -288
  52. data/lib/origami/actions.rb +268 -288
  53. data/lib/origami/annotations.rb +697 -722
  54. data/lib/origami/array.rb +258 -184
  55. data/lib/origami/boolean.rb +74 -84
  56. data/lib/origami/catalog.rb +397 -434
  57. data/lib/origami/collections.rb +144 -0
  58. data/lib/origami/destinations.rb +233 -194
  59. data/lib/origami/dictionary.rb +253 -232
  60. data/lib/origami/encryption.rb +1274 -1243
  61. data/lib/origami/export.rb +232 -268
  62. data/lib/origami/extensions/fdf.rb +307 -220
  63. data/lib/origami/extensions/ppklite.rb +368 -435
  64. data/lib/origami/filespec.rb +197 -0
  65. data/lib/origami/filters.rb +301 -295
  66. data/lib/origami/filters/ascii.rb +177 -180
  67. data/lib/origami/filters/ccitt.rb +528 -535
  68. data/lib/origami/filters/crypt.rb +26 -35
  69. data/lib/origami/filters/dct.rb +46 -52
  70. data/lib/origami/filters/flate.rb +95 -94
  71. data/lib/origami/filters/jbig2.rb +49 -55
  72. data/lib/origami/filters/jpx.rb +38 -44
  73. data/lib/origami/filters/lzw.rb +189 -183
  74. data/lib/origami/filters/predictors.rb +221 -235
  75. data/lib/origami/filters/runlength.rb +103 -104
  76. data/lib/origami/font.rb +173 -186
  77. data/lib/origami/functions.rb +67 -81
  78. data/lib/origami/graphics.rb +25 -21
  79. data/lib/origami/graphics/colors.rb +178 -187
  80. data/lib/origami/graphics/instruction.rb +79 -85
  81. data/lib/origami/graphics/path.rb +142 -148
  82. data/lib/origami/graphics/patterns.rb +160 -167
  83. data/lib/origami/graphics/render.rb +43 -50
  84. data/lib/origami/graphics/state.rb +138 -153
  85. data/lib/origami/graphics/text.rb +188 -205
  86. data/lib/origami/graphics/xobject.rb +819 -815
  87. data/lib/origami/header.rb +63 -78
  88. data/lib/origami/javascript.rb +596 -597
  89. data/lib/origami/linearization.rb +285 -290
  90. data/lib/origami/metadata.rb +139 -148
  91. data/lib/origami/name.rb +112 -148
  92. data/lib/origami/null.rb +53 -62
  93. data/lib/origami/numeric.rb +162 -175
  94. data/lib/origami/obfuscation.rb +186 -174
  95. data/lib/origami/object.rb +593 -573
  96. data/lib/origami/outline.rb +42 -47
  97. data/lib/origami/outputintents.rb +73 -82
  98. data/lib/origami/page.rb +703 -592
  99. data/lib/origami/parser.rb +238 -290
  100. data/lib/origami/parsers/fdf.rb +41 -33
  101. data/lib/origami/parsers/pdf.rb +75 -95
  102. data/lib/origami/parsers/pdf/lazy.rb +137 -0
  103. data/lib/origami/parsers/pdf/linear.rb +64 -66
  104. data/lib/origami/parsers/ppklite.rb +34 -70
  105. data/lib/origami/pdf.rb +1030 -1005
  106. data/lib/origami/reference.rb +102 -102
  107. data/lib/origami/signature.rb +591 -609
  108. data/lib/origami/stream.rb +668 -551
  109. data/lib/origami/string.rb +397 -373
  110. data/lib/origami/template/patterns.rb +56 -0
  111. data/lib/origami/template/widgets.rb +151 -0
  112. data/lib/origami/trailer.rb +144 -158
  113. data/lib/origami/tree.rb +62 -0
  114. data/lib/origami/version.rb +23 -0
  115. data/lib/origami/webcapture.rb +88 -79
  116. data/lib/origami/xfa.rb +2863 -2882
  117. data/lib/origami/xreftable.rb +472 -384
  118. data/test/dataset/calc.pdf +85 -0
  119. data/test/dataset/crypto.pdf +82 -0
  120. data/test/dataset/empty.pdf +49 -0
  121. data/test/test_actions.rb +27 -0
  122. data/test/test_annotations.rb +90 -0
  123. data/test/test_pages.rb +31 -0
  124. data/test/test_pdf.rb +16 -0
  125. data/test/test_pdf_attachment.rb +34 -0
  126. data/test/test_pdf_create.rb +24 -0
  127. data/test/test_pdf_encrypt.rb +95 -0
  128. data/test/test_pdf_parse.rb +96 -0
  129. data/test/test_pdf_sign.rb +58 -0
  130. data/test/test_streams.rb +182 -0
  131. data/test/test_xrefs.rb +67 -0
  132. metadata +88 -58
  133. data/README +0 -67
  134. data/bin/pdf2graph +0 -121
  135. data/bin/pdfcocoon +0 -104
  136. data/lib/origami/file.rb +0 -233
  137. data/samples/README.txt +0 -45
  138. data/samples/actions/launch/calc.rb +0 -87
  139. data/samples/actions/launch/winparams.rb +0 -22
  140. data/samples/actions/loop/loopgoto.rb +0 -24
  141. data/samples/actions/loop/loopnamed.rb +0 -21
  142. data/samples/actions/named/named.rb +0 -31
  143. data/samples/actions/samba/smbrelay.rb +0 -26
  144. data/samples/actions/webbug/submitform.js +0 -26
  145. data/samples/actions/webbug/webbug-browser.rb +0 -68
  146. data/samples/actions/webbug/webbug-js.rb +0 -67
  147. data/samples/actions/webbug/webbug-reader.rb +0 -90
  148. data/samples/attachments/attach.rb +0 -40
  149. data/samples/attachments/attached.txt +0 -1
  150. data/samples/crypto/crypto.rb +0 -28
  151. data/samples/digsig/signed.rb +0 -46
  152. data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
  153. data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
  154. data/samples/exploits/exploit_customdictopen.rb +0 -55
  155. data/samples/exploits/getannots.rb +0 -69
  156. data/samples/flash/flash.rb +0 -31
  157. data/samples/javascript/attached.txt +0 -1
  158. data/samples/javascript/js.rb +0 -52
  159. data/templates/patterns.rb +0 -66
  160. data/templates/widgets.rb +0 -173
  161. data/templates/xdp.rb +0 -92
  162. data/test/ts_pdf.rb +0 -50
@@ -1,59 +1,54 @@
1
1
  =begin
2
2
 
3
- = File
4
- outline.rb
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
- =end
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
- module Origami
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
- class Outline < Dictionary
29
- include StandardObject
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
- field :Type, :Type => Name, :Default => :Outlines
32
- field :First, :Type => Dictionary
33
- field :Last, :Type => Dictionary
34
- field :Count, :Type => Integer
35
- end
19
+ =end
36
20
 
37
- class OutlineItem < Dictionary
38
- include StandardObject
21
+ module Origami
39
22
 
40
- module Style
41
- ITALIC = 1 << 0
42
- BOLD = 1 << 1
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
- field :Title, :Type => String, :Required => true
46
- field :Parent, :Type => Dictionary, :Required => true
47
- field :Prev, :Type => Dictionary
48
- field :Next, :Type => Dictionary
49
- field :First, :Type => Dictionary
50
- field :Last, :Type => Dictionary
51
- field :Count, :Type => Integer
52
- field :Dest, :Type => [ Name, String, Array ]
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
- = File
4
- outputintents.rb
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é.
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
- class OutputIntent < Dictionary
29
- include StandardObject
30
-
31
- module Intent
32
- PDFX = :GTS_PDFX
33
- PDFA1 = :GTS_PDFA1
34
- PDFE1 = :GTS_PDFE1
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
- field :Type, :Type => Name, :Default => :OutputIntent
38
- field :S, :Type => Name, :Version => '1.4', :Required => true
39
- field :OutputCondition, :Type => String
40
- field :OutputConditionIdentifier, :Type => String
41
- field :RegistryName, :Type => String
42
- field :Info, :Type => String
43
- field :DestOutputProfile, :Type => Stream
44
- end
45
-
46
- class PDF
47
-
48
- def is_a_pdfa1?
49
- self.Catalog.OutputIntents.is_a?(Array) and
50
- self.Catalog.OutputIntents.any?{|intent|
51
- intent = intent.solve;
52
- intent.S == OutputIntent::Intent::PDFA1
53
- } and
54
- self.has_metadata? and (
55
- doc = REXML::Document.new self.Catalog.Metadata.data;
56
- REXML::XPath.match(doc, "*/*/rdf:Description[@xmlns:pdfaid]").any? {|desc|
57
- desc.elements["pdfaid:conformance"].text == "A" and
58
- desc.elements["pdfaid:part"].text == "1"
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
-
@@ -1,660 +1,771 @@
1
1
  =begin
2
2
 
3
- = File
4
- page.rb
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é.
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
- class PDF
23
+ class PDF
29
24
 
30
- #
31
- # Appends a page or list of pages to the end of the page tree.
32
- #
33
- def append_page(page = Page.new, *more)
34
- raise InvalidPDFError, "Invalid page tree" if not self.Catalog or not self.Catalog.Pages or not self.Catalog.Pages.is_a?(PageTreeNode)
35
-
36
- pages = [ page ].concat(more).map! do |pg|
37
- if pg.pdf and pg.pdf != self
38
- # Page from another document must be exported.
39
- pg.export
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
- # Page from another document must be exported.
65
- page = page.export if page.pdf and page.pdf != self
38
+ treeroot.Kids ||= [] #:nodoc:
39
+ treeroot.Kids.push(page)
40
+ treeroot.Count ||= 0
41
+ treeroot.Count += 1
66
42
 
67
- self.Catalog.Pages.insert_page(index, page)
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
- # Iterate through each page, returns self.
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
- # Get the n-th Page object.
92
- #
93
- def get_page(n)
94
- raise InvalidPDFError, "Invalid page tree" if not self.Catalog or not self.Catalog.Pages or not self.Catalog.Pages.is_a?(PageTreeNode)
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
- self.Catalog.Pages.get_page(n)
97
- end
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
- end
114
-
115
- module ResourcesHolder
69
+ self
70
+ end
116
71
 
117
- def add_extgstate(extgstate, name = nil)
118
- add_resource(Resources::EXTGSTATE, extgstate, name)
119
- end
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
- def add_colorspace(colorspace, name = nil)
122
- add_resource(Resources::COLORSPACE, colorspace, name)
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
- def add_xobject(xobject, name = nil)
134
- add_resource(Resources::XOBJECT, xobject, name)
135
- end
136
-
137
- def add_font(font, name = nil)
138
- add_resource(Resources::FONT, font, name)
139
- end
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
- def add_properties(properties, name = nil)
142
- add_resource(Resources::PROPERTIES, properties, name)
143
- end
91
+ self.Catalog.Pages.each_page(&b)
92
+ end
144
93
 
145
- def add_resource(type, rsrc, name = nil)
146
- return existing if not name and existing = ls_resources(type).key(rsrc)
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
- name = new_id(type) unless name
149
- target = self.is_a?(Resources) ? self : (self.Resources ||= Resources.new)
102
+ self.Catalog.Pages.get_page(n)
103
+ end
150
104
 
151
- rsrc_dict = target.send(type) || (target[type] = Dictionary.new)
152
- rsrc_dict[name] = rsrc
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
- name
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
- def ls_resources(type)
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
- rsrc
166
- end
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
- def extgstates; ls_resources(Resources::EXTGSTATE) end
169
- def colorspaces; ls_resources(Resources::COLORSPACE) end
170
- def patterns; ls_resources(Resources::PATTERN) end
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
- def new_extgstate_id; new_id(Resources::EXTGSTATE) end
210
- def new_colorspace_id; new_id(Resources::COLORSPACE) end
211
- def new_pattern_id; new_id(Resources::PATTERN) end
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
- def pre_build #:nodoc:
272
- self.Count = self.children.length
273
-
274
- super
275
- end
137
+ def add_xobject(xobject, name = nil)
138
+ add_resource(Resources::XOBJECT, xobject, name)
139
+ end
276
140
 
277
- def insert_page(index, page)
278
-
279
- if index > self.Count
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
- # Returns an array of Page inheriting this tree node.
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
- # Iterate through each page of that node.
341
- #
342
- def each_page(&b)
343
- unless self.Count.nil?
344
- [ self.Count.value, self.Kids.length ].min.times do |n|
345
- node = self.Kids[n].is_a?(Reference) ? self.Kids[n].solve : self.Kids[n]
346
- case node
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
- # Get the n-th Page object in this node, starting from 1.
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
- def pre_build
471
- self.Resources = Resources.new.pre_build unless self.has_key?(:Resources)
161
+ rsrc_dict = (target[type] and target[type].solve) || (target[type] = Dictionary.new)
162
+ rsrc_dict[name] = rsrc
472
163
 
473
- super
474
- end
164
+ name
165
+ end
475
166
 
476
- #
477
- # Add an Annotation to the Page.
478
- #
479
- def add_annot(*annotations)
480
- unless annotations.all?{|annot| annot.is_a?(Annotation) or annot.is_a?(Reference)}
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
- annots.each do |annot|
500
- b.call(annot.solve)
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
- # Returns the array of Annotation objects of the Page.
506
- #
507
- def annotations
508
- annots = self.Annots
509
- return [] unless annots.is_a?(Array)
510
-
511
- annots.map{|annot| annot.solve}
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
- # Embed a SWF Flash application in the page.
248
+ # Class representing a Resources Dictionary for a Page.
516
249
  #
517
- def add_flash_application(swfspec, params = {})
518
- options =
519
- {
520
- :windowed => false,
521
- :transparent => false,
522
- :navigation_pane => false,
523
- :toolbar => false,
524
- :pass_context_click => false,
525
- :activation => Annotation::RichMedia::Activation::PAGE_OPEN,
526
- :deactivation => Annotation::RichMedia::Deactivation::PAGE_CLOSE,
527
- :flash_vars => nil
528
- }
529
- options.update(params)
530
-
531
- annot = create_richmedia(:Flash, swfspec, options)
532
- add_annot(annot)
533
-
534
- annot
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
- # Will execute an action when the page is closed.
282
+ # Class representing a node in a Page tree.
553
283
  #
554
- def onClose(action)
555
- unless action.is_a?(Action) or action.is_a?(Reference)
556
- raise TypeError, "An Action object must be passed."
557
- end
558
-
559
- self.AA ||= PageAdditionalActions.new
560
- self.AA.C = action
561
-
562
- self
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
- # Will execute an action when navigating forward from this page.
567
- #
568
- def onNavigateForward(action) #:nodoc:
569
- unless action.is_a?(Action) or action.is_a?(Reference)
570
- raise TypeError, "An Action object must be passed."
571
- end
572
-
573
- self.PresSteps ||= NavigationNode.new
574
- self.PresSteps.NA = action
575
-
576
- self
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
- # Will execute an action when navigating backward from this page.
463
+ # Class representing a Page in the PDF document.
581
464
  #
582
- def onNavigateBackward(action) #:nodoc:
583
- unless action.is_a?(Action) or action.is_a?(Reference)
584
- raise TypeError, "An Action object must be passed."
585
- end
586
-
587
- self.PresSteps ||= NavigationNode.new
588
- self.PresSteps.PA = action
589
-
590
- self
591
- end
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
- private
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
- def create_richmedia(type, content, params) #:nodoc:
596
- content.set_indirect(true)
597
- richmedia = Annotation::RichMedia.new.set_indirect(true)
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
- rminstance = Annotation::RichMedia::Instance.new.set_indirect(true)
600
- rmparams = rminstance.Params = Annotation::RichMedia::Parameters.new
601
- rmparams.Binding = Annotation::RichMedia::Parameters::Binding::BACKGROUND
602
- rmparams.FlashVars = params[:flash_vars]
603
- rminstance.Asset = content
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
- rmconfig = Annotation::RichMedia::Configuration.new.set_indirect(true)
606
- rmconfig.Instances = [ rminstance ]
607
- rmconfig.Subtype = type
732
+ def create_richmedia(type, content, params) #:nodoc:
733
+ content.set_indirect(true)
734
+ richmedia = Annotation::RichMedia.new.set_indirect(true)
608
735
 
609
- rmcontent = richmedia.RichMediaContent = Annotation::RichMedia::Content.new.set_indirect(true)
610
- rmcontent.Assets = NameTreeNode.new
611
- rmcontent.Assets.Names = NameLeaf.new(content.F.value => content)
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
- rmcontent.Configurations = [ rmconfig ]
742
+ rmconfig = Annotation::RichMedia::Configuration.new.set_indirect(true)
743
+ rmconfig.Instances = [ rminstance ]
744
+ rmconfig.Subtype = type
614
745
 
615
- rmsettings = richmedia.RichMediaSettings = Annotation::RichMedia::Settings.new
616
- rmactivation = rmsettings.Activation = Annotation::RichMedia::Activation.new
617
- rmactivation.Condition = params[:activation]
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
- rmdeactivation = rmsettings.Deactivation = Annotation::RichMedia::Deactivation.new
628
- rmdeactivation.Condition = params[:deactivation]
750
+ rmcontent.Configurations = [ rmconfig ]
629
751
 
630
- richmedia
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