Class: Origami::Stream

Inherits:
Object
  • Object
show all
Extended by:
TypeGuessing
Includes:
Enumerable, FieldAccessor, Object, StandardObject
Defined in:
lib/origami/stream.rb,
lib/origami/obfuscation.rb

Overview

Class representing a PDF Stream Object. Streams can be used to hold any kind of data, especially binary data.

Constant Summary collapse

TOKENS =

:nodoc:

[ "stream" + WHITECHARS_NORET + "(\\r\\n|\\r|\\n)" , "endstream" ]
DEFINED_FILTERS =

Actually only 5 first ones are implemented, other ones are mainly about image data processing (JPEG, JPEG2000 …)

%i[
  ASCIIHexDecode
  ASCII85Decode
  LZWDecode
  FlateDecode
  RunLengthDecode

  CCITTFaxDecode
  JBIG2Decode
  DCTDecode
  JPXDecode

  AHx
  A85
  LZW
  Fl
  RL
  CCF
  DCT
]
@@regexp_open =
Regexp.new(WHITESPACES + TOKENS.first)
@@regexp_close =
Regexp.new(TOKENS.last)

Constants included from StandardObject

Origami::StandardObject::DEFAULT_ATTRIBUTES

Instance Attribute Summary collapse

Attributes included from Object

#file_offset, #generation, #no, #objstm_offset, #parent

Class Method Summary collapse

Instance Method Summary collapse

Methods included from TypeGuessing

guess_type

Methods included from FieldAccessor

#method_missing, #respond_to_missing?

Methods included from StandardObject

included, #version_required

Methods included from Object

#copy, #document, #export, included, #indirect?, #indirect_parent, #logicalize, #logicalize!, #native_type, #numbered?, #reference, #set_document, #set_indirect, skip_until_next_obj, #solve, #to_o, #type, typeof, #version_required, #xrefs

Constructor Details

#initialize(data = "", dictionary = {}) ⇒ Stream

Creates a new PDF Stream.

data

The Stream uncompressed data.

dictionary

A hash representing the Stream attributes.



87
88
89
90
91
92
93
94
95
# File 'lib/origami/stream.rb', line 87

def initialize(data = "", dictionary = {})
    super()

    set_indirect(true)

    @encoded_data = nil
    @dictionary, @data = Dictionary.new(dictionary), data
    @dictionary.parent = self
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Origami::FieldAccessor

Instance Attribute Details

#dictionaryObject

Returns the value of attribute dictionary.



72
73
74
# File 'lib/origami/stream.rb', line 72

def dictionary
  @dictionary
end

Class Method Details

.parse(stream, parser = nil) ⇒ Object

:nodoc:



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/origami/stream.rb', line 114

def self.parse(stream, parser = nil) #:nodoc:
    scanner = Parser.init_scanner(stream)
    dictionary = Dictionary.parse(scanner, parser)
    return dictionary if not scanner.skip(@@regexp_open)

    length = dictionary[:Length]
    if not length.is_a?(Integer)
        raw_data = scanner.scan_until(@@regexp_close)
        if raw_data.nil?
            raise InvalidStreamObjectError,
                    "Stream shall end with a 'endstream' statement"
        end
    else
        length = length.value
        raw_data = scanner.peek(length)
        scanner.pos += length

        if not ( unmatched = scanner.scan_until(@@regexp_close) )
            raise InvalidStreamObjectError,
                "Stream shall end with a 'endstream' statement"
        end

        raw_data << unmatched
    end

    stm =
        if Origami::OPTIONS[:enable_type_guessing]
            self.guess_type(dictionary).new('', dictionary)
        else
            Stream.new('', dictionary)
        end

    raw_data.chomp!(TOKENS.last)

    if raw_data[-1,1] == "\n"
        if raw_data[-2,1] == "\r"
            raw_data = raw_data[0, raw_data.size - 2]
        else
            raw_data = raw_data[0, raw_data.size - 1]
        end
    end
    #raw_data.chomp! if length.is_a?(Integer) and length < raw_data.length

    stm.encoded_data = raw_data
    stm.file_offset = dictionary.file_offset

    stm
end

Instance Method Details

#[](key) ⇒ Object

:nodoc:



347
348
349
# File 'lib/origami/stream.rb', line 347

def [](key) #:nodoc:
    @dictionary[key]
end

#[]=(key, val) ⇒ Object

:nodoc:



351
352
353
# File 'lib/origami/stream.rb', line 351

def []=(key, val) #:nodoc:
    @dictionary[key] = val
end

#cast_to(type, _parser = nil) ⇒ Object



219
220
221
222
223
224
225
226
227
# File 'lib/origami/stream.rb', line 219

def cast_to(type, _parser = nil)
    assert_cast_type(type)

    cast = type.new("", self.dictionary.copy)
    cast.encoded_data = self.encoded_data.dup
    cast.file_offset = self.file_offset

    transfer_attributes(cast)
end

#dataObject Also known as: decoded_data

Returns the uncompressed stream content.



236
237
238
239
240
# File 'lib/origami/stream.rb', line 236

def data
    self.decode! unless decoded?

    @data
end

#data=(str) ⇒ Object Also known as: decoded_data=

Sets the uncompressed stream content.

str

The new uncompressed data.



247
248
249
250
# File 'lib/origami/stream.rb', line 247

def data=(str)
    @encoded_data = nil
    @data = str
end

#decode!Object

Uncompress the stream data.



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/origami/stream.rb', line 274

def decode!
    self.decrypt! if self.is_a?(Encryption::EncryptedStream)
    return if decoded?

    filters = self.filters
    dparams = decode_params

    @data = @encoded_data.dup
    @data.freeze

    filters.each_with_index do |filter, layer|
        params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}

        # Handle Crypt filters.
        if filter == :Crypt
            raise Filter::Error, "Crypt filter must be the first filter" unless layer.zero?

            # Skip the Crypt filter.
            next
        end

        begin
            @data = decode_data(@data, filter, params)
        rescue Filter::Error => error
            @data = error.decoded_data
            raise
        end
    end

    self
end

#each_filterObject

Iterates over each Filter in the Stream.



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/origami/stream.rb', line 166

def each_filter
    filters = self.Filter

    return enum_for(__method__) do
        case filters
        when NilClass then 0
        when Array then filters.length
        else
            1
        end
    end unless block_given?

    return if filters.nil?

    if filters.is_a?(Array)
        filters.each do |filter| yield(filter) end
    else
        yield(filters)
    end

    self
end

#each_key(&b) ⇒ Object

:nodoc:



355
356
357
# File 'lib/origami/stream.rb', line 355

def each_key(&b) #:nodoc:
    @dictionary.each_key(&b)
end

#each_pair(&b) ⇒ Object Also known as: each

:nodoc



359
360
361
# File 'lib/origami/stream.rb', line 359

def each_pair(&b) #:nodoc
    @dictionary.each_pair(&b)
end

#encode!Object

Compress the stream data.



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/origami/stream.rb', line 309

def encode!
    return if encoded?

    filters = self.filters
    dparams = decode_params

    @encoded_data = @data.dup
    (filters.length - 1).downto(0) do |layer|
        params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
        filter = filters[layer]

        # Handle Crypt filters.
        if filter == :Crypt
            raise Filter::Error, "Crypt filter must be the first filter" unless layer.zero?

            # Skip the Crypt filter.
            next
        end

        @encoded_data = encode_data(@encoded_data, filter, params)
    end

    self.Length = @encoded_data.length

    self
end

#encoded_dataObject

Returns the raw compressed stream content.



256
257
258
259
260
# File 'lib/origami/stream.rb', line 256

def encoded_data
    self.encode! unless encoded?

    @encoded_data
end

#encoded_data=(str) ⇒ Object

Sets the raw compressed stream content.

str

the new raw data.



266
267
268
269
# File 'lib/origami/stream.rb', line 266

def encoded_data=(str)
    @encoded_data = str
    @data = nil
end

#filtersObject

Returns an Array of Filters for this Stream.



192
193
194
# File 'lib/origami/stream.rb', line 192

def filters
    self.each_filter.to_a
end

#key?(name) ⇒ Boolean Also known as: has_key?

Returns:



364
365
366
# File 'lib/origami/stream.rb', line 364

def key?(name)
    @dictionary.key?(name)
end

#keysObject



369
370
371
# File 'lib/origami/stream.rb', line 369

def keys
    @dictionary.keys
end

#post_buildObject



108
109
110
111
112
# File 'lib/origami/stream.rb', line 108

def post_build
    self.Length = @encoded_data.length

    super
end

#pre_buildObject



102
103
104
105
106
# File 'lib/origami/stream.rb', line 102

def pre_build
    encode!

    super
end

#set_predictor(predictor, colors: 1, bitspercomponent: 8, columns: 1) ⇒ Object

Set predictor type for the current Stream. Applies only for LZW and FlateDecode filters.



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/origami/stream.rb', line 200

def set_predictor(predictor, colors: 1, bitspercomponent: 8, columns: 1)
    filters = self.filters

    layer = filters.index(:FlateDecode) or filters.index(:LZWDecode)
    if layer.nil?
        raise InvalidStreamObjectError, 'Predictor functions can only be used with Flate or LZW filters'
    end

    params = Filter::LZW::DecodeParms.new
    params[:Predictor] = predictor
    params[:Colors] = colors if colors != 1
    params[:BitsPerComponent] = bitspercomponent if bitspercomponent != 8
    params[:Columns] = columns if columns != 1

    set_decode_params(layer, params)

    self
end

#to_obfuscated_strObject



220
221
222
223
224
225
226
227
228
229
# File 'lib/origami/obfuscation.rb', line 220

def to_obfuscated_str
    content = ""

    content << @dictionary.to_obfuscated_str
    content << "stream" + $/
    content << self.encoded_data
    content << $/ << TOKENS.last

    super(content)
end

#to_s(indent: 1, tab: "\t", eol: $/) ⇒ Object

:nodoc:



336
337
338
339
340
341
342
343
344
345
# File 'lib/origami/stream.rb', line 336

def to_s(indent: 1, tab: "\t", eol: $/) #:nodoc:
    content = ""

    content << @dictionary.to_s(indent: indent, tab: tab)
    content << "stream" + eol
    content << self.encoded_data
    content << eol << TOKENS.last

    super(content, eol: eol)
end

#valueObject

:nodoc:



229
230
231
# File 'lib/origami/stream.rb', line 229

def value #:nodoc:
    self
end