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.
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