#-- # PDF::Writer for Ruby. # http://rubyforge.org/projects/ruby-pdf/ # Copyright 2003 - 2005 Austin Ziegler. # # Licensed under a MIT-style licence. See LICENCE in the main distribution # for full licensing information. # # $Id: image.rb,v 1.2 2005/05/16 03:59:21 austin Exp $ #++ require 'pdf/writer/oreader' # an image object, will be an XObject in the document, includes description and data class PDF::Writer::External::Image < PDF::Writer::External attr_reader :label attr_reader :image_info def initialize(parent, data, image, label) super(parent) @data = data @image_info = image @info = { 'Type' => '/XObject', 'Subtype' => '/Image', 'Width' => image.width, 'Height' => image.height } case image.format when "JPEG" case image.channels when 1 @info['ColorSpace'] = '/DeviceGray' when 4 @info['ColorSpace'] = '/DeviceCMYK' # This should fix problems with CMYK JPEG colours inverted in # Adobe Acrobat. Enable only if appropriate. # @info['Decode'] = '[1.0 0.0 1.0 0.0 1.0 0.0 1.0 0.0]' else @info['ColorSpace'] = '/DeviceRGB' end @info['Filter'] = '/DCTDecode' @info['BitsPerComponent'] = 8 when "PNG" if image.info[:compression_method] != 0 raise TypeError, PDF::Writer::Message[:png_unsupp_compres] end if image.info[:filter_method] != 0 raise TypeError, PDF::Writer::Message[:png_unsupp_filter] end data = data.dup data.extend(PDF::Writer::OffsetReader) data.read_o(8) # Skip the default header ok = true length = data.size palette = "" idat = "" while ok chunk_size = data.read_o(4).unpack("N")[0] section = data.read_o(4) case section when 'PLTE' palette << data.read_o(chunk_size) when 'IDAT' idat << data.read_o(chunk_size) when 'tRNS' # This chunk can only occur once and it must occur after the # PLTE chunk and before the IDAT chunk trans = {} case image.info[:color_type] when 3 # Indexed colour, RGB. Each byte in this chunk is an alpha for # the palette index in the PLTE ("palette") chunk up until the # last non-opaque entry. Set up an array, stretching over all # palette entries which will be 0 (opaque) or 1 (transparent). trans[:type] = 'indexed' trans[:data] = data.read_o(chunk_size).unpack("C*") when 0 # Greyscale. Corresponding to entries in the PLTE chunk. # Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1 trans[:grayscale] = data.read_o(2).unpack("n") trans[:type] = 'indexed' trans[:data] = data.read_o.unpack("C") when 2 # True colour with proper alpha channel. trans[:rgb] = data.read_o(6).unpack("nnn") end else data.offset += chunk_size end ok = section != "IEND" data.read_o(4) # Skip the CRC end if image.bits > 8 raise TypeError, PDF::Writer::Message[:png_8bit_colour] end if image.info[:interlace_method] != 0 raise TypeError, PDF::Writer::Message[:png_interlace] end ncolor = 1 colour = 'DeviceRGB' case image.info[:color_type] when 3 nil when 2 ncolor = 3 when 0 colour = 'DeviceGray' else raise TypeError, PDF::Writer::Message[:png_alpha_trans] end @info['Filter'] = '[/FlateDecode]' @info['DecodeParms'] = "[<>]" @info['BitsPerComponent'] = image.bits.to_s unless palette.empty? @info['ColorSpace'] = " [ /Indexed /DeviceRGB #{(palette.size / 3) - 1} " contents = PDF::Writer::Contents.new contents.data = palette @info['ColorSpace'] << "#{contents.oid} 0 R ]" if trans case trans[:type] when 'indexed' @info['Mask'] = " [ #{trans[:data].join(' ')} ] " end end else @info['ColorSpace'] = "/#{colour}" end @data = idat end @label = label # assign it a place in the named resource dictionary as an external # object, according to the label passed in with it. @parent.pages << self # also make sure that we have the right procset object for it. @parent.procset << 'ImageC' end def to_s tmp = @data.dup res = "\n#{@oid} 0 obj\n<<" @info.each { |k, v| res << "\n/#{k} #{v}"} if (@parent.encrypted?) @parent.arc4.prepare(self) tmp = @parent.arc4.encrypt(tmp) end res << "\n/Length #{tmp.size} >>\nstream\n#{tmp}\nendstream\nendobj\n" res end end