/*
% Copyright (C) 2003-2019 GraphicsMagick Group
% Copyright (C) 2002 ImageMagick Studio
% Copyright 1991-1999 E. I. du Pont de Nemours and Company
%
% This program is covered by multiple licenses, which are described in
% Copyright.txt. You should have received a copy of Copyright.txt with this
% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%                            PPPP   DDDD   FFFFF                              %
%                            P   P  D   D  F                                  %
%                            PPPP   D   D  FFF                                %
%                            P      D   D  F                                  %
%                            P      DDDD   F                                  %
%                                                                             %
%                                                                             %
%                  Read/Write Portable Document Format.                       %
%                                                                             %
%                                                                             %
%                              Software Design                                %
%                                John Cristy                                  %
%                                 July 1992                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/

/*
  Include declarations.
*/
#include "magick/studio.h"
#include "magick/analyze.h"
#include "magick/attribute.h"
#include "magick/blob.h"
#include "magick/compress.h"
#include "magick/constitute.h"
#include "magick/delegate.h"
#include "magick/enum_strings.h"
#include "magick/log.h"
#include "magick/magick.h"
#include "magick/monitor.h"
#include "magick/pixel_cache.h"
#include "magick/resize.h"
#include "magick/shear.h"
#include "magick/tempfile.h"
#include "magick/utility.h"
#include "magick/version.h"

/*
  Forward declarations.
*/
static unsigned int WritePDFImage(const ImageInfo *,Image *);
#if defined(HasZLIB)
static unsigned int ZLIBEncodeImage(Image *,const size_t,const unsigned long,unsigned char *);
#endif


/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   I s P D F                                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method IsPDF returns True if the image format type, identified by the
%  magick string, is PDF.
%
%  The format of the IsPDF method is:
%
%      unsigned int IsPDF(const unsigned char *magick,const size_t offset)
%
%  A description of each parameter follows:
%
%    o status:  Method IsPDF returns True if the image format type is PDF.
%
%    o magick: This string is generally the first few bytes of an image file
%      or blob.
%
%    o offset: Specifies the offset of the magick string.
%
%
*/
static unsigned int IsPDF(const unsigned char *magick,const size_t offset)
{
  if (offset < 5)
    return(False);
  if (LocaleNCompare((char *) magick,"%PDF-",5) == 0)
    return(True);
  return(False);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e a d P D F I m a g e                                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method ReadPDFImage reads a Portable Document Format image file and
%  returns it.  It allocates the memory necessary for the new Image structure
%  and returns a pointer to the new image.
%
%  The format of the ReadPDFImage method is:
%
%      Image *ReadPDFImage(const ImageInfo *image_info,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image:  Method ReadPDFImage returns a pointer to the image after
%      reading.  A null image is returned if there is a memory shortage or
%      if the image cannot be read.
%
%    o image_info: Specifies a pointer to a ImageInfo structure.
%
%    o exception: return any errors or warnings in this structure.
%
%
*/
static Image *ReadPDFImage(const ImageInfo *image_info,ExceptionInfo *exception)
{
#define MediaBox  "/MediaBox"
#define RenderPostscriptText  "[%s] Rendering postscript..."

  char
    density[MaxTextExtent],
    command[MaxTextExtent],
    filename[MaxTextExtent],
    postscript_filename[MaxTextExtent];

  const char
    *value;

  const DelegateInfo
    *delegate_info;

  double
    dx_resolution,
    dy_resolution;

  FILE
    *file;

  Image
    *image,
    *next_image;

  ImageInfo
    *clone_info;

  int
    count,
    status;

  unsigned int
    antialias=4;

  MagickBool
    use_crop_box = MagickFalse;

  MagickBool
    pdf_stop_on_error = MagickFalse;


  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);

  if ((value=AccessDefinition(image_info,"pdf","use-cropbox")))
    {
      if (strcmp(value,"true") == 0)
        use_crop_box = True;
    }

   if ((value=AccessDefinition(image_info,"pdf","stop-on-error")))
     {
       if (strcmp(value,"true") == 0)
         pdf_stop_on_error = True;
     }

  /*
    Select Postscript delegate driver
  */
  delegate_info=GetPostscriptDelegateInfo(image_info,&antialias,exception);
  if (delegate_info == (const DelegateInfo *) NULL)
    return((Image *) NULL);
  /*
    Open image file.
  */
  image=AllocateImage(image_info);
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
  if (status == False)
    ThrowReaderException(FileOpenError,UnableToOpenFile,image);
  /*
    Open temporary output file.
  */
  file=AcquireTemporaryFileStream(postscript_filename,BinaryFileIOMode);
  if (file == (FILE *) NULL)
    ThrowReaderTemporaryFileException(postscript_filename);
  /*
    Set the page density.
  */
  dx_resolution=72.0;
  dy_resolution=72.0;
  if ((image->x_resolution == 0.0) || (image->y_resolution == 0.0))
    {
      (void) strlcpy(density,PSDensityGeometry,sizeof(density));
      count=GetMagickDimension(density,&image->x_resolution,&image->y_resolution,NULL,NULL);
      if (count != 2)
        image->y_resolution=image->x_resolution;
    }
  FormatString(density,"%gx%g",image->x_resolution,image->y_resolution);
  {
    /*
      Determine page geometry from the PDF media box.

      Note that we can use Ghostscript to obtain the bounding box info like

      gs -q -dBATCH -dNOPAUSE -sDEVICE=bbox ENV.003.01.pdf
      %%BoundingBox: 70 61 2089 2954
      %%HiResBoundingBox: 70.737537 61.199998 2088.587889 2953.601629
    */

    char
      geometry[MaxTextExtent];

    RectangleInfo
      box,
      page;

    SegmentInfo
      bounds;

    unsigned long
      height,
      width;

    int
      rotate;

    register char
      *p,
      *q;

    register long
      c;

    rotate=0;
    (void) memset(&page,0,sizeof(RectangleInfo));
    (void) memset(&box,0,sizeof(RectangleInfo));
    for (p=command; ; )
      {
        c=ReadBlobByte(image);
        if (c == EOF)
          break;
        (void) fputc(c,file);
        *p++=(char) c;
        if ((c != '\n') && (c != '\r') && ((p-command) < (MaxTextExtent-1)))
          continue;
        *p='\0';
        p=command;
        /*
          Continue unless this is a MediaBox statement.
        */
        if (LocaleNCompare(command,"/Rotate ",8) == 0)
          {
            count=sscanf(command,"/Rotate %d",&rotate);
            if (count > 0)
              {
                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                      "Rotate by %d degrees",rotate);
              }
          }
        q=strstr(command,MediaBox);
        if (q == (char *) NULL)
          continue;
        count=sscanf(q,"/MediaBox [%lf %lf %lf %lf",&bounds.x1,&bounds.y1,
                     &bounds.x2,&bounds.y2);
        if (count != 4)
          count=sscanf(q,"/MediaBox[%lf %lf %lf %lf",&bounds.x1,&bounds.y1,
                       &bounds.x2,&bounds.y2);
        if (count == 4)
          {
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Parsed: MediaBox %lf %lf %lf %lf",
                                  bounds.x1,bounds.y1,
                                  bounds.x2,bounds.y2);
          }
        if (count != 4)
          continue;
        if ((bounds.x1 > bounds.x2) || (bounds.y1 > bounds.y2))
          continue;
        /*
          Set Postscript render geometry.
        */
        width=(unsigned long) (bounds.x2-bounds.x1+0.5);
        height=(unsigned long) (bounds.y2-bounds.y1+0.5);
        if ((width <= box.width) && (height <= box.height))
          continue;
        page.width=width;
        page.height=height;
        box=page;
      }
    /*
      If page is rotated right or left, then swap width and height values.
    */
    if ((90 == AbsoluteValue(rotate)) || (270 == AbsoluteValue(rotate)))
      {
        double
          value;

        value=page.width;
        page.width=page.height;
        page.height=value;
      }
    if ((page.width == 0) || (page.height == 0))
      {
        SetGeometry(image,&page);
        (void) GetGeometry(PSPageGeometry,&page.x,&page.y,&page.width,
                           &page.height);
      }
    if (image_info->page != (char *) NULL)
      (void) GetGeometry(image_info->page,&page.x,&page.y,&page.width,
                         &page.height);
    geometry[0]='\0';
    FormatString(geometry,"%lux%lu",
                 (unsigned long) ceil(page.width*image->x_resolution/dx_resolution-0.5),
                 (unsigned long) ceil(page.height*image->y_resolution/dy_resolution-0.5));
    if (ferror(file))
      {
        (void) fclose(file);
        ThrowReaderException(CorruptImageError,AnErrorHasOccurredWritingToFile,
                             image);
      }
  }

  (void) fclose(file);
  CloseBlob(image);
  /*
    Use Ghostscript to convert Postscript image.
  */
  {
    char
      options[MaxTextExtent],
      arg[MaxTextExtent];

    options[0]='\0';

    if (use_crop_box)
      (void) strlcat(options,"-dUseCropBox",sizeof(options));

    if (pdf_stop_on_error)
      {
        if (options[0] != '\0')
          (void) strlcat(options," ",sizeof(options));
        (void) strlcat(options,"-dPDFSTOPONERROR",sizeof(options));
      }

    /*
      Append subrange.
    */
    if (image_info->subrange != 0)
      {
        FormatString(arg,"-dFirstPage=%lu -dLastPage=%lu",
                     image_info->subimage+1,
                     image_info->subimage+image_info->subrange);
        if (options[0] != '\0')
          (void) strlcat(options," ",sizeof(options));
        (void) strlcat(options,arg,sizeof(options));
      }

    /*
      Append authentication string.
    */
    if (image_info->authenticate != (char *) NULL)
      {
        FormatString(arg,"-sPDFPassword=%.1024s", image_info->authenticate);
        if (options[0] != '\0')
          (void) strlcat(options," ",sizeof(options));
        (void) strlcat(options,arg,sizeof(options));
      }
    (void) strlcpy(filename,image_info->filename,MaxTextExtent);
    clone_info=CloneImageInfo(image_info);
    if (!AcquireTemporaryFileName(clone_info->filename))
      {
        DestroyImageInfo(clone_info);
        ThrowReaderTemporaryFileException(clone_info->filename);
      }
    FormatString(command,delegate_info->commands,antialias,
                 antialias,density,options,clone_info->filename,
                 postscript_filename);
  }
  (void) MagickMonitorFormatted(0,8,&image->exception,RenderPostscriptText,
                                image->filename);
  status=InvokePostscriptDelegate(clone_info->verbose,command,exception);
  (void) MagickMonitorFormatted(7,8,&image->exception,RenderPostscriptText,
                                image->filename);
  DestroyImage(image);
  image=(Image *) NULL;
  if (IsAccessibleAndNotEmpty(clone_info->filename))
    {
      /*
        Read Ghostscript output.
      */
      clone_info->blob=(void *) NULL;
      clone_info->length=0;
      clone_info->magick[0]='\0';
      clone_info->subimage=0;
      clone_info->subrange=0;
      MagickFreeMemory(clone_info->tile);
      image=ReadImage(clone_info,exception);
    }
  (void) LiberateTemporaryFile(postscript_filename);
  (void) LiberateTemporaryFile(clone_info->filename);
  DestroyImageInfo(clone_info);
  if (image == (Image *) NULL)
    {
      if (UndefinedException == exception->severity)
        ThrowException(exception,DelegateError,PostscriptDelegateFailed,filename);
    }
  else
    {
      do
        {
          (void) strlcpy(image->magick,"PDF",sizeof(image->magick));
          (void) strlcpy(image->filename,filename,sizeof(image->filename));
          next_image=SyncNextImageInList(image);
          if (next_image != (Image *) NULL)
            image=next_image;
        } while (next_image != (Image *) NULL);
      while (image->previous != (Image *) NULL)
        image=image->previous;
      if (image_info->subimage != 0)
        {
          unsigned long
            scene = image_info->subimage;

          for (next_image=image;
               next_image != (Image *) NULL;
               next_image=next_image->next)
            next_image->scene = scene++;
        }
    }
  return(image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e g i s t e r P D F I m a g e                                           %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method RegisterPDFImage adds attributes for the PDF image format to
%  the list of supported formats.  The attributes include the image format
%  tag, a method to read and/or write the format, whether the format
%  supports the saving of more than one frame to the same file or blob,
%  whether the format supports native in-memory I/O, and a brief
%  description of the format.
%
%  The format of the RegisterPDFImage method is:
%
%      RegisterPDFImage(void)
%
*/
ModuleExport void RegisterPDFImage(void)
{
  MagickInfo
    *entry;

  entry=SetMagickInfo("EPDF");
  entry->decoder=(DecoderHandler) ReadPDFImage;
  entry->encoder=(EncoderHandler) WritePDFImage;
  entry->adjoin=False;
  entry->blob_support=False;
  entry->seekable_stream=True;
  entry->description="Encapsulated Portable Document Format";
  entry->module="PDF";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("PDF");
  entry->decoder=(DecoderHandler) ReadPDFImage;
  entry->encoder=(EncoderHandler) WritePDFImage;
  entry->magick=(MagickHandler) IsPDF;
  entry->blob_support=False;
  entry->seekable_stream=True;
  entry->description="Portable Document Format";
  entry->module="PDF";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   U n r e g i s t e r P D F I m a g e                                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method UnregisterPDFImage removes format registrations made by the
%  PDF module from the list of supported formats.
%
%  The format of the UnregisterPDFImage method is:
%
%      UnregisterPDFImage(void)
%
*/
ModuleExport void UnregisterPDFImage(void)
{
  (void) UnregisterMagickInfo("EPDF");
  (void) UnregisterMagickInfo("PDF");
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   W r i t e P D F I m a g e                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method WritePDFImage writes an image in the Portable Document image
%  format.
%
%  The format of the WritePDFImage method is:
%
%      unsigned int WritePDFImage(const ImageInfo *image_info,Image *image)
%
%  A description of each parameter follows.
%
%    o status: Method WritePDFImage return True if the image is written.
%      False is returned is there is a memory shortage or if the image file
%      fails to write.
%
%    o image_info: Specifies a pointer to a ImageInfo structure.
%
%    o image:  A pointer to an Image structure.
%
%
*/

static char *EscapeParenthesis(const char *text, char *paren_buffer)
{
  register char
    *p;

  register long
    i;

  unsigned long
    escapes;

  escapes=0;
  p=paren_buffer;
  for (i=0; i < (long) Min(strlen(text),(MaxTextExtent-escapes-1)); i++)
  {
    if ((text[i] == '(') || (text[i] == ')'))
      {
        *p++='\\';
        escapes++;
      }
    *p++=text[i];
  }
  *p='\0';
  return(paren_buffer);
}

#define ThrowPDFWriterException(code_,reason_,image_) \
  {                                                   \
    MagickFreeMemory(fax_blob);                       \
    MagickFreeMemory(xref);                           \
    ThrowWriterException(code_,reason_,image_);       \
  }
#define ThrowPDFWriterException2(code_,reason_,image_)  \
  {                                                     \
    MagickFreeMemory(fax_blob);                         \
    MagickFreeMemory(xref);                             \
    ThrowWriterException2(code_,reason_,image_);        \
  }
static unsigned int WritePDFImage(const ImageInfo *image_info,Image *image)
{
#define CFormat  "/Filter [ /%.1024s ]\n"
#define ObjectsPerImage  9

  char
    basename[MaxTextExtent],
    buffer[MaxTextExtent],
    date[MaxTextExtent],
    density[MaxTextExtent],
    **labels,
    page_geometry[MaxTextExtent],
    paren_buffer[MaxTextExtent];

  unsigned char
    *fax_blob=(unsigned char *) NULL;

  const ImageAttribute
    *attribute;

  double
    dx_resolution,
    dy_resolution,
    x_resolution,
    x_scale,
    y_resolution,
    y_scale;

  ExtendedSignedIntegralType
    offset;

  long
    count,
    y;

  RectangleInfo
    geometry,
    media_info;

  register const PixelPacket
    *p;

  register const IndexPacket
    *indexes;

  register unsigned char
    *q;

  register long
    i,
    x;

  size_t
    fax_blob_length,
    length;

  struct tm
    *time_meridian;

  time_t
    seconds;

  unsigned char
    *pixels;

  MagickPassFail
    status;

  unsigned long
    info_id,
    number_pixels,
    object,
    pages_id,
    root_id,
    scene,
    text_size;

  ExtendedSignedIntegralType
    *xref = (ExtendedSignedIntegralType *) NULL;

  size_t
    image_list_length;

  /*
    Open output image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  image_list_length=GetImageListLength(image);
  status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
  if (status == False)
    ThrowPDFWriterException(FileOpenError,UnableToOpenFile,image);

  /*
    Allocate X ref memory.
  */
  xref=MagickAllocateMemory(ExtendedSignedIntegralType *,
                            2048*sizeof(ExtendedSignedIntegralType));
  if (xref == (ExtendedSignedIntegralType *) NULL)
    ThrowPDFWriterException(ResourceLimitError,MemoryAllocationFailed,image);
  (void) memset(xref,0,2048*sizeof(ExtendedSignedIntegralType));
  /*
    Write Documentation Information Dictionary
  */
  object=0;
  (void) WriteBlobString(image,"%PDF-1.2 \n");
  xref[object++]=TellBlob(image);
  info_id=object;
  FormatString(buffer,"%lu 0 obj\n",object);
  (void) WriteBlobString(image,buffer);
  (void) WriteBlobString(image,"<<\n");
  seconds=time((time_t *) NULL);
  time_meridian=localtime(&seconds);
  FormatString(date,"D:%04d%02d%02d%02d%02d%02d",time_meridian->tm_year+1900,
               time_meridian->tm_mon+1,time_meridian->tm_mday,time_meridian->tm_hour,
               time_meridian->tm_min,time_meridian->tm_sec);
  GetPathComponent(image->filename,BasePath,basename);

  FormatString(buffer,"/Title (%.1024s)\n",
               EscapeParenthesis(basename,paren_buffer));
  (void) WriteBlobString(image,buffer);
  FormatString(buffer,"/CreationDate (%.1024s)\n",date);
  (void) WriteBlobString(image,buffer);
  FormatString(buffer,"/ModDate (%.1024s)\n",date);
  (void) WriteBlobString(image,buffer);
  FormatString(buffer,"/Producer (%.1024s)\n",
               EscapeParenthesis(GetMagickVersion((unsigned long *) NULL),
                                 paren_buffer));
  (void) WriteBlobString(image,buffer);
  (void) WriteBlobString(image,">>\n");
  (void) WriteBlobString(image,"endobj\n");
  /*
    Write Catalog object.
  */
  xref[object++]=TellBlob(image);
  root_id=object;
  FormatString(buffer,"%lu 0 obj\n",object);
  (void) WriteBlobString(image,buffer);
  (void) WriteBlobString(image,"<<\n");
  (void) WriteBlobString(image,"/Type /Catalog\n");
  FormatString(buffer,"/Pages %lu 0 R\n",object+1);
  (void) WriteBlobString(image,buffer);
  (void) WriteBlobString(image,">>\n");
  (void) WriteBlobString(image,"endobj\n");
  /*
    Write Pages object.
  */
  xref[object++]=TellBlob(image);
  pages_id=object;
  FormatString(buffer,"%lu 0 obj\n",object);
  (void) WriteBlobString(image,buffer);
  (void) WriteBlobString(image,"<<\n");
  (void) WriteBlobString(image,"/Type /Pages\n");
  FormatString(buffer,"/Kids [ %lu 0 R ",object+1);
  (void) WriteBlobString(image,buffer);
  count=(long) (pages_id+ObjectsPerImage+1);
  if (image_info->adjoin)
    {
      Image
        *kid_image;

      /*
        Predict page object id's.
      */
      kid_image=image;
      for ( ; kid_image->next != (Image *) NULL; count+=ObjectsPerImage)
        {
          FormatString(buffer,"%ld 0 R ",count);
          (void) WriteBlobString(image,buffer);
          kid_image=kid_image->next;
        }
      MagickReallocMemory(ExtendedSignedIntegralType *,xref,
                          (count+2048)*sizeof(ExtendedSignedIntegralType));
      if (xref == (ExtendedSignedIntegralType *) NULL)
        ThrowPDFWriterException(ResourceLimitError,MemoryAllocationFailed,image);
    }
  (void) WriteBlobString(image,"]\n");
  FormatString(buffer,"/Count %lu\n",(count-pages_id)/ObjectsPerImage);
  (void) WriteBlobString(image,buffer);
  (void) WriteBlobString(image,">>\n");
  (void) WriteBlobString(image,"endobj\n");
  scene=0;
  do
    {
      ImageCharacteristics
        characteristics;

      CompressionType
        compression;

      /*
        Analyze image properties.
      */
      (void) GetImageCharacteristics(image,&characteristics,
                                     (OptimizeType == image_info->type),
                                     &image->exception);

      compression=image->compression;
      if (image_info->compression != UndefinedCompression)
        {
          /*
            ImageInfo compression always prevails if it is set.
          */
          compression=image_info->compression;
        }
      else
        {
          /*
            Default to Zip compression unless the image was JPEG
            compressed and is not now monochrome or colormapped.
          */
          if ((JPEGCompression != compression) ||
              (characteristics.monochrome) ||
              (characteristics.palette))
            {
#if defined(HasZLIB)
              compression=ZipCompression;
#else
              compression=LZWCompression;
#endif
            }
        }

      switch (compression)
        {
#if !defined(HasJPEG)
        case JPEGCompression:
          {
            /*
              If JPEG compression is not supported, then use RLE compression
              and report a warning to user.
            */
            compression=RLECompression;
            ThrowException(&image->exception,MissingDelegateError,JPEGLibraryIsNotAvailable,image->filename);
            break;
          }
#endif
#if !defined(HasZLIB)
        case ZipCompression:
          {
            /*
              If ZIP compression is not supported, then use RLE compression
              and report a warning to user.
            */
            compression=RLECompression;
            ThrowException(&image->exception,MissingDelegateError,ZipLibraryIsNotAvailable,image->filename);
            break;
          }
#endif
        default:
          break;
        }
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "%s compression.",
                            CompressionTypeToString(compression));

      /*
        Scale image to size of Portable Document page.
      */
      text_size=0;
      attribute=GetImageAttribute(image,"label");
      if (attribute != (const ImageAttribute *) NULL)
        text_size=(unsigned int)
          (MultilineCensus(attribute->value)*image_info->pointsize+12);
      SetGeometry(image,&geometry);
      geometry.y=(long) text_size;
      FormatString(page_geometry,"%lux%lu",image->columns,image->rows);
      if (image_info->page != (char *) NULL)
        {
          (void) strlcpy(page_geometry,image_info->page,MaxTextExtent);
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Page: %s (from ImageInfo) ", page_geometry);

        }
      else
        {
          if ((image->page.width != 0) && (image->page.height != 0))
            {
              (void) FormatString(page_geometry,"%lux%lu%+ld%+ld",image->page.width,
                                  image->page.height,image->page.x,image->page.y);
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Page: %s (from Image)", page_geometry);
            }
          else
            {
              if (LocaleCompare(image_info->magick,"PDF") == 0)
                {
                  (void) strlcpy(page_geometry,PSPageGeometry,sizeof(page_geometry));
                  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                        "Page: %s (defaulted)", page_geometry);
                }
              else
                {
                  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                        "Page: %s (from Image columns/rows)",
                                        page_geometry);

                }
            }
        }
      (void) GetMagickGeometry(page_geometry,&geometry.x,&geometry.y,
                               &geometry.width,&geometry.height);
      (void) GetGeometry(page_geometry,&media_info.x,&media_info.y,
                         &media_info.width,&media_info.height);
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Image Resolution: %gx%g %s",
                            image->x_resolution,
                            image->y_resolution,
                            ResolutionTypeToString(image->units));
      /*
        Scale relative to dots-per-inch.
      */
      dx_resolution=72.0;
      dy_resolution=72.0;
      x_resolution=72.0;
      (void) strlcpy(density,PSDensityGeometry,sizeof(density));
      count=GetMagickDimension(density,&x_resolution,&y_resolution,NULL,NULL);
      if (count != 2)
        y_resolution=x_resolution;
      /*
        Use override resolution information if it appears to be valid.
      */
      if ((image_info->density != (char *) NULL) &&
          ((image_info->units == PixelsPerInchResolution) ||
           (image_info->units == PixelsPerCentimeterResolution)))
        {
          count=GetMagickDimension(image_info->density,&x_resolution,
                                   &y_resolution,NULL,NULL);
          if (count != 2)
            y_resolution=x_resolution;
          if (image_info->units == PixelsPerCentimeterResolution)
            {
              x_resolution *= 2.54;
              y_resolution *= 2.54;
            }
        }
      /*
        Use image resolution information if it appears to be valid.
      */
      else if ((image->x_resolution > 0.0) && (image->y_resolution > 0.0) &&
               ((image->units == PixelsPerInchResolution) ||
                (image->units == PixelsPerCentimeterResolution)))
        {
          x_resolution = image->x_resolution;
          y_resolution = image->y_resolution;
          if (image->units == PixelsPerCentimeterResolution)
            {
              x_resolution *= 2.54;
              y_resolution *= 2.54;
            }
        }
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "PDF Resolution: %gx%g DPI",
                            x_resolution,y_resolution);

      x_scale=(geometry.width*dx_resolution)/x_resolution;
      geometry.width=(unsigned long) (x_scale+0.5);
      y_scale=(geometry.height*dy_resolution)/y_resolution;
      geometry.height=(unsigned long) (y_scale+0.5);
      /*
        Write Page object.
      */
      xref[object++]=TellBlob(image);
      FormatString(buffer,"%lu 0 obj\n",object);
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,"<<\n");
      (void) WriteBlobString(image,"/Type /Page\n");
      FormatString(buffer,"/Parent %lu 0 R\n",pages_id);
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,"/Resources <<\n");
      FormatString(buffer,"/XObject << /Im%lu %lu 0 R >>\n",image->scene,
                   object+4);
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"/ProcSet %lu 0 R >>\n",object+3);
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"/MediaBox [0 0 %ld %ld]\n",
                   media_info.width,media_info.height);
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"/CropBox [%ld %ld %ld %ld]\n",geometry.x,geometry.y,
                   geometry.x+geometry.width,geometry.y+geometry.height);
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"/Contents %lu 0 R\n",object+1);
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,">>\n");
      (void) WriteBlobString(image,"endobj\n");
      /*
        Write Contents object.
      */
      xref[object++]=TellBlob(image);
      FormatString(buffer,"%lu 0 obj\n",object);
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,"<<\n");
      FormatString(buffer,"/Length %lu 0 R\n",object+1);
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,">>\n");
      (void) WriteBlobString(image,"stream\n");
      offset=TellBlob(image);
      (void) WriteBlobString(image,"q\n");
      labels=(char **) NULL;
      attribute=GetImageAttribute(image,"label");
      if (attribute != (const ImageAttribute *) NULL)
        labels=StringToList(attribute->value);
      if (labels != (char **) NULL)
        {
          for (i=0; labels[i] != (char *) NULL; i++)
            {
              (void) WriteBlobString(image,"BT\n");
              FormatString(buffer,"/F%lu %g Tf\n",image->scene,
                           image_info->pointsize);
              (void) WriteBlobString(image,buffer);
              FormatString(buffer,"%ld %g Td\n",geometry.x,geometry.y+
                           geometry.height+i*image_info->pointsize+12);
              (void) WriteBlobString(image,buffer);
              FormatString(buffer,"(%.1024s) Tj\n",labels[i]);
              (void) WriteBlobString(image,buffer);
              (void) WriteBlobString(image,"ET\n");
              MagickFreeMemory(labels[i]);
            }
          MagickFreeMemory(labels);
        }
      FormatString(buffer,"%g 0 0 %g %ld %ld cm\n",x_scale,y_scale,geometry.x,
                   geometry.y);
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"/Im%lu Do\n",image->scene);
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,"Q\n");
      offset=TellBlob(image)-offset;
      (void) WriteBlobString(image,"endstream\n");
      (void) WriteBlobString(image,"endobj\n");
      /*
        Write Length object.
      */
      xref[object++]=TellBlob(image);
      FormatString(buffer,"%lu 0 obj\n",object);
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"%lu\n",(unsigned long) offset);
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,"endobj\n");
      /*
        Write Procset object.
      */
      xref[object++]=TellBlob(image);
      FormatString(buffer,"%lu 0 obj\n",object);
      (void) WriteBlobString(image,buffer);
      if ((image->storage_class == DirectClass) || (image->colors > 256))
        (void) strlcpy(buffer,"[ /PDF /Text /ImageC",sizeof(buffer));
      else
        if (compression == FaxCompression)
          (void) strlcpy(buffer,"[ /PDF /Text /ImageB",sizeof(buffer));
        else
          (void) strlcpy(buffer,"[ /PDF /Text /ImageI",sizeof(buffer));
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image," ]\n");
      (void) WriteBlobString(image,"endobj\n");
      /*
        Write XObject object.
      */
      xref[object++]=TellBlob(image);
      FormatString(buffer,"%lu 0 obj\n",object);
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,"<<\n");
      (void) WriteBlobString(image,"/Type /XObject\n");
      (void) WriteBlobString(image,"/Subtype /Image\n");
      FormatString(buffer,"/Name /Im%lu\n",image->scene);
      (void) WriteBlobString(image,buffer);
      switch (compression)
        {
        case NoCompression:
          {
            FormatString(buffer,CFormat,"ASCII85Decode");
            break;
          }
        case JPEGCompression:
          {
            FormatString(buffer,CFormat,"DCTDecode");
            if (image->colorspace != CMYKColorspace)
              break;
            (void) WriteBlobString(image,buffer);
            (void) strlcpy(buffer,"/Decode [1 0 1 0 1 0 1 0]\n",sizeof(buffer));
            break;
          }
        case LZWCompression:
          {
            FormatString(buffer,CFormat,"LZWDecode");
            break;
          }
        case ZipCompression:
          {
            FormatString(buffer,CFormat,"FlateDecode");
            break;
          }
        case FaxCompression:
          {
            char
              CCITTParam[4];

            ExceptionInfo
              exception;

            /*
              Try compressing page to Group4 to see if it is
              supported, otherwise we will fall back to Group3.
            */
            (void) strlcpy(CCITTParam,"0",sizeof(CCITTParam));
            GetExceptionInfo(&exception);
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Executing ImageToHuffman2DBlob for CCITT Fax4 ...");
            fax_blob=ImageToHuffman2DBlob(image,image_info,&fax_blob_length,
                                          &exception);
            if (fax_blob != (unsigned char *) NULL)
              {
                (void) strlcpy(CCITTParam,"-1",sizeof(CCITTParam));
                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                      "ImageToHuffman2DBlob reports success!");
              }
            DestroyExceptionInfo(&exception);
            (void) strlcpy(buffer,"/Filter [ /CCITTFaxDecode ]\n",sizeof(buffer));
            (void) WriteBlobString(image,buffer);
            (void) strlcpy(buffer,"/Interpolate false\n",sizeof(buffer));
            (void) WriteBlobString(image,buffer);
            FormatString(buffer,
                         "/DecodeParms [ << /K %.1024s /BlackIs1 false /Columns %ld /Rows %ld >> ]\n",
                         CCITTParam,image->columns,image->rows);
            break;
          }
        default:
          {
            FormatString(buffer,CFormat,"RunLengthDecode");
            break;
          }
        }
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"/Width %lu\n",image->columns);
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"/Height %lu\n",image->rows);
      (void) WriteBlobString(image,buffer);
      if (compression == FaxCompression)
        strlcpy(buffer,"/ColorSpace /DeviceGray\n",MaxTextExtent);
      else
        FormatString(buffer,"/ColorSpace %lu 0 R\n",object+2);
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"/BitsPerComponent %d\n",
                   compression == FaxCompression ? 1 : 8);
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"/Length %lu 0 R\n",object+1);
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,">>\n");
      (void) WriteBlobString(image,"stream\n");
      offset=TellBlob(image);
      number_pixels=image->columns*image->rows;
      if ((compression == FaxCompression) ||
          ((image_info->type != TrueColorType) &&
           characteristics.grayscale))
        {
          /*
            Write grayscale output.
          */
          switch (compression)
            {
            case FaxCompression:
              {
                /*
                  Try Group4 first and use Group3 as a fallback.
                */
                if (fax_blob != (unsigned char *) NULL)
                  {
                    (void) WriteBlob(image,fax_blob_length,fax_blob);
                    MagickFreeMemory(fax_blob);
                  }
                else
                  {
                    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                          "Executing HuffmanEncodeImage for CCITT Fax3 ...");
                    if (!HuffmanEncodeImage(image_info,image))
                      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                            "HuffmanEncodeImage reports failure!");
                  }
                break;
              }
            case JPEGCompression:
              {
                unsigned char
                  *jpeg_blob;

                /*
                  Write image in JPEG format.
                */
                jpeg_blob=ImageToJPEGBlob(image,image_info,&length,&image->exception);
                if (jpeg_blob == (unsigned char *) NULL)
                  ThrowPDFWriterException2(CoderError,image->exception.reason,image);
                (void) WriteBlob(image,length,jpeg_blob);
                MagickFreeMemory(jpeg_blob);
                break;
              }
            case RLECompression:
            default:
              {
                /*
                  Allocate pixel array.
                */
                length=number_pixels;
                pixels=MagickAllocateMemory(unsigned char *,length);
                if (pixels == (unsigned char *) NULL)
                  ThrowPDFWriterException(ResourceLimitError,MemoryAllocationFailed,
                                          image);
                /*
                  Dump Runlength encoded pixels.
                */
                q=pixels;
                for (y=0; y < (long) image->rows; y++)
                  {
                    p=AcquireImagePixels(image,0,y,image->columns,1,
                                         &image->exception);
                    if (p == (const PixelPacket *) NULL)
                      break;
                    for (x=0; x < (long) image->columns; x++)
                      {
                        *q++=ScaleQuantumToChar(PixelIntensityToQuantum(p));
                        p++;
                      }
                    if (image->previous == (Image *) NULL)
                      if (QuantumTick(y,image->rows))
                        {
                          status=MagickMonitorFormatted(y,image->rows,
                                                        &image->exception,
                                                        SaveImageText,
                                                        image->filename,
                                                        image->columns,image->rows);
                          if (status == False)
                            break;
                        }
                  }
#if defined(HasZLIB)
                if (compression == ZipCompression)
                  status=ZLIBEncodeImage(image,length,image_info->quality,pixels);
                else
#endif /* defined(HasZLIB) */
                  if (compression == LZWCompression)
                    status=LZWEncodeImage(image,length,pixels);
                  else
                    status=PackbitsEncodeImage(image,length,pixels);
                MagickFreeMemory(pixels);
                if (!status)
                  {
                    CloseBlob(image);
                    MagickFreeMemory(xref);
                    return(MagickFail);
                  }
                break;
              }
            case NoCompression:
              {
                /*
                  Dump uncompressed PseudoColor packets.
                */
                Ascii85Initialize(image);
                for (y=0; y < (long) image->rows; y++)
                  {
                    p=AcquireImagePixels(image,0,y,image->columns,1,
                                         &image->exception);
                    if (p == (const PixelPacket *) NULL)
                      break;
                    for (x=0; x < (long) image->columns; x++)
                      {
                        Ascii85Encode(image,
                                      ScaleQuantumToChar(PixelIntensityToQuantum(p)));
                        p++;
                      }
                    if (image->previous == (Image *) NULL)
                      if (QuantumTick(y,image->rows))
                        {
                          status=MagickMonitorFormatted(y,image->rows,
                                                        &image->exception,
                                                        SaveImageText,
                                                        image->filename,
                                                        image->columns,image->rows);
                          if (status == False)
                            break;
                        }
                  }
                Ascii85Flush(image);
                break;
              }
            }
        }
      else
        if ((image->storage_class == DirectClass) || (image->colors > 256) ||
            (compression == JPEGCompression))
          switch (compression)
            {
            case JPEGCompression:
              {
                unsigned char
                  *jpeg_blob;

                /*
                  Write image in JPEG format.
                */
                jpeg_blob=ImageToJPEGBlob(image,image_info,&length,&image->exception);
                if (jpeg_blob == (unsigned char *) NULL)
                  ThrowPDFWriterException2(CoderError,image->exception.reason,image);
                (void) WriteBlob(image,length,jpeg_blob);
                MagickFreeMemory(jpeg_blob);
                break;
              }
            case RLECompression:
            default:
              {
                /*
                  Allocate pixel array.
                */
                length=(image->colorspace == CMYKColorspace ? 4 : 3)*number_pixels;
                pixels=MagickAllocateMemory(unsigned char *,length);
                if (pixels == (unsigned char *) NULL)
                  ThrowPDFWriterException(ResourceLimitError,MemoryAllocationFailed,
                                          image);
                /*
                  Dump runoffset encoded pixels.
                */
                q=pixels;
                for (y=0; y < (long) image->rows; y++)
                  {
                    p=AcquireImagePixels(image,0,y,image->columns,1,
                                         &image->exception);
                    if (p == (const PixelPacket *) NULL)
                      break;
                    for (x=0; x < (long) image->columns; x++)
                      {
                        if (image->matte && (p->opacity == TransparentOpacity))
                          {
                            *q++=ScaleQuantumToChar(MaxRGB);
                            *q++=ScaleQuantumToChar(MaxRGB);
                            *q++=ScaleQuantumToChar(MaxRGB);
                            p++;
                            continue;
                          }
                        *q++=ScaleQuantumToChar(p->red);
                        *q++=ScaleQuantumToChar(p->green);
                        *q++=ScaleQuantumToChar(p->blue);
                        if (image->colorspace == CMYKColorspace)
                          *q++=ScaleQuantumToChar(p->opacity);
                        p++;
                      }
                    if (image->previous == (Image *) NULL)
                      if (QuantumTick(y,image->rows))
                        {
                          status=MagickMonitorFormatted(y,image->rows,
                                                        &image->exception,
                                                        SaveImageText,
                                                        image->filename,
                                                        image->columns,image->rows);
                          if (status == False)
                            break;
                        }
                  }
#if defined(HasZLIB)
                if (compression == ZipCompression)
                  status=ZLIBEncodeImage(image,length,image_info->quality,pixels);
                else
#endif /* defined(HasZLIB) */
                  if (compression == LZWCompression)
                    status=LZWEncodeImage(image,length,pixels);
                  else
                    status=PackbitsEncodeImage(image,length,pixels);
                MagickFreeMemory(pixels);
                if (!status)
                  {
                    CloseBlob(image);
                    MagickFreeMemory(xref);
                    return(False);
                  }
                break;
              }
            case NoCompression:
              {
                /*
                  Dump uncompressed DirectColor packets.
                */
                Ascii85Initialize(image);
                for (y=0; y < (long) image->rows; y++)
                  {
                    p=AcquireImagePixels(image,0,y,image->columns,1,
                                         &image->exception);
                    if (p == (const PixelPacket *) NULL)
                      break;
                    for (x=0; x < (long) image->columns; x++)
                      {
                        if (image->matte && (p->opacity == TransparentOpacity))
                          {
                            Ascii85Encode(image,ScaleQuantumToChar(MaxRGB));
                            Ascii85Encode(image,ScaleQuantumToChar(MaxRGB));
                            Ascii85Encode(image,ScaleQuantumToChar(MaxRGB));
                            continue;
                          }
                        Ascii85Encode(image,ScaleQuantumToChar(p->red));
                        Ascii85Encode(image,ScaleQuantumToChar(p->green));
                        Ascii85Encode(image,ScaleQuantumToChar(p->blue));
                        if (image->colorspace == CMYKColorspace)
                          Ascii85Encode(image,ScaleQuantumToChar(p->opacity));
                        p++;
                      }
                    if (image->previous == (Image *) NULL)
                      if (QuantumTick(y,image->rows))
                        {
                          status=MagickMonitorFormatted(y,image->rows,
                                                        &image->exception,
                                                        SaveImageText,
                                                        image->filename,
                                                        image->columns,image->rows);
                          if (status == False)
                            break;
                        }
                  }
                Ascii85Flush(image);
                break;
              }
            }
        else
          {
            /*
              Dump number of colors and colormap.
            */
            switch (compression)
              {
              case RLECompression:
              default:
                {
                  /*
                    Allocate pixel array.
                  */
                  length=number_pixels;
                  pixels=MagickAllocateMemory(unsigned char *,length);
                  if (pixels == (unsigned char *) NULL)
                    ThrowPDFWriterException(ResourceLimitError,MemoryAllocationFailed,image);
                  /*
                    Dump Runlength encoded pixels.
                  */
                  q=pixels;
                  for (y=0; y < (long) image->rows; y++)
                    {
                      p=AcquireImagePixels(image,0,y,image->columns,1,
                                           &image->exception);
                      if (p == (const PixelPacket *) NULL)
                        break;
                      indexes=AccessImmutableIndexes(image);
                      for (x=0; x < (long) image->columns; x++)
                        *q++=indexes[x];
                      if (image->previous == (Image *) NULL)
                        if (QuantumTick(y,image->rows))
                          {
                            status=MagickMonitorFormatted(y,image->rows,
                                                          &image->exception,
                                                          SaveImageText,
                                                          image->filename,
                                                          image->columns,image->rows);
                            if (status == False)
                              break;
                          }
                    }
#if defined(HasZLIB)
                  if (compression == ZipCompression)
                    status=ZLIBEncodeImage(image,length,image_info->quality,pixels);
                  else
#endif /* defined(HasZLIB) */
                    if (compression == LZWCompression)
                      status=LZWEncodeImage(image,length,pixels);
                    else
                      status=PackbitsEncodeImage(image,length,pixels);
                  MagickFreeMemory(pixels);
                  if (!status)
                    {
                      CloseBlob(image);
                      MagickFreeMemory(xref);
                      return(False);
                    }
                  break;
                }
              case NoCompression:
                {
                  /*
                    Dump uncompressed PseudoColor packets.
                  */
                  Ascii85Initialize(image);
                  for (y=0; y < (long) image->rows; y++)
                    {
                      p=AcquireImagePixels(image,0,y,image->columns,1,
                                           &image->exception);
                      if (p == (const PixelPacket *) NULL)
                        break;
                      indexes=AccessImmutableIndexes(image);
                      for (x=0; x < (long) image->columns; x++)
                        Ascii85Encode(image,indexes[x]);
                      if (image->previous == (Image *) NULL)
                        if (QuantumTick(y,image->rows))
                          {
                            status=MagickMonitorFormatted(y,image->rows,
                                                          &image->exception,
                                                          SaveImageText,
                                                          image->filename,
                                                          image->columns,image->rows);
                            if (status == False)
                              break;
                          }
                    }
                  Ascii85Flush(image);
                  break;
                }
              }
          }
      offset=TellBlob(image)-offset;
      (void) WriteBlobString(image,"\nendstream\n");
      (void) WriteBlobString(image,"endobj\n");
      /*
        Write Length object.
      */
      xref[object++]=TellBlob(image);
      FormatString(buffer,"%lu 0 obj\n",object);
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"%lu\n",(unsigned long) offset);
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,"endobj\n");
      /*
        Write Colorspace object.
      */
      xref[object++]=TellBlob(image);
      FormatString(buffer,"%lu 0 obj\n",object);
      (void) WriteBlobString(image,buffer);
      if (image->colorspace == CMYKColorspace)
        {
          (void) strlcpy(buffer,"/DeviceCMYK\n",sizeof(buffer));
        }
      else
        {
          if ((compression == FaxCompression) ||
              (compression == Group4Compression) ||
              ((image_info->type != TrueColorType) &&
               (characteristics.grayscale)))
            {
              (void) strlcpy(buffer,"/DeviceGray\n",sizeof(buffer));
            }
          else
            {
              if ((image->storage_class == DirectClass) || (image->colors > 256) ||
                  (compression == JPEGCompression))
                (void) strlcpy(buffer,"/DeviceRGB\n",sizeof(buffer));
              else
                FormatString(buffer,"[ /Indexed /DeviceRGB %u %lu 0 R ]\n",
                             (unsigned int) image->colors-1,object+1);
            }
        }
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,"endobj\n");
      /*
        Write Colormap object.
      */
      xref[object++]=TellBlob(image);
      FormatString(buffer,"%lu 0 obj\n",object);
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,"<<\n");
      if ((image->storage_class != DirectClass) && (image->colors <= 256) &&
          (compression != FaxCompression))
        {
          if (compression == NoCompression)
            (void) WriteBlobString(image,"/Filter [ /ASCII85Decode ]\n");
          FormatString(buffer,"/Length %lu 0 R\n",object+1);
          (void) WriteBlobString(image,buffer);
          (void) WriteBlobString(image,">>\n");
          (void) WriteBlobString(image,"stream\n");
          offset=TellBlob(image);
          if (compression == NoCompression)
            Ascii85Initialize(image);
          for (i=0; i < (long) image->colors; i++)
            {
              if (compression == NoCompression)
                {
                  Ascii85Encode(image,ScaleQuantumToChar(image->colormap[i].red));
                  Ascii85Encode(image,ScaleQuantumToChar(image->colormap[i].green));
                  Ascii85Encode(image,ScaleQuantumToChar(image->colormap[i].blue));
                  continue;
                }
              (void) WriteBlobByte(image,
                                   ScaleQuantumToChar(image->colormap[i].red));
              (void) WriteBlobByte(image,
                                   ScaleQuantumToChar(image->colormap[i].green));
              (void) WriteBlobByte(image,
                                   ScaleQuantumToChar(image->colormap[i].blue));
            }
          if (compression == NoCompression)
            Ascii85Flush(image);
        }
      offset=TellBlob(image)-offset;
      (void) WriteBlobString(image,"\nendstream\n");
      (void) WriteBlobString(image,"endobj\n");
      /*
        Write Length object.
      */
      xref[object++]=TellBlob(image);
      FormatString(buffer,"%lu 0 obj\n",object);
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"%lu\n",(unsigned long) offset);
      (void) WriteBlobString(image,buffer);
      (void) WriteBlobString(image,"endobj\n");
      if (image->next == (Image *) NULL)
        break;
      image=SyncNextImageInList(image);
      status=MagickMonitorFormatted(scene++,image_list_length,
                                    &image->exception,SaveImagesText,
                                    image->filename);
      if (status == False)
        break;
    } while (image_info->adjoin);
  if (image_info->adjoin)
    while (image->previous != (Image *) NULL)
      image=image->previous;
  /*
    Write Xref object.
  */
  offset=TellBlob(image)-xref[0]+10;
  (void) WriteBlobString(image,"xref\n");
  FormatString(buffer,"0 %lu\n",object+1);
  (void) WriteBlobString(image,buffer);
  (void) WriteBlobString(image,"0000000000 65535 f \n");
  for (i=0; i < (long) object; i++)
    {
      FormatString(buffer,"%010lu 00000 n \n",(unsigned long) xref[i]);
      (void) WriteBlobString(image,buffer);
    }
  (void) WriteBlobString(image,"trailer\n");
  (void) WriteBlobString(image,"<<\n");
  FormatString(buffer,"/Size %lu\n",object+1);
  (void) WriteBlobString(image,buffer);
  FormatString(buffer,"/Info %lu 0 R\n",info_id);
  (void) WriteBlobString(image,buffer);
  FormatString(buffer,"/Root %lu 0 R\n",root_id);
  (void) WriteBlobString(image,buffer);
  (void) WriteBlobString(image,">>\n");
  (void) WriteBlobString(image,"startxref\n");
  FormatString(buffer,"%lu\n",(unsigned long) offset);
  (void) WriteBlobString(image,buffer);
  (void) WriteBlobString(image,"%%EOF\n");
  MagickFreeMemory(xref);
  CloseBlob(image);
  MagickFreeMemory(fax_blob);
  return(MagickPass);
}

#if defined(HasZLIB)
#include "zlib.h"
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   Z L I B E n c o d e I m a g e                                             %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method ZLIBEncodeImage compresses an image via ZLIB-coding specific to
%  Postscript Level II or Portable Document Format.  To ensure portability, the
%  binary ZLIB bytes are encoded as ASCII base-85.
%
%  The format of the ZLIBEncodeImage method is:
%
%      unsigned int ZLIBEncodeImage(Image *image,const size_t length,
%        const unsigned long quality,unsigned char *pixels)
%
%  A description of each parameter follows:
%
%    o status:  Method ZLIBEncodeImage returns True if all the pixels are
%      compressed without error, otherwise False.
%
%    o file: The address of a structure of type FILE.  ZLIB encoded pixels
%      are written to this file.
%
%    o length:  A value that specifies the number of pixels to compress.
%
%    o quality: the compression level (0-100).
%
%    o pixels: The address of an unsigned array of characters containing the
%      pixels to compress.
%
%
*/
static voidpf ZLIBAllocFunc(voidpf opaque, uInt items, uInt size) MAGICK_FUNC_MALLOC;
static voidpf ZLIBAllocFunc(voidpf opaque, uInt items, uInt size)
{
  ARG_NOT_USED(opaque);
  return MagickMallocCleared(MagickArraySize(items,size));
}
static void ZLIBFreeFunc(voidpf opaque, voidpf address)
{
  ARG_NOT_USED(opaque);
  MagickFree(address);
}
static unsigned int ZLIBEncodeImage(Image *image,const size_t length,
  const unsigned long quality,unsigned char *pixels)
{
  int
    status;

  register long
    i;

  unsigned char
    *compressed_pixels;

  unsigned long
    compressed_packets;

  z_stream
    stream;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  compressed_packets=(unsigned long) (1.001*length+12);
  compressed_pixels=MagickAllocateMemory(unsigned char *,compressed_packets);
  if (compressed_pixels == (unsigned char *) NULL)
    ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,
      (char *) NULL);
  (void) memset(&stream,0,sizeof(stream));
  stream.next_in=pixels;
  stream.avail_in=(unsigned int) length;
  stream.next_out=compressed_pixels;
  stream.avail_out=(unsigned int) compressed_packets;
  stream.zalloc=ZLIBAllocFunc;
  stream.zfree=ZLIBFreeFunc;
  stream.opaque=(voidpf) NULL;
  status=deflateInit(&stream,(int) Min(quality/10,9));
  if (status == Z_OK)
    {
      status=deflate(&stream,Z_FINISH);
      if (status == Z_STREAM_END)
        status=deflateEnd(&stream);
      else
        (void) deflateEnd(&stream);
      compressed_packets=stream.total_out;
    }
  if (status)
    ThrowBinaryException(CoderError,UnableToZipCompressImage,(char *) NULL)
  else
    for (i=0; i < (long) compressed_packets; i++)
      (void) WriteBlobByte(image,compressed_pixels[i]);
  MagickFreeMemory(compressed_pixels);
  return(!status);
}
#endif
