#encoding: ASCII-8BIT #-- # 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$ #++ require 'pdf/writer/oreader' # An image object. This will be an /XObject dictionary in the document. This # includes description and data. The diectionary includes: # # Type:: (name, Optional) The type of PDF object that this # dictionary describes; if present, must be /XObject for # an image XObject. # Subtype:: (name, Required) The type of XObject that this # dictionary describes; must be /Image for an image # /XObject. # Width:: (integer, Required) The width of the image, in samples. # Height:: (integer, Required) The height of the image, in samples. # ColorSpace:: (name or array, Required for images, except those that # use the /JPXDecode filter; not allowed for image # masks) The color space in which image samples are # specified; it can be any type of color space except # /Pattern. If the image uses the JPXDecode filter, this # entry is optional: * If /ColorSpace is present, any # color space specifications in the JPEG2000 data are # ignored. * If /ColorSpace is absent, the color space # specifications in the JPEG2000 data are used. The # /Decode array is also ignored unless /ImageMask is # true. # BitsPerComponent:: (integer, Required except for image masks and images # that use the JPXDecode filter) The number of bits used # to represent each color component. Only a single value # may be specified; the number of bits is the same for # all color components. Valid values are 1, 2, 4, 8, and # (in PDF 1.5) 16. If /ImageMask is true, this entry is # optional, and if specified, its value must be 1. If # the image stream uses a filter, the value of # BitsPerComponent must be consistent with the size of # the data samples that the filter delivers. In # particular, a CCITTFaxDecode or JBIG2Decode filter # always delivers 1-bit samples, a RunLengthDecode or # DCTDecode filter delivers 8-bit samples, and an # LZWDecode or FlateDecode filter delivers samples of a # specified size if a predictor function is used. If the # image stream uses the JPXDecode filter, this entry is # optional and ignored if present. The bit depth is # determined in the process of decoding the JPEG2000 # image. # Intent:: (name, Optional; PDF 1.1) The name of a color # rendering intent to be used in rendering the image # (see “Rendering Intents” on page 230). Default value: # the current rendering intent in the graphics state. # ImageMask:: (boolean, Optional) A flag indicating whether the # image is to be treated as an image mask (see Section # 4.8.5, “Masked Images”). If this flag is true, the # value of /BitsPerComponent must be 1 and Mask and # /ColorSpace should not be specified; unmasked areas # are painted using the current nonstroking color. # Default value: false. # Mask:: (stream or array, Optional except for image masks; not # allowed for image masks; PDF 1.3) An image XObject # defining an image mask to be applied to this image # (see “Explicit Masking” on page 321), or an array # specifying a range of colors to be applied to it as a # color key mask (see “Color Key Masking” on page 321). # If ImageMask is true, this entry must not be present. # (See implementation note 51 in Appendix H.) # Decode:: (array, Optional) An array of numbers describing how # to map image samples into the range of values # appropriate for the image’s color space (see “Decode # Arrays” on page 314). If ImageMask is true, the array # must be either [0 1] or [1 0]; otherwise, its length # must be twice the number of color components required # by ColorSpace. If the image uses the JPXDecode filter # and ImageMask is false, Decode is ignored. Default # value: see “Decode Arrays” on page 314. # Interpolate:: (boolean, Optional) A flag indicating whether image # interpolation is to be performed (see “Image # Interpolation” on page 316). Default value: false. # Alternates:: (array, Optional; PDF 1.3) An array of alternate image # dictionaries for this image (see “Alternate Images” on # page 317). The order of elements within the array has # no significance. This entry may not be present in an # image XObject that is itself an alternate image. # SMask:: (stream, Optional; PDF 1.4) A subsidiary image XObject # defining a soft-mask image (see “Soft-Mask Images” on # page 522) to be used as a source of mask shape or mask # opacity values in the transparent imaging model. The # alpha source parameter in the graphics state # determines whether the mask values are interpreted as # shape or opacity. If present, this entry overrides the # current soft mask in the graphics state, as well as # the image’s Mask entry, if any. (However, the other # transparency related graphics state parameters—blend # mode and alpha constant—remain in effect.) If SMask is # absent, the image has no associated soft mask # (although the current soft mask in the graphics state # may still apply). # SMaskInData:: (integer, Optional for images that use the JPXDecode # filter, meaningless otherwise; PDF 1.5) A code # specifying how soft-mask information (see “Soft-Mask # Images” on page 522) encoded with image samples should # be used: (0) If present, encoded soft-mask image # information should be ignored. (1) The image’s data # stream includes encoded soft-mask values. An # application can create a soft-mask image from the # information to be used as a source of mask shape or # mask opacity in the transparency imaging model. (2) # The image’s data stream includes color channels that # have been preblended with a background; the image data # also includes an opacity channel. An application can # create a soft-mask image with a Matte entry from the # opacity channel information to be used as a source of # mask shape or mask opacity in the transparency model. # * If this entry has a nonzero value, SMask should not # be specified. See also Section 3.3.8, “JPXDecode # Filter.” Default value: 0. # Name:: (name, Required in PDF 1.0; optional otherwise) The # name by which this image XObject is referenced in the # XObject subdictionary of the current resource # dictionary (see Section 3.7.2, “Resource # Dictionaries”). Note: This entry is obsolescent and # its use is no longer recommended. (See implementation # note 52 in Appendix H.) # StructParent:: (integer, Required if the image is a structural # content item; PDF 1.3) The integer key of the image’s # entry in the structural parent tree (see “Finding # Structure Elements from Content Items” on page 797). # ID:: (string, Optional; PDF 1.3; indirect reference # preferred) The digital identifier of the image’s # parent Web Capture content set (see Section 10.9.5, # “Object Attributes Related to Web Capture”). # OPI:: (dictionary, Optional; PDF 1.2) An OPI version # dictionary for the image (see Section 10.10.6, “Open # Prepress Interface (OPI)”). If ImageMask is true, this # entry is ignored. # Metadata:: (stream, Optional; PDF 1.4) A metadata stream # containing metadata for the image (see Section 10.2.2, # “Metadata Streams”). # OC:: (dictionary, Optional; PDF 1.5) An optional content # group or optional content membership dictionary (see # Section 4.10, “Optional Content”), specifying the # optional content properties for this image XObject. # Before the image is processed, its visibility is # determined based on this entry. If it is determined to # be invisible, the entire image is skipped, as if there # were no Do operator to invoke it. 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::Lang[:png_unsupp_compres] end if image.info[:filter_method] != 0 raise TypeError, PDF::Writer::Lang[: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::Lang[:png_8bit_colour] end if image.info[:interlace_method] != 0 raise TypeError, PDF::Writer::Lang[: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::Lang[: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::Object::Contents.new(parent, self) 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}"} res << "\n/Length #{tmp.size} >>\nstream\n#{tmp}\nendstream\nendobj\n" res end end