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,705 +1,707 @@
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
+ require 'strscan'
5
22
 
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.
23
+ module Origami
24
+ class InvalidStreamObjectError < InvalidObjectError # :nodoc:
25
+ end
10
26
 
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
+ # Forward declaration.
28
+ class FileSpec < Dictionary; end
15
29
 
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
+ #
31
+ # Class representing a PDF Stream Object.
32
+ # Streams can be used to hold any kind of data, especially binary data.
33
+ #
34
+ class Stream
35
+ include Origami::Object
36
+ include StandardObject
37
+ include FieldAccessor
38
+ include Enumerable
39
+ extend TypeGuessing
18
40
 
19
- =end
41
+ TOKENS = ["stream" + WHITECHARS_NORET + "(\\r\\n|\\r|\\n)", "endstream"] # :nodoc:
20
42
 
21
- require 'strscan'
43
+ @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first)
44
+ @@regexp_close = Regexp.new(TOKENS.last)
22
45
 
23
- module Origami
46
+ #
47
+ # Actually only 5 first ones are implemented,
48
+ # other ones are mainly about image data processing (JPEG, JPEG2000 ...)
49
+ #
50
+ DEFINED_FILTERS = %i[
51
+ ASCIIHexDecode
52
+ ASCII85Decode
53
+ LZWDecode
54
+ FlateDecode
55
+ RunLengthDecode
56
+
57
+ CCITTFaxDecode
58
+ JBIG2Decode
59
+ DCTDecode
60
+ JPXDecode
61
+
62
+ AHx
63
+ A85
64
+ LZW
65
+ Fl
66
+ RL
67
+ CCF
68
+ DCT
69
+ ]
70
+
71
+ attr_reader :dictionary
72
+
73
+ field :Length, Type: Integer, Required: true
74
+ field :Filter, Type: [Name, Array.of(Name)]
75
+ field :DecodeParms, Type: [Dictionary, Array.of(Dictionary)]
76
+ field :F, Type: FileSpec, Version: "1.2"
77
+ field :FFilter, Type: [Name, Array.of(Name)], Version: "1.2"
78
+ field :FDecodeParms, Type: [Dictionary, Array.of(Dictionary)], Version: "1.2"
79
+ field :DL, Type: Integer, Version: "1.5"
24
80
 
25
- class InvalidStreamObjectError < InvalidObjectError #:nodoc:
26
- end
27
-
28
- # Forward declaration.
29
- class FileSpec < Dictionary; end
30
-
31
- #
32
- # Class representing a PDF Stream Object.
33
- # Streams can be used to hold any kind of data, especially binary data.
34
- #
35
- class Stream
36
- include Origami::Object
37
- include StandardObject
38
- include FieldAccessor
39
- include Enumerable
40
- extend TypeGuessing
41
-
42
- TOKENS = [ "stream" + WHITECHARS_NORET + "(\\r\\n|\\r|\\n)" , "endstream" ] #:nodoc:
43
-
44
- @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first)
45
- @@regexp_close = Regexp.new(TOKENS.last)
46
-
47
- #
48
- # Actually only 5 first ones are implemented,
49
- # other ones are mainly about image data processing (JPEG, JPEG2000 ...)
50
- #
51
- DEFINED_FILTERS = %i[
52
- ASCIIHexDecode
53
- ASCII85Decode
54
- LZWDecode
55
- FlateDecode
56
- RunLengthDecode
57
-
58
- CCITTFaxDecode
59
- JBIG2Decode
60
- DCTDecode
61
- JPXDecode
62
-
63
- AHx
64
- A85
65
- LZW
66
- Fl
67
- RL
68
- CCF
69
- DCT
70
- ]
71
-
72
- attr_reader :dictionary
73
-
74
- field :Length, :Type => Integer, :Required => true
75
- field :Filter, :Type => [ Name, Array.of(Name) ]
76
- field :DecodeParms, :Type => [ Dictionary, Array.of(Dictionary) ]
77
- field :F, :Type => FileSpec, :Version => "1.2"
78
- field :FFilter, :Type => [ Name, Array.of(Name) ], :Version => "1.2"
79
- field :FDecodeParms, :Type => [ Dictionary, Array.of(Dictionary) ], :Version => "1.2"
80
- field :DL, :Type => Integer, :Version => "1.5"
81
-
82
- #
83
- # Creates a new PDF Stream.
84
- # _data_:: The Stream uncompressed data.
85
- # _dictionary_:: A hash representing the Stream attributes.
86
- #
87
- def initialize(data = "", dictionary = {})
88
- super()
89
-
90
- set_indirect(true)
91
-
92
- @encoded_data = nil
93
- @dictionary, @data = Dictionary.new(dictionary), data
94
- @dictionary.parent = self
95
- end
81
+ #
82
+ # Creates a new PDF Stream.
83
+ # _data_:: The Stream uncompressed data.
84
+ # _dictionary_:: A hash representing the Stream attributes.
85
+ #
86
+ def initialize(data = "", dictionary = {})
87
+ super()
96
88
 
97
- def dictionary=(dict)
98
- @dictionary = dict
99
- @dictionary.parent = self
100
- end
89
+ set_indirect(true)
101
90
 
102
- def pre_build
103
- encode!
91
+ @encoded_data = nil
92
+ @dictionary, @data = Dictionary.new(dictionary), data
93
+ @dictionary.parent = self
94
+ end
104
95
 
105
- super
106
- end
96
+ def dictionary=(dict)
97
+ @dictionary = dict
98
+ @dictionary.parent = self
99
+ end
107
100
 
108
- def post_build
109
- self.Length = @encoded_data.length
101
+ def pre_build
102
+ encode!
110
103
 
111
- super
112
- end
104
+ super
105
+ end
113
106
 
114
- def self.parse(stream, parser = nil) #:nodoc:
115
- scanner = Parser.init_scanner(stream)
116
- dictionary = Dictionary.parse(scanner, parser)
117
- return dictionary if not scanner.skip(@@regexp_open)
118
-
119
- length = dictionary[:Length]
120
- if not length.is_a?(Integer)
121
- raw_data = scanner.scan_until(@@regexp_close)
122
- if raw_data.nil?
123
- raise InvalidStreamObjectError,
124
- "Stream shall end with a 'endstream' statement"
125
- end
126
- else
127
- length = length.value
128
- raw_data = scanner.peek(length)
129
- scanner.pos += length
130
-
131
- if not ( unmatched = scanner.scan_until(@@regexp_close) )
132
- raise InvalidStreamObjectError,
133
- "Stream shall end with a 'endstream' statement"
134
- end
135
-
136
- raw_data << unmatched
137
- end
138
-
139
- stm =
140
- if Origami::OPTIONS[:enable_type_guessing]
141
- self.guess_type(dictionary).new('', dictionary)
142
- else
143
- Stream.new('', dictionary)
144
- end
145
-
146
- raw_data.chomp!(TOKENS.last)
147
-
148
- if raw_data[-1,1] == "\n"
149
- if raw_data[-2,1] == "\r"
150
- raw_data = raw_data[0, raw_data.size - 2]
151
- else
152
- raw_data = raw_data[0, raw_data.size - 1]
153
- end
154
- end
155
- #raw_data.chomp! if length.is_a?(Integer) and length < raw_data.length
156
-
157
- stm.encoded_data = raw_data
158
- stm.file_offset = dictionary.file_offset
159
-
160
- stm
161
- end
107
+ def post_build
108
+ self.Length = @encoded_data.length
162
109
 
163
- #
164
- # Iterates over each Filter in the Stream.
165
- #
166
- def each_filter
167
- filters = self.Filter
168
-
169
- return enum_for(__method__) do
170
- case filters
171
- when NilClass then 0
172
- when Array then filters.length
173
- else
174
- 1
175
- end
176
- end unless block_given?
177
-
178
- return if filters.nil?
179
-
180
- if filters.is_a?(Array)
181
- filters.each do |filter| yield(filter) end
182
- else
183
- yield(filters)
184
- end
185
-
186
- self
187
- end
110
+ super
111
+ end
112
+
113
+ def self.parse(stream, parser = nil) # :nodoc:
114
+ scanner = Parser.init_scanner(stream)
115
+ dictionary = Dictionary.parse(scanner, parser)
116
+ return dictionary if !scanner.skip(@@regexp_open)
188
117
 
189
- #
190
- # Returns an Array of Filters for this Stream.
191
- #
192
- def filters
193
- self.each_filter.to_a
118
+ length = dictionary[:Length]
119
+ if !length.is_a?(Integer)
120
+ raw_data = scanner.scan_until(@@regexp_close)
121
+ if raw_data.nil?
122
+ raise InvalidStreamObjectError,
123
+ "Stream shall end with a 'endstream' statement"
194
124
  end
125
+ else
126
+ length = length.value
127
+ raw_data = scanner.peek(length)
128
+ scanner.pos += length
195
129
 
196
- #
197
- # Set predictor type for the current Stream.
198
- # Applies only for LZW and FlateDecode filters.
199
- #
200
- def set_predictor(predictor, colors: 1, bitspercomponent: 8, columns: 1)
201
- filters = self.filters
130
+ if !(unmatched = scanner.scan_until(@@regexp_close))
131
+ raise InvalidStreamObjectError,
132
+ "Stream shall end with a 'endstream' statement"
133
+ end
202
134
 
203
- layer = filters.index(:FlateDecode) or filters.index(:LZWDecode)
204
- if layer.nil?
205
- raise InvalidStreamObjectError, 'Predictor functions can only be used with Flate or LZW filters'
206
- end
135
+ raw_data << unmatched
136
+ end
207
137
 
208
- params = Filter::LZW::DecodeParms.new
209
- params[:Predictor] = predictor
210
- params[:Colors] = colors if colors != 1
211
- params[:BitsPerComponent] = bitspercomponent if bitspercomponent != 8
212
- params[:Columns] = columns if columns != 1
138
+ stm =
139
+ if Origami::OPTIONS[:enable_type_guessing]
140
+ guess_type(dictionary).new('', dictionary)
141
+ else
142
+ Stream.new('', dictionary)
143
+ end
213
144
 
214
- set_decode_params(layer, params)
145
+ raw_data.chomp!(TOKENS.last)
215
146
 
216
- self
147
+ if raw_data[-1, 1] == "\n"
148
+ raw_data = if raw_data[-2, 1] == "\r"
149
+ raw_data[0, raw_data.size - 2]
150
+ else
151
+ raw_data[0, raw_data.size - 1]
217
152
  end
153
+ end
154
+ # raw_data.chomp! if length.is_a?(Integer) and length < raw_data.length
218
155
 
219
- def cast_to(type, _parser = nil)
220
- assert_cast_type(type)
156
+ stm.encoded_data = raw_data
157
+ stm.file_offset = dictionary.file_offset
221
158
 
222
- cast = type.new("", self.dictionary.copy)
223
- cast.encoded_data = self.encoded_data.dup
224
- cast.file_offset = self.file_offset
159
+ stm
160
+ end
225
161
 
226
- transfer_attributes(cast)
227
- end
162
+ #
163
+ # Iterates over each Filter in the Stream.
164
+ #
165
+ def each_filter
166
+ filters = self.Filter
228
167
 
229
- def value #:nodoc:
230
- self
168
+ unless block_given?
169
+ return enum_for(__method__) do
170
+ case filters
171
+ when NilClass then 0
172
+ when Array then filters.length
173
+ else
174
+ 1
175
+ end
231
176
  end
177
+ end
232
178
 
233
- #
234
- # Returns the uncompressed stream content.
235
- #
236
- def data
237
- self.decode! unless decoded?
179
+ return if filters.nil?
238
180
 
239
- @data
240
- end
241
- alias decoded_data data
242
-
243
- #
244
- # Sets the uncompressed stream content.
245
- # _str_:: The new uncompressed data.
246
- #
247
- def data=(str)
248
- @encoded_data = nil
249
- @data = str
250
- end
251
- alias decoded_data= data=
181
+ if filters.is_a?(Array)
182
+ filters.each do |filter| yield(filter) end
183
+ else
184
+ yield(filters)
185
+ end
252
186
 
253
- #
254
- # Returns the raw compressed stream content.
255
- #
256
- def encoded_data
257
- self.encode! unless encoded?
187
+ self
188
+ end
258
189
 
259
- @encoded_data
260
- end
190
+ #
191
+ # Returns an Array of Filters for this Stream.
192
+ #
193
+ def filters
194
+ each_filter.to_a
195
+ end
261
196
 
262
- #
263
- # Sets the raw compressed stream content.
264
- # _str_:: the new raw data.
265
- #
266
- def encoded_data=(str)
267
- @encoded_data = str
268
- @data = nil
269
- end
197
+ #
198
+ # Set predictor type for the current Stream.
199
+ # Applies only for LZW and FlateDecode filters.
200
+ #
201
+ def set_predictor(predictor, colors: 1, bitspercomponent: 8, columns: 1)
202
+ filters = self.filters
270
203
 
271
- #
272
- # Uncompress the stream data.
273
- #
274
- def decode!
275
- self.decrypt! if self.is_a?(Encryption::EncryptedStream)
276
- return if decoded?
204
+ layer = filters.index(:FlateDecode) or filters.index(:LZWDecode)
205
+ if layer.nil?
206
+ raise InvalidStreamObjectError, 'Predictor functions can only be used with Flate or LZW filters'
207
+ end
277
208
 
278
- filters = self.filters
279
- dparams = decode_params
209
+ params = Filter::LZW::DecodeParms.new
210
+ params[:Predictor] = predictor
211
+ params[:Colors] = colors if colors != 1
212
+ params[:BitsPerComponent] = bitspercomponent if bitspercomponent != 8
213
+ params[:Columns] = columns if columns != 1
280
214
 
281
- @data = @encoded_data.dup
282
- @data.freeze
215
+ set_decode_params(layer, params)
283
216
 
284
- filters.each_with_index do |filter, layer|
285
- params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
217
+ self
218
+ end
286
219
 
287
- # Handle Crypt filters.
288
- if filter == :Crypt
289
- raise Filter::Error, "Crypt filter must be the first filter" unless layer.zero?
220
+ def cast_to(type, _parser = nil)
221
+ assert_cast_type(type)
290
222
 
291
- # Skip the Crypt filter.
292
- next
293
- end
223
+ cast = type.new("", dictionary.copy)
224
+ cast.encoded_data = encoded_data.dup
225
+ cast.file_offset = file_offset
294
226
 
295
- begin
296
- @data = decode_data(@data, filter, params)
297
- rescue Filter::Error => error
298
- @data = error.decoded_data
299
- raise
300
- end
301
- end
227
+ transfer_attributes(cast)
228
+ end
302
229
 
303
- self
304
- end
230
+ def value # :nodoc:
231
+ self
232
+ end
305
233
 
306
- #
307
- # Compress the stream data.
308
- #
309
- def encode!
310
- return if encoded?
234
+ #
235
+ # Returns the uncompressed stream content.
236
+ #
237
+ def data
238
+ decode! unless decoded?
311
239
 
312
- filters = self.filters
313
- dparams = decode_params
240
+ @data
241
+ end
242
+ alias_method :decoded_data, :data
314
243
 
315
- @encoded_data = @data.dup
316
- (filters.length - 1).downto(0) do |layer|
317
- params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
318
- filter = filters[layer]
244
+ #
245
+ # Sets the uncompressed stream content.
246
+ # _str_:: The new uncompressed data.
247
+ #
248
+ def data=(str)
249
+ @encoded_data = nil
250
+ @data = str
251
+ end
252
+ alias_method :decoded_data=, :data=
319
253
 
320
- # Handle Crypt filters.
321
- if filter == :Crypt
322
- raise Filter::Error, "Crypt filter must be the first filter" unless layer.zero?
254
+ #
255
+ # Returns the raw compressed stream content.
256
+ #
257
+ def encoded_data
258
+ encode! unless encoded?
323
259
 
324
- # Skip the Crypt filter.
325
- next
326
- end
260
+ @encoded_data
261
+ end
327
262
 
328
- @encoded_data = encode_data(@encoded_data, filter, params)
329
- end
263
+ #
264
+ # Sets the raw compressed stream content.
265
+ # _str_:: the new raw data.
266
+ #
267
+ def encoded_data=(str)
268
+ @encoded_data = str
269
+ @data = nil
270
+ end
330
271
 
331
- self.Length = @encoded_data.length
272
+ #
273
+ # Uncompress the stream data.
274
+ #
275
+ def decode!
276
+ decrypt! if is_a?(Encryption::EncryptedStream)
277
+ return if decoded?
332
278
 
333
- self
334
- end
279
+ filters = self.filters
280
+ dparams = decode_params
335
281
 
336
- def to_s(indent: 1, tab: "\t", eol: $/) #:nodoc:
337
- content = ""
282
+ @data = @encoded_data.dup
283
+ @data.freeze
338
284
 
339
- content << @dictionary.to_s(indent: indent, tab: tab)
340
- content << "stream" + eol
341
- content << self.encoded_data
342
- content << eol << TOKENS.last
285
+ filters.each_with_index do |filter, layer|
286
+ params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
343
287
 
344
- super(content, eol: eol)
345
- end
288
+ # Handle Crypt filters.
289
+ if filter == :Crypt
290
+ raise Filter::Error, "Crypt filter must be the first filter" unless layer.zero?
346
291
 
347
- def [](key) #:nodoc:
348
- @dictionary[key]
292
+ # Skip the Crypt filter.
293
+ next
349
294
  end
350
295
 
351
- def []=(key, val) #:nodoc:
352
- @dictionary[key] = val
296
+ begin
297
+ @data = decode_data(@data, filter, params)
298
+ rescue Filter::Error => error
299
+ @data = error.decoded_data
300
+ raise
353
301
  end
302
+ end
354
303
 
355
- def each_key(&b) #:nodoc:
356
- @dictionary.each_key(&b)
357
- end
304
+ self
305
+ end
358
306
 
359
- def each_pair(&b) #:nodoc
360
- @dictionary.each_pair(&b)
361
- end
362
- alias each each_pair
307
+ #
308
+ # Compress the stream data.
309
+ #
310
+ def encode!
311
+ return if encoded?
363
312
 
364
- def key?(name)
365
- @dictionary.key?(name)
366
- end
367
- alias has_key? key?
313
+ filters = self.filters
314
+ dparams = decode_params
368
315
 
369
- def keys
370
- @dictionary.keys
371
- end
316
+ @encoded_data = @data.dup
317
+ (filters.length - 1).downto(0) do |layer|
318
+ params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
319
+ filter = filters[layer]
372
320
 
373
- private
321
+ # Handle Crypt filters.
322
+ if filter == :Crypt
323
+ raise Filter::Error, "Crypt filter must be the first filter" unless layer.zero?
374
324
 
375
- def decoded? #:nodoc:
376
- not @data.nil?
325
+ # Skip the Crypt filter.
326
+ next
377
327
  end
378
328
 
379
- def encoded? #:nodoc:
380
- not @encoded_data.nil?
381
- end
329
+ @encoded_data = encode_data(@encoded_data, filter, params)
330
+ end
382
331
 
383
- def each_decode_params
384
- params = self.DecodeParms
332
+ self.Length = @encoded_data.length
385
333
 
386
- return enum_for(__method__) do
387
- case params
388
- when NilClass then 0
389
- when Array then params.length
390
- else
391
- 1
392
- end
393
- end unless block_given?
334
+ self
335
+ end
394
336
 
395
- return if params.nil?
337
+ def to_s(indent: 1, tab: "\t", eol: $/) # :nodoc:
338
+ content = +""
396
339
 
397
- if params.is_a?(Array)
398
- params.each do |param| yield(param) end
399
- else
400
- yield(params)
401
- end
340
+ content << @dictionary.to_s(indent: indent, tab: tab)
341
+ content << "stream" + eol
342
+ content << encoded_data
343
+ content << eol << TOKENS.last
402
344
 
403
- self
404
- end
345
+ super(content, eol: eol)
346
+ end
405
347
 
406
- def decode_params
407
- each_decode_params.to_a
408
- end
348
+ def [](key) # :nodoc:
349
+ @dictionary[key]
350
+ end
409
351
 
410
- def set_decode_params(layer, params) #:nodoc:
411
- dparms = self.DecodeParms
412
- unless dparms.is_a? ::Array
413
- @dictionary[:DecodeParms] = dparms = []
414
- end
352
+ def []=(key, val) # :nodoc:
353
+ @dictionary[key] = val
354
+ end
415
355
 
416
- if layer > dparms.length - 1
417
- dparms.concat(::Array.new(layer - dparms.length + 1, Null.new))
418
- end
356
+ def each_key(&b) # :nodoc:
357
+ @dictionary.each_key(&b)
358
+ end
419
359
 
420
- dparms[layer] = params
421
- @dictionary[:DecodeParms] = dparms.first if dparms.length == 1
360
+ def each_pair(&b) # :nodoc
361
+ @dictionary.each_pair(&b)
362
+ end
363
+ alias_method :each, :each_pair
422
364
 
423
- self
424
- end
365
+ def key?(name)
366
+ @dictionary.key?(name)
367
+ end
368
+ alias_method :has_key?, :key?
425
369
 
426
- def decode_data(data, filter, params) #:nodoc:
427
- filter_module(filter).decode(data, params)
428
- end
370
+ def keys
371
+ @dictionary.keys
372
+ end
429
373
 
430
- def encode_data(data, filter, params) #:nodoc:
431
- mod = filter_module(filter)
374
+ private
432
375
 
433
- encoded = mod.encode(data, params)
376
+ def decoded? # :nodoc:
377
+ !@data.nil?
378
+ end
434
379
 
435
- if %i[ASCIIHexDecode ASCII85Decode AHx A85].include?(filter.value)
436
- encoded << mod::EOD
437
- end
380
+ def encoded? # :nodoc:
381
+ !@encoded_data.nil?
382
+ end
438
383
 
439
- encoded
384
+ def each_decode_params
385
+ params = self.DecodeParms
386
+
387
+ unless block_given?
388
+ return enum_for(__method__) do
389
+ case params
390
+ when NilClass then 0
391
+ when Array then params.length
392
+ else
393
+ 1
394
+ end
440
395
  end
396
+ end
441
397
 
442
- def filter_module(name)
443
- unless name.is_a?(Name)
444
- raise InvalidObjectStreamObjectError, "Filter has invalid type #{name.type}"
445
- end
398
+ return if params.nil?
446
399
 
447
- unless DEFINED_FILTERS.include?(name.value)
448
- raise InvalidStreamObjectError, "Invalid filter : #{name}"
449
- end
400
+ if params.is_a?(Array)
401
+ params.each do |param| yield(param) end
402
+ else
403
+ yield(params)
404
+ end
450
405
 
451
- Filter.const_get(name.value.to_s.sub(/Decode$/, ""))
452
- end
406
+ self
453
407
  end
454
408
 
455
- #
456
- # Class representing an external Stream.
457
- #
458
- class ExternalStream < Stream
409
+ def decode_params
410
+ each_decode_params.to_a
411
+ end
459
412
 
460
- def initialize(filespec, hash = {})
461
- hash[:F] = filespec
462
- super('', hash)
463
- end
413
+ def set_decode_params(layer, params) # :nodoc:
414
+ dparms = self.DecodeParms
415
+ unless dparms.is_a? ::Array
416
+ @dictionary[:DecodeParms] = dparms = []
417
+ end
418
+
419
+ if layer > dparms.length - 1
420
+ dparms.concat(::Array.new(layer - dparms.length + 1, Null.new))
421
+ end
422
+
423
+ dparms[layer] = params
424
+ @dictionary[:DecodeParms] = dparms.first if dparms.length == 1
425
+
426
+ self
464
427
  end
465
428
 
466
- class InvalidObjectStreamObjectError < InvalidStreamObjectError #:nodoc:
429
+ def decode_data(data, filter, params) # :nodoc:
430
+ filter_module(filter).decode(data, params)
467
431
  end
468
432
 
469
- #
470
- # Class representing a Stream containing other Objects.
471
- #
472
- class ObjectStream < Stream
473
- include Enumerable
433
+ def encode_data(data, filter, params) # :nodoc:
434
+ mod = filter_module(filter)
474
435
 
475
- NUM = 0 #:nodoc:
476
- OBJ = 1 #:nodoc:
436
+ encoded = mod.encode(data, params)
477
437
 
478
- field :Type, :Type => Name, :Default => :ObjStm, :Required => true, :Version => "1.5"
479
- field :N, :Type => Integer, :Required => true
480
- field :First, :Type => Integer, :Required => true
481
- field :Extends, :Type => ObjectStream
438
+ if %i[ASCIIHexDecode ASCII85Decode AHx A85].include?(filter.value)
439
+ encoded << mod::EOD
440
+ end
482
441
 
483
- #
484
- # Creates a new Object Stream.
485
- # _dictionary_:: A hash of attributes to set to the Stream.
486
- # _raw_data_:: The Stream data.
487
- #
488
- def initialize(raw_data = "", dictionary = {})
489
- super
442
+ encoded
443
+ end
490
444
 
491
- @objects = nil
492
- end
445
+ def filter_module(name)
446
+ unless name.is_a?(Name)
447
+ raise InvalidObjectStreamObjectError, "Filter has invalid type #{name.type}"
448
+ end
493
449
 
494
- def pre_build #:nodoc:
495
- load!
450
+ unless DEFINED_FILTERS.include?(name.value)
451
+ raise InvalidStreamObjectError, "Invalid filter : #{name}"
452
+ end
496
453
 
497
- prolog = ""
498
- data = ""
499
- objoff = 0
500
- @objects.to_a.sort.each do |num,obj|
454
+ Filter.const_get(name.value.to_s.sub(/Decode$/, ""))
455
+ end
456
+ end
457
+
458
+ #
459
+ # Class representing an external Stream.
460
+ #
461
+ class ExternalStream < Stream
462
+ def initialize(filespec, hash = {})
463
+ hash[:F] = filespec
464
+ super('', hash)
465
+ end
466
+ end
501
467
 
502
- obj.set_indirect(false)
503
- obj.objstm_offset = objoff
468
+ class InvalidObjectStreamObjectError < InvalidStreamObjectError # :nodoc:
469
+ end
504
470
 
505
- prolog << "#{num} #{objoff} "
506
- objdata = "#{obj} "
471
+ #
472
+ # Class representing a Stream containing other Objects.
473
+ #
474
+ class ObjectStream < Stream
475
+ include Enumerable
507
476
 
508
- objoff += objdata.size
509
- data << objdata
510
- obj.set_indirect(true)
511
- obj.no = num
512
- end
477
+ NUM = 0 # :nodoc:
478
+ OBJ = 1 # :nodoc:
513
479
 
514
- self.data = prolog + data
480
+ field :Type, Type: Name, Default: :ObjStm, Required: true, Version: "1.5"
481
+ field :N, Type: Integer, Required: true
482
+ field :First, Type: Integer, Required: true
483
+ field :Extends, Type: ObjectStream
515
484
 
516
- @dictionary[:N] = @objects.size
517
- @dictionary[:First] = prolog.size
485
+ #
486
+ # Creates a new Object Stream.
487
+ # _dictionary_:: A hash of attributes to set to the Stream.
488
+ # _raw_data_:: The Stream data.
489
+ #
490
+ def initialize(raw_data = "", dictionary = {})
491
+ super
518
492
 
519
- super
520
- end
493
+ @objects = nil
494
+ end
521
495
 
522
- #
523
- # Adds a new Object to this Stream.
524
- # _object_:: The Object to append.
525
- #
526
- def <<(object)
527
- unless object.generation == 0
528
- raise InvalidObjectError, "Cannot store an object with generation > 0 in an ObjectStream"
529
- end
496
+ def pre_build # :nodoc:
497
+ load!
530
498
 
531
- if object.is_a?(Stream)
532
- raise InvalidObjectError, "Cannot store a Stream in an ObjectStream"
533
- end
499
+ prolog = +""
500
+ data = +""
501
+ objoff = 0
502
+ @objects.to_a.sort.each do |num, obj|
503
+ obj.set_indirect(false)
504
+ obj.objstm_offset = objoff
534
505
 
535
- # We must have an associated document to generate new object numbers.
536
- if @document.nil?
537
- raise InvalidObjectError, "The ObjectStream must be added to a document before inserting objects"
538
- end
506
+ prolog << "#{num} #{objoff} "
507
+ objdata = "#{obj} "
539
508
 
540
- # The object already belongs to a document.
541
- unless object.document.nil?
542
- object = import_object_from_document(object)
543
- end
509
+ objoff += objdata.size
510
+ data << objdata
511
+ obj.set_indirect(true)
512
+ obj.no = num
513
+ end
544
514
 
545
- load!
515
+ self.data = prolog + data
546
516
 
547
- object.no, object.generation = @document.allocate_new_object_number if object.no == 0
548
- store_object(object)
517
+ @dictionary[:N] = @objects.size
518
+ @dictionary[:First] = prolog.size
549
519
 
550
- Reference.new(object.no, 0)
551
- end
552
- alias insert <<
520
+ super
521
+ end
553
522
 
554
- #
555
- # Deletes Object _no_.
556
- #
557
- def delete(no)
558
- load!
523
+ #
524
+ # Adds a new Object to this Stream.
525
+ # _object_:: The Object to append.
526
+ #
527
+ def <<(object)
528
+ unless object.generation == 0
529
+ raise InvalidObjectError, "Cannot store an object with generation > 0 in an ObjectStream"
530
+ end
559
531
 
560
- @objects.delete(no)
561
- end
532
+ if object.is_a?(Stream)
533
+ raise InvalidObjectError, "Cannot store a Stream in an ObjectStream"
534
+ end
562
535
 
563
- #
564
- # Returns the index of Object _no_.
565
- #
566
- def index(no)
567
- @objects.to_a.sort.index { |num, _| num == no }
568
- end
536
+ # We must have an associated document to generate new object numbers.
537
+ if @document.nil?
538
+ raise InvalidObjectError, "The ObjectStream must be added to a document before inserting objects"
539
+ end
569
540
 
570
- #
571
- # Returns a given decompressed object contained in the Stream.
572
- # _no_:: The Object number.
573
- #
574
- def extract(no)
575
- load!
541
+ # The object already belongs to a document.
542
+ unless object.document.nil?
543
+ object = import_object_from_document(object)
544
+ end
576
545
 
577
- @objects[no]
578
- end
546
+ load!
579
547
 
580
- #
581
- # Returns a given decompressed object by index.
582
- # _index_:: The Object index in the ObjectStream.
583
- #
584
- def extract_by_index(index)
585
- load!
548
+ object.no, object.generation = @document.allocate_new_object_number if object.no == 0
549
+ store_object(object)
586
550
 
587
- raise TypeError, "index must be an integer" unless index.is_a?(::Integer)
588
- raise IndexError, "index #{index} out of range" if index < 0 or index >= @objects.size
551
+ Reference.new(object.no, 0)
552
+ end
553
+ alias_method :insert, :<<
589
554
 
590
- @objects.to_a.sort[index][1]
591
- end
555
+ #
556
+ # Deletes Object _no_.
557
+ #
558
+ def delete(no)
559
+ load!
592
560
 
593
- #
594
- # Returns whether a specific object is contained in this stream.
595
- # _no_:: The Object number.
596
- #
597
- def include?(no)
598
- load!
561
+ @objects.delete(no)
562
+ end
599
563
 
600
- @objects.include?(no)
601
- end
564
+ #
565
+ # Returns the index of Object _no_.
566
+ #
567
+ def index(no)
568
+ @objects.to_a.sort.index { |num, _| num == no }
569
+ end
602
570
 
603
- #
604
- # Iterates over each object in the stream.
605
- #
606
- def each(&b)
607
- load!
571
+ #
572
+ # Returns a given decompressed object contained in the Stream.
573
+ # _no_:: The Object number.
574
+ #
575
+ def extract(no)
576
+ load!
608
577
 
609
- @objects.values.each(&b)
610
- end
611
- alias each_object each
578
+ @objects[no]
579
+ end
612
580
 
613
- #
614
- # Returns the number of objects contained in the stream.
615
- #
616
- def length
617
- raise InvalidObjectStreamObjectError, "Invalid number of objects" unless self.N.is_a?(Integer)
581
+ #
582
+ # Returns a given decompressed object by index.
583
+ # _index_:: The Object index in the ObjectStream.
584
+ #
585
+ def extract_by_index(index)
586
+ load!
618
587
 
619
- self.N.to_i
620
- end
588
+ raise TypeError, "index must be an integer" unless index.is_a?(::Integer)
589
+ raise IndexError, "index #{index} out of range" if (index < 0) || (index >= @objects.size)
621
590
 
622
- #
623
- # Returns the array of inner objects.
624
- #
625
- def objects
626
- load!
591
+ @objects.to_a.sort[index][1]
592
+ end
627
593
 
628
- @objects.values
629
- end
594
+ #
595
+ # Returns whether a specific object is contained in this stream.
596
+ # _no_:: The Object number.
597
+ #
598
+ def include?(no)
599
+ load!
630
600
 
631
- private
601
+ @objects.include?(no)
602
+ end
632
603
 
633
- #
634
- # Preprocess the object in case it already belongs to a document.
635
- # If the document is the same as the current object stream, remove the duplicate object from our document.
636
- # If the object comes from another document, use the export method to create a version without references.
637
- #
638
- def import_object_from_document(object)
639
- obj_doc = object.document
604
+ #
605
+ # Iterates over each object in the stream.
606
+ #
607
+ def each(&b)
608
+ load!
640
609
 
641
- # Remove the previous instance if the object is indirect to avoid duplicates.
642
- if obj_doc.equal?(@document)
643
- @document.delete_object(object.reference) if object.indirect?
610
+ @objects.values.each(&b)
611
+ end
612
+ alias_method :each_object, :each
644
613
 
645
- # Otherwise, create a exported version of the object.
646
- else
647
- object = object.export
648
- end
614
+ #
615
+ # Returns the number of objects contained in the stream.
616
+ #
617
+ def length
618
+ raise InvalidObjectStreamObjectError, "Invalid number of objects" unless self.N.is_a?(Integer)
649
619
 
650
- object
651
- end
620
+ self.N.to_i
621
+ end
652
622
 
653
- def store_object(object) #:nodoc:
654
- object.set_indirect(true) # all stored objects are indirect.
655
- object.parent = self # set this stream as the parent.
656
- object.set_document(@document) # inherit document information.
623
+ #
624
+ # Returns the array of inner objects.
625
+ #
626
+ def objects
627
+ load!
657
628
 
658
- @objects[object.no] = object
659
- end
629
+ @objects.values
630
+ end
631
+
632
+ private
633
+
634
+ #
635
+ # Preprocess the object in case it already belongs to a document.
636
+ # If the document is the same as the current object stream, remove the duplicate object from our document.
637
+ # If the object comes from another document, use the export method to create a version without references.
638
+ #
639
+ def import_object_from_document(object)
640
+ obj_doc = object.document
660
641
 
661
- def load! #:nodoc:
662
- return unless @objects.nil?
642
+ # Remove the previous instance if the object is indirect to avoid duplicates.
643
+ if obj_doc.equal?(@document)
644
+ @document.delete_object(object.reference) if object.indirect?
663
645
 
664
- decode!
646
+ # Otherwise, create a exported version of the object.
647
+ else
648
+ object = object.export
649
+ end
665
650
 
666
- @objects = {}
667
- return if @data.empty?
651
+ object
652
+ end
668
653
 
669
- data = StringScanner.new(@data)
670
- nums = []
671
- offsets = []
672
- first_offset = first_object_offset
654
+ def store_object(object) # :nodoc:
655
+ object.set_indirect(true) # all stored objects are indirect.
656
+ object.parent = self # set this stream as the parent.
657
+ object.set_document(@document) # inherit document information.
673
658
 
674
- self.length.times do
675
- nums << Integer.parse(data).to_i
676
- offsets << Integer.parse(data).to_i
677
- end
659
+ @objects[object.no] = object
660
+ end
678
661
 
679
- self.length.times do |i|
680
- unless (0...@data.size).cover?(first_object_offset + offsets[i]) and offsets[i] >= 0
681
- raise InvalidObjectStreamObjectError, "Invalid offset '#{offsets[i]} for object #{nums[i]}"
682
- end
662
+ def load! # :nodoc:
663
+ return unless @objects.nil?
683
664
 
684
- data.pos = first_offset + offsets[i]
685
- type = Object.typeof(data)
686
- raise InvalidObjectStreamObjectError,
687
- "Bad embedded object format in object stream" if type.nil?
665
+ decode!
688
666
 
689
- embeddedobj = type.parse(data)
690
- embeddedobj.no = nums[i] # object number
691
- embeddedobj.objstm_offset = offsets[i]
667
+ @objects = {}
668
+ return if @data.empty?
692
669
 
693
- store_object(embeddedobj)
694
- end
695
- end
670
+ data = StringScanner.new(@data)
671
+ nums = []
672
+ offsets = []
673
+ first_offset = first_object_offset
696
674
 
697
- def first_object_offset #:nodoc:
698
- raise InvalidObjectStreamObjectError, "Invalid First offset" unless self.First.is_a?(Integer)
699
- raise InvalidObjectStreamObjectError, "Negative object offset" if self.First < 0
675
+ length.times do
676
+ nums << Integer.parse(data).to_i
677
+ offsets << Integer.parse(data).to_i
678
+ end
679
+
680
+ length.times do |i|
681
+ unless (0...@data.size).cover?(first_object_offset + offsets[i]) && (offsets[i] >= 0)
682
+ raise InvalidObjectStreamObjectError, "Invalid offset '#{offsets[i]} for object #{nums[i]}"
683
+ end
700
684
 
701
- return self.First.to_i
685
+ data.pos = first_offset + offsets[i]
686
+ type = Object.typeof(data)
687
+ if type.nil?
688
+ raise InvalidObjectStreamObjectError,
689
+ "Bad embedded object format in object stream"
702
690
  end
691
+
692
+ embeddedobj = type.parse(data)
693
+ embeddedobj.no = nums[i] # object number
694
+ embeddedobj.objstm_offset = offsets[i]
695
+
696
+ store_object(embeddedobj)
697
+ end
703
698
  end
704
699
 
700
+ def first_object_offset # :nodoc:
701
+ raise InvalidObjectStreamObjectError, "Invalid First offset" unless self.First.is_a?(Integer)
702
+ raise InvalidObjectStreamObjectError, "Negative object offset" if self.First < 0
703
+
704
+ self.First.to_i
705
+ end
706
+ end
705
707
  end