origami-docspring 2.2.0 → 2.3.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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/examples/attachments/attachment.rb +7 -8
  4. data/examples/attachments/nested_document.rb +6 -5
  5. data/examples/encryption/encryption.rb +5 -4
  6. data/examples/events/events.rb +7 -6
  7. data/examples/flash/flash.rb +10 -9
  8. data/examples/forms/javascript.rb +14 -13
  9. data/examples/forms/xfa.rb +67 -66
  10. data/examples/javascript/hello_world.rb +6 -5
  11. data/examples/javascript/js_emulation.rb +26 -26
  12. data/examples/loop/goto.rb +12 -11
  13. data/examples/loop/named.rb +17 -16
  14. data/examples/signature/signature.rb +11 -11
  15. data/examples/uri/javascript.rb +25 -24
  16. data/examples/uri/open-uri.rb +5 -4
  17. data/examples/uri/submitform.rb +11 -10
  18. data/lib/origami/3d.rb +330 -334
  19. data/lib/origami/acroform.rb +267 -268
  20. data/lib/origami/actions.rb +266 -278
  21. data/lib/origami/annotations.rb +659 -670
  22. data/lib/origami/array.rb +192 -196
  23. data/lib/origami/boolean.rb +66 -70
  24. data/lib/origami/catalog.rb +360 -363
  25. data/lib/origami/collections.rb +132 -133
  26. data/lib/origami/compound.rb +125 -129
  27. data/lib/origami/destinations.rb +226 -237
  28. data/lib/origami/dictionary.rb +155 -154
  29. data/lib/origami/encryption.rb +967 -923
  30. data/lib/origami/extensions/fdf.rb +270 -275
  31. data/lib/origami/extensions/ppklite.rb +323 -328
  32. data/lib/origami/filespec.rb +170 -173
  33. data/lib/origami/filters/ascii.rb +162 -167
  34. data/lib/origami/filters/ccitt/tables.rb +248 -252
  35. data/lib/origami/filters/ccitt.rb +309 -312
  36. data/lib/origami/filters/crypt.rb +31 -34
  37. data/lib/origami/filters/dct.rb +47 -50
  38. data/lib/origami/filters/flate.rb +57 -60
  39. data/lib/origami/filters/jbig2.rb +50 -53
  40. data/lib/origami/filters/jpx.rb +40 -43
  41. data/lib/origami/filters/lzw.rb +151 -155
  42. data/lib/origami/filters/predictors.rb +250 -255
  43. data/lib/origami/filters/runlength.rb +111 -115
  44. data/lib/origami/filters.rb +319 -325
  45. data/lib/origami/font.rb +173 -177
  46. data/lib/origami/functions.rb +62 -66
  47. data/lib/origami/graphics/colors.rb +203 -208
  48. data/lib/origami/graphics/instruction.rb +79 -81
  49. data/lib/origami/graphics/path.rb +141 -144
  50. data/lib/origami/graphics/patterns.rb +156 -160
  51. data/lib/origami/graphics/render.rb +51 -47
  52. data/lib/origami/graphics/state.rb +144 -142
  53. data/lib/origami/graphics/text.rb +185 -188
  54. data/lib/origami/graphics/xobject.rb +818 -804
  55. data/lib/origami/graphics.rb +25 -26
  56. data/lib/origami/header.rb +63 -65
  57. data/lib/origami/javascript.rb +718 -651
  58. data/lib/origami/linearization.rb +284 -285
  59. data/lib/origami/metadata.rb +156 -135
  60. data/lib/origami/name.rb +98 -100
  61. data/lib/origami/null.rb +49 -51
  62. data/lib/origami/numeric.rb +133 -135
  63. data/lib/origami/obfuscation.rb +180 -182
  64. data/lib/origami/object.rb +634 -631
  65. data/lib/origami/optionalcontent.rb +147 -149
  66. data/lib/origami/outline.rb +46 -48
  67. data/lib/origami/outputintents.rb +76 -77
  68. data/lib/origami/page.rb +637 -596
  69. data/lib/origami/parser.rb +214 -221
  70. data/lib/origami/parsers/fdf.rb +44 -45
  71. data/lib/origami/parsers/pdf/lazy.rb +147 -154
  72. data/lib/origami/parsers/pdf/linear.rb +104 -109
  73. data/lib/origami/parsers/pdf.rb +109 -107
  74. data/lib/origami/parsers/ppklite.rb +44 -46
  75. data/lib/origami/pdf.rb +886 -896
  76. data/lib/origami/reference.rb +116 -120
  77. data/lib/origami/signature.rb +617 -625
  78. data/lib/origami/stream.rb +560 -558
  79. data/lib/origami/string.rb +366 -368
  80. data/lib/origami/template/patterns.rb +50 -52
  81. data/lib/origami/template/widgets.rb +111 -114
  82. data/lib/origami/trailer.rb +153 -157
  83. data/lib/origami/tree.rb +55 -57
  84. data/lib/origami/version.rb +19 -19
  85. data/lib/origami/webcapture.rb +87 -90
  86. data/lib/origami/xfa/config.rb +409 -414
  87. data/lib/origami/xfa/connectionset.rb +113 -117
  88. data/lib/origami/xfa/datasets.rb +38 -42
  89. data/lib/origami/xfa/localeset.rb +33 -37
  90. data/lib/origami/xfa/package.rb +49 -52
  91. data/lib/origami/xfa/pdf.rb +54 -59
  92. data/lib/origami/xfa/signature.rb +33 -37
  93. data/lib/origami/xfa/sourceset.rb +34 -38
  94. data/lib/origami/xfa/stylesheet.rb +35 -39
  95. data/lib/origami/xfa/template.rb +1630 -1634
  96. data/lib/origami/xfa/xdc.rb +33 -37
  97. data/lib/origami/xfa/xfa.rb +132 -123
  98. data/lib/origami/xfa/xfdf.rb +34 -38
  99. data/lib/origami/xfa/xmpmeta.rb +34 -38
  100. data/lib/origami/xfa.rb +50 -53
  101. data/lib/origami/xreftable.rb +462 -462
  102. data/lib/origami.rb +37 -38
  103. data/test/test_actions.rb +22 -20
  104. data/test/test_annotations.rb +54 -52
  105. data/test/test_forms.rb +23 -21
  106. data/test/test_native_types.rb +82 -78
  107. data/test/test_object_tree.rb +25 -24
  108. data/test/test_pages.rb +43 -41
  109. data/test/test_pdf.rb +2 -0
  110. data/test/test_pdf_attachment.rb +23 -21
  111. data/test/test_pdf_create.rb +16 -15
  112. data/test/test_pdf_encrypt.rb +69 -66
  113. data/test/test_pdf_parse.rb +131 -129
  114. data/test/test_pdf_parse_lazy.rb +53 -53
  115. data/test/test_pdf_sign.rb +67 -67
  116. data/test/test_streams.rb +145 -143
  117. data/test/test_xrefs.rb +46 -45
  118. metadata +64 -8
@@ -1,713 +1,780 @@
1
- =begin
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # This file is part of Origami, PDF manipulation framework for Ruby
5
+ # Copyright (C) 2016 Guillaume Delugré.
6
+ #
7
+ # Origami is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # Origami is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with Origami. If not, see <http://www.gnu.org/licenses/>.
19
+ #
2
20
 
3
- This file is part of Origami, PDF manipulation framework for Ruby
4
- Copyright (C) 2016 Guillaume Delugré.
21
+ module Origami
22
+ begin
23
+ require 'v8'
24
+
25
+ class PDF
26
+ module JavaScript
27
+ module Platforms
28
+ WINDOWS = "WIN"
29
+ UNIX = "UNIX"
30
+ MAC = "MAC"
31
+ end
5
32
 
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.
33
+ module Viewers
34
+ ADOBE_READER = "Reader"
35
+ end
10
36
 
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.
37
+ class Error < Origami::Error; end
15
38
 
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/>.
39
+ class MissingArgError < Error
40
+ def initialize
41
+ super("Missing required argument.")
42
+ end
43
+ end
18
44
 
19
- =end
45
+ class TypeError < Error
46
+ def initialize
47
+ super("Incorrect argument type.")
48
+ end
49
+ end
20
50
 
21
- module Origami
51
+ class InvalidArgsError < Error
52
+ def initialize
53
+ super("Incorrect arguments.")
54
+ end
55
+ end
56
+
57
+ class NotAllowedError < Error
58
+ def initialize
59
+ super("Security settings prevent access to this property or method.")
60
+ end
61
+ end
62
+
63
+ class HelpError < Error
64
+ def initialize
65
+ super("Help")
66
+ end
67
+ end
68
+
69
+ class GeneralError < Error
70
+ def initialize
71
+ super("Operation failed.")
72
+ end
73
+ end
74
+
75
+ class Arg
76
+ attr_reader :name, :type, :required, :default
77
+
78
+ def initialize(declare = {})
79
+ @name = declare[:name]
80
+ @type = declare[:type]
81
+ @required = declare[:required]
82
+ @default = declare[:default]
83
+ end
84
+
85
+ def self.[](declare = {})
86
+ new(declare)
87
+ end
88
+
89
+ def self.inspect(obj)
90
+ case obj
91
+ when V8::Function then "function #{obj.name}"
92
+ when V8::Array then obj.to_a.inspect
93
+ when V8::Object
94
+ "{#{obj.to_a.map { |k, v| "#{k}:#{Arg.inspect(v)}" }.join(", ")}}"
95
+ else
96
+ obj.inspect
97
+ end
98
+ end
99
+ end
100
+
101
+ class AcrobatObject
102
+ def initialize(engine)
103
+ @engine = engine
104
+ end
105
+
106
+ def self.check_method_args(args, def_args)
107
+ if args.first.is_a?(V8::Object)
108
+ check_method_named_args(args.first, def_args)
109
+ else
110
+ check_method_ordered_args(args, def_args)
111
+ end
112
+ end
113
+
114
+ def self.check_method_named_args(object, def_args)
115
+ members = object.entries.map { |k, _| k }
116
+ argv = []
117
+ def_args.each do |def_arg|
118
+ raise MissingArgError if def_arg.required && !members.include?(def_arg.name)
119
+
120
+ if members.include?(def_arg.name)
121
+ arg = object[def_arg.name]
122
+ raise TypeError if def_arg.type && !arg.is_a?(def_arg.type)
123
+ else
124
+ arg = def_arg.default
125
+ end
126
+
127
+ argv.push(arg)
128
+ end
129
+
130
+ argv
131
+ end
132
+ private_class_method :check_method_named_args
133
+
134
+ def self.check_method_ordered_args(args, def_args)
135
+ def_args.each_with_index do |def_arg, index|
136
+ raise MissingArgError if def_arg.required && (index >= args.length)
137
+ raise TypeError if def_arg.type && !args[index].is_a?(def_arg.type)
138
+
139
+ args.push(def_arg.default) if index >= args.length
140
+ end
141
+
142
+ args
143
+ end
144
+ private_class_method :check_method_ordered_args
145
+
146
+ def self.acro_method(name, *def_args, &b)
147
+ define_method(name) do |*args|
148
+ if @engine.options[:log_method_calls]
149
+ @engine.options[:console].puts(
150
+ "LOG: #{self.class}.#{name}(#{args.map { |arg| Arg.inspect(arg) }.join(",")})"
151
+ )
152
+ end
153
+
154
+ args = AcrobatObject.check_method_args(args, def_args)
155
+ instance_exec(*args, &b) if b
156
+ end
157
+ end
158
+
159
+ def self.acro_method_protected(name, *def_args, &b)
160
+ define_method(name) do |*args|
161
+ if @engine.options[:log_method_calls]
162
+ @engine.options[:console].puts(
163
+ "LOG: #{self.class}.#{name}(#{args.map { |arg| arg.inspect }.join(",")})"
164
+ )
165
+ end
166
+
167
+ unless @engine.privileged?
168
+ raise NotAllowedError, "Security settings prevent access to this property or method."
169
+ end
170
+
171
+ args = AcrobatObject.check_method_args(args, def_args)
172
+ instance_exec(*args, &b) if b
173
+ end
174
+ end
175
+
176
+ def to_s
177
+ "[object #{self.class.to_s.split("::").last}]"
178
+ end
179
+ alias_method :inspect, :to_s
180
+ end
181
+
182
+ class AcroTimer < AcrobatObject
183
+ def initialize(engine, timeout, code, repeat)
184
+ @thr = Thread.start(engine, timeout, code, repeat) do
185
+ loop do
186
+ sleep(timeout / 1000.0)
187
+ engine.exec(code.to_s)
188
+ break if !repeat
189
+ end
190
+ end
191
+ end
192
+ end
22
193
 
23
- begin
24
- require 'v8'
25
-
26
- class PDF
27
-
28
- module JavaScript
29
- module Platforms
30
- WINDOWS = "WIN"
31
- UNIX = "UNIX"
32
- MAC = "MAC"
33
- end
34
-
35
- module Viewers
36
- ADOBE_READER = "Reader"
37
- end
38
-
39
- class Error < Origami::Error; end
40
-
41
- class MissingArgError < Error
42
- def initialize; super("Missing required argument.") end
43
- end
44
-
45
- class TypeError < Error
46
- def initialize; super("Incorrect argument type.") end
47
- end
48
-
49
- class InvalidArgsError < Error
50
- def initialize; super("Incorrect arguments.") end
51
- end
52
-
53
- class NotAllowedError < Error
54
- def initialize; super("Security settings prevent access to this property or method.") end
55
- end
56
-
57
- class HelpError < Error
58
- def initialize; super("Help") end
59
- end
60
-
61
- class GeneralError < Error
62
- def initialize; super("Operation failed.") end
63
- end
64
-
65
- class Arg
66
- attr_reader :name, :type, :required, :default
67
-
68
- def initialize(declare = {})
69
- @name = declare[:name]
70
- @type = declare[:type]
71
- @required = declare[:required]
72
- @default = declare[:default]
73
- end
74
-
75
- def self.[](declare = {})
76
- self.new(declare)
77
- end
78
-
79
- def self.inspect(obj)
80
- case obj
81
- when V8::Function then "function #{obj.name}"
82
- when V8::Array then obj.to_a.inspect
83
- when V8::Object
84
- "{#{obj.to_a.map{|k,v| "#{k}:#{Arg.inspect(v)}"}.join(', ')}}"
85
- else
86
- obj.inspect
87
- end
88
- end
89
- end
90
-
91
- class AcrobatObject
92
- def initialize(engine)
93
- @engine = engine
94
- end
95
-
96
- def self.check_method_args(args, def_args)
97
- if args.first.is_a?(V8::Object)
98
- check_method_named_args(args.first, def_args)
99
- else
100
- check_method_ordered_args(args, def_args)
101
- end
102
- end
103
-
104
- def self.check_method_named_args(object, def_args)
105
- members = object.entries.map {|k, _| k}
106
- argv = []
107
- def_args.each do |def_arg|
108
- raise MissingArgError if def_arg.required and not members.include?(def_arg.name)
109
-
110
- if members.include?(def_arg.name)
111
- arg = object[def_arg.name]
112
- raise TypeError if def_arg.type and not arg.is_a?(def_arg.type)
113
- else
114
- arg = def_arg.default
115
- end
116
-
117
- argv.push(arg)
118
- end
119
-
120
- argv
121
- end
122
- private_class_method :check_method_named_args
123
-
124
- def self.check_method_ordered_args(args, def_args)
125
- def_args.each_with_index do |def_arg, index|
126
- raise MissingArgError if def_arg.required and index >= args.length
127
- raise TypeError if def_arg.type and not args[index].is_a?(def_arg.type)
128
-
129
- args.push(def_arg.default) if index >= args.length
130
- end
131
-
132
- args
133
- end
134
- private_class_method :check_method_ordered_args
135
-
136
- def self.acro_method(name, *def_args, &b)
137
- define_method(name) do |*args|
138
- if @engine.options[:log_method_calls]
139
- @engine.options[:console].puts(
140
- "LOG: #{self.class}.#{name}(#{args.map{|arg| Arg.inspect(arg)}.join(',')})"
141
- )
142
- end
143
-
144
- args = AcrobatObject.check_method_args(args, def_args)
145
- self.instance_exec(*args, &b) if b
146
- end
147
- end
148
-
149
- def self.acro_method_protected(name, *def_args, &b)
150
- define_method(name) do |*args|
151
- if @engine.options[:log_method_calls]
152
- @engine.options[:console].puts(
153
- "LOG: #{self.class}.#{name}(#{args.map{|arg| arg.inspect}.join(',')})"
154
- )
155
- end
156
-
157
- unless @engine.privileged?
158
- raise NotAllowedError, "Security settings prevent access to this property or method."
159
- end
160
-
161
- args = AcrobatObject.check_method_args(args, def_args)
162
- self.instance_exec(*args, &b) if b
163
- end
164
- end
165
-
166
- def to_s
167
- "[object #{self.class.to_s.split('::').last}]"
168
- end
169
- alias inspect to_s
170
- end
171
-
172
- class AcroTimer < AcrobatObject
173
- def initialize(engine, timeout, code, repeat)
174
- @thr = Thread.start(engine, timeout, code, repeat) do
175
- loop do
176
- sleep(timeout / 1000.0)
177
- engine.exec(code.to_s)
178
- break if not repeat
179
- end
180
- end
181
- end
182
- end
183
-
184
- class TimeOut < AcroTimer
185
- def initialize(engine, timeout, code)
186
- super(engine, timeout, code, false)
187
- end
188
- end
189
-
190
- class Interval < AcroTimer
191
- def initialize(engine, timeout, code)
192
- super(engine, timeout, code, true)
193
- end
194
- end
195
-
196
- class ReadStream < AcrobatObject
197
- def initialize(engine, data)
198
- super(engine)
199
-
200
- @data = data
201
- end
202
-
203
- acro_method 'read', Arg[name: 'nBytes', type: Numeric, required: true] do |nBytes|
204
- @data.slice!(0, nBytes).unpack("H*")[0]
205
- end
206
- end
207
-
208
- class Acrohelp < AcrobatObject; end
209
-
210
- class Global < AcrobatObject
211
- def initialize(engine)
212
- super(engine)
213
-
214
- @vars = {}
215
- end
216
-
217
- def []=(name, value)
218
- @vars[name] ||= {callbacks: []}
219
- @vars[name][:value] = value
220
- @vars[name][:callbacks].each do |callback|
221
- callback.call(value)
222
- end
223
- end
224
-
225
- def [](name)
226
- @vars[name][:value] if @vars.include?(name)
227
- end
228
-
229
- acro_method 'setPersistent',
230
- Arg[name: 'cVariable', required: true],
231
- Arg[name: 'bPersist', required: true] do |cVariable, _bPersist|
232
-
233
- raise GeneralError unless @vars.include?(cVariable)
234
- end
235
-
236
- acro_method 'subscribe',
237
- Arg[name: 'cVariable', required: true],
238
- Arg[name: 'fCallback', type: V8::Function, require: true] do |cVariable, fCallback|
239
-
240
- if @vars.include?(cVariable)
241
- @vars[cVariable][:callbacks].push(fCallback)
242
- fCallback.call(@vars[cVariable][:value])
243
- end
244
- end
245
- end
246
-
247
- class Doc < AcrobatObject
248
- attr_reader :info
249
- attr_accessor :disclosed
250
- attr_reader :hidden
251
-
252
- attr_reader :app, :acrohelp, :global, :console, :util
253
-
254
- class Info < AcrobatObject
255
- def initialize(engine, doc)
256
- super(engine)
257
-
258
- @doc = doc
259
- end
260
-
261
- def title; @doc.title.to_s end
262
- def author; @doc.author.to_s end
263
- def subject; @doc.subject.to_s end
264
- def keywords; @doc.keywords.to_s end
265
- def creator; @doc.creator.to_s end
266
- def creationDate; @doc.creation_date.to_s end
267
- def modDate; @doc.mod_date.to_s end
268
- end
269
-
270
- def initialize(*args)
271
- engine, pdf = args # XXX: Bypass therubyracer bug #238. Temporary.
272
- super(engine)
273
-
274
- @pdf = pdf
275
- @disclosed = false
276
- @hidden = false
277
- @info = Info.new(@engine, pdf)
278
-
279
- @app = JavaScript::App.new(@engine)
280
- @acrohelp = JavaScript::Acrohelp.new(@engine)
281
- @global = JavaScript::Global.new(@engine)
282
- @console = JavaScript::Console.new(@engine)
283
- @util = JavaScript::Util.new(@engine)
284
- end
285
-
286
- ### PROPERTIES ###
287
-
288
- def numFields
289
- fields = @pdf.fields
290
-
291
- fields.size
292
- end
293
-
294
- def numPages; @pdf.pages.size end
295
-
296
- def title; @info.title end
297
- def author; @info.author end
298
- def subject; @info.subject end
299
- def keywords; @info.keywords end
300
- def creator; @info.creator end
301
- def creationDate; @info.creationDate end
302
- def modDate; @info.modDate end
303
-
304
- def metadata
305
- meta = @pdf.Catalog.Metadata
306
-
307
- (meta.data if meta.is_a?(Stream)).to_s
308
- end
309
-
310
- def filesize; @pdf.original_filesize end
311
- def path; @pdf.original_filename.to_s end
312
- def documentFileName; File.basename(self.path) end
313
- def URL; "file://#{self.path}" end
314
- def baseURL; '' end
315
-
316
- def dataObjects
317
- data_objs = []
318
- @pdf.each_attachment do |name, file_desc|
319
- if file_desc and file_desc.EF and (f = file_desc.EF.F)
320
- data_objs.push Data.new(@engine, name, f.data.size) if f.is_a?(Stream)
321
- end
322
- end
194
+ class TimeOut < AcroTimer
195
+ def initialize(engine, timeout, code)
196
+ super(engine, timeout, code, false)
197
+ end
198
+ end
199
+
200
+ class Interval < AcroTimer
201
+ def initialize(engine, timeout, code)
202
+ super(engine, timeout, code, true)
203
+ end
204
+ end
323
205
 
324
- data_objs
325
- end
206
+ class ReadStream < AcrobatObject
207
+ def initialize(engine, data)
208
+ super(engine)
326
209
 
327
- ### METHODS ###
210
+ @data = data
211
+ end
212
+
213
+ acro_method 'read', Arg[name: 'nBytes', type: Numeric, required: true] do |nBytes|
214
+ @data.slice!(0, nBytes).unpack1("H*")
215
+ end
216
+ end
328
217
 
329
- acro_method 'closeDoc'
218
+ class Acrohelp < AcrobatObject; end
330
219
 
331
- acro_method 'getDataObject',
332
- Arg[name: 'cName', type: ::String, required: true] do |cName|
220
+ class Global < AcrobatObject
221
+ def initialize(engine)
222
+ super
223
+
224
+ @vars = {}
225
+ end
226
+
227
+ def []=(name, value)
228
+ @vars[name] ||= {callbacks: []}
229
+ @vars[name][:value] = value
230
+ @vars[name][:callbacks].each do |callback|
231
+ callback.call(value)
232
+ end
233
+ end
234
+
235
+ def [](name)
236
+ @vars[name][:value] if @vars.include?(name)
237
+ end
238
+
239
+ acro_method 'setPersistent',
240
+ Arg[name: 'cVariable', required: true],
241
+ Arg[name: 'bPersist', required: true] do |cVariable, _bPersist|
242
+ raise GeneralError unless @vars.include?(cVariable)
243
+ end
244
+
245
+ acro_method 'subscribe',
246
+ Arg[name: 'cVariable', required: true],
247
+ Arg[name: 'fCallback', type: V8::Function, require: true] do |cVariable, fCallback|
248
+ if @vars.include?(cVariable)
249
+ @vars[cVariable][:callbacks].push(fCallback)
250
+ fCallback.call(@vars[cVariable][:value])
251
+ end
252
+ end
253
+ end
254
+
255
+ class Doc < AcrobatObject
256
+ attr_reader :info
257
+ attr_accessor :disclosed
258
+ attr_reader :hidden
259
+
260
+ attr_reader :app, :acrohelp, :global, :console, :util
261
+
262
+ class Info < AcrobatObject
263
+ def initialize(engine, doc)
264
+ super(engine)
265
+
266
+ @doc = doc
267
+ end
268
+
269
+ def title
270
+ @doc.title.to_s
271
+ end
333
272
 
334
- file_desc = @pdf.resolve_name(Names::EMBEDDED_FILES, cName)
273
+ def author
274
+ @doc.author.to_s
275
+ end
335
276
 
336
- if file_desc and file_desc.EF and (f = file_desc.EF.F)
337
- Data.new(@engine, cName, f.data.size) if f.is_a?(Stream)
338
- else
339
- raise TypeError
340
- end
341
- end
277
+ def subject
278
+ @doc.subject.to_s
279
+ end
342
280
 
343
- acro_method 'getDataObjectContents',
344
- Arg[name: 'cName', type: ::String, required: true],
345
- Arg[name: 'bAllowAuth', default: false] do |cName, _bAllowAuth|
281
+ def keywords
282
+ @doc.keywords.to_s
283
+ end
346
284
 
347
- file_desc = @pdf.resolve_name(Names::EMBEDDED_FILES, cName)
348
-
349
- if file_desc and file_desc.EF and (f = file_desc.EF.F)
350
- ReadStream.new(@engine, f.data) if f.is_a?(Stream)
351
- else
352
- raise TypeError
353
- end
354
- end
355
-
356
- acro_method 'exportDataObject',
357
- Arg[name: 'cName', type: ::String, required: true],
358
- Arg[name: 'cDIPath' ],
359
- Arg[name: 'bAllowAuth'],
360
- Arg[name: 'nLaunch'] do |cName, _cDIPath, _bAllowAuth, _nLaunch|
361
-
362
- file_desc = @pdf.resolve_name(Names::EMBEDDED_FILES, cName)
363
-
364
- if file_desc and file_desc.EF and (f = file_desc.EF.F)
365
- else
366
- raise TypeError
367
- end
285
+ def creator
286
+ @doc.creator.to_s
287
+ end
368
288
 
369
- raise TypeError if f.nil?
370
- end
289
+ def creationDate
290
+ @doc.creation_date.to_s
291
+ end
371
292
 
372
- acro_method 'getField',
373
- Arg[name: 'cName', type: ::Object, required: true] do |cName|
293
+ def modDate
294
+ @doc.mod_date.to_s
295
+ end
296
+ end
374
297
 
375
- field = @pdf.get_field(cName)
298
+ def initialize(*args)
299
+ engine, pdf = args # XXX: Bypass therubyracer bug #238. Temporary.
300
+ super(engine)
376
301
 
377
- Field.new(@engine, field) if field
378
- end
302
+ @pdf = pdf
303
+ @disclosed = false
304
+ @hidden = false
305
+ @info = Info.new(@engine, pdf)
379
306
 
380
- acro_method 'getNthFieldName',
381
- Arg[name: 'nIndex', type: ::Object, required: true] do |nIndex|
307
+ @app = JavaScript::App.new(@engine)
308
+ @acrohelp = JavaScript::Acrohelp.new(@engine)
309
+ @global = JavaScript::Global.new(@engine)
310
+ @console = JavaScript::Console.new(@engine)
311
+ @util = JavaScript::Util.new(@engine)
312
+ end
382
313
 
383
- nIndex =
384
- case nIndex
385
- when false then 0
386
- when true then 1
387
- else
388
- @engine.parseInt.call(nIndex)
389
- end
314
+ ### PROPERTIES ###
390
315
 
391
- raise TypeError if (nIndex.is_a?(Float) and nIndex.nan?) or nIndex < 0
392
- fields = @pdf.fields
316
+ def numFields
317
+ fields = @pdf.fields
393
318
 
394
- if fields and nIndex <= fields.size - 1
395
- Field.new(@engine, fields.take(nIndex + 1).last).name.to_s
396
- else
397
- ""
398
- end
399
- end
400
- end
319
+ fields.size
320
+ end
401
321
 
402
- class App < AcrobatObject
322
+ def numPages
323
+ @pdf.pages.size
324
+ end
403
325
 
404
- def platform; @engine.options[:platform] end
405
- def viewerType; @engine.options[:viewerType] end
406
- def viewerVariation; @engine.options[:viewerVariation] end
407
- def viewerVersion; @engine.options[:viewerVersion] end
326
+ def title
327
+ @info.title
328
+ end
408
329
 
409
- def activeDocs; [] end
410
-
411
- ### METHODS ###
330
+ def author
331
+ @info.author
332
+ end
412
333
 
413
- acro_method 'setInterval',
414
- Arg[name: 'cExpr', required: true],
415
- Arg[name: 'nMilliseconds', type: Numeric, required: true] do |cExpr, nMilliseconds|
334
+ def subject
335
+ @info.subject
336
+ end
416
337
 
417
- Interval.new(@engine, nMilliseconds, cExpr)
418
- end
338
+ def keywords
339
+ @info.keywords
340
+ end
419
341
 
420
- acro_method 'setTimeOut',
421
- Arg[name: 'cExpr', required: true],
422
- Arg[name: 'nMilliseconds', type: Numeric, required: true] do |cExpr, nMilliseconds|
342
+ def creator
343
+ @info.creator
344
+ end
423
345
 
424
- TimeOut.new(@engine, nMilliseconds, cExpr)
425
- end
346
+ def creationDate
347
+ @info.creationDate
348
+ end
426
349
 
427
- acro_method 'clearInterval',
428
- Arg[name: 'oInterval', type: Interval, required: true] do |oInterval|
350
+ def modDate
351
+ @info.modDate
352
+ end
429
353
 
430
- oInterval.instance_variable_get(:@thr).terminate
431
- nil
432
- end
354
+ def metadata
355
+ meta = @pdf.Catalog.Metadata
433
356
 
434
- acro_method 'clearTimeOut',
435
- Arg[name: 'oInterval', type: TimeOut, required: true] do |oInterval|
436
-
437
- oInterval.instance_variable_get(:@thr).terminate
438
- nil
439
- end
440
-
441
- acro_method_protected 'addMenuItem'
442
- acro_method_protected 'addSubMenu'
443
- acro_method 'addToolButton'
444
- acro_method_protected 'beginPriv'
445
- acro_method 'beep'
446
- acro_method_protected 'browseForDoc'
447
- acro_method_protected 'endPriv'
448
- end
449
-
450
- class Console < AcrobatObject
451
- def println(*args)
452
- raise MissingArgError unless args.length > 0
357
+ (meta.data if meta.is_a?(Stream)).to_s
358
+ end
453
359
 
454
- @engine.options[:console].puts(args.first.to_s)
455
- end
456
-
457
- acro_method 'show'
458
- acro_method 'clear'
459
- acro_method 'hide'
460
- end
360
+ def filesize
361
+ @pdf.original_filesize
362
+ end
461
363
 
462
- class Util < AcrobatObject
463
- acro_method 'streamFromString',
464
- Arg[name: 'cString', type: ::Object, required: true],
465
- Arg[name: 'cCharset', type: ::Object, default: 'utf-8'] do |cString, _cCharset|
364
+ def path
365
+ @pdf.original_filename.to_s
366
+ end
466
367
 
467
- ReadStream.new(@engine, cString.to_s)
468
- end
368
+ def documentFileName
369
+ File.basename(path)
370
+ end
469
371
 
470
- acro_method 'stringFromStream',
471
- Arg[name: 'oStream', type: ReadStream, required: true],
472
- Arg[name: 'cCharset', type: ::Object, default: 'utf-8'] do |oStream, _cCharset|
372
+ def URL
373
+ "file://#{path}"
374
+ end
473
375
 
474
- oStream.instance_variable_get(:@data).dup
475
- end
476
- end
477
-
478
- class Field < AcrobatObject
479
- def initialize(engine, field)
480
- super(engine)
481
-
482
- @field = field
483
- end
484
-
485
- def doc; Doc.new(@field.document) end
486
- def name
487
- (@field.T.value if @field.has_key?(:T)).to_s
488
- end
376
+ def baseURL
377
+ ''
378
+ end
489
379
 
490
- def value
491
- @field.V.value if @field.has_key?(:V)
492
- end
380
+ def dataObjects
381
+ data_objs = []
382
+ @pdf.each_attachment do |name, file_desc|
383
+ if file_desc&.EF && (f = file_desc.EF.F)
384
+ data_objs.push Data.new(@engine, name, f.data.size) if f.is_a?(Stream)
385
+ end
386
+ end
493
387
 
494
- def valueAsString
495
- self.value.to_s
496
- end
388
+ data_objs
389
+ end
497
390
 
498
- def type
499
- return '' unless @field.key?(:FT)
391
+ ### METHODS ###
500
392
 
501
- type_name =
502
- case @field.FT.value
503
- when PDF::Field::Type::BUTTON
504
- button_type
393
+ acro_method 'closeDoc'
505
394
 
506
- when PDF::Field::Type::TEXT then 'text'
507
- when PDF::Field::Type::SIGNATURE then 'signature'
508
- when PDF::Field::Type::CHOICE
509
- choice_type
510
- end
511
-
512
- type_name.to_s
513
- end
514
-
515
- private
516
-
517
- def button_type
518
- return if @field.key?(:Ff) and not @field.Ff.is_a?(Integer)
519
-
520
- flags = @field.Ff.to_i
521
-
522
- if (flags & Annotation::Widget::Button::Flags::PUSHBUTTON) != 0
523
- 'button'
524
- elsif (flags & Annotation::Widget::Button::Flags::RADIO) != 0
525
- 'radiobox'
526
- else
527
- 'checkbox'
528
- end
529
- end
530
-
531
- def choice_type
532
- return if @field.key?(:Ff) and not @field.Ff.is_a?(Integer)
533
-
534
- if (@field.Ff.to_i & Annotation::Widget::Choice::Flags::COMBO) != 0
535
- 'combobox'
536
- else
537
- 'listbox'
538
- end
539
- end
540
- end
541
-
542
- class Data < AcrobatObject
543
- attr_reader :name, :path, :size
544
- attr_reader :creationDate, :modDate
545
- attr_reader :description, :MIMEType
546
-
547
- def initialize(engine, name, size, **metadata)
548
- super(engine)
395
+ acro_method 'getDataObject',
396
+ Arg[name: 'cName', type: ::String, required: true] do |cName|
397
+ file_desc = @pdf.resolve_name(Names::EMBEDDED_FILES, cName)
549
398
 
550
- @name, @size = name, size
551
-
552
- @path, @creationDate, @modDate,
553
- @description, @MIMEType = metadata.values_at(:path, :creationDate, :modDate, :description, :MIMEType)
554
- end
555
- end
399
+ if file_desc&.EF && (f = file_desc.EF.F)
400
+ Data.new(@engine, cName, f.data.size) if f.is_a?(Stream)
401
+ else
402
+ raise TypeError
556
403
  end
404
+ end
557
405
 
558
- class JavaScript::EngineError < Origami::Error; end
559
-
560
- class JavaScript::Engine
561
- attr_reader :doc
562
- attr_reader :context
563
- attr_reader :options
564
- attr_reader :privileged_mode
565
- attr_reader :parseInt
566
-
567
- def initialize(pdf)
568
- @options =
569
- {
570
- formsVersion: 11.008,
571
- viewerVersion: 11.008,
572
- viewerType: JavaScript::Viewers::ADOBE_READER,
573
- viewerVariation: JavaScript::Viewers::ADOBE_READER,
574
- platform: JavaScript::Platforms::WINDOWS,
575
- console: STDOUT,
576
- log_method_calls: false,
577
- privileged_mode: false
578
- }
579
-
580
- @doc = JavaScript::Doc.new(self, pdf)
581
- @context = V8::Context.new(with: @doc)
582
- @privileged_mode = @options[:privileged_mode]
583
-
584
- @parseInt = V8::Context.new['parseInt']
585
- @hooks = {}
586
- end
587
-
588
- #
589
- # Returns true if the engine is set to execute in privileged mode.
590
- # Allows execution of security protected methods.
591
- #
592
- def privileged?
593
- @privileged_mode
594
- end
595
-
596
- #
597
- # Evaluates a JavaScript code in the current context.
598
- #
599
- def exec(script)
600
- @context.eval(script)
601
- end
602
-
603
- #
604
- # Set a hook on a JavaScript method.
605
- #
606
- def hook(name, &callback)
607
- ns = name.split('.')
608
- previous = @context
609
-
610
- ns.each do |n|
611
- raise JavaScript::EngineError, "#{name} does not exist" if previous.nil?
612
- previous = previous[n]
613
- end
614
-
615
- case previous
616
- when V8::Function, UnboundMethod, nil then
617
- @context[name] = lambda do |*args|
618
- callback[previous, *args]
619
- end
620
-
621
- @hooks[name] = [previous, callback]
622
- else
623
- raise JavaScript::EngineError, "#{name} is not a function"
624
- end
625
- end
626
-
627
- #
628
- # Removes an existing hook on a JavaScript method.
629
- #
630
- def unhook(name)
631
- @context[name] = @hooks[name][0] if @hooks.has_key?(name)
632
- end
633
-
634
- #
635
- # Returns an Hash of all defined members in specified object name.
636
- #
637
- def members(obj)
638
- members = {}
639
- list = @context.eval <<-JS
640
- (function(base) {
641
- var members = [];
642
- for (var i in base) members.push([i, base[i]]);
643
- return members;
644
- })(#{obj})
645
- JS
646
-
647
- list.each do |var|
648
- members[var[0]] = var[1]
649
- end
650
-
651
- members
652
- end
653
-
654
- #
655
- # Returns all members in the global scope.
656
- #
657
- def scope
658
- members('this')
659
- end
660
-
661
- #
662
- # Binds the V8 remote debugging agent on the specified TCP _port_.
663
- #
664
- def enable_debugger(port = 5858)
665
- V8::C::Debug.EnableAgent("Origami", port)
666
- end
667
-
668
- def debugger_break
669
- exec 'debugger'
670
- end
406
+ acro_method 'getDataObjectContents',
407
+ Arg[name: 'cName', type: ::String, required: true],
408
+ Arg[name: 'bAllowAuth', default: false] do |cName, _bAllowAuth|
409
+ file_desc = @pdf.resolve_name(Names::EMBEDDED_FILES, cName)
410
+
411
+ if file_desc&.EF && (f = file_desc.EF.F)
412
+ ReadStream.new(@engine, f.data) if f.is_a?(Stream)
413
+ else
414
+ raise TypeError
415
+ end
416
+ end
417
+
418
+ acro_method 'exportDataObject',
419
+ Arg[name: 'cName', type: ::String, required: true],
420
+ Arg[name: 'cDIPath'],
421
+ Arg[name: 'bAllowAuth'],
422
+ Arg[name: 'nLaunch'] do |cName, _cDIPath, _bAllowAuth, _nLaunch|
423
+ file_desc = @pdf.resolve_name(Names::EMBEDDED_FILES, cName)
424
+
425
+ if file_desc&.EF && (f = file_desc.EF.F)
426
+ else
427
+ raise TypeError
671
428
  end
672
- end
673
429
 
674
- module String
675
- #
676
- # Evaluates the current String as JavaScript.
677
- #
678
- def eval_js
679
- self.document.eval_js(self.value)
430
+ raise TypeError if f.nil?
431
+ end
432
+
433
+ acro_method 'getField',
434
+ Arg[name: 'cName', type: ::Object, required: true] do |cName|
435
+ field = @pdf.get_field(cName)
436
+
437
+ Field.new(@engine, field) if field
438
+ end
439
+
440
+ acro_method 'getNthFieldName',
441
+ Arg[name: 'nIndex', type: ::Object, required: true] do |nIndex|
442
+ nIndex =
443
+ case nIndex
444
+ when false then 0
445
+ when true then 1
446
+ else
447
+ @engine.parseInt.call(nIndex)
448
+ end
449
+
450
+ raise TypeError if (nIndex.is_a?(Float) && nIndex.nan?) || (nIndex < 0)
451
+ fields = @pdf.fields
452
+
453
+ if fields && (nIndex <= fields.size - 1)
454
+ Field.new(@engine, fields.take(nIndex + 1).last).name.to_s
455
+ else
456
+ ""
680
457
  end
458
+ end
681
459
  end
682
460
 
683
- class Stream
684
- #
685
- # Evaluates the current Stream as JavaScript.
686
- #
687
- def eval_js
688
- self.document.eval_js(self.data)
689
- end
461
+ class App < AcrobatObject
462
+ def platform
463
+ @engine.options[:platform]
464
+ end
465
+
466
+ def viewerType
467
+ @engine.options[:viewerType]
468
+ end
469
+
470
+ def viewerVariation
471
+ @engine.options[:viewerVariation]
472
+ end
473
+
474
+ def viewerVersion
475
+ @engine.options[:viewerVersion]
476
+ end
477
+
478
+ def activeDocs
479
+ []
480
+ end
481
+
482
+ ### METHODS ###
483
+
484
+ acro_method 'setInterval',
485
+ Arg[name: 'cExpr', required: true],
486
+ Arg[name: 'nMilliseconds', type: Numeric, required: true] do |cExpr, nMilliseconds|
487
+ Interval.new(@engine, nMilliseconds, cExpr)
488
+ end
489
+
490
+ acro_method 'setTimeOut',
491
+ Arg[name: 'cExpr', required: true],
492
+ Arg[name: 'nMilliseconds', type: Numeric, required: true] do |cExpr, nMilliseconds|
493
+ TimeOut.new(@engine, nMilliseconds, cExpr)
494
+ end
495
+
496
+ acro_method 'clearInterval',
497
+ Arg[name: 'oInterval', type: Interval, required: true] do |oInterval|
498
+ oInterval.instance_variable_get(:@thr).terminate
499
+ nil
500
+ end
501
+
502
+ acro_method 'clearTimeOut',
503
+ Arg[name: 'oInterval', type: TimeOut, required: true] do |oInterval|
504
+ oInterval.instance_variable_get(:@thr).terminate
505
+ nil
506
+ end
507
+
508
+ acro_method_protected 'addMenuItem'
509
+ acro_method_protected 'addSubMenu'
510
+ acro_method 'addToolButton'
511
+ acro_method_protected 'beginPriv'
512
+ acro_method 'beep'
513
+ acro_method_protected 'browseForDoc'
514
+ acro_method_protected 'endPriv'
690
515
  end
691
516
 
692
- class PDF
693
- #
694
- # Executes a JavaScript script in the current document context.
695
- #
696
- def eval_js(code)
697
- js_engine.exec(code)
517
+ class Console < AcrobatObject
518
+ def println(*args)
519
+ raise MissingArgError unless args.length > 0
520
+
521
+ @engine.options[:console].puts(args.first.to_s)
522
+ end
523
+
524
+ acro_method 'show'
525
+ acro_method 'clear'
526
+ acro_method 'hide'
527
+ end
528
+
529
+ class Util < AcrobatObject
530
+ acro_method 'streamFromString',
531
+ Arg[name: 'cString', type: ::Object, required: true],
532
+ Arg[name: 'cCharset', type: ::Object, default: 'utf-8'] do |cString, _cCharset|
533
+ ReadStream.new(@engine, cString.to_s)
534
+ end
535
+
536
+ acro_method 'stringFromStream',
537
+ Arg[name: 'oStream', type: ReadStream, required: true],
538
+ Arg[name: 'cCharset', type: ::Object, default: 'utf-8'] do |oStream, _cCharset|
539
+ oStream.instance_variable_get(:@data).dup
540
+ end
541
+ end
542
+
543
+ class Field < AcrobatObject
544
+ def initialize(engine, field)
545
+ super(engine)
546
+
547
+ @field = field
548
+ end
549
+
550
+ def doc
551
+ Doc.new(@field.document)
552
+ end
553
+
554
+ def name
555
+ (@field.T.value if @field.has_key?(:T)).to_s
556
+ end
557
+
558
+ def value
559
+ @field.V.value if @field.has_key?(:V)
560
+ end
561
+
562
+ def valueAsString
563
+ value.to_s
564
+ end
565
+
566
+ def type
567
+ return '' unless @field.key?(:FT)
568
+
569
+ type_name =
570
+ case @field.FT.value
571
+ when PDF::Field::Type::BUTTON
572
+ button_type
573
+
574
+ when PDF::Field::Type::TEXT then 'text'
575
+ when PDF::Field::Type::SIGNATURE then 'signature'
576
+ when PDF::Field::Type::CHOICE
577
+ choice_type
578
+ end
579
+
580
+ type_name.to_s
581
+ end
582
+
583
+ private
584
+
585
+ def button_type
586
+ return if @field.key?(:Ff) && !@field.Ff.is_a?(Integer)
587
+
588
+ flags = @field.Ff.to_i
589
+
590
+ if (flags & Annotation::Widget::Button::Flags::PUSHBUTTON) != 0
591
+ 'button'
592
+ elsif (flags & Annotation::Widget::Button::Flags::RADIO) != 0
593
+ 'radiobox'
594
+ else
595
+ 'checkbox'
698
596
  end
597
+ end
598
+
599
+ def choice_type
600
+ return if @field.key?(:Ff) && !@field.Ff.is_a?(Integer)
699
601
 
700
- #
701
- # Returns the JavaScript engine (if JavaScript support is present).
702
- #
703
- def js_engine
704
- @js_engine ||= PDF::JavaScript::Engine.new(self)
602
+ if (@field.Ff.to_i & Annotation::Widget::Choice::Flags::COMBO) != 0
603
+ 'combobox'
604
+ else
605
+ 'listbox'
705
606
  end
607
+ end
608
+ end
609
+
610
+ class Data < AcrobatObject
611
+ attr_reader :name, :path, :size
612
+ attr_reader :creationDate, :modDate
613
+ attr_reader :description, :MIMEType
614
+
615
+ def initialize(engine, name, size, **metadata)
616
+ super(engine)
617
+
618
+ @name, @size = name, size
619
+
620
+ @path, @creationDate, @modDate,
621
+ @description, @MIMEType = metadata.values_at(:path, :creationDate, :modDate, :description, :MIMEType)
622
+ end
623
+ end
624
+ end
625
+
626
+ class JavaScript::EngineError < Origami::Error; end
627
+
628
+ class JavaScript::Engine
629
+ attr_reader :doc
630
+ attr_reader :context
631
+ attr_reader :options
632
+ attr_reader :privileged_mode
633
+ attr_reader :parseInt
634
+
635
+ def initialize(pdf)
636
+ @options =
637
+ {
638
+ formsVersion: 11.008,
639
+ viewerVersion: 11.008,
640
+ viewerType: JavaScript::Viewers::ADOBE_READER,
641
+ viewerVariation: JavaScript::Viewers::ADOBE_READER,
642
+ platform: JavaScript::Platforms::WINDOWS,
643
+ console: $stdout,
644
+ log_method_calls: false,
645
+ privileged_mode: false
646
+ }
647
+
648
+ @doc = JavaScript::Doc.new(self, pdf)
649
+ @context = V8::Context.new(with: @doc)
650
+ @privileged_mode = @options[:privileged_mode]
651
+
652
+ @parseInt = V8::Context.new['parseInt']
653
+ @hooks = {}
706
654
  end
707
655
 
708
- rescue LoadError
709
656
  #
710
- # V8 unavailable.
657
+ # Returns true if the engine is set to execute in privileged mode.
658
+ # Allows execution of security protected methods.
711
659
  #
660
+ def privileged?
661
+ @privileged_mode
662
+ end
663
+
664
+ #
665
+ # Evaluates a JavaScript code in the current context.
666
+ #
667
+ def exec(script)
668
+ @context.eval(script)
669
+ end
670
+
671
+ #
672
+ # Set a hook on a JavaScript method.
673
+ #
674
+ def hook(name, &callback)
675
+ ns = name.split('.')
676
+ previous = @context
677
+
678
+ ns.each do |n|
679
+ raise JavaScript::EngineError, "#{name} does not exist" if previous.nil?
680
+ previous = previous[n]
681
+ end
682
+
683
+ case previous
684
+ when V8::Function, UnboundMethod, nil
685
+ @context[name] = lambda do |*args|
686
+ callback[previous, *args]
687
+ end
688
+
689
+ @hooks[name] = [previous, callback]
690
+ else
691
+ raise JavaScript::EngineError, "#{name} is not a function"
692
+ end
693
+ end
694
+
695
+ #
696
+ # Removes an existing hook on a JavaScript method.
697
+ #
698
+ def unhook(name)
699
+ @context[name] = @hooks[name][0] if @hooks.has_key?(name)
700
+ end
701
+
702
+ #
703
+ # Returns an Hash of all defined members in specified object name.
704
+ #
705
+ def members(obj)
706
+ members = {}
707
+ list = @context.eval <<-JS
708
+ (function(base) {
709
+ var members = [];
710
+ for (var i in base) members.push([i, base[i]]);
711
+ return members;
712
+ })(#{obj})
713
+ JS
714
+
715
+ list.each do |var|
716
+ members[var[0]] = var[1]
717
+ end
718
+
719
+ members
720
+ end
721
+
722
+ #
723
+ # Returns all members in the global scope.
724
+ #
725
+ def scope
726
+ members('this')
727
+ end
728
+
729
+ #
730
+ # Binds the V8 remote debugging agent on the specified TCP _port_.
731
+ #
732
+ def enable_debugger(port = 5858)
733
+ V8::C::Debug.EnableAgent("Origami", port)
734
+ end
735
+
736
+ def debugger_break
737
+ exec 'debugger'
738
+ end
739
+ end
740
+ end
741
+
742
+ module String
743
+ #
744
+ # Evaluates the current String as JavaScript.
745
+ #
746
+ def eval_js
747
+ document.eval_js(value)
748
+ end
749
+ end
750
+
751
+ class Stream
752
+ #
753
+ # Evaluates the current Stream as JavaScript.
754
+ #
755
+ def eval_js
756
+ document.eval_js(data)
757
+ end
758
+ end
759
+
760
+ class PDF
761
+ #
762
+ # Executes a JavaScript script in the current document context.
763
+ #
764
+ def eval_js(code)
765
+ js_engine.exec(code)
766
+ end
767
+
768
+ #
769
+ # Returns the JavaScript engine (if JavaScript support is present).
770
+ #
771
+ def js_engine
772
+ @js_engine ||= PDF::JavaScript::Engine.new(self)
773
+ end
712
774
  end
775
+ rescue LoadError
776
+ #
777
+ # V8 unavailable.
778
+ #
779
+ end
713
780
  end