class HexaPDF::Stream

Forward declaration of Stream to circumvent circular require problem

Implements Stream objects of the PDF object system.

Stream Objects

A stream may also be associated with a PDF object but only if the value is a PDF dictionary. This associated dictionary further describes the stream, like its length or how it is encoded.

Such a stream object in PDF contains string data but of possibly unlimited length. Therefore it is used for large amounts of data like images, page descriptions or embedded files.

The basic Object class cannot hold stream data, only this subclass contains the necessary methods to conveniently work with the stream data!

Note that support for external streams (/F, /FFilter, /FDecodeParms) is not yet implemented!

See: PDF1.7 s7.3.8, Dictionary

Public Instance Methods

must_be_indirect?() click to toggle source

Stream objects must always be indirect.

# File lib/hexapdf/stream.rb, line 148
def must_be_indirect?
  true
end
raw_stream() click to toggle source

Returns the raw stream object.

The returned value can be of many different types (see stream=). For working with the decoded stream contents use stream.

# File lib/hexapdf/stream.rb, line 178
def raw_stream
  data.stream
end
set_filter(filter, decode_parms = nil) click to toggle source

Sets the filters that should be used for encoding the stream.

The arguments filter as well as decode_parms can either be a single items or arrays.

The filters have to be specified in the *decoding order*! For example, if the filters would be [:A85, :Fl], the stream would first be encoded with the Flate and then with the ASCII85 filter.

# File lib/hexapdf/stream.rb, line 244
def set_filter(filter, decode_parms = nil)
  if filter.nil? || (filter.kind_of?(Array) && filter.empty?)
    delete(:Filter)
  else
    self[:Filter] = filter
  end
  if decode_parms.nil? || (decode_parms.kind_of?(Array) && decode_parms.empty?) ||
      !key?(:Filter)
    delete(:DecodeParms)
  else
    self[:DecodeParms] = decode_parms
  end
end
stream() click to toggle source

Returns the (possibly decoded) stream data as string.

Note that modifications done to the returned string are not reflected in the Stream object itself. The modified string must explicitly be assigned via stream= to take effect.

# File lib/hexapdf/stream.rb, line 166
def stream
  if data.stream.kind_of?(String)
    data.stream.dup
  else
    HexaPDF::Filter.string_from_source(stream_decoder)
  end
end
stream=(stream) click to toggle source

Assigns a new stream data object.

The stream argument can be a HexaPDF::StreamData object, a String object or nil.

If stream is nil, an empty binary string is used instead.

# File lib/hexapdf/stream.rb, line 157
def stream=(stream)
  data.stream = stream
  after_data_change
end
stream_decoder() click to toggle source

Returns the decoder Fiber for the stream data.

See the Filter module for more information on how to work with the fiber.

# File lib/hexapdf/stream.rb, line 194
def stream_decoder
  source = stream_source

  if data.stream.kind_of?(StreamData)
    data.stream.filter.zip(data.stream.decode_parms) do |filter, decode_parms|
      source = filter_for_name(filter).decoder(source, decode_parms)
    end
  end

  source
end
stream_encoder click to toggle source

Returns the encoder Fiber for the stream data.

See the Filter module for more information on how to work with the fiber.

# File lib/hexapdf/stream.rb, line 212
def stream_encoder(source = stream_source)
  encoder_data = [document.unwrap(self[:Filter])].flatten.
    zip([document.unwrap(self[:DecodeParms])].flatten).
    delete_if {|f, _| f.nil? }

  if data.stream.kind_of?(StreamData)
    decoder_data = data.stream.filter.zip(data.stream.decode_parms)

    while !decoder_data.empty? && !encoder_data.empty? && decoder_data.last == encoder_data.last
      decoder_data.pop
      encoder_data.pop
    end

    decoder_data.each do |filter, decode_parms|
      source = filter_for_name(filter).decoder(source, decode_parms)
    end
  end

  encoder_data.reverse!.each do |filter, decode_parms|
    source = filter_for_name(filter).encoder(source, decode_parms)
  end

  source
end
stream_source() click to toggle source

Returns the Fiber representing the unprocessed content of the stream.

# File lib/hexapdf/stream.rb, line 183
def stream_source
  if data.stream.kind_of?(String)
    HexaPDF::Filter.source_from_string(data.stream)
  else
    data.stream.fiber(config['io.chunk_size'])
  end
end

Private Instance Methods

after_data_change() click to toggle source

Makes sure that the stream data is either a String or a HexaPDF::StreamData object.

Calls superclass method HexaPDF::Object#after_data_change
# File lib/hexapdf/stream.rb, line 261
def after_data_change
  super
  data.stream ||= ''.b
  unless data.stream.kind_of?(StreamData) || data.stream.kind_of?(String)
    raise ArgumentError, "Object of class #{data.stream.class} cannot be used as stream value"
  end
end
filter_for_name(filter_name) click to toggle source

Returns the filter object that corresponds to the given filter name.

See: HexaPDF::Filter

# File lib/hexapdf/stream.rb, line 272
def filter_for_name(filter_name)
  config.constantize('filter.map', filter_name) do
    raise HexaPDF::Error, "Unknown stream filter '#{filter_name}' encountered"
  end
end