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,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}."