Class: Origami::Stream

Inherits:
Object
  • Object
show all
Includes:
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", "endstream" ]
@@regexp_open =
Regexp.new(WHITESPACES + TOKENS.first)
@@regexp_close =
Regexp.new(TOKENS.last)
@@defined_filters =

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

[ 
  :ASCIIHexDecode, 
  :ASCII85Decode, 
  :LZWDecode, 
  :FlateDecode, 
  :RunLengthDecode,
  # TODO
  :CCITTFaxDecode,
  :JBIG2Decode,
  :DCTDecode,
  :JPXDecode
]

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 StandardObject

#do_type_check, #has_field?, included, #pdf_version_required, #set_default_value, #set_default_values

Methods included from Object

#<=>, #copy, #indirect_parent, #is_indirect?, #pdf, #pdf_version_required, #reference, #set_indirect, #set_pdf, #size, skip_until_next_obj, #solve, #to_o, #type, typeof, #xrefs

Constructor Details

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

Creates a new PDF Stream.

data

The Stream uncompressed data.

dictionary

A hash representing the Stream attributes.



75
76
77
78
79
80
81
82
# File 'lib/origami/stream.rb', line 75

def initialize(data = "", dictionary = {})
    super()
    
    set_indirect(true)
   
    @dictionary, @data = Dictionary.new(dictionary), data
    @dictionary.parent = self
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(field, *args) ⇒ Object

:nodoc:



96
97
98
99
100
101
102
103
# File 'lib/origami/stream.rb', line 96

def method_missing(field, *args) #:nodoc:
  if field.to_s[-1,1] == '='
    self[field.to_s[0..-2].to_sym] = args.first
  else
    obj = self[field]; 
    obj.is_a?(Reference) ? obj.solve : obj
  end
end

Instance Attribute Details

#dictionaryObject

Returns the value of attribute dictionary.



60
61
62
# File 'lib/origami/stream.rb', line 60

def dictionary
  @dictionary
end

Class Method Details

.parse(stream) ⇒ Object

:nodoc:



105
106
107
108
109
110
111
112
113
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
162
163
164
165
166
167
168
# File 'lib/origami/stream.rb', line 105

def self.parse(stream) #:nodoc:
  
  dictionary = Dictionary.parse(stream)
  return dictionary if not stream.skip(@@regexp_open)

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

    if not ( unmatched = stream.scan_until(@@regexp_close) )
      raise InvalidStreamObjectError, 
        "Stream shall end with a 'endstream' statement"
    end
    
    rawdata << unmatched
  end
   
  stm = 
    if Origami::OPTIONS[:enable_type_guessing]
      type, subtype = dictionary[:Type], dictionary[:Subtype]
      
      if type.is_a?(Name)
        if STM_SPECIAL_TYPES.include?(type.value)
          STM_SPECIAL_TYPES[type.value].new("", dictionary.to_h)
        else
          if type == :XObject and subtype.is_a?(Name) and STM_XOBJ_SUBTYPES.include?(subtype.value)
            STM_XOBJ_SUBTYPES[subtype.value].new("", dictionary.to_h)
          else
            Stream.new('', dictionary.to_h)
          end
        end
                
      else
        Stream.new('', dictionary.to_h)
      end

    else
      Stream.new('', dictionary.to_h)
    end
  
  rawdata.chomp!(TOKENS.last)

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

  stm
end

Instance Method Details

#[](key) ⇒ Object

:nodoc:



315
316
317
# File 'lib/origami/stream.rb', line 315

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

#[]=(key, val) ⇒ Object

:nodoc:



319
320
321
# File 'lib/origami/stream.rb', line 319

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

#dataObject

Returns the uncompressed stream content.



199
200
201
202
203
# File 'lib/origami/stream.rb', line 199

def data
  self.decode! if @data.nil?

  @data 
end

#data=(str) ⇒ Object

Sets the uncompressed stream content.

str

The new uncompressed data.



209
210
211
212
# File 'lib/origami/stream.rb', line 209

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

#decode!Object

Uncompress the stream data.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/origami/stream.rb', line 235

def decode!
  self.decrypt! if self.is_a?(Encryption::EncryptedStream)
  
  unless is_decoded?
    filters = self.Filter
    
    if filters.nil?
      @data = @rawdata.dup
    else
      case filters
        when Array, Name then
          dparams = self.DecodeParms || []

          dparams = [ dparams ] unless dparams.is_a?(::Array)
          filters = [ filters ] unless filters.is_a?(::Array)
      
          @data = @rawdata.dup
          filters.length.times do |layer|
            params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
            filter = filters[layer]

            @data = decode_data(@data, filter, params)
          end
        else
          raise InvalidStreamObjectError, "Invalid Filter type parameter"
      end
    end
  end
  
  self
end

#each_key(&b) ⇒ Object

:nodoc:



323
324
325
# File 'lib/origami/stream.rb', line 323

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

#encode!Object

Compress the stream data.



270
271
272
273
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
# File 'lib/origami/stream.rb', line 270

def encode!

  unless is_encoded?
    filters = self.Filter
    
    if filters.nil?
      @rawdata = @data.dup
    else
      case filters
        when Array, Name then
          dparams = self.DecodeParms || []

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

            @rawdata = encode_data(@rawdata, filter, params)
          end
        else
          raise InvalidStreamObjectError, "Invalid filter type parameter"
      end
    end
    
    self.Length = @rawdata.length
  end
  
  self
end

#post_buildObject



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

def post_build
  self.Length = @rawdata.length
 
  super
end

#pre_buildObject



84
85
86
87
88
# File 'lib/origami/stream.rb', line 84

def pre_build
  encode!
 
  super
end

#rawdataObject

Returns the raw compressed stream content.



217
218
219
220
221
# File 'lib/origami/stream.rb', line 217

def rawdata
  self.encode! if @rawdata.nil? 

  @rawdata
end

#rawdata=(str) ⇒ Object

Sets the raw compressed stream content.

str

the new raw data.



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

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

#real_typeObject



327
# File 'lib/origami/stream.rb', line 327

def real_type ; Stream end

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



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

def set_predictor(predictor, colors = 1, bitspercomponent = 8, columns = 1)
  
  filters = self.Filter
  filters = [ filters ] unless filters.is_a?(::Array)

  if not filters.include?(:FlateDecode) and not filters.include?(:LZWDecode)
    raise InvalidStreamObjectError, 'Predictor functions can only be used with Flate or LZW filters'
  end

  layer = filters.index(:FlateDecode) or filters.index(:LZWDecode)

  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



206
207
208
209
210
211
212
213
214
215
# File 'lib/origami/obfuscation.rb', line 206

def to_obfuscated_str
  content = ""
  
  content << @dictionary.to_obfuscated_str
  content << "stream" + EOL
  content << self.rawdata
  content << EOL << TOKENS.last
  
  super(content)
end

#to_s(indent = 1) ⇒ Object

:nodoc:



303
304
305
306
307
308
309
310
311
312
313
# File 'lib/origami/stream.rb', line 303

def to_s(indent = 1) #:nodoc:
  
  content = ""
  
  content << @dictionary.to_s(indent)
  content << "stream" + EOL
  content << self.rawdata
  content << EOL << TOKENS.last
  
  super(content)
end

#valueObject

:nodoc:



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

def value #:nodoc:
  self
end