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,233 +0,0 @@
1
- =begin
2
-
3
- = File
4
- file.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/>.
23
-
24
- =end
25
-
26
- module Origami
27
-
28
- class PDF
29
-
30
- #
31
- # Attachs an embedded file to the PDF.
32
- # _path_:: The path to the file to attach.
33
- # _options_:: A set of options to configure the attachment.
34
- #
35
- def attach_file(path, options = {})
36
-
37
- #
38
- # Default options.
39
- #
40
- params =
41
- {
42
- :Register => true, # Shall the file be registered in the name directory ?
43
- :EmbeddedName => nil, # The inner filename of the attachment.
44
- :Filter => :FlateDecode, # The stream filter used to store data.
45
- }.update(options)
46
-
47
- if path.is_a? FileSpec
48
- filespec = path
49
- params[:EmbeddedName] ||= ''
50
- else
51
- if path.respond_to?(:read)
52
- fd = path
53
- params[:EmbeddedName] ||= ''
54
- else
55
- fd = File.open(File.expand_path(path), 'r').binmode
56
- params[:EmbeddedName] ||= File.basename(path)
57
- end
58
-
59
- fstream = EmbeddedFileStream.new
60
-
61
- if ''.respond_to? :force_encoding
62
- fstream.data = fd.read.force_encoding('binary') # 1.9
63
- else
64
- fstream.data = fd.read
65
- end
66
-
67
- fd.close
68
-
69
- fstream.setFilter(params[:Filter])
70
- filespec = FileSpec.new(:F => fstream)
71
- end
72
-
73
- name = params[:EmbeddedName]
74
- fspec = FileSpec.new.setType(:Filespec).setF(name.dup).setEF(
75
- filespec
76
- )
77
-
78
- register(
79
- Names::Root::EMBEDDEDFILES,
80
- name.dup,
81
- fspec
82
- ) if params[:Register] == true
83
-
84
- fspec
85
- end
86
-
87
- #
88
- # Lookup embedded file in the embedded files name directory.
89
- #
90
- def get_embedded_file_by_name(name)
91
- resolve_name Names::Root::EMBEDDEDFILES, name
92
- end
93
-
94
- #
95
- # Calls block for each named embedded file.
96
- #
97
- def each_named_embedded_file(&b)
98
- each_name(Names::Root::EMBEDDEDFILES, &b)
99
- end
100
- end
101
-
102
- #
103
- # Class used to convert system-dependent pathes into PDF pathes.
104
- # PDF path specification offers a single form for representing file pathes over operating systems.
105
- #
106
- class Filename
107
-
108
- class << self
109
-
110
- #
111
- # Converts UNIX file path into PDF file path.
112
- #
113
- def Unix(file)
114
- ByteString.new(file)
115
- end
116
-
117
- #
118
- # Converts MacOS file path into PDF file path.
119
- #
120
- def Mac(file)
121
- ByteString.new("/" + file.gsub(":", "/"))
122
- end
123
-
124
- #
125
- # Converts Windows file path into PDF file path.
126
- #
127
- def DOS(file)
128
- path = ""
129
- # Absolute vs relative path
130
- if file.include? ":"
131
- path << "/"
132
- file.sub!(":","")
133
- end
134
-
135
- file.gsub!("\\", "/")
136
- ByteString.new(path + file)
137
- end
138
-
139
- end
140
-
141
- end
142
-
143
-
144
- #
145
- # Class representing a file specification.
146
- # File specifications can be used to reference external files, as well as embedded files and URIs.
147
- #
148
- class FileSpec < Dictionary
149
-
150
- include StandardObject
151
-
152
- field :Type, :Type => Name, :Default => :FileSpec
153
- field :FS, :Type => Name, :Default => :URL
154
- field :F, :Type => [ ByteString, Stream ]
155
- field :UF, :Type => String
156
- field :DOS, :Type => ByteString
157
- field :Mac, :Type => ByteString
158
- field :Unix, :Type => ByteString
159
- field :ID, :Type => Array
160
- field :V, :Type => Boolean, :Default => false, :Version => "1.2"
161
- field :EF, :Type => Dictionary, :Version => "1.3"
162
- field :RF, :Type => Dictionary, :Version => "1.3"
163
- field :Desc, :Type => ByteString, :Version => "1.6"
164
- field :CI, :Type => Dictionary, :Version => "1.7"
165
- field :Thumb, :Type => Stream, :Version => "1.7", :ExtensionLevel => 3
166
-
167
- end
168
-
169
- #
170
- # Class representing a Uniform Resource Locator (URL)
171
- #
172
- class URL < FileSpec
173
-
174
- field :Type, :Type => Name, :Default => :URL, :Required => true
175
-
176
- def initialize(url)
177
- super(:F => url)
178
- end
179
- end
180
-
181
- #
182
- # A class representing a file outside the current PDF file.
183
- #
184
- class ExternalFile < FileSpec
185
- field :Type, :Type => Name, :Default => :FileSpec #, :Required => true
186
-
187
- #
188
- # Creates a new external file specification.
189
- # _dos_:: The Windows path to this file.
190
- # _mac_:: The MacOS path to this file.
191
- # _unix_:: The UNIX path to this file.
192
- #
193
- def initialize(dos, mac = "", unix = "")
194
-
195
- if not mac.empty? or not unix.empty?
196
- super(:DOS => Filename.DOS(dos), :Mac => Filename.Mac(mac), :Unix => Filename.Unix(unix))
197
- else
198
- super(:F => dos)
199
- end
200
-
201
- end
202
-
203
- end
204
-
205
- #
206
- # Class representing the data of an embedded file.
207
- #
208
- class EmbeddedFileStream < Stream
209
-
210
- include StandardObject
211
-
212
- field :Type, :Type => Name, :Default => :EmbeddedFile
213
- field :Subtype, :Type => Name
214
- field :Params, :Type => Dictionary
215
-
216
- end
217
-
218
- #
219
- # Class representing parameters for a EmbeddedFileStream.
220
- #
221
- class EmbeddedFileParameters < Dictionary
222
-
223
- include StandardObject
224
-
225
- field :Size, :Type => Integer
226
- field :CreationDate, :Type => ByteString
227
- field :ModDate, :Type => ByteString
228
- field :Mac, :Type => Dictionary
229
- field :Checksum, :Type => String
230
-
231
- end
232
-
233
- end
@@ -1,45 +0,0 @@
1
- :: SUBDIRECTORIES
2
- =================
3
-
4
- ``attachments/``
5
- * Adding a file attachment to a PDF document.
6
-
7
- ``crypto/``
8
- * PDF encryption (supports RC4 40-128 bits, and AES128).
9
- - crypto.rb : Create a simple encrypted document.
10
- - encrypt.rb : Encrypt an existing document.
11
-
12
- ``digsig/``
13
- * PDF digital signatures. Create a new document and signs it with test.key.
14
-
15
- ``exploits/``
16
- * Basic exploits PoC generation.
17
-
18
- ``flash/``
19
- * PDF with Flash object. Create a document with an embedded SWF file.
20
-
21
- ``actions/launch/``
22
- * Launch action. Create a document launching the calculator on Windows, Unix and MacOS.
23
-
24
- ``actions/loop/``
25
- * Create a looping document using GoTo and Named actions (see also moebius in the scripts directory).
26
-
27
- ``actions/named/``
28
- * Named action. Create a document prompting for printing.
29
-
30
- ``actions/triggerevents/``
31
- * Create a document launching JS scripts on various events.
32
-
33
- ``actions/webbug/``
34
- * Create a document connecting to a remote server.
35
- - webbug-browser.rb : Connection using a URI action.
36
- - webbug-reader.rb : Connection using a SubmitForm action.
37
- - webbug-js.rb : Connection using JS script.
38
-
39
- ``actions/samba/``
40
- * Implementation of a SMB relay attack using PDF. When opened in a
41
- browser on Windows, the document tries to access a document shared
42
- on a malicious SMB server (on a LAN). The server will then be able
43
- to steal user credentials. This script merely forges the malicious
44
- PDF document.
45
-
@@ -1,87 +0,0 @@
1
- #!/usr/bin/ruby
2
-
3
- begin
4
- require 'origami'
5
- rescue LoadError
6
- ORIGAMIDIR = "#{File.dirname(__FILE__)}/../../../lib"
7
- $: << ORIGAMIDIR
8
- require 'origami'
9
- end
10
- include Origami
11
-
12
- OUTPUTFILE = "#{File.basename(__FILE__, ".rb")}.pdf"
13
-
14
- puts "Generating a pdf launching calc on several OS!!"
15
-
16
- pdf = PDF.new
17
-
18
- #�Reader7.0 �
19
- #�A popup firstly says it cannot open the specified file. Then the
20
- #�file is opened once the "Open" button is clicked...
21
-
22
- # Reader8.0
23
- #�It "opens" the file, but does not execute it. By default, it seems
24
- # it is passed to the default browser.
25
- # Only local files can be opened this way.
26
- #�If the file does not exist, it displays the content of the current
27
- # directory
28
-
29
- cmd = FileSpec.new
30
- cmd.Unix = "/usr/bin/xcalc"
31
- cmd.Mac = "/Applications/Calculator.app"
32
- cmd.DOS = "C:\\\\WINDOWS\\\\system32\\\\calc.exe"
33
-
34
- action = Action::Launch.new
35
- action.F = cmd
36
-
37
- pdf.onDocumentOpen( action )
38
-
39
- contents = ContentStream.new
40
- contents.write OUTPUTFILE,
41
- :x => 350, :y => 750, :rendering => Text::Rendering::STROKE, :size => 30
42
-
43
- contents.write "This page is empty but it should start calc :-D",
44
- :x => 233, :y => 690, :rendering => Text::Rendering::FILL, :size => 15, :color => Graphics::Color::RGB.new(0, 150, 0)
45
-
46
- contents.write "Dont be afraid of the pop-ups, just click them...",
47
- :x => 233, :y => 670, :size => 15
48
-
49
- contents.write "Comments:",
50
- :x => 75, :y => 620, :rendering => Text::Rendering::FILL_AND_STROKE, :size => 14
51
-
52
- content = <<-EOS
53
- Windows:
54
- - Foxit 2: runs calc.exe at the document opening without any user confirmation message (!)
55
- - Acrobat Reader *:
56
- 1. popup proposing to open \"calc.exe\" (warning)
57
- 2. starts \"calc.exe\"
58
-
59
- Mac:
60
- - Preview does not support PDF keyword /Launch
61
- - Acrobat Reader 8.1.2: starts Calculator.app
62
-
63
- Linux:
64
- ! Assumes xcalc is in /usr/bin/xcalc
65
- - poppler: does not support PDF keyword /Launch
66
- - Acrobat Reader 7:
67
- 1. popup telling it can not open \"xcalc\" (dumb reasons)
68
- 2. popup proposing to open \"xcalc\" (warning)
69
- 3. starts \"xcalc\"
70
- - Acrobat Reader 8.1.2: based on xdg-open
71
- - if you are running KDE, Gnome or xfce, xcalc is started after a popup
72
- - otherwise, your brower is started and tries to download \"xcalc\"
73
-
74
- Note:
75
- For Linux and Mac, no argument can be given to the command...
76
-
77
- EOS
78
-
79
- contents.write content,
80
- :x => 75, :y => 600, :rendering => Text::Rendering::FILL
81
-
82
- page = Page.new.setContents( contents )
83
- pdf.append_page( page )
84
-
85
- pdf.save(OUTPUTFILE)
86
- puts "PDF file saved as #{OUTPUTFILE}."
87
-
@@ -1,22 +0,0 @@
1
- #!/usr/bin/ruby
2
-
3
- begin
4
- require 'origami'
5
- rescue LoadError
6
- ORIGAMIDIR = "#{File.dirname(__FILE__)}/../../../lib"
7
- $: << ORIGAMIDIR
8
- require 'origami'
9
- end
10
- include Origami
11
-
12
- OUTPUTFILE = "#{File.basename(__FILE__, ".rb")}.pdf"
13
-
14
- params = Action::Launch::WindowsLaunchParams.new
15
- params.F = "C:\\\\WINDOWS\\\\system32\\\\notepad.exe" # application or document to launch
16
- params.D = "C:\\\\WINDOWS\\\\system32" # new current directory
17
- params.P = "test.txt" # parameter to pass if F is an application
18
-
19
- action = Action::Launch.new
20
- action.Win = params
21
-
22
- PDF.new.onDocumentOpen( action ).save(OUTPUTFILE)
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- begin
4
- require 'origami'
5
- rescue LoadError
6
- ORIGAMIDIR = "#{File.dirname(__FILE__)}/../../../lib"
7
- $: << ORIGAMIDIR
8
- require 'origami'
9
- end
10
- include Origami
11
-
12
- pdf = PDF.read("sample.pdf", :verbosity => Parser::VERBOSE_DEBUG )
13
-
14
- index = 1
15
- pages = pdf.pages
16
-
17
- pages.each do |page|
18
- page.onOpen Action::GoTo Destination::GlobalFit.new pages[index % pages.size]
19
-
20
- index = index + 1
21
- end
22
-
23
- pdf.save("loopgoto_sample.pdf")
24
-
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- begin
4
- require 'origami'
5
- rescue LoadError
6
- ORIGAMIDIR = "#{File.dirname(__FILE__)}/../../../lib"
7
- $: << ORIGAMIDIR
8
- require 'origami'
9
- end
10
- include Origami
11
-
12
- pdf = PDF.read("sample.pdf", :verbosity => Parser::VERBOSE_DEBUG )
13
-
14
- pages = pdf.pages
15
-
16
- pages.each do |page|
17
- page.onOpen Action::Named::NEXTPAGE unless page == pages.last
18
- end
19
- pages.last.onOpen Action::Named::FIRSTPAGE
20
-
21
- pdf.save("loopnamed_sample.pdf")
@@ -1,31 +0,0 @@
1
- #!/usr/bin/ruby
2
-
3
- begin
4
- require 'origami'
5
- rescue LoadError
6
- ORIGAMIDIR = "#{File.dirname(__FILE__)}/../../../lib"
7
- $: << ORIGAMIDIR
8
- require 'origami'
9
- end
10
- include Origami
11
-
12
- OUTPUTFILE = "#{File.basename(__FILE__, ".rb")}.pdf"
13
-
14
- puts "Now generating a new PDF file from scratch!"
15
-
16
- pdf = PDF.new
17
-
18
- page = Page.new
19
-
20
- contents = ContentStream.new
21
- contents.write OUTPUTFILE,
22
- :x => 350, :y => 750, :rendering => Text::Rendering::STROKE, :size => 30
23
-
24
- page.Contents = contents
25
- pdf.append_page(page)
26
-
27
- pdf.onDocumentOpen Action::Named::PRINT
28
-
29
- pdf.save(OUTPUTFILE)
30
-
31
- puts "PDF file saved as #{OUTPUTFILE}."