/* This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks.

    Copyright (C) 2002-2025 by Jin-Hwan Cho and Shunsaku Hirata,
    the dvipdfmx project team.
    
    Copyright (C) 1998, 1999 by Mark A. Wicks <mwicks@kettering.edu>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <ctype.h>
#include <string.h>
/* floor and abs */
#include <math.h>

#include "system.h"
#include "mem.h"
#include "error.h"
#include "mfileio.h"
#include "dpxconf.h"
#include "dpxutil.h"

#include "pdflimits.h"
#include "pdfencrypt.h"
#include "pdfparse.h"

#ifdef HAVE_ZLIB
#include <zlib.h>
#endif /* HAVE_ZLIB */

#include "pdfobj.h"
#include "pdfdev.h"

#define STREAM_ALLOC_SIZE      4096u
#define ARRAY_ALLOC_SIZE       256
#define IND_OBJECTS_ALLOC_SIZE 512

#define OBJ_NO_OBJSTM   (1 << 0)
/* Objects with this flag will not be put into an object stream.
   For instance, all stream objects have this flag set.          */
#define OBJ_NO_ENCRYPT  (1 << 1)
/* Objects with this flag will not be encrypted.
   This implies OBJ_NO_OBJSTM if encryption is turned on.        */

/* Any of these types can be represented as follows */
struct pdf_obj 
{
  int      type;

  uint32_t label;      /* Only used for indirect objects all other "label" to zero */
  uint16_t generation; /* Only used if "label" is used */
  int      refcount;   /* Number of links to this object */
  int32_t  flags;
  void    *data;

#if defined(PDFOBJ_DEBUG)
  int      obj_id;
#endif
};

#if defined(PDFOBJ_DEBUG)
static pdf_obj *bucket[65535];
static int cur_obj_id = 0;
#endif

struct pdf_boolean
{
  char   value;
};

struct pdf_number
{
  double value;
};

struct pdf_string
{
  unsigned char *string;
  size_t         length;
};

struct pdf_name
{
  char *name;
};

struct pdf_array
{
  size_t           max;
  size_t           size;
  struct pdf_obj **values;
};

struct pdf_dict
{
  struct pdf_obj  *key;
  struct pdf_obj  *value;
  struct pdf_dict *next;
};

/* DecodeParms for FlateDecode */
 struct decode_parms {
  int     predictor;
  int     colors;
  int     bits_per_component;
  int32_t columns;
 };

/* 2015/12/27 Added support for predictor functions
 *
 * There are yet no way to specify the use of predictor functions.
 * Using TIFF2 or PNG predictor usually gives better compression for images
 * but there is a case that compression speed becomes significantly slower.
 * Please use -C 0x20 option to disable the use of predictor functions.
 *
 * See, e.g., for a heuristic approach for selecting filters
 *   http://www.w3.org/TR/PNG-Encoders.html#E.Filter-selection
 */

struct pdf_stream
{
  struct pdf_obj     *dict;
  unsigned char      *stream;
  int                *objstm_data;    /* used for object streams */
  size_t              stream_length;
  size_t              max_length;
  int32_t             _flags;
  struct decode_parms decodeparms;
};

struct pdf_indirect
{
  pdf_file *pf;
  pdf_obj  *obj;   /* used when PF == NULL */
  uint32_t  label;
  uint16_t  generation;
};

typedef void                pdf_null;
typedef struct pdf_boolean  pdf_boolean;
typedef struct pdf_number   pdf_number;
typedef struct pdf_string   pdf_string;
typedef struct pdf_name     pdf_name;
typedef struct pdf_array    pdf_array;
typedef struct pdf_dict     pdf_dict;
typedef struct pdf_stream   pdf_stream;
typedef struct pdf_indirect pdf_indirect;

typedef struct xref_entry
{
  uint8_t      type;       /* object storage type              */
  uint32_t     field2;     /* offset in file or object stream  */
  uint16_t     field3;     /* generation or index              */
  pdf_obj     *direct;     /* used for imported objects        */
  pdf_obj     *indirect;   /* used for imported objects        */
} xref_entry;

struct pdf_file
{
  FILE         *file;
  pdf_obj      *trailer;
  xref_entry   *xref_table;
  pdf_obj      *catalog;
  int           num_obj;
  int           file_size;
  int           version;
};

static int error_out = 0;

#define OBJSTM_MAX_OBJS  200
/* the limit is only 100 for linearized PDF */

struct pdf_out {
  struct {
    int         enc_mode; /* boolean */
  } state;

  unsigned char id1[16];
  unsigned char id2[16];

  struct {
    int         major;
    int         minor; 
  } version;

  struct {
    struct {
      int       level;
      int       use_predictor;
    } compression;

    int         enable_encrypt;
    int         use_objstm;
  } options;

  struct {
    FILE       *file;
    size_t      file_position;
    int         line_position;
    size_t      compression_saved;
  } output;

  struct {
    uint32_t    next_label;
    uint32_t    max_ind_objects;
  } obj;

  pdf_sec      *sec_data;

  pdf_obj      *trailer;
  uint32_t      startxref;
  xref_entry *  xref_table;

  pdf_obj      *xref_stream;
  pdf_obj      *output_stream;
  pdf_obj      *current_objstm;
  /* The following flag bits are (8,338,607+1)/8 bytes data
   * each bit represenging if the object is freed.
   * Where the value 8,338,607 is taken from PDF ref. manual, v.1.7,
   * Appendix C, "Implementation Limits". 
   */
  char         *free_list;
};

#if defined(LIBDPX)
size_t output_file_size;
#endif /* LIBDPX */

/* Underway to reform PDF related code... For a moment place pdf_out
 * object as a static variable. */
static pdf_out pout;

static pdf_out *
current_output (void)
{
  return &pout;
}

static void
init_pdf_out_struct (pdf_out *p)
{
  ASSERT(p);

  p->state.enc_mode = 0;

  memset(p->id1, 0, 16);
  memset(p->id2, 0, 16);

  p->version.major  = 1;
  p->version.minor  = PDF_VERSION_DEFAULT;

  p->options.compression.level = 9;
  p->options.compression.use_predictor = 1;
  p->options.enable_encrypt    = 0;
  p->options.use_objstm        = 1;

  p->output.file = NULL;
  p->output.file_position = 0;
  p->output.line_position = 0;
  p->output.compression_saved = 0;
#if defined(LIBDPX)
  output_file_size = 0;
#endif /* LIBDPX */

  p->obj.next_label = 1;
  p->obj.max_ind_objects = 0;

  p->sec_data   = NULL;
  p->trailer    = NULL;
  p->startxref  = 0;
  p->xref_table = NULL;

  p->xref_stream    = NULL;
  p->output_stream  = NULL;
  p->current_objstm = NULL;

  p->free_list = NEW((PDF_NUM_INDIRECT_MAX+1)/8, char);
  memset(p->free_list, 0, (PDF_NUM_INDIRECT_MAX+1)/8);
}

static void
clean_pdf_out_struct (pdf_out *p)
{
  if (p->free_list)
    RELEASE(p->free_list);
  memset(p, 0, sizeof(pdf_out));
}

/* Internal static routines */

static int      check_for_pdf_version (FILE *file);

static void     pdf_flush_obj (pdf_out *p, pdf_obj *object);
static void     pdf_label_obj (pdf_out *p, pdf_obj *object);
static void     pdf_write_obj (pdf_out *p, pdf_obj *object);

static void     set_objstm_data (pdf_obj *objstm, int *data);
static int     *get_objstm_data (pdf_obj *objstm);
static void     release_objstm  (pdf_obj *objstm);

static void     pdf_out_char (pdf_out *p, char c);
static void     pdf_out_str  (pdf_out *p, const void *buffer, size_t length);

static pdf_obj *pdf_new_ref      (pdf_out *p, pdf_obj *object);
static void     release_indirect (pdf_indirect *data);
static void     write_indirect   (pdf_out *p, pdf_indirect *indirect);

static void     release_boolean (pdf_obj *data);
static void     write_boolean   (pdf_out *p, pdf_boolean *data);

static void     write_null      (pdf_out *p);

static void     release_number  (pdf_number *number);
static void     write_number    (pdf_out *p, pdf_number *number);

static void     write_string    (pdf_out *p, pdf_string *str);
static void     release_string  (pdf_string *str);

static void     write_name      (pdf_out *p, pdf_name *name);
static void     release_name    (pdf_name *name);

static void     write_array     (pdf_out *p, pdf_array *array);
static void     release_array   (pdf_array *array);

static void     write_dict      (pdf_out *p, pdf_dict *dict);
static void     release_dict    (pdf_dict *dict);

static void     write_stream    (pdf_out *p, pdf_stream *stream);
static void     release_stream  (pdf_stream *stream);

static void
pdf_out_set_compression (pdf_out *p, int level)
{
  ASSERT(p);

#ifndef   HAVE_ZLIB
  ERROR("You don't have compression compiled in. Possibly libz wasn't found by configure.");
#else
#ifndef HAVE_ZLIB_COMPRESS2
  if (level != 0) 
    WARN("Unable to set compression level -- your zlib doesn't have compress2().");
#endif
  if (level >= 0 && level <= 9) 
    p->options.compression.level = level;
  else {
    ERROR("set_compression: invalid compression level: %d", level);
  }
#endif /* !HAVE_ZLIB */

  return;
}

FILE *
pdf_get_output_file (void)
{
  pdf_out *p = current_output();
  return p->output.file;
}

static void
pdf_out_set_version (pdf_out *p, int ver_major, int ver_minor)
{
  int version;

  ASSERT(p);

  version = ver_major * 10 + ver_minor;
  /* Don't forget to update CIDFont_stdcc_def[] in cid.c too! */
  if (version >= PDF_VERSION_MIN && version <= PDF_VERSION_MAX) {
    p->version.major = ver_major;
    p->version.minor = ver_minor;
  } else {
    WARN("Unsupported PDF version %d.%d ... Ignoring.", ver_major, ver_minor);
  }
}

int
pdf_get_version (void)
{
  pdf_out *p = current_output();
  return (p->version.major * 10 + p->version.minor);
}

int
pdf_get_version_major (void)
{
  pdf_out *p = current_output();
  return p->version.major;
}

int
pdf_get_version_minor (void)
{
  pdf_out *p = current_output();
  return p->version.minor;
}

int
pdf_check_version (int major, int minor)
{
  pdf_out *p = current_output();
  if (p->version.major > major)
    return 0;
  else if (p->version.major < major)
    return -1;
  else {
    return (p->version.minor >= minor) ? 0 : -1;
  }

  return -1;
}

static void
add_xref_entry (pdf_out *p,
                uint32_t label, uint8_t type, uint32_t field2, uint16_t field3)
{
  ASSERT(p);

  if (label >= p->obj.max_ind_objects) {
    p->obj.max_ind_objects = (label/IND_OBJECTS_ALLOC_SIZE+1)*IND_OBJECTS_ALLOC_SIZE;
    p->xref_table = RENEW(p->xref_table, p->obj.max_ind_objects, xref_entry);
  }

  p->xref_table[label].type     = type;
  p->xref_table[label].field2   = field2;
  p->xref_table[label].field3   = field3;
  p->xref_table[label].direct   = NULL;
  p->xref_table[label].indirect = NULL;
}

#define BINARY_MARKER "%\344\360\355\370\n"
pdf_out *
pdf_out_init (const char *filename,
              const unsigned char *id1,
              const unsigned char *id2,
              int ver_major, int ver_minor, int compression_level,
              int enable_encrypt,
              int enable_objstm,
              int enable_predictor)
{
  pdf_out  *p = current_output();
  char      v;

  init_pdf_out_struct(p);

  pdf_out_set_version(p, ver_major, ver_minor);
  pdf_out_set_compression(p, compression_level);

  add_xref_entry(p, 0, 0, 0, 0xffff);

  /* This must be set before pdf_set_root() is called */
  p->options.enable_encrypt = enable_encrypt;
  if (pdf_check_version(1, 5) == 0) {
    if (enable_objstm) {
      p->xref_stream         = pdf_new_stream(STREAM_COMPRESS);
      p->xref_stream->flags |= OBJ_NO_ENCRYPT;
      p->trailer             = pdf_stream_dict(p->xref_stream);
      pdf_add_dict(p->trailer, pdf_new_name("Type"), pdf_new_name("XRef"));
      p->options.use_objstm  = 1;
    } else {
      p->xref_stream         = NULL;
      p->trailer             = pdf_new_dict();
      p->options.use_objstm  = 0;
    }
  } else {
    p->xref_stream        = NULL;
    p->trailer            = pdf_new_dict();
    p->options.use_objstm = 0;
  }

  p->output_stream = NULL;

  if (filename == NULL) { /* no filename: writing to stdout */
#ifdef WIN32
    setmode(fileno(stdout), _O_BINARY);
#endif
    p->output.file = stdout;
  } else {
    p->output.file = MFOPEN(filename, FOPEN_WBIN_MODE);
    if (!p->output.file) {
      if (strlen(filename) < 128)
        ERROR("Unable to open \"%s\".", filename);
      else
        ERROR("Unable to open file.");
    }
  }
  pdf_out_str(p, "%PDF-", strlen("%PDF-"));
  v = '0' + p->version.major;
  pdf_out_str(p, &v, 1);
  pdf_out_str(p, ".", 1);
  v = '0' + p->version.minor;
  pdf_out_str(p, &v, 1);
  pdf_out_str(p, "\n", 1);
  pdf_out_str(p, BINARY_MARKER, strlen(BINARY_MARKER));

  /* Set trailer ID and setup security handler */
  {
    pdf_obj       *id_array;

    memcpy(p->id1, id1, 16);
    memcpy(p->id2, id2, 16);
    id_array = pdf_new_array();
    pdf_add_array(id_array, pdf_new_string(p->id1, 16));
    pdf_add_array(id_array, pdf_new_string(p->id2, 16));
    pdf_add_dict(p->trailer, pdf_new_name("ID"), id_array);
  }
  p->state.enc_mode = 0;
  p->options.compression.use_predictor = enable_predictor;

  return p;
}

void
pdf_out_set_encrypt (int keybits, int32_t permission,
                     const char *opasswd, const char *upasswd,
                     int use_aes, int encrypt_metadata)
{
  pdf_out *p = current_output();

  pdf_obj *encrypt, *extension, *catalog;

  p->sec_data = pdf_enc_init(p->id1, keybits, permission,
                               opasswd, upasswd, use_aes, encrypt_metadata);
  if (!p->sec_data) {
    p->options.enable_encrypt = 0;
    return;
  }

  encrypt = pdf_enc_get_encrypt_dict(p->sec_data);
  pdf_add_dict(p->trailer,
               pdf_new_name("Encrypt"), pdf_ref_obj(encrypt));
  encrypt->flags |= OBJ_NO_ENCRYPT;
  encrypt->flags |= OBJ_NO_OBJSTM;
  pdf_release_obj(encrypt);
  
  extension = pdf_enc_get_extension_dict(p->sec_data);
  if (extension) {
    catalog = pdf_doc_catalog();
    pdf_add_dict(catalog, pdf_new_name("Extensions"), extension);
  }
}

static void
dump_xref_table (pdf_out *p)
{
  int   i, length;
  char  buf[32];

  ASSERT(p);

  pdf_out_str(p, "xref\n", 5);

  length = sprintf(buf, "%d %u\n", 0, p->obj.next_label);
  pdf_out_str(p, buf, length);

  /*
   * Every space counts.  The space after the 'f' and 'n' is * *essential*.
   * The PDF spec says the lines must be 20 characters long including the
   * end of line character.
   */
  for (i = 0; i < p->obj.next_label; i++) {
    uint8_t type = p->xref_table[i].type;
    if (type > 1)
      ERROR("object type %hu not allowed in xref table", type);
    length = sprintf(buf, "%010u %05hu %c \n",
                     p->xref_table[i].field2, p->xref_table[i].field3,
                     type ? 'n' : 'f');
    pdf_out_str(p, buf, length);
  }
}

static void
dump_trailer (pdf_out *p)
{
  ASSERT(p);

  pdf_out_str(p, "trailer\n", 8);
  p->state.enc_mode = 0;
  write_dict(p, p->trailer->data);
  pdf_release_obj(p->trailer);
  p->trailer = NULL;
  pdf_out_char(p, '\n');
}

/*
 * output a PDF 1.5 cross-reference stream;
 * contributed by Matthias Franz (March 21, 2007)
 */
static void
dump_xref_stream (pdf_out *p)
{
  uint32_t      pos, i;
  uint32_t      poslen;
  unsigned char buf[7] = {0, 0, 0, 0, 0};
  pdf_obj      *w;

  ASSERT(p);

  /* determine the necessary size of the offset field */
  pos = p->startxref; /* maximal offset value */
  poslen = 1;
  while (pos >>= 8)
    poslen++;

  w = pdf_new_array();
  pdf_add_array(w, pdf_new_number(1));      /* type                */
  pdf_add_array(w, pdf_new_number(poslen)); /* offset (big-endian) */
  pdf_add_array(w, pdf_new_number(2));      /* generation          */
  pdf_add_dict(p->trailer, pdf_new_name("W"), w);

  /* We need the xref entry for the xref stream right now */
  add_xref_entry(p, p->obj.next_label - 1, 1, p->startxref, 0);

  for (i = 0; i < p->obj.next_label; i++) {
    size_t   j;
    uint16_t f3;
    buf[0] = p->xref_table[i].type;
    pos    = p->xref_table[i].field2;
    for (j = poslen; j--; ) {
      buf[1+j] = (unsigned char) pos;
      pos >>= 8;
    }
    f3 = p->xref_table[i].field3;
    buf[poslen+1] = (unsigned char) (f3 >> 8);
    buf[poslen+2] = (unsigned char) (f3);
    pdf_add_stream(p->xref_stream, &buf, poslen+3);
  }

  pdf_release_obj(p->xref_stream);
  p->xref_stream = NULL;
}

#if defined(LIBDPX)
long
pdf_output_stats (void)
{
  return (long) output_file_size;
}
#endif /* LIBDPX */

void
pdf_out_flush (void)
{
  pdf_out *p = current_output();
  char     buf[16];

  if (p->output.file) {
    int  length;

    /* Flush current object stream */
    if (p->current_objstm) {
      release_objstm(p->current_objstm);
      p->current_objstm =NULL;
    }

    /*
     * Label xref stream - we need the number of correct objects
     * for the xref stream dictionary (= trailer).
     * Labelling it in pdf_out_init (with 1)  does not work (why?).
     */
    if (p->xref_stream)
      pdf_label_obj(p, p->xref_stream);

    /* Record where this xref is for trailer */
    p->startxref = p->output.file_position;

    pdf_add_dict(p->trailer,
                 pdf_new_name("Size"), pdf_new_number(p->obj.next_label));

    if (p->xref_stream)
      dump_xref_stream(p);
    else {
      dump_xref_table(p);
      dump_trailer(p);
    }

    /* Done with xref table */
    RELEASE(p->xref_table);
    p->xref_table = NULL;

    pdf_out_str(p, "startxref\n", 10);
    length = sprintf(buf, "%u\n", p->startxref);
    pdf_out_str(p, buf, length);
    pdf_out_str(p, "%%EOF\n", 6);

#if !defined(LIBDPX)
    MESG("\n");
#endif /* !LIBDPX */
    if (dpx_conf.verbose_level > 0) {
      if (p->options.compression.level > 0) {
        MESG("Compression saved %ld bytes\n", p->output.compression_saved);
      }
    }
#if !defined(LIBDPX)
    MESG("%ld bytes written", p->output.file_position);
#else
    output_file_size = p->output.file_position;
#endif /* !LIBDPX */

    MFCLOSE(p->output.file);
    p->output.file = NULL;
    p->output.file_position = 0;
    p->output.line_position = 0;
  }
  if (p->sec_data)  
    pdf_enc_close(&p->sec_data);
#if defined(PDFOBJ_DEBUG)
  {
    int i;
    error_out = 1;
    MESG("\ndebug>> %d PDF objects created.", cur_obj_id);
    for (i = 0; i < cur_obj_id; i++) {
      pdf_obj *obj = bucket[i];
      if (obj) {
        if (obj->label > 0) {
          WARN("Object obj_id=<%lu, %u> unreleased...", obj->label, obj->generation);
          WARN("Reference count=%d", obj->refcount);
          pdf_write_obj(p, obj);
          MESG("\n");
        } else {
          WARN("Unreleased object found: (unlabeled) id=%d", i);
          pdf_write_obj(p, obj);
          MESG("\n");
        }
      }
    }
  }
#endif
  clean_pdf_out_struct(p);
}

void
pdf_error_cleanup (void)
{
  pdf_out *p = current_output();
  /*
   * This routine is the cleanup required for an abnormal exit.
   * For now, simply close the file.
   */
  if (p->output.file)
    MFCLOSE(p->output.file);
  p->output.file = NULL;
}


void
pdf_set_root (pdf_obj *object)
{
  pdf_out *p = current_output();

  if (pdf_lookup_dict(p->trailer, "Root"))
    ERROR("Root object already set!");
  pdf_add_dict(p->trailer, pdf_new_name("Root"), pdf_ref_obj(object));
  /* Adobe Readers don't like a document catalog inside an encrypted
   * object stream, although the PDF v1.5 spec seems to allow this.
   * Note that we don't set OBJ_NO_ENCRYPT since the name dictionary in
   * a document catalog may contain strings, which should be encrypted.
   */
  if (p->options.enable_encrypt)
    object->flags |= OBJ_NO_OBJSTM;
}

void
pdf_set_info (pdf_obj *object)
{
  pdf_out *p = current_output();

  if (pdf_lookup_dict(p->trailer, "Info"))
    ERROR ("Info object already set!");
  pdf_add_dict(p->trailer, pdf_new_name("Info"), pdf_ref_obj(object));
}



static void
pdf_out_char (pdf_out *p, char c)
{
  ASSERT(p);

  if (error_out) {
    fputc(c, stderr);
  } else {
    if (p->output_stream)
    pdf_add_stream(p->output_stream, &c, 1);
    else {
      fputc(c, p->output.file);
      p->output.file_position += 1;
      if (c == '\n')
        p->output.line_position  = 0;
      else
        p->output.line_position += 1;
    }
  }
}

static char xchar[] = "0123456789abcdef";

static void
pdf_out_xchar (pdf_out *p, char c)
{
  ASSERT(p);

  pdf_out_char(p, xchar[(c >> 4) & 0x0f]);
  pdf_out_char(p, xchar[c & 0x0f]);
}

static void
pdf_out_str (pdf_out *p, const void *buffer, size_t length)
{
  ASSERT(p);

  if (error_out)
    fwrite(buffer, 1, length, stderr);
  else {
    if (p->output_stream)
      pdf_add_stream(p->output_stream, buffer, length);
    else {
      fwrite(buffer, 1, length, p->output.file);
      p->output.file_position += length;
      p->output.line_position += length;
      /* "foo\nbar\n "... */
      if (length > 0 &&
          ((const char *)buffer)[length-1] == '\n')
        p->output.line_position = 0;
    }
  }
}

/*  returns 1 if a white-space character is necessary to separate
    an object of type1 followed by an object of type2              */
static
int pdf_need_white (int type1, int type2)
{
  return !(type1 == PDF_STRING || type1 == PDF_ARRAY || type1 == PDF_DICT ||
	   type2 == PDF_STRING || type2 == PDF_NAME ||
	   type2 == PDF_ARRAY || type2 == PDF_DICT);
}

static
void pdf_out_white (pdf_out *p)
{
  ASSERT(p);

  if (p->output.line_position >= 80) {
    pdf_out_char(p, '\n');
  } else {
    pdf_out_char(p, ' ');
  }
}

#define TYPECHECK(o,t) if (!(o) || (o)->type != (t)) {\
  ERROR("typecheck: Invalid object type: %d %d (line %d)", (o) ? (o)->type : -1, (t), __LINE__);\
}

#define INVALIDOBJ(o)  ((o) == NULL || (o)->type <= 0 || (o)->type > PDF_UNDEFINED)

static pdf_obj *
pdf_new_obj(int type)
{
  pdf_obj *result;

  if (type > PDF_UNDEFINED || type < 0)
    ERROR("Invalid object type: %d", type);

  result = NEW(1, pdf_obj);
  result->type  = type;
  result->data  = NULL;
  result->label      = 0;
  result->generation = 0;
  result->refcount   = 1;
  result->flags      = 0;

#if defined(PDFOBJ_DEBUG)
  result->obj_id = cur_obj_id;
  bucket[cur_obj_id] = result;
  cur_obj_id++;
#endif

  return result;
}

int
pdf_obj_typeof (pdf_obj *object)
{
  if (INVALIDOBJ(object))
    return PDF_OBJ_INVALID;

  return object->type;
}

static void
pdf_label_obj (pdf_out *p, pdf_obj *object)
{
  ASSERT(p);

  if (INVALIDOBJ(object))
    ERROR("pdf_label_obj(): passed invalid object.");

  /*
   * Don't change label on an already labeled object. Ignore such calls.
   */
  if (object->label == 0) {
    if (p->obj.next_label == PDF_NUM_INDIRECT_MAX) {
      ERROR("Number of indirect object has reached its maximum value!");
    }
    object->label      = p->obj.next_label++;
    object->generation = 0;
  }
}

/*
 * Transfer the label assigned to the object src to the object dst.
 * The object dst must not yet have been labeled.
 */
void
pdf_transfer_label (pdf_obj *dst, pdf_obj *src)
{
  ASSERT(dst && !dst->label && src);

  dst->label      = src->label;
  dst->generation = src->generation;
  src->label      = 0;
  src->generation = 0;
}

/*
 * This doesn't really copy the object, but allows it to be used without
 * fear that somebody else will free it.
 */
pdf_obj *
pdf_link_obj (pdf_obj *object)
{
  if (INVALIDOBJ(object))
    ERROR("pdf_link_obj(): passed invalid object.");

  object->refcount += 1;

  return object;
}


pdf_obj *
pdf_ref_obj (pdf_obj *object)
{
  pdf_out *p = current_output();

  if (INVALIDOBJ(object))
    ERROR("pdf_ref_obj(): passed invalid object.");
  
  if (object->refcount == 0) {
    MESG("\nTrying to refer already released object!!!\n");
    error_out = 1;
    pdf_write_obj(p, object);
    ERROR("Cannot continue...");
    error_out = 0;
  }

  if (PDF_OBJ_INDIRECTTYPE(object)) {
    return pdf_link_obj(object);
  } else {
    return pdf_new_ref(p, object);
  }
}

static void
release_indirect (pdf_indirect *data)
{
  RELEASE(data);
}

static void
write_indirect (pdf_out *p, pdf_indirect *indirect)
{
  int   length;
  char  buf[64];

  ASSERT(p);
  ASSERT(!indirect->pf);

  length = sprintf(buf, "%u %hu R",
                   indirect->label, indirect->generation);
  pdf_out_str(p, buf, length);
}

/* The undefined object is used as a placeholder in pdfnames.c
 * for objects which are referenced before they are defined.
 */
pdf_obj *
pdf_new_undefined (void)
{
  pdf_obj *result;

  result = pdf_new_obj(PDF_UNDEFINED);
  result->data = NULL;

  return result;
}

pdf_obj *
pdf_new_null (void)
{
  pdf_obj *result;

  result = pdf_new_obj(PDF_NULL);
  result->data = NULL;

  return result;
}

static void
write_null (pdf_out *p)
{
  pdf_out_str(p, "null", 4);
}

pdf_obj *
pdf_new_boolean (char value)
{
  pdf_obj     *result;
  pdf_boolean *data;

  result = pdf_new_obj(PDF_BOOLEAN);
  data   = NEW(1, pdf_boolean);
  data->value  = value;
  result->data = data;

  return result;
}

static void
release_boolean (pdf_obj *data)
{
  RELEASE (data);
}

static void
write_boolean (pdf_out *p, pdf_boolean *data)
{
  if (data->value) {
    pdf_out_str(p, "true", 4);
  } else {
    pdf_out_str(p, "false", 5);
  }
}

char
pdf_boolean_value (pdf_obj *object)
{
  pdf_boolean *data;

  TYPECHECK(object, PDF_BOOLEAN);

  data = object->data;

  return data->value;
}

pdf_obj *
pdf_new_number (double value)
{
  pdf_obj    *result;
  pdf_number *data;

  result = pdf_new_obj(PDF_NUMBER);
  data   = NEW(1, pdf_number);
  data->value  = value;
  result->data = data;

  return result;
}

static void
release_number (pdf_number *data)
{
  RELEASE (data);
}

static void
write_number (pdf_out *p, pdf_number *number)
{
  int  count;
  char buf[512];

  count = pdf_sprint_number(buf, number->value);

  pdf_out_str(p, buf, count);
}


void
pdf_set_number (pdf_obj *object, double value)
{
  pdf_number *data;

  TYPECHECK(object, PDF_NUMBER);

  data = object->data;
  data->value = value;
}

double
pdf_number_value (pdf_obj *object)
{
  pdf_number *data;

  TYPECHECK(object, PDF_NUMBER);

  data = object->data;

  return data->value;
}

pdf_obj *
pdf_new_string (const void *str, unsigned length)
{
  pdf_obj    *result;
  pdf_string *data;

  ASSERT(str);

  result = pdf_new_obj(PDF_STRING);
  data   = NEW(1, pdf_string);
  result->data = data;
  data->length = length;

  if (length) {
    data->string = NEW(length+1, unsigned char);
    memcpy(data->string, str, length);
    /* Shouldn't assume NULL terminated. */
    data->string[length] = '\0';
  } else
    data->string = NULL;

  return result;
}

void *
pdf_string_value (pdf_obj *object)
{
  pdf_string *data;

  TYPECHECK(object, PDF_STRING);

  data = object->data;

  return data->string;
}

unsigned
pdf_string_length (pdf_obj *object)
{
  pdf_string *data;

  TYPECHECK(object, PDF_STRING);

  data = object->data;

  return (unsigned) data->length;
}

/*
 * This routine escapes non printable characters and control
 * characters in an output string.
 */
int
pdfobj_escape_str (char *buffer, int bufsize, const unsigned char *s, int len)
{
  size_t result = 0;
  size_t i;

  for (i = 0; i < len; i++) {
    unsigned char ch;

    ch = s[i];
    if (result > bufsize - 4)
      ERROR("pdfobj_escape_str: Buffer overflow");

    /*
     * We always write three octal digits. Optimization only gives few Kb
     * smaller size for most documents when zlib compressed.
     */
    if (ch < 32 || ch > 126) {
      buffer[result++] = '\\';
#if 0
      if (i < len - 1 && !isdigit(s[i+1]))
	result += sprintf(buffer+result, "%o", ch);
      else
	result += sprintf(buffer+result, "%03o", ch);
#endif
      result += sprintf(buffer+result, "%03o", ch);
    } else {
      switch (ch) {
      case '(':
	buffer[result++] = '\\';
	buffer[result++] = '(';
	break;
      case ')':
	buffer[result++] = '\\';
	buffer[result++] = ')';
	break;
      case '\\':
	buffer[result++] = '\\';
	buffer[result++] = '\\';
	break;
      default:
	buffer[result++] = ch;
	break;
      }
    }
  }

  return (int) result;
}

static void
write_string (pdf_out *p, pdf_string *str)
{
  unsigned char *s   = NULL;
  size_t         i, nescc = 0;
  size_t         len = 0;

  ASSERT(p);

  if (p->state.enc_mode) {
    pdf_encrypt_data(p->sec_data, str->string, str->length, &s, &len);
  } else {
    s   = str->string;
    len = str->length;
  }

  /*
   * Count all ASCII non-printable characters.
   */
  for (i = 0; i < len; i++) {
    if (!isprint(s[i]))
      nescc++;
  }
  /*
   * If the string contains much escaped chars, then we write it as
   * ASCII hex string.
   */
  if (nescc > len / 3) {
    pdf_out_char(p, '<');
    for (i = 0; i < len; i++) {
      pdf_out_xchar(p, s[i]);
    }
    pdf_out_char(p, '>');
  } else {
    char   *buf;
    size_t  size, count;
  
    /* At most len/3 is to be escaped here. (see above)
     * Thus 4*len/3 + 2*len/3 + 2 is enough for buffer size.
     */
    size = len * 2 + 3;
    buf  = NEW(size, char);

    pdf_out_char(p, '(');
    /*
     * This section of code probably isn't speed critical.  Escaping the
     * characters in the string one at a time may seem slow, but it's
     * safe if the formatted string length exceeds FORMAT_BUF_SIZE.
     * Occasionally you see some long strings in PDF.  pdfobj_escape_str
     * is also used for strings of text with no kerning.  These must be
     * handled as quickly as possible since there are so many of them.
     */ 
    for (i = 0; i < len; i++) {
      count = pdfobj_escape_str(buf, size, &(s[i]), 1);
      pdf_out_str(p, buf, count);
    }
    pdf_out_char(p, ')');
    RELEASE(buf);
  }

  if (p->state.enc_mode && s)
    RELEASE(s);
}

static void
release_string (pdf_string *data)
{
  if (data->string != NULL) {
    RELEASE(data->string);
    data->string = NULL;
  }
  RELEASE(data);
}

void
pdf_set_string (pdf_obj *object, unsigned char *str, unsigned length)
{
  pdf_string *data;

  TYPECHECK(object, PDF_STRING);

  data = object->data;
  if (data->string != 0) {
    RELEASE(data->string);
  }
  if (length != 0) {
    data->length = length;
    data->string = NEW(length + 1, unsigned char);
    memcpy(data->string, str, length);
    data->string[length] = '\0';
  } else {
    data->length = 0;
    data->string = NULL;
  }
}

/* Name does *not* include the /. */ 
pdf_obj *
pdf_new_name (const char *name)
{
  pdf_obj  *result;
  size_t    length;
  pdf_name *data;

  result = pdf_new_obj(PDF_NAME);
  data   = NEW (1, pdf_name);
  result->data = data;
  length = strlen(name);
  if (length != 0) {
    data->name = NEW(length+1, char);
    memcpy(data->name, name, length);
    data->name[length] = '\0';
  } else {
    data->name = NULL;
  }

  return result;
}

static void
write_name (pdf_out *p, pdf_name *name)
{
  char   *s;
  size_t  i, length;

  ASSERT(p);

  s      = name->name;
  length = name->name ? strlen(name->name) : 0;
  /*
   * From PDF Reference, 3rd ed., p.33:
   *
   *  Beginning with PDF 1.2, any character except null (character code 0)
   *  may be included in a name by writing its 2-digit hexadecimal code,
   *  preceded bythe number sign character (#); see implementation notes 3
   *  and 4 in Appendix H. This syntax is required in order to represent
   *  any of the delimiter or white-space characters or the number sign
   *  character itself; it is recommended but not required for characters
   *  whose codes are outside the range 33 (!) to 126 (~).
   */
#ifndef is_delim
  /* Avoid '{' and '}' for PostScript compatibility? */
#define is_delim(c) ((c) == '(' || (c) == ')' || \
                     (c) == '/' || \
                     (c) == '<' || (c) == '>' || \
                     (c) == '[' || (c) == ']' || \
                     (c) == '{' || (c) == '}' || \
                     (c) == '%')
#endif
  pdf_out_char(p, '/');
  for (i = 0; i < length; i++) {
    if (s[i] < '!' || s[i] > '~' || s[i] == '#' || is_delim(s[i])) {
      /*     ^ "space" is here. */
      pdf_out_char (p, '#');
      pdf_out_xchar(p, s[i]);
    } else {
      pdf_out_char (p, s[i]);
    }
  }
}

static void
release_name (pdf_name *data)
{
  if (data->name != NULL) {
    RELEASE(data->name);
    data->name = NULL;
  }
  RELEASE(data);
}

char *
pdf_name_value (pdf_obj *object)
{
  pdf_name *data;

  TYPECHECK(object, PDF_NAME);

  data = object->data;

  return data->name;
}

/*
 * We do not have pdf_name_length() since '\0' is not allowed
 * in PDF name object.
 */

pdf_obj *
pdf_new_array (void)
{
  pdf_obj   *result;
  pdf_array *data;

  result = pdf_new_obj(PDF_ARRAY);
  data   = NEW(1, pdf_array);
  data->values = NULL;
  data->max    = 0;
  data->size   = 0;
  result->data = data;

  return result;
}

static void
write_array (pdf_out *p, pdf_array *array)
{
  ASSERT(p);

  pdf_out_char(p, '[');
  if (array->size > 0) {
    size_t i;
    int    type1 = PDF_UNDEFINED, type2;
    
    for (i = 0; i < array->size; i++) {
      if (array->values[i]) {
        type2 = array->values[i]->type;
        if (type1 != PDF_UNDEFINED && pdf_need_white(type1, type2))
          pdf_out_white(p);
        type1 = type2;
        pdf_write_obj(p, array->values[i]);
      } else
        WARN("PDF array element %ld undefined.", i);
    }
  }
  pdf_out_char(p, ']');
}

pdf_obj *
pdf_get_array (pdf_obj *array, int idx)
{
  pdf_obj   *result = NULL;
  pdf_array *data;

  TYPECHECK(array, PDF_ARRAY);

  data = array->data;
  if (idx < 0)
    result = data->values[idx + data->size];
  else if (idx < data->size) {
    result = data->values[idx];
  }

  return result;
}

unsigned
pdf_array_length (pdf_obj *array)
{
  pdf_array *data;

  TYPECHECK(array, PDF_ARRAY);

  data = (pdf_array *) array->data;

  return (unsigned) data->size;
}

static void
release_array (pdf_array *data)
{
  size_t  i;

  if (data->values) {
    for (i = 0; i < data->size; i++) {
      pdf_release_obj(data->values[i]);
      data->values[i] = NULL;
    }
    RELEASE(data->values);
    data->values = NULL;
  }
  RELEASE(data);
}

/*
 * The name pdf_add_array is misleading. It behaves differently than
 * pdf_add_dict(). This should be pdf_push_array().
 */
void
pdf_add_array (pdf_obj *array, pdf_obj *object)
{
  pdf_array *data;

  TYPECHECK(array, PDF_ARRAY);

  data = array->data;
  if (data->size >= data->max) {
    data->max   += ARRAY_ALLOC_SIZE;
    data->values = RENEW(data->values, data->max, pdf_obj *);
  }
  data->values[data->size] = object;
  data->size++;

  return;
}

#if 0
void
pdf_put_array (pdf_obj *array, int idx, pdf_obj *object)
{
  pdf_array *data;
  int        i;

  TYPECHECK(array, PDF_ARRAY);

  data = array->data;
  if (idx + 1 > data->max) {
    data->max   += ARRAY_ALLOC_SIZE;
    data->values = RENEW(data->values, data->max, pdf_obj *);
  }
  /*
   * Rangecheck error in PostScript interpreters if
   * idx > data->size - 1. But pdf_new_array() doesn't set
   * array size, pdf_add_array() dynamically increases size
   * of array. This might confusing...
   */
  if (idx + 1 > data->size) {
    for (i = data->size; i < idx; i++)
      data->values[i] = pdf_new_null(); /* release_array() won't work without this */
    data->values[idx] = object;
    data->size = idx + 1;
  } else {
    if (data->values[idx])
      pdf_release_obj(data->values[idx]);
    data->values[idx] = object;
  }
}

/* Easily leaks memory... */
pdf_obj *
pdf_shift_array (pdf_obj *array)
{
  pdf_obj   *result = NULL;
  pdf_array *data;

  TYPECHECK(array, PDF_ARRAY);

  data = array->data;
  if (data->size > 0) {
    size_t i;

    result = data->values[0];
    for (i = 1; i < data->size; i++)
      data->values[i-1] = data->values[i];
    data->size--;
  }

  return result;
}
#endif

/* Prepend an object to an array */
static void
pdf_unshift_array (pdf_obj *array, pdf_obj *object)
{
  pdf_array *data;

  TYPECHECK(array, PDF_ARRAY);

  data = array->data;
  if (data->size >= data->max) {
    data->max   += ARRAY_ALLOC_SIZE;
    data->values = RENEW(data->values, data->max, pdf_obj *);
  }
  memmove(&data->values[1], data->values, data->size * sizeof(pdf_obj *));
  data->values[0] = object;
  data->size++;
}

#if 0
pdf_obj *
pdf_pop_array (pdf_obj *array)
{
  pdf_obj   *result;
  pdf_array *data;

  TYPECHECK(array, PDF_ARRAY);

  data = array->data;
  if (data->size > 0) {
    result = data->values[data->size - 1];
    data->size--;
  } else {
    result = NULL;
  }

  return result;
}
#endif

static void
write_dict (pdf_out *p, pdf_dict *dict)
{
#if 0
  pdf_out_str (p, "<<\n", 3); /* dropping \n saves few kb. */
#else
  pdf_out_str(p, "<<", 2);
#endif
  while (dict->key != NULL) {
    pdf_write_obj(p, dict->key);
    if (pdf_need_white(PDF_NAME, (dict->value)->type)) {
      pdf_out_white(p);
    }
    pdf_write_obj(p, dict->value);
#if 0
    pdf_out_char (file, '\n'); /* removing this saves few kb. */
#endif
    dict = dict->next;
  }
  pdf_out_str(p, ">>", 2);
}

pdf_obj *
pdf_new_dict (void)
{
  pdf_obj  *result;
  pdf_dict *data;

  result = pdf_new_obj(PDF_DICT);
  data   = NEW(1, pdf_dict);
  data->key    = NULL;
  data->value  = NULL;
  data->next   = NULL;
  result->data = data;

  return result;
}

static void
release_dict (pdf_dict *data)
{
  pdf_dict *next;

  while (data != NULL && data->key != NULL) {
    pdf_release_obj(data->key);
    pdf_release_obj(data->value);
    data->key   = NULL;
    data->value = NULL;
    next = data->next;
    RELEASE(data);
    data = next;
  }
  RELEASE(data);
}

/* Array is ended by a node with NULL this pointer */
/* pdf_add_dict returns 0 if the key is new and non-zero otherwise */
int
pdf_add_dict (pdf_obj *dict, pdf_obj *key, pdf_obj *value)
{
  pdf_dict *data, *new_node;

  TYPECHECK(dict, PDF_DICT);
  TYPECHECK(key,  PDF_NAME);

  /* It seems that NULL is sometimes used for null object... */
  if (value != NULL && INVALIDOBJ(value))
    ERROR("pdf_add_dict(): Passed invalid value");

  /* If this key already exists, simply replace the value */
  for (data = dict->data; data->key != NULL; data = data->next) {
    if (!strcmp(pdf_name_value(key), pdf_name_value(data->key))) {
      /* Release the old value */
      pdf_release_obj(data->value);
      /* Release the new key (we don't need it) */
      pdf_release_obj(key);
      data->value = value;
      return 1;
    }
  }
  /*
   * We didn't find the key. We build a new "end" node and add
   * the new key just before the end
   */
  new_node = NEW (1, pdf_dict);
  new_node->key = NULL;
  new_node->value = NULL;
  new_node->next = NULL;
  data->next  = new_node;
  data->key   = key;
  data->value = value;
  return 0;
}

#if 0
void
pdf_put_dict (pdf_obj *dict, const char *key, pdf_obj *value)
{
  pdf_dict *data;

  TYPECHECK(dict, PDF_DICT);

  if (!key) {
    ERROR("pdf_put_dict(): Passed invalid key.");
  }
  /* It seems that NULL is sometimes used for null object... */
  if (value != NULL && INVALIDOBJ(value)) {
    ERROR("pdf_add_dict(): Passed invalid value.");
  }

  data = dict->data;

  while (data->key != NULL) {
    if (!strcmp(key, pdf_name_value(data->key))) {
      pdf_release_obj(data->value);
      data->value = value;
      break;
    }
    data = data->next;
  }

  /*
   * If we didn't find the key, build a new "end" node and add
   * the new key just before the end
   */
  if (data->key == NULL) {
    pdf_dict *new_node;

    new_node = NEW (1, pdf_dict);
    new_node->key   = NULL;
    new_node->value = NULL;
    new_node->next  = NULL;
    data->next  = new_node;
    data->key   = pdf_new_name(key);
    data->value = value;
  }
}
#endif

/* pdf_merge_dict makes a link for each item in dict2 before stealing it */
void
pdf_merge_dict (pdf_obj *dict1, pdf_obj *dict2)
{
  pdf_dict *data;

  TYPECHECK(dict1, PDF_DICT);
  TYPECHECK(dict2, PDF_DICT);

  data = dict2->data;
  while (data->key != NULL) {
    pdf_add_dict(dict1, pdf_link_obj(data->key), pdf_link_obj(data->value));
    data = data->next;
  }
}

int
pdf_foreach_dict (pdf_obj *dict,
		  int (*proc) (pdf_obj *, pdf_obj *, void *), void *pdata)
{
  int       error = 0;
  pdf_dict *data;

  ASSERT(proc);

  TYPECHECK(dict, PDF_DICT);

  data = dict->data;
  while (!error &&
	 data->key != NULL) {
    error = proc(data->key, data->value, pdata);
    data = data->next;
  }

  return error;
}

#define pdf_match_name(o,s) ((o) && (s) && !strcmp(((pdf_name *)(o)->data)->name, (s)))
pdf_obj *
pdf_lookup_dict (pdf_obj *dict, const char *name)
{
  pdf_dict *data;

  ASSERT(name);

  TYPECHECK(dict, PDF_DICT);

  data = dict->data;
  while (data->key != NULL) {
    if (!strcmp(name, pdf_name_value(data->key))) {
      return data->value;
    }
    data = data->next;
  }

  return NULL;
}

/* Returns array of dictionary keys */
pdf_obj *
pdf_dict_keys (pdf_obj *dict)
{
  pdf_obj  *keys;
  pdf_dict *data;

  TYPECHECK(dict, PDF_DICT);

  keys = pdf_new_array();
  for (data = dict->data; (data &&
			   data->key != NULL); data = data->next) {
    /* We duplicate name object rather than linking keys.
     * If we forget to free keys, broken PDF is generated.
     */
    pdf_add_array(keys, pdf_new_name(pdf_name_value(data->key)));
  }

  return keys;
}

void
pdf_remove_dict (pdf_obj *dict, const char *name)
{
  pdf_dict *data, **data_p;

  TYPECHECK(dict, PDF_DICT);

  data   = dict->data;
  data_p = (pdf_dict **) (void *) &(dict->data);
  while (data->key != NULL) {
    if (pdf_match_name(data->key, name)) {
      pdf_release_obj(data->key);
      pdf_release_obj(data->value);
      *data_p = data->next;
      RELEASE(data);
      break;
    }
    data_p = &(data->next);
    data   = data->next;
  }
}

pdf_obj *
pdf_new_stream (int flags)
{
  pdf_obj    *result;
  pdf_stream *data;

  result = pdf_new_obj(PDF_STREAM);
  data   = NEW(1, pdf_stream);
  /*
   * Although we are using an arbitrary pdf_object here, it must have
   * type=PDF_DICT and cannot be an indirect reference.  This will be
   * checked by the output routine.
   */
  data->dict   = pdf_new_dict();
  data->_flags = flags;
  data->stream = NULL;
  data->stream_length = 0;
  data->max_length    = 0;
  data->objstm_data = NULL;

  data->decodeparms.predictor = 2;
  data->decodeparms.columns   = 0;
  data->decodeparms.bits_per_component = 0;
  data->decodeparms.colors    = 0;

  result->data = data;
  result->flags |= OBJ_NO_OBJSTM;

  return result;
}

void
pdf_stream_set_predictor (pdf_obj *stream,
                          int predictor, int32_t columns, int bpc, int colors)
{
  struct pdf_stream *data;

  if (pdf_obj_typeof(stream) != PDF_STREAM)
    return;
  else if (columns < 0 || bpc < 0 || colors < 0)
    return;

  data = (struct pdf_stream *) stream->data;
  data->decodeparms.predictor = predictor;
  data->decodeparms.columns   = columns;
  data->decodeparms.bits_per_component = bpc;
  data->decodeparms.colors    = colors;
  data->_flags |= STREAM_USE_PREDICTOR;
}

/* Adaptive PNG filter
 * We use the "minimum sum of absolute differences" heuristic approach
 * for finding the most optimal filter to be used.
 *
 * From http://www.libpng.org/pub/png/book/chapter09.html
 *
 *   For grayscale and truecolor images of 8 or more bits per sample, with or
 *   without alpha channels, dynamic filtering is almost always beneficial. The
 *   approach that has by now become standard is known as the minimum sum of
 *   absolute differences heuristic and was first proposed by Lee Daniel
 *   Crocker in February 1995.
 */
static unsigned char *
filter_PNG15_apply_filter (unsigned char *raster,
                           int32_t columns, int32_t rows,
                           int8_t bpc, int8_t colors, int32_t *length)
{
  unsigned char *dst;
  int      bits_per_pixel  = colors * bpc;
  int      bytes_per_pixel = (bits_per_pixel + 7) / 8;
  int32_t  rowbytes = columns * bytes_per_pixel;
  int32_t  i, j;

  ASSERT(raster && length);

  /* Result */
  dst = NEW((rowbytes+1)*rows, unsigned char);
  *length = (rowbytes + 1) * rows;

  for (j = 0; j < rows; j++) {
    int type = 0;
    unsigned char *pp = dst + j * (rowbytes + 1);
    unsigned char *p  = raster + j * rowbytes;
    uint32_t sum[5]   = {0, 0, 0, 0, 0};
    /* First calculated sum of values to make a heuristic guess
     * of optimal predictor function.
     */
    for (i = 0; i < rowbytes; i++) {
      int left  = (i - bytes_per_pixel >= 0) ? p[i - bytes_per_pixel] : 0;
      int up    = (j > 0) ? *(p+i-rowbytes) : 0;
      int uplft = (j > 0) ?
                    ((i - bytes_per_pixel >= 0) ?
                      *(p+i-rowbytes-bytes_per_pixel) : 0) : 0;
      /* Type 0 -- None */
      sum[0] += p[i];
      /* Type 1 -- Sub */
      sum[1] += abs((int) p[i] - left);
      /* Type 2 -- Up */
      sum[2] += abs((int) p[i] - up);
      /* Type 3 -- Average */
      {
        int tmp = floor((up + left) / 2);
        sum[3] += abs((int) p[i] - tmp);
      }
      /* Type 4 -- Peath */
      {
        int q = left + up - uplft;
        int qa = abs(q - left), qb = abs(q - up), qc = abs(q - uplft);
        if (qa <= qb && qa <= qc)
          sum[4] += abs((int) p[i] - left);
        else if (qb <= qc)
          sum[4] += abs((int) p[i] - up);
        else
          sum[4] += abs((int) p[i] - uplft);
      }
    }
    {
      int min = sum[0], min_idx = 0;
      for (i = 0; i < 5; i++) {
        if (sum[i] < min) {
          min = sum[i]; min_idx = i;
        }
      }
      type = min_idx;
    }
    /* Now we actually apply filter. */
    pp[0] = type;
    switch (type) {
    case 0:
      memcpy(pp+1, p, rowbytes);
      break;
    case 1:
      for (i = 0; i < rowbytes; i++) {
        int left = (i - bytes_per_pixel >= 0) ? p[i - bytes_per_pixel] : 0;
        pp[i+1] = p[i] - left;
      }
      break;
    case 2:
      for (i = 0; i < rowbytes; i++) {
        int up  = (j > 0) ? *(p+i - rowbytes) : 0;
        pp[i+1] = p[i] - up;
      }
      break;
    case 3:
      {
        for (i = 0; i < rowbytes; i++) {
          int up   = (j > 0) ? *(p+i-rowbytes) : 0;
          int left = (i - bytes_per_pixel >= 0) ? p[i - bytes_per_pixel] : 0;
          int tmp  = floor((up + left) / 2);
          pp[i+1]  = p[i] - tmp;
        }
      }
      break;
    case 4: /* Peath */
      {
        for (i = 0; i < rowbytes; i++) {
          int up   = (j > 0) ? *(p+i-rowbytes) : 0;
          int left = (i - bytes_per_pixel >= 0) ? p[i - bytes_per_pixel] : 0;
          int uplft = (j > 0) ?
                        ((i - bytes_per_pixel >= 0) ?
                          *(p+i-rowbytes-bytes_per_pixel) : 0) : 0;
          int q = left + up - uplft;
          int qa = abs(q - left), qb = abs(q - up), qc = abs(q - uplft);
          if (qa <= qb && qa <= qc)
            pp[i+1] = p[i] - left;
          else if (qb <= qc)
            pp[i+1] = p[i] - up;
          else
            pp[i+1] = p[i] - uplft;
        }
      }
      break;
    }
  }

  return  dst;
}

/* TIFF predictor filter support
 * This modifies "raster" itself!
 */
static void
apply_filter_TIFF2_1_2_4 (unsigned char *raster,
                          int32_t width, int32_t height,
                          int8_t bpc, int8_t num_comp)
{
  int32_t   rowbytes = (bpc * num_comp * width + 7) / 8;
  uint8_t   mask     = (1 << bpc) - 1;
  uint16_t *prev;
  int32_t   i, j;

  ASSERT(raster);
  ASSERT( bpc > 0 && bpc <= 8 );

  prev = NEW(num_comp, uint16_t);

  /* Generic routine for 1 to 16 bit.
   * It supports, e.g., 7 bpc images too.
   * Actually, it is not necessary to have 16 bit inbuf and outbuf
   * since we only need 1, 2, and 4 bit support here. 8 bit is enough.
   */
  for (j = 0; j < height; j++) {
    int32_t  k, l, inbits, outbits;
    uint16_t inbuf, outbuf;
    int      c;

    memset(prev, 0, sizeof(uint16_t)*num_comp);
    inbuf = outbuf = 0; inbits = outbits = 0;
    l = k = j * rowbytes;
    for (i = 0; i < width; i++) {
      for (c = 0; c < num_comp; c++) {
        uint8_t cur;
        int8_t  sub;
        if (inbits < bpc) { /* need more byte */
          inbuf   = (inbuf << 8) | raster[l]; l++;
          inbits += 8;
        }
        cur     = (inbuf >> (inbits - bpc)) & mask;
        inbits -= bpc; /* consumed bpc bits */
        sub     = cur - prev[c];
        prev[c] = cur;
        if (sub < 0)
          sub += (1 << bpc);
        /* Append newly filtered component value */
        outbuf   = (outbuf << bpc) | sub;
        outbits += bpc;
        /* flush */
        if (outbits >= 8) {
          raster[k] = (outbuf >> (outbits - 8)); k++;
          outbits  -= 8;
        }
      }
    }
    if (outbits > 0) {
      raster[k] = (outbuf << (8 - outbits));
      k++;
    }
  }
  RELEASE(prev);
}

static unsigned char *
filter_TIFF2_apply_filter (unsigned char *raster,
                           int32_t columns, int32_t rows,
                           int8_t bpc, int8_t colors, int32_t *length)
{
  unsigned char *dst;
  uint16_t      *prev;
  int32_t        rowbytes = (bpc * colors * columns + 7) / 8;
  int32_t        i, j;

  ASSERT(raster && length);

  dst = NEW(rowbytes*rows, unsigned char);
  memcpy(dst, raster, rowbytes*rows);
  *length = rowbytes * rows;

  switch (bpc) {
  case 1: case 2: case 4:
    apply_filter_TIFF2_1_2_4(dst, columns, rows, bpc, colors);
    break;

  case 8:
    prev = NEW(colors, uint16_t);
    for (j = 0; j < rows; j++) {
      memset(prev, 0, sizeof(uint16_t)*colors);
      for (i = 0; i < columns; i++) {
        int     c;
        int32_t pos = colors * (columns * j + i);
        for (c = 0; c < colors; c++) {
          uint8_t cur = raster[pos+c];
          int32_t sub = cur - prev[c];
          prev[c]     = cur;
          dst[pos+c]  = sub;
        }
      }
    }
    RELEASE(prev);
    break;

  case 16:
    prev = NEW(colors, uint16_t);
    for (j = 0; j < rows; j++) {
      memset(prev, 0, sizeof(uint16_t)*colors);
      for (i = 0; i < columns; i++) {
        int     c;
        int32_t pos = 2 * colors * (columns * j + i);
        for (c = 0; c < colors; c++) {
          uint16_t cur   = ((uint8_t)raster[pos+2*c])*256 +
                             (uint8_t)raster[pos+2*c+1];
          uint16_t sub   = cur - prev[c];
          prev[c]        = cur;
          dst[pos+2*c  ] = (sub >> 8) & 0xff;
          dst[pos+2*c+1] = sub & 0xff;
        }
      }
    }
    RELEASE(prev);
    break;

  }

  return  dst;
}

static pdf_obj *
filter_create_predictor_dict (int predictor, int32_t columns,
                              int bpc, int colors)
{
  pdf_obj *parms;

  parms = pdf_new_dict();
  pdf_add_dict(parms, pdf_new_name("BitsPerComponent"), pdf_new_number(bpc));
  pdf_add_dict(parms, pdf_new_name("Colors"),  pdf_new_number(colors));
  pdf_add_dict(parms, pdf_new_name("Columns"), pdf_new_number(columns));
  pdf_add_dict(parms, pdf_new_name("Predictor"), pdf_new_number(predictor));

  return  parms;
}

static void
write_stream (pdf_out *p, pdf_stream *stream)
{
  unsigned char *filtered;
  size_t         filtered_length;
#ifdef HAVE_ZLIB
  uLong          buffer_length;
#else
  size_t         buffer_length;
#endif
  unsigned char *buffer;

  ASSERT(p);

  /*
   * Always work from a copy of the stream. All filters read from
   * "filtered" and leave their result in "filtered".
   */
#if 0
  filtered = NEW(stream->stream_length + 1, unsigned char);
#endif
  filtered = NEW(stream->stream_length, unsigned char);
  memcpy(filtered, stream->stream, stream->stream_length);
  filtered_length = stream->stream_length;

  /* PDF/A requires Metadata to be not filtered. */
  {
    pdf_obj *type;
    type = pdf_lookup_dict(stream->dict, "Type");
    if (type && !strcmp("Metadata", pdf_name_value(type))) {
      stream->_flags &= ~STREAM_COMPRESS;
    }
  }

#ifdef HAVE_ZLIB
  /* Apply compression filter if requested */
  if (stream->stream_length > 0 &&
      (stream->_flags & STREAM_COMPRESS) &&
      p->options.compression.level > 0) {
    pdf_obj *filters;

    /* First apply predictor filter if requested. */
    if ( p->options.compression.use_predictor &&
        (stream->_flags & STREAM_USE_PREDICTOR) &&
        !pdf_lookup_dict(stream->dict, "DecodeParms")) {
      int      bits_per_pixel  = stream->decodeparms.colors *
                                   stream->decodeparms.bits_per_component;
      int32_t  len  = (stream->decodeparms.columns * bits_per_pixel + 7) / 8;
      int32_t  rows = stream->stream_length / len;
      unsigned char *filtered2 = NULL;
      int32_t        length2 = stream->stream_length;
      pdf_obj       *parms;

      parms = filter_create_predictor_dict(stream->decodeparms.predictor,
                                        stream->decodeparms.columns,
                                        stream->decodeparms.bits_per_component,
                                        stream->decodeparms.colors);

      switch (stream->decodeparms.predictor) {
      case 2: /* TIFF2 */
        filtered2 = filter_TIFF2_apply_filter(filtered,
                                         stream->decodeparms.columns,
                                         rows,
                                         stream->decodeparms.bits_per_component,
                                         stream->decodeparms.colors, &length2);
        break;
      case 15: /* PNG optimun */
        filtered2 = filter_PNG15_apply_filter(filtered,
                                         stream->decodeparms.columns,
                                         rows,
                                         stream->decodeparms.bits_per_component,
                                         stream->decodeparms.colors, &length2);
        break;
      default:
        WARN("Unknown/unsupported Predictor function %d.",
             stream->decodeparms.predictor);
        break;
      }
      if (parms && filtered2) {
        RELEASE(filtered);
        filtered = filtered2;
        filtered_length = length2;
        pdf_add_dict(stream->dict, pdf_new_name("DecodeParms"), parms);
      }
    }

    filters = pdf_lookup_dict(stream->dict, "Filter");

    buffer_length = filtered_length + filtered_length/1000 + 14;
    buffer = NEW(buffer_length, unsigned char);
    {
      pdf_obj *filter_name = pdf_new_name("FlateDecode");

      if (filters)
        /*
         * FlateDecode is the first filter to be applied to the stream.
         */
        pdf_unshift_array(filters, filter_name);
      else
        /*
         * Adding the filter as a name instead of a one-element array
         * is crucial because otherwise Adobe Reader cannot read the
         * cross-reference stream any more, cf. the PDF v1.5 Errata.
         */
        pdf_add_dict(stream->dict, pdf_new_name("Filter"), filter_name);
    }
#ifdef HAVE_ZLIB_COMPRESS2    
    if (compress2(buffer, &buffer_length, filtered,
        filtered_length, p->options.compression.level)) {
      ERROR("Zlib error");
    }
#else 
    if (compress(buffer, &buffer_length, filtered,
        filtered_length)) {
      ERROR ("Zlib error");
    }
#endif /* HAVE_ZLIB_COMPRESS2 */
    RELEASE(filtered);
    p->output.compression_saved +=
      filtered_length - buffer_length
        - (filters ? strlen("/FlateDecode "): strlen("/Filter/FlateDecode\n"));

    filtered        = buffer;
    filtered_length = buffer_length;
  }
#endif /* HAVE_ZLIB */

  /* AES will change the size of data! */
  if (p->state.enc_mode) {
    unsigned char *cipher = NULL;
    size_t         cipher_len = 0;
    pdf_encrypt_data(p->sec_data, filtered, filtered_length, &cipher, &cipher_len);
    RELEASE(filtered);
    filtered        = cipher;
    filtered_length = cipher_len;
  }

  #if 0
  /*
   * An optional end-of-line marker preceding the "endstream" is
   * not part of stream data. See, PDF Reference 4th ed., p. 38.
   */
  /* Add a '\n' if the last character wasn't one */
  if (filtered_length > 0 &&
      filtered[filtered_length-1] != '\n') {
    filtered[filtered_length] = '\n';
    filtered_length++;
  }
#endif

  pdf_add_dict(stream->dict,
	       pdf_new_name("Length"), pdf_new_number(filtered_length));

  pdf_write_obj(p, stream->dict);

  pdf_out_str(p, "\nstream\n", 8);

  if (filtered_length > 0)
    pdf_out_str(p, filtered, filtered_length);
  RELEASE(filtered);

  /*
   * This stream length "object" gets reset every time write_stream is
   * called for the stream object.
   * If this stream gets written more than once with different
   * filters, this could be a problem.
   */

  pdf_out_str(p, "\n", 1);
  pdf_out_str(p, "endstream", 9);
}

static void
release_stream (pdf_stream *stream)
{
  pdf_release_obj(stream->dict);
  stream->dict = NULL;

  if (stream->stream) {
    RELEASE(stream->stream);
    stream->stream = NULL;
  }

  if (stream->objstm_data) {
    RELEASE(stream->objstm_data);
    stream->objstm_data = NULL;
  }

  RELEASE(stream);
}

pdf_obj *
pdf_stream_dict (pdf_obj *stream)
{
  pdf_stream *data;

  TYPECHECK(stream, PDF_STREAM);

  data = stream->data;

  return data->dict;
}

const void *
pdf_stream_dataptr (pdf_obj *stream)
{
  pdf_stream *data;

  TYPECHECK(stream, PDF_STREAM);

  data = stream->data;

  return (const void *) data->stream;
}

int
pdf_stream_length (pdf_obj *stream)
{
  pdf_stream *data;

  TYPECHECK(stream, PDF_STREAM);

  data = stream->data;

  return (int) data->stream_length;
}

static void
set_objstm_data (pdf_obj *objstm, int *data) {
  TYPECHECK(objstm, PDF_STREAM);

  ((pdf_stream *) objstm->data)->objstm_data = data;
}

static int *
get_objstm_data (pdf_obj *objstm) {
  TYPECHECK(objstm, PDF_STREAM);

  return ((pdf_stream *) objstm->data)->objstm_data;
}

void
pdf_add_stream (pdf_obj *stream, const void *stream_data, int length)
{
  pdf_stream *data;

  TYPECHECK(stream, PDF_STREAM);

  if (length < 1)
    return;
  data = stream->data;
  if (data->stream_length + length > data->max_length) {
    data->max_length += length + STREAM_ALLOC_SIZE;
    data->stream      = RENEW(data->stream, data->max_length, unsigned char);
  }
  memcpy(data->stream + data->stream_length, stream_data, length);
  data->stream_length += length;
}

#if HAVE_ZLIB
#define WBUF_SIZE 4096

static int
filter_get_DecodeParms_FlateDecode (struct decode_parms *parms, pdf_obj *dict)
{
  pdf_obj *tmp;

  ASSERT(dict && parms);
  ASSERT(PDF_OBJ_DICTTYPE(dict));

  /* Fill with default values */
  parms->predictor = 1;
  parms->colors    = 1;
  parms->bits_per_component = 8;
  parms->columns   = 1;

  tmp = pdf_deref_obj(pdf_lookup_dict(dict, "Predictor"));
  if (tmp) {
    parms->predictor = pdf_number_value(tmp);
    pdf_release_obj(tmp);
  }
  tmp = pdf_deref_obj(pdf_lookup_dict(dict, "Colors"));
  if (tmp) {
    parms->colors = pdf_number_value(tmp);
    pdf_release_obj(tmp);
  }
  tmp = pdf_deref_obj(pdf_lookup_dict(dict, "BitsPerComponent"));
  if (tmp) {
    parms->bits_per_component = pdf_number_value(tmp);
    pdf_release_obj(tmp);
  }
  tmp = pdf_deref_obj(pdf_lookup_dict(dict, "Columns"));
  if (tmp) {
    parms->columns = pdf_number_value(tmp);
    pdf_release_obj(tmp);
  }
  if (parms->bits_per_component != 1 &&
      parms->bits_per_component != 2 &&
      parms->bits_per_component != 4 &&
      parms->bits_per_component != 8 &&
      parms->bits_per_component != 16) {
      WARN("Invalid BPC value in DecodeParms: %d", parms->bits_per_component);
      return -1;
  } else if (parms->predictor <= 0 || parms->colors <= 0 ||
             parms->columns <= 0)
    return -1;
  return 0;
}

/* From Xpdf version 3.04
 * I'm not sure if I properly ported... Untested.
 */
static int
filter_row_TIFF2 (unsigned char *dst, const unsigned char *src,
                  struct decode_parms *parms)
{
  const unsigned char *p = src;
  unsigned char *col;
  /* bits_per_component < 8 here */
  int  mask = (1 << parms->bits_per_component) - 1;
  int  inbuf, outbuf; /* 2 bytes buffer */
  int  i, ci, j, k, inbits, outbits;

  col = NEW(parms->colors, unsigned char);
  memset(col, 0, parms->colors);
  inbuf = outbuf = 0; inbits = outbits = 0;
  j = k = 0;
  for (i = 0; i < parms->columns; i++) {
    /* expanding each color component into an 8-bits bytes array */
    for (ci = 0; ci < parms->colors; ci++) {
      if (inbits < parms->bits_per_component) {
         /* need more byte */
         inbuf   = (inbuf << 8) | p[j++];
         inbits += 8;
      }
      /* predict current color component */
      col[ci]  = (unsigned char) ((col[ci] +
                 (inbuf >> (inbits - parms->bits_per_component))) & mask);
      inbits  -= parms->bits_per_component; /* consumed bpc bits */
      /* append newly predicted color component value */
      outbuf   = (outbuf << parms->bits_per_component) | col[ci];
      outbits += parms->bits_per_component;
      if (outbits >= 8) { /* flush */
        dst[k++] = (unsigned char) (outbuf >> (outbits - 8));
        outbits -= 8;
      }
    }
  }
  if (outbits > 0) {
    dst[k] = (unsigned char) (outbuf << (8 - outbits));
  }
  RELEASE(col);

  return 0;
}

/* This routine is inefficient. Length is typically 4 for Xref streams.
 * Especially, calling pdf_add_stream() for each 4 bytes append is highly
 * inefficient.
 */
static pdf_obj *
filter_stream_decode_Predictor (const void *src, size_t srclen, struct decode_parms *parms)
{
  pdf_obj             *dst;
  const unsigned char *p = (const unsigned char *) src;
  const unsigned char *endptr = p + srclen;
  unsigned char       *prev, *buf;
  int                  bits_per_pixel  = parms->colors * parms->bits_per_component;
  int                  bytes_per_pixel = (bits_per_pixel + 7) / 8;
  int                  length = (parms->columns * bits_per_pixel + 7) / 8;
  int                  i, error = 0;

  dst  = pdf_new_stream(0);

  prev = NEW(length, unsigned char);
  buf  = NEW(length, unsigned char);

  memset(prev, 0, length);
  switch (parms->predictor) {
  case 1 : /* No prediction */
    pdf_add_stream(dst, src, srclen); /* Just copy */
    break;
  case 2: /* TIFF Predictor 2 */
    {
      if (parms->bits_per_component == 8) {
        while (p + length < endptr) {
          /* Same as PNG Sub */
          for (i = 0; i < length; i++) {
            int pv = i - bytes_per_pixel >= 0 ? buf[i - bytes_per_pixel] : 0;
            buf[i] = (unsigned char)(((int) p[i] + pv) & 0xff);
          }
          pdf_add_stream(dst, buf, length);
          p += length;
        }
      } else if (parms->bits_per_component == 16) {
        while (p + length < endptr) {
          for (i = 0; i < length; i += 2) {
            int  b  = i - bytes_per_pixel;
            char hi = b >= 0 ? buf[b] : 0;
            char lo = b >= 0 ? buf[b + 1] : 0;
            int  pv = (hi << 8) | lo;
            int  cv = (p[i] << 8) | p[i + 1];
            int  c  = pv + cv;
            buf[i]     = (unsigned char) (c >> 8);
            buf[i + 1] = (unsigned char) (c & 0xff);
          }
          pdf_add_stream(dst, buf, length);
          p += length;
        }
      } else { /* bits per component 1, 2, 4 */
        while (!error && p + length < endptr) {
          error = filter_row_TIFF2(buf, p, parms);
          if (!error) {
            pdf_add_stream(dst, buf, length);
            p += length;
          }
        }
      }
    }
    break;
  /* PNG predictors: first byte of each rows is predictor type */
  case 10: /* PNG None */
  case 11: /* PNG Sub on all rows */
  case 12: /* PNG UP on all rows */
  case 13: /* PNG Average on all rows */
  case 14: /* PNG Paeth on all rows */
  case 15: /* PNG optimun: prediction algorithm can change from line to line. */
    {
      int type = parms->predictor - 10;

      while (!error && p + length < endptr) {
        if (parms->predictor == 15)
          type = *p;
        else if (*p != type) {
          WARN("Mismatched Predictor type in data stream.");
          error = -1;
        }
        p++;
        switch (type) {
        case 0: /* Do nothing just skip first byte */
          memcpy(buf, p, length);
          break;
        case 1:
          for (i = 0; i < length; i++) {
            int pv = i - bytes_per_pixel >= 0 ? buf[i - bytes_per_pixel] : 0;
            buf[i] = (unsigned char)(((int) p[i] + pv) & 0xff);
          }
          break;
        case 2:
          for (i = 0; i < length; i++) {
            buf[i] = (unsigned char)(((int) p[i] + (int) prev[i]) & 0xff);
          }
          break;
        case 3:
          for (i = 0; i < length; i++) {
            int up   = prev[i];
            int left = i - bytes_per_pixel >= 0 ? buf[i - bytes_per_pixel] : 0;
            int tmp  = floor((up + left) / 2);
            buf[i] = (unsigned char)((p[i] + tmp) & 0xff);
          }
          break;
        case 4:
          for (i = 0; i < length; i++) {
            int a = i - bytes_per_pixel >= 0 ? buf[i - bytes_per_pixel] : 0; /* left */
            int b = prev[i]; /* above */
            int c = i - bytes_per_pixel >= 0 ? prev[i - bytes_per_pixel] : 0; /* upper left */
            int q = a + b - c;
            int qa = q - a, qb = q - b, qc = q - c;
            qa = qa < 0 ? -qa : qa;
            qb = qb < 0 ? -qb : qb;
            qc = qc < 0 ? -qc : qc;
            if (qa <= qb && qa <= qc)
              buf[i] = (unsigned char) (((int) p[i] + a) & 0xff);
            else if (qb <= qc)
              buf[i] = (unsigned char) (((int) p[i] + b) & 0xff);
            else
              buf[i] = (unsigned char) (((int) p[i] + c) & 0xff);
          }
          break;
        default:
          WARN("Unknown PNG predictor type: %d", type);
          error = -1;
        }
        if (!error) {
          pdf_add_stream(dst, buf, length); /* highly inefficient */
          memcpy(prev, buf, length);
          p += length;
        }
      }
    }
    break;
  default:
    WARN("Unknown Predictor type value :%d", parms->predictor);
    error = -1;
  }

  RELEASE(prev);
  RELEASE(buf);

  if (error) {
    pdf_release_obj(dst);
    dst = NULL;
  }

  return dst;
}

static pdf_obj *
filter_stream_decode_FlateDecode (const void *data, size_t len, struct decode_parms *parms)
{
  pdf_obj  *dst;
  pdf_obj  *tmp;
  z_stream  z;
  Bytef     wbuf[WBUF_SIZE];

  z.zalloc = Z_NULL; z.zfree = Z_NULL; z.opaque = Z_NULL;

  z.next_in  = (z_const Bytef *) data; z.avail_in  = len;
  z.next_out = (Bytef *) wbuf; z.avail_out = WBUF_SIZE;

  if (inflateInit(&z) != Z_OK) {
    WARN("inflateInit() failed.");
    return NULL;
  }

  tmp = pdf_new_stream(0);
  for (;;) {
    int status;
    status = inflate(&z, Z_NO_FLUSH);
    if (status == Z_STREAM_END) {
      break;
    } else if (status == Z_DATA_ERROR && z.avail_in == 0) {
      WARN("Ignoring zlib error: status=%d, message=\"%s\"", status, z.msg);
      break;
    } else if (status != Z_OK) {
      WARN("inflate() failed. Broken PDF file?");
      inflateEnd(&z);
      pdf_release_obj(tmp);
      return NULL;
    }

    if (z.avail_out == 0) {
      pdf_add_stream(tmp, wbuf, WBUF_SIZE);
      z.next_out  = wbuf;
      z.avail_out = WBUF_SIZE;
    }
  }

  if (WBUF_SIZE - z.avail_out > 0)
    pdf_add_stream(tmp, wbuf, WBUF_SIZE - z.avail_out);

  if (inflateEnd(&z) == Z_OK) {
    if (parms) { 
      dst = filter_stream_decode_Predictor(pdf_stream_dataptr(tmp), pdf_stream_length(tmp), parms);
    } else {
      dst = pdf_link_obj(tmp);
    }
  } else {
    dst = NULL;
  }
  pdf_release_obj(tmp);

  return dst;
}
#endif

static pdf_obj *
filter_stream_decode_ASCIIHexDecode (const void *data, size_t len)
{
  pdf_obj       *dst;
  int            eod, error;
  const char    *p = (const char *) data;
  const char    *endptr = p + len;
  unsigned char *buf, ch;
  size_t         pos, n;

  buf = NEW((len+1)/2, unsigned char);
  skip_white(&p, endptr);
  ch = 0; n = 0; pos = 0; eod = 0; error = 0;
  while (p < endptr && !error && !eod) {
    char c1, val;
    c1 = p[0];
    if (c1 >= 'A' && c1 <= 'F') {
      val = c1 - 'A' + 10;
    } else if (c1 >= 'a' && c1 <= 'f') {
      val = c1 - 'a' + 10;
    } else if (c1 >= '0' && c1 <= '9') {
      val = c1 - '0';
    } else if (c1 == '>') {
      val = 0;
      eod = 1;
      if ((pos % 2) == 0)
        break;
    } else {
      error = -1;
      break;
    }
    if (pos % 2) {
      buf[n] = ch + val;
      n++;
      ch = 0;
    } else {
      ch = val << 4;
    }
    pos++; p++;
    skip_white(&p, endptr);
  }
  if (error || !eod) {
    WARN("Invalid ASCIIHex data seen: %s", error ? "Invalid character" : "No EOD marker");
    dst = NULL;
  } else {
    dst = pdf_new_stream(0);
    pdf_add_stream(dst, buf, n);
  }
  RELEASE(buf);

  return dst;
}

/* Percent sign is not start of comment here.
 * We need this for reading Ascii85 encoded data.
 */
#define is_space(c) ((c) == ' '  || (c) == '\t' || (c) == '\f' || \
		     (c) == '\r' || (c) == '\n' || (c) == '\0')
static void
skip_white_a85 (const char **p, const char *endptr)
{
  while (*p < endptr && (is_space(**p))) {
    (*p)++;
  } 
}

static pdf_obj *
filter_stream_decode_ASCII85Decode (const void *data, size_t len)
{
  pdf_obj       *dst;
  int            eod, error;
  const char    *p = (const char *) data;
  const char    *endptr = p + len;
  unsigned char *buf;
  size_t         n;

  buf = NEW(((len+4)/5)*4, unsigned char);
  skip_white_a85(&p, endptr);
  n = 0; eod = 0; error = 0;
  while (p < endptr && !error && !eod) {
    char q[5] = {'u', 'u', 'u', 'u', 'u'};
    int  m;
    char ch;

    ch = p[0];
    p++;
    skip_white_a85(&p, endptr);
    if (ch == 'z') {
      memset(buf+n, 0, 4);
      n += 4;
      continue;
    } else if (ch == '~') {
      if (p < endptr && p[0] == '>') {
        eod = 1;
        p++;
      } else {
        error = -1;
      }
      break;
    }
    q[0] = ch;
    for (m = 1; m < 5 && p < endptr; m++) {
      ch = p[0];
      p++;
      skip_white_a85(&p, endptr);
      if (ch == '~') {
        if (p < endptr && p[0] == '>') {
          eod = 1;
          p++;
        } else {
          error = -1;
        }
        break;
      } else if (ch < '!' || ch > 'u') {
        error = -1;
        break;
      } else {
        q[m] = ch;
      }
    }
    if (!error) {
      uint32_t val = 0;
      int      i;
      if (m <= 1) {
        error = -1;
        break;
      }
      val = 85*85*85*(q[0] - '!') + 85*85*(q[1] - '!')
              + 85*(q[2] - '!') + (q[3] - '!');
      /* Check overflow */
      if (val > UINT32_MAX / 85) {
        error = -1;
        break;
      } else {
        val = 85 * val;
        if (val > UINT32_MAX - (q[4] - '!')) {
          error = -1;
          break;
        }
        val += (q[4] - '!');
      }
      if (!error) {
        for (i = 3; i >= 0; i--) {
          buf[n + i] = val & 0xff;
          val /= 256;
        }
        n += m - 1;
      }
    }
  }

  if (error) {
    WARN("Error in reading ASCII85 data.");
    dst = NULL;
  } else if (!eod) {
    WARN("Error in reading ASCII85 data: No EOD");
    dst = NULL;
  } else {
    dst = pdf_new_stream(0);
    pdf_add_stream(dst, buf, n);
  }
  RELEASE(buf);

  return dst;
}

static pdf_obj *
filter_stream_decode (const char *filter_name, pdf_obj *src, pdf_obj *parm)
{
  pdf_obj    *dec;
  const char *stream_data;
  size_t      stream_length;

  if (!filter_name)
    return pdf_link_obj(src);

  stream_data   = pdf_stream_dataptr(src);
  stream_length = pdf_stream_length(src);

  if (!strcmp(filter_name, "ASCIIHexDecode")) {
    dec = filter_stream_decode_ASCIIHexDecode(stream_data, stream_length);
  } else if (!strcmp(filter_name, "ASCII85Decode")) {
    dec = filter_stream_decode_ASCII85Decode(stream_data, stream_length);
#if HAVE_ZLIB
  } else if (!strcmp(filter_name, "FlateDecode")) {
    struct decode_parms decode_parm;
    if (parm)
      filter_get_DecodeParms_FlateDecode(&decode_parm, parm);
    dec = filter_stream_decode_FlateDecode(stream_data, stream_length, parm ? &decode_parm : NULL);
#endif /* HAVE_ZLIB */
  } else {
    WARN("DecodeFilter \"%s\" not supported.", filter_name);
    dec = NULL;
  }

  return dec;
}

int
pdf_concat_stream (pdf_obj *dst, pdf_obj *src)
{
  pdf_obj *filtered;
  pdf_obj *stream_dict;
  pdf_obj *filter, *parms;
  int      error = 0;

  if (!PDF_OBJ_STREAMTYPE(dst) || !PDF_OBJ_STREAMTYPE(src)) {
    WARN("Passed invalid type in pdf_concat_stream().");
    return -1;
  }

  stream_dict = pdf_stream_dict(src);

  filter = pdf_lookup_dict(stream_dict, "Filter");
  if (!filter) {
    pdf_add_stream(dst, pdf_stream_dataptr(src), pdf_stream_length(src));
    return 0;
  }
  if (pdf_lookup_dict(stream_dict, "DecodeParms")) {
    /* Dictionary or array */
    parms = pdf_deref_obj(pdf_lookup_dict(stream_dict, "DecodeParms"));
    if (!parms) {
      WARN("Failed to deref DeocdeParms...");
      return -1;
    } else if (!PDF_OBJ_ARRAYTYPE(parms) && !PDF_OBJ_DICTTYPE(parms)) {
      WARN("PDF dict or array expected for DecodeParms...");
      pdf_release_obj(parms);
      return -1;
    }
  } else {
    parms = NULL;
  }
  if (PDF_OBJ_ARRAYTYPE(filter)) {
    int      i, num;
    pdf_obj *prev = NULL;

    num = pdf_array_length(filter);
    if (parms) {
      if (!PDF_OBJ_ARRAYTYPE(parms) || pdf_array_length(parms) != num) {
        WARN("Invalid DecodeParam object found.");
        pdf_release_obj(parms);
        return -1;
      }
    }
    if (num == 0) {
      filtered = pdf_link_obj(src);
    } else {
      filtered = NULL;
      prev = pdf_link_obj(src);
      for (i = 0; i < num && prev != NULL; i++) {
        pdf_obj *tmp1, *tmp2;

        tmp1 = pdf_deref_obj(pdf_get_array(filter, i));
        if (parms) {
          tmp2 = pdf_deref_obj(pdf_get_array(parms, i));
        } else {
          tmp2 = NULL;
        }
        if (PDF_OBJ_NAMETYPE(tmp1)) {
          filtered = filter_stream_decode(pdf_name_value(tmp1), prev, tmp2);
        } else if (PDF_OBJ_NULLTYPE(tmp1)) {
          filtered = pdf_link_obj(prev);
        } else {
          WARN("Unexpected object found for /Filter...");
          filtered = NULL;
        }
        if (prev)
          pdf_release_obj(prev);
        if (tmp1)
          pdf_release_obj(tmp1);
        if (tmp2)
          pdf_release_obj(tmp2);
        prev = filtered;      
      }
    }
  } else if (PDF_OBJ_NAMETYPE(filter)) {
    filtered = filter_stream_decode(pdf_name_value(filter), src, parms);
  } else {
    WARN("Invalid value for /Filter found.");
    filtered = NULL;
  }
  if (parms)
    pdf_release_obj(parms);
  if (filtered) {
    pdf_add_stream(dst, pdf_stream_dataptr(filtered), pdf_stream_length(filtered));
    pdf_release_obj(filtered);
    error = 0;
  } else {
    error = -1;
  }

  return error;
}

static pdf_obj *
pdf_stream_uncompress (pdf_obj *src) {
  pdf_obj *dst = pdf_new_stream(0);

  TYPECHECK(src, PDF_STREAM);

  pdf_merge_dict(pdf_stream_dict(dst), pdf_stream_dict(src));
  pdf_remove_dict(pdf_stream_dict(dst), "Length");
  pdf_concat_stream(dst, src);

  return dst;
}

#if 0
void
pdf_stream_set_flags (pdf_obj *stream, int flags)
{
  pdf_stream *data;

  TYPECHECK(stream, PDF_STREAM);

  data = stream->data;
  data->_flags = flags;
}

int
pdf_stream_get_flags (pdf_obj *stream)
{
  pdf_stream *data;

  TYPECHECK(stream, PDF_STREAM);

  data = stream->data;

  return data->_flags;
}
#endif

static void
pdf_write_obj (pdf_out *p, pdf_obj *object)
{
  ASSERT(p);

  if (object == NULL) {
    write_null(p);
    return;
  }

  if (INVALIDOBJ(object) || PDF_OBJ_UNDEFINED(object))
    ERROR("pdf_write_obj: Invalid object, type = %d\n", object->type);

  if (error_out)
    fprintf(stderr, "{%d}", object->refcount);

  switch (object->type) {
  case PDF_BOOLEAN:
    write_boolean(p, object->data);
    break;
  case PDF_NUMBER:
    write_number (p, object->data);
    break;
  case PDF_STRING:
    write_string (p, object->data);
    break;
  case PDF_NAME:
    write_name(p, object->data);
    break;
  case PDF_ARRAY:
    write_array(p, object->data);
    break;
  case PDF_DICT:
    write_dict (p, object->data);
    break;
  case PDF_STREAM:
    write_stream(p, object->data);
    break;
  case PDF_NULL:
    write_null(p);
    break;
  case PDF_INDIRECT:
    write_indirect(p, object->data);
    break;
  }
}

/* Write the object to the file */ 
static void
pdf_flush_obj (pdf_out *p, pdf_obj *object)
{
  size_t length;
  char   buf[64];

  /*
   * Record file position
   */
  add_xref_entry(p, object->label, 1,
                 p->output.file_position, object->generation);
  length = sprintf(buf, "%u %hu obj\n", object->label, object->generation);
  p->state.enc_mode =
    (p->options.enable_encrypt && !(object->flags & OBJ_NO_ENCRYPT)) ? 1 : 0;
  if (p->state.enc_mode) {
    pdf_enc_set_label(p->sec_data, object->label);
    pdf_enc_set_generation(p->sec_data, object->generation);
  }
  pdf_out_str(p, buf, length);
  pdf_write_obj(p, object);
  pdf_out_str(p, "\nendobj\n", 8);
}

static int
pdf_add_objstm (pdf_out *p, pdf_obj *objstm, pdf_obj *object)
{
  int *data, pos;

  TYPECHECK(objstm, PDF_STREAM);
  ASSERT(p);

  data = get_objstm_data(objstm);
  pos = ++data[0];

  data[2*pos]   = object->label;
  data[2*pos+1] = pdf_stream_length(objstm);

  add_xref_entry(p, object->label, 2, objstm->label, pos-1);
 
  /* redirect output into objstm */
  p->output_stream  = objstm;
  p->state.enc_mode = 0;
  pdf_write_obj(p, object);
  pdf_out_char(p, '\n');
  p->output_stream = NULL;

  return pos;
}

static void
release_objstm (pdf_obj *objstm)
{
  int *data = get_objstm_data(objstm);
  int pos = data[0];
  pdf_obj *dict;
  pdf_stream *stream;
  unsigned char *old_buf;
  size_t         old_length;
  stream = (pdf_stream *) objstm->data;

  /* Precede stream data by offset table */
  old_buf = stream->stream;
  old_length = stream->stream_length;
  /* Reserve 22 bytes for each entry (two 10 digit numbers plus two spaces) */
  stream->stream = NEW(old_length + 22*pos, unsigned char);
  stream->stream_length = 0;

  {
    int i = 2*pos, *val = data+2;
    while (i--) {
      char buf[32];
      size_t length = sprintf(buf, "%d ", *(val++));
      pdf_add_stream(objstm, buf, length);
    }
  }

  dict = pdf_stream_dict(objstm);
  pdf_add_dict(dict, pdf_new_name("Type"), pdf_new_name("ObjStm"));
  pdf_add_dict(dict, pdf_new_name("N"), pdf_new_number(pos));
  pdf_add_dict(dict, pdf_new_name("First"), pdf_new_number(stream->stream_length));
  
  pdf_add_stream(objstm, old_buf, old_length);
  RELEASE(old_buf);
  pdf_release_obj(objstm);
}

#define is_free(b,c) (((b)[(c)/8]) & (1 << (7-((c)%8))))

void
pdf_release_obj (pdf_obj *object)
{
  pdf_out *p = current_output();

  if (object == NULL)
    return;
  if (INVALIDOBJ(object) || object->refcount <= 0) {
    MESG("\npdf_release_obj: object=%p, type=%d, refcount=%d\n",
         object, object->type, object->refcount);
    error_out = 1;
    pdf_write_obj(p, object);
    ERROR("pdf_release_obj:  Called with invalid object.");
    error_out = 0;
  }
  object->refcount -= 1;
  if (object->refcount == 0) {
#if defined(PDFOBJ_DEBUG)
  bucket[object->obj_id] = NULL;
#endif
    /*
     * Nothing is using this object so it's okay to remove it.
     * Nonzero "label" means object needs to be written before it's destroyed.
     */
    if (object->label) {
      p->free_list[object->label/8] |= (1 << (7-(object->label % 8)));
      if (p->output.file != NULL) {
        if (!p->options.use_objstm || object->flags & OBJ_NO_OBJSTM ||
            (p->options.enable_encrypt && (object->flags & OBJ_NO_ENCRYPT)) ||
            object->generation) {
          pdf_flush_obj(p, object);
        } else {
          if (!p->current_objstm) {
            int *data = NEW(2*OBJSTM_MAX_OBJS+2, int);
            data[0] = data[1] = 0;
            p->current_objstm = pdf_new_stream(STREAM_COMPRESS);
            set_objstm_data(p->current_objstm, data);
            pdf_label_obj(p, p->current_objstm);
          }
          if (pdf_add_objstm(p, p->current_objstm, object) == OBJSTM_MAX_OBJS) {
            release_objstm(p->current_objstm);
            p->current_objstm = NULL;
          }
        }
      }
    }
    switch (object->type) {
    case PDF_BOOLEAN:
      release_boolean(object->data);
      break;
    case PDF_NULL:
      break;
    case PDF_NUMBER:
      release_number(object->data);
      break;
    case PDF_STRING:
      release_string(object->data);
      break;
    case PDF_NAME:
      release_name(object->data);
      break;
    case PDF_ARRAY:
      release_array(object->data);
      break;
    case PDF_DICT:
      release_dict(object->data);
      break;
    case PDF_STREAM:
      release_stream(object->data);
      break;
    case PDF_INDIRECT:
      release_indirect(object->data);
      break;
    }
    /* This might help detect freeing already freed objects */
    object->type = -1;
    object->data = NULL;
    RELEASE(object);
  }
}

/* Reading external PDF files
 *
 */
static int
backup_line (FILE *pdf_input_file)
{
  int ch = -1;

  /*
   * Note: this code should work even if \r\n is eol. It could fail on a
   * machine where \n is eol and there is a \r in the stream --- Highly
   * unlikely in the last few bytes where this is likely to be used.
   */
  if (tell_position(pdf_input_file) > 1)
    do {
      seek_relative (pdf_input_file, -2);
    } while (tell_position(pdf_input_file) > 0 &&
	     (ch = fgetc(pdf_input_file)) >= 0 &&
	     (ch != '\n' && ch != '\r' ));
  if (ch < 0) {
    return 0;
  }

  return 1;
}

static int
find_xref (FILE *pdf_input_file)
{
  size_t  len, xref_pos = 0;
  int     tries = 10;

  do {
    size_t currentpos;

    if (!backup_line(pdf_input_file)) {
      tries = 0;
      break;
    }
    currentpos = tell_position(pdf_input_file);
    fread(work_buffer, sizeof(char), strlen("startxref"), pdf_input_file);
    seek_absolute(pdf_input_file, currentpos);
    tries--;
  } while (tries > 0 &&
	   strncmp(work_buffer, "startxref", strlen("startxref")));
  if (tries <= 0)
    return 0;

  /* Skip rest of this line */
  mfgets(work_buffer, WORK_BUFFER_SIZE, pdf_input_file);
  /* Next line of input file should contain actual xref location */
  len = mfreadln(work_buffer, WORK_BUFFER_SIZE, pdf_input_file);
  if (len <= 0)
   WARN("Reading xref location data failed... Not a PDF file?");
  else {
    const char *start, *end;
    char *number;

    start = work_buffer;
    end   = start + len;
    skip_white(&start, end);
    number   = parse_number(&start, end);
    xref_pos = (int) atof(number);
    RELEASE(number);
  }

  return xref_pos;
}

/*
 * This routine must be called with the file pointer located
 * at the start of the trailer.
 */
static pdf_obj *
parse_trailer (pdf_file *pf)
{
  pdf_obj *result;
  /*
   * Fill work_buffer and hope trailer fits. This should
   * be made a bit more robust sometime.
   */
  if (fread(work_buffer, sizeof(char),
	    WORK_BUFFER_SIZE, pf->file) == 0 ||
      strncmp(work_buffer, "trailer", strlen("trailer"))) {
    WARN("No trailer.  Are you sure this is a PDF file?");
    WARN("buffer:\n->%s<-\n", work_buffer);
    result = NULL;
  } else {
    const char *p = work_buffer + strlen("trailer");
    skip_white(&p, work_buffer + WORK_BUFFER_SIZE);
    result = parse_pdf_dict(&p, work_buffer + WORK_BUFFER_SIZE, pf);
  }

  return result;
}

/*
 * This routine tries to estimate an upper bound for character position
 * of the end of the object, so it knows how big the buffer must be.
 * The parsing routines require that the entire object be read into
 * memory. It would be a major pain to rewrite them.  The worst case
 * is that an object before an xref table will grab the whole table
 * :-(
 */
static int
next_object_offset (pdf_file *pf, uint32_t obj_num)
{
  uint32_t next = pf->file_size;  /* Worst case */
  size_t   i;
  uint32_t curr;

  curr = pf->xref_table[obj_num].field2;
  /* Check all other type 1 objects to find next one */
  for (i = 0; i < pf->num_obj; i++) {
    if (pf->xref_table[i].type == 1 &&
        pf->xref_table[i].field2 > curr &&
        pf->xref_table[i].field2 < next)
      next = pf->xref_table[i].field2;
  }

  return  next;
}

#define checklabel(pf, n, g) ((n) > 0 && (n) < (pf)->num_obj && ( \
  ((pf)->xref_table[(n)].type == 1 && (pf)->xref_table[(n)].field3 == (g)) || \
  ((pf)->xref_table[(n)].type == 2 && !(g))))

pdf_obj *
pdf_new_indirect (pdf_file *pf, uint32_t obj_num, uint16_t obj_gen)
{
  pdf_obj      *result;
  pdf_indirect *indirect;

  indirect = NEW(1, pdf_indirect);
  indirect->pf         = pf;
  indirect->obj        = NULL;
  indirect->label      = obj_num;
  indirect->generation = obj_gen;

  result   = pdf_new_obj(PDF_INDIRECT);
  result->data = indirect;

  return result;
}

static pdf_obj *
pdf_read_object (uint32_t obj_num, uint16_t obj_gen, pdf_file *pf, size_t offset, size_t limit)
{
  pdf_obj    *result = NULL; 
  size_t      length;
  char       *buffer;
  const char *p, *endptr;

  length = limit - offset;

  if (length <= 0)
    return NULL;

  buffer = NEW(length + 1, char);

  seek_absolute(pf->file, offset);
  fread(buffer, sizeof(char), length, pf->file);

  p      = buffer;
  endptr = p + length;

  /* Check for obj_num and obj_gen */
  {
    const char   *q = p; /* <== p */
    char         *sp;
    uint32_t      n, g;

    skip_white(&q, endptr);
    sp = parse_unsigned(&q, endptr);
    if (!sp) {
      RELEASE(buffer);
      return NULL;
    }
    n = strtoul(sp, NULL, 10);
    RELEASE(sp);

    skip_white(&q, endptr);
    sp = parse_unsigned(&q, endptr);
    if (!sp) {
      RELEASE(buffer);
      return NULL;
    }
    g = strtoul(sp, NULL, 10);
    RELEASE(sp);

    if (obj_num && (n != obj_num || g != obj_gen)) {
      RELEASE(buffer);
      return NULL;
    }

    p = q; /* ==> p */
  }


  skip_white(&p, endptr);
  if (memcmp(p, "obj", strlen("obj"))) {
    WARN("Didn't find \"obj\".");
    RELEASE(buffer);
    return NULL;
  }
  p += strlen("obj");

  result = parse_pdf_object(&p, endptr, pf);

  skip_white(&p, endptr);
  if (memcmp(p, "endobj", strlen("endobj"))) {
    WARN("Didn't find \"endobj\".");
    if (result)
      pdf_release_obj(result);
    result = NULL;
  }
  RELEASE(buffer);

  return result;
}

static pdf_obj *
read_objstm (pdf_file *pf, uint32_t num)
{
  size_t      offset = pf->xref_table[num].field2;
  uint16_t    gen    = pf->xref_table[num].field3;
  size_t      limit  = next_object_offset(pf, num);
  int         n, first, *header = NULL;
  char       *data = NULL, *q;
  const char *p, *endptr;
  int         i;
  pdf_obj    *objstm, *dict, *type, *n_obj, *first_obj;

  objstm = pdf_read_object(num, gen, pf, offset, limit);

  if (!PDF_OBJ_STREAMTYPE(objstm))
    goto error;

  {
    pdf_obj *tmp = pdf_stream_uncompress(objstm);
    if (!tmp)
      goto error;
    pdf_release_obj(objstm);
    objstm = tmp;
  }

  dict = pdf_stream_dict(objstm);

  type = pdf_lookup_dict(dict, "Type");
  if (!PDF_OBJ_NAMETYPE(type) ||
      strcmp(pdf_name_value(type), "ObjStm"))
    goto error;

  n_obj = pdf_lookup_dict(dict, "N");
  if (!PDF_OBJ_NUMBERTYPE(n_obj))
    goto error;
  n = (int) pdf_number_value(n_obj);

  first_obj = pdf_lookup_dict(dict, "First");
  if (!PDF_OBJ_NUMBERTYPE(first_obj))
    goto error;
  first = (int) pdf_number_value(first_obj);
  /* reject object streams without object data */
  if (first >= pdf_stream_length(objstm))
    goto error;

  header = NEW(2*(n+1), int);
  set_objstm_data(objstm, header);
  *(header++) = n;
  *(header++) = first;

  /* avoid parsing beyond offset table */
  data = NEW(first + 1, char);
  memcpy(data, pdf_stream_dataptr(objstm), first);
  data[first] = 0;

  p      = data;
  endptr = p + first;
  i = 2*n;
  while (i--) {
    *(header++) = strtoul(p, &q, 10);
    if (q == p)
      goto error;
    p = q;
  }

  /* Any garbage after last entry? */
  skip_white(&p, endptr);
  if (p != endptr)
    goto error;
  RELEASE(data);
  
  return pf->xref_table[num].direct = objstm;

 error:
  WARN("Cannot parse object stream.");
  if (data)
    RELEASE(data);
  if (objstm)
    pdf_release_obj(objstm);
  return NULL;
}

/* Label without corresponding object definition are replaced by the
 * null object, as required by the PDF spec. This is important to parse
 * several cross-reference sections.
 */ 
static pdf_obj *
pdf_get_object (pdf_file *pf, uint32_t obj_num, uint16_t obj_gen)
{
  pdf_obj *result;

  if (!checklabel(pf, obj_num, obj_gen)) {
    WARN("Trying to read nonexistent or deleted object: %lu %u",
         obj_num, obj_gen);
    return pdf_new_null();
  }

  if ((result = pf->xref_table[obj_num].direct)) {
    return pdf_link_obj(result);
  }

  if (pf->xref_table[obj_num].type == 1) {
    /* type == 1 */
    uint32_t offset;
    size_t   limit;
    offset = pf->xref_table[obj_num].field2;
    limit  = next_object_offset(pf, obj_num);
    result = pdf_read_object(obj_num, obj_gen, pf, offset, limit);
  } else {
    /* type == 2 */
    uint32_t    objstm_num = pf->xref_table[obj_num].field2;
    uint16_t    index = pf->xref_table[obj_num].field3;
    pdf_obj    *objstm;
    int        *data, n, first, length;
    const char *p, *q;

    if (objstm_num >= pf->num_obj ||
        pf->xref_table[objstm_num].type != 1 ||
        !((objstm = pf->xref_table[objstm_num].direct) ||
          (objstm = read_objstm(pf, objstm_num))))
      goto error;

    data  = get_objstm_data(objstm);
    n     = *(data++);
    first = *(data++);

    if (index >= n || data[2*index] != obj_num)
      goto error;

    length = pdf_stream_length(objstm);
    p = (const char *) pdf_stream_dataptr(objstm) + first + data[2*index+1];
    q = (const char *) pdf_stream_dataptr(objstm)
                       + (index == n-1 ? length : first+data[2*index+3]);
    result = parse_pdf_object(&p, q, pf);
    if (!result)
      goto error;
  }

  /* Make sure the caller doesn't free this object */
  pf->xref_table[obj_num].direct = pdf_link_obj(result);

  return result;

 error:
  WARN("Could not read object from object stream.");
  return pdf_new_null();
}

#define OBJ_FILE(o) (((pdf_indirect *)((o)->data))->pf)
#define OBJ_OBJ(o)  (((pdf_indirect *)((o)->data))->obj)
#define OBJ_NUM(o)  (((pdf_indirect *)((o)->data))->label)
#define OBJ_GEN(o)  (((pdf_indirect *)((o)->data))->generation)

static pdf_obj *
pdf_new_ref (pdf_out *p, pdf_obj *object)
{
  pdf_obj *result;

  ASSERT(p);

  if (object->label == 0) {
    pdf_label_obj(p, object);
  }
  result = pdf_new_indirect(NULL, object->label, object->generation);
  OBJ_OBJ(result) = object;
  return result;
}

/* pdf_deref_obj always returns a link instead of the original   */
/* It never return the null object, but the NULL pointer instead */
pdf_obj *
pdf_deref_obj (pdf_obj *obj)
{
  int count = PDF_OBJ_MAX_DEPTH;

  if (obj)
    obj = pdf_link_obj(obj);

  while (PDF_OBJ_INDIRECTTYPE(obj) && --count) {
    pdf_file *pf = OBJ_FILE(obj);
    if (pf) {
      uint32_t obj_num = OBJ_NUM(obj);
      uint16_t obj_gen = OBJ_GEN(obj);
      pdf_release_obj(obj);
      obj = pdf_get_object(pf, obj_num, obj_gen);
    } else {
      pdf_out      *p    = current_output();
      pdf_indirect *data = obj->data;

      if ((p->free_list[data->label/8] & (1 << (7-((data->label) % 8))))) {
        pdf_release_obj(obj);
        return NULL;
      } else {
        pdf_obj *next_obj = OBJ_OBJ(obj);
        if (!next_obj) {
          ERROR("Undefined object reference"); 
        }
        pdf_release_obj(obj);
        obj = pdf_link_obj(next_obj);
      }
    }
  }

  if (!count)
    ERROR("Loop in object hierarchy detected. Broken PDF file?");

  if (PDF_OBJ_NULLTYPE(obj)) {
    pdf_release_obj(obj);
    return NULL;
  } else
    return obj;
}

static void
extend_xref (pdf_file *pf, size_t new_size) 
{
  size_t  i;

  pf->xref_table = RENEW(pf->xref_table, new_size, xref_entry);
  for (i = pf->num_obj; i < new_size; i++) {
    pf->xref_table[i].direct   = NULL;
    pf->xref_table[i].indirect = NULL;
    pf->xref_table[i].type     = 0;
    pf->xref_table[i].field3 = 0;
    pf->xref_table[i].field2 = 0L;
  }
  pf->num_obj = new_size;
}

/* Returns < 0 for error, 1 for success, and 0 when xref stream found. */
static int
parse_xref_table (pdf_file *pf, size_t xref_pos)
{
  FILE       *pdf_input_file = pf->file;
  const char *p, *endptr;
  char        buf[256]; /* See, PDF ref. v.1.7, p.91 for "255+1" here. */
  size_t      len;

  /*
   * This routine reads one xref segment. It may be called multiple times
   * on the same file.  xref tables sometimes come in pieces.
   */
  seek_absolute(pf->file, xref_pos);
  len = mfreadln(buf, 255, pdf_input_file);
  /* We should have already checked that "startxref" section exists.
   * So, EOF here (len = -1) is impossible.  We don't treat too long line
   * case seriously.
   */
  if (len < 0) {
    WARN("Something went wrong while reading xref table...giving up.");
    return -1;
  }
  p      = buf;
  endptr = buf + len;
  /* No skip_white() here. There should not be any white-spaces here. */
  if (memcmp(p, "xref", strlen("xref"))) {
    /* Might be an xref stream and not an xref table */
    return 0;
  }
  p += strlen("xref");
  skip_white(&p, endptr);
  if (p != endptr) {
    WARN("Garbage after \"xref\" keyword found.");
    return -1;
  }

  /* Next line in file has first item and size of table */
  for (;;) {
    char      flag;
    size_t    current_pos, size, offset;
    int       i;
    uint32_t  first, obj_gen;

    current_pos = tell_position(pdf_input_file);
    len = mfreadln(buf, 255, pdf_input_file);
    if (len == 0) /* empty line... just skip. */
      continue;
    else if (len < 0) {
      WARN("Reading a line failed in xref table.");
      return -1;
    }

    p      = buf;
    endptr = buf + len;
    skip_white(&p, endptr);
    if (p == endptr) { /* Only white-spaces and/or comment found. */
      continue;
    } else if (!strncmp(p, "trailer", strlen ("trailer"))) {
      /*
       * Backup... This is ugly, but it seems like the safest thing to
       * do.  It is possible the trailer dictionary starts on the same
       * logical line as the word trailer.  In that case, the mfgets
       * call might have started to read the trailer dictionary and
       * parse_trailer would fail.
       */
      current_pos += p - buf; /* Jump to the beginning of "trailer" keyword. */
      seek_absolute(pdf_input_file, current_pos);
      break;
    }
    /* Line containing something other than white-space characters found.
     *
     * Start reading xref subsection
     *
     * This section just reads two nusigned integers, namely, the object number
     * of first object and the size of the xref subsection. PDF reference says
     * that only "a space" is allowed between those two numbers but we allow
     * more white-space characters.
     */
    {
      char *q;

      /* Object number of the first object whithin this xref subsection. */
      q = parse_unsigned(&p, endptr);
      if (!q) {
        WARN("An unsigned integer expected but could not find. (xref)");
        return -1;
      }
      first = atoi(q);
      RELEASE(q);
      skip_white(&p, endptr);

      /* Nnumber of objects in this xref subsection. */
      q = parse_unsigned(&p, endptr);
      if (!q) {
        WARN("An unsigned integer expected but could not find. (xref)");
        return -1;
      }
      size = atoi(q);
      RELEASE(q);
      skip_white(&p, endptr);

      /* Check for unrecognized tokens */
      if (p != endptr) {
        WARN("Unexpected token found in xref table.");
        return -1;
      }
    }

    /* The first line of a xref subsection OK. */
    if (pf->num_obj < first + size) {
      extend_xref(pf, first + size);
    }

    /* Start parsing xref subsection body... */
    for (i = first; i < first + size; ) {
     /* PDF spec. requires each xref subsection lines being exactly 20 bytes
      * long [including end-of-line marker(s)], offset 10 decimal digits,
      * generation number being 5 decimal digits, and each entries delimitted
      * by "a single space". However, we don't srtictly follow this rule:
      * More than one "white-spaces" allowed, can be ended with a comment,
      * and so on.
      */
      len = mfreadln(buf, 255, pdf_input_file);
      if (len == 0) /* empty line...just skip. */
        continue;
      else if (len < 0) {
        WARN("Something went wrong while reading xref subsection...");
        return -1;
      }
      p      = buf;
      endptr = buf + len;
      skip_white(&p, endptr);
      if (p == endptr) /* Only white-spaces and/or comment. */
        continue;

      /*
       * Don't overwrite positions that have already been set by a
       * modified xref table.  We are working our way backwards
       * through the reference table, so we only set "position" 
       * if it hasn't been set yet.
       */
      offset = 0UL; obj_gen = 0; flag = 0;
      {
        char *q;

        /* Offset value -- 10 digits (0 padded) */
        q = parse_unsigned(&p, endptr);
        if (!q) {
          WARN("An unsigned integer expected but could not find. (xref)");
          return -1;
        } else if (strlen(q) != 10) { /* exactly 10 digits */
          WARN(("Offset must be a 10 digits number. (xref)"));
          RELEASE(q);
          return -1;
        }
        /* FIXME: Possible overflow here. Consider using strtoll(). */
        offset = atoi(q);
        RELEASE(q);
        skip_white(&p, endptr);

        /* Generation number -- 5 digits (0 padded) */
        q = parse_unsigned(&p, endptr);
        if (!q) {
          WARN("An unsigned integer expected but could not find. (xref)");
          return -1;
        } else if (strlen(q) != 5) { /* exactly 5 digits */
          WARN(("Expecting a 5 digits number. (xref)"));
          RELEASE(q);
          return -1;
        }
        obj_gen = atoi(q);
        RELEASE(q);
        skip_white(&p, endptr);
      }
      if (p == endptr) {
        WARN("Unexpected EOL reached while reading a xref subsection entry.");
        return -1;
      }

      /* Flag -- a char */
      flag = *p; p++;
      skip_white(&p, endptr);
      if (p < endptr) {
        WARN("Garbage in xref subsection entry found...");
        return -1;
      } else if (((flag != 'n' && flag != 'f') ||
                  (flag == 'n' &&
                  (offset >= pf->file_size || (offset > 0 && offset < 4))))) {
        WARN("Invalid xref table entry [%lu]. PDF file is corrupt...", i);
        return -1;
      }

      /* Everything seems to be OK. */
      if (!pf->xref_table[i].field2) {
        pf->xref_table[i].type   = (flag == 'n');
        pf->xref_table[i].field2 = offset;
        pf->xref_table[i].field3 = obj_gen;
      }
      i++;
    }
  }

  return  1;
}

static uint32_t
parse_xrefstm_field (const char **p, size_t length, uint32_t def)
{
  uint32_t val = 0;

  if (!length)
    return def;

  while (length--) {
    val <<= 8;
    val |= (unsigned char) *((*p)++);
  }

  return val;
}

static int
parse_xrefstm_subsec (pdf_file *pf,
                      const char **p, size_t *length,
                      int *W, int wsum, int first, int size)
{
  xref_entry *e;

  if ((*length -= wsum*size) < 0)
    return -1;

  if (pf->num_obj < first+size)
    extend_xref(pf, first+size);  /* TODO: change! why? */

  e = pf->xref_table + first;
  while (size--) {
    uint8_t  type;
    uint32_t field2;
    uint16_t field3;

    type = (unsigned char) parse_xrefstm_field(p, W[0], 1);
    if (type > 2)
      WARN("Unknown cross-reference stream entry type.");
#if 0
    /* Not sure */
    else if (!W[1] || (type != 1 && !W[2]))
      return -1;
#endif

    field2 = (uint32_t) parse_xrefstm_field(p, W[1], 0);
    field3 = (uint16_t) parse_xrefstm_field(p, W[2], 0);

    if (!e->field2) {
      e->type   = type;
      e->field2 = field2;
      e->field3 = field3;	
      }
    e++;
  }

  return 0;
}

static int
parse_xref_stream (pdf_file *pf, size_t xref_pos, pdf_obj **trailer)
{
  pdf_obj    *xrefstm, *size_obj, *W_obj, *index_obj;
  uint32_t    size;
  size_t      length;
  int         W[3], i, wsum = 0;
  const char *p;

  xrefstm = pdf_read_object(0, 0, pf, xref_pos, pf->file_size);
  if (!PDF_OBJ_STREAMTYPE(xrefstm))
    goto error;

  {
    pdf_obj *tmp = pdf_stream_uncompress(xrefstm);
    if (!tmp)
      goto error;
    pdf_release_obj(xrefstm);
    xrefstm = tmp;
  }

  *trailer = pdf_link_obj(pdf_stream_dict(xrefstm));

  size_obj = pdf_lookup_dict(*trailer, "Size");
  if (!PDF_OBJ_NUMBERTYPE(size_obj))
    goto error;
  size = (uint32_t) pdf_number_value(size_obj);

  length = pdf_stream_length(xrefstm);

  W_obj = pdf_lookup_dict(*trailer, "W");
  if (!PDF_OBJ_ARRAYTYPE(W_obj) || pdf_array_length(W_obj) != 3)
    goto error;

  for (i = 0; i < 3; i++) {
    pdf_obj *tmp = pdf_get_array(W_obj, i);
    if (!PDF_OBJ_NUMBERTYPE(tmp))
      goto error;
    wsum += (W[i] = (int) pdf_number_value(tmp));
  }

  p = pdf_stream_dataptr(xrefstm);

  index_obj = pdf_lookup_dict(*trailer, "Index");
  if (index_obj) {
    size_t index_len;
    if (!PDF_OBJ_ARRAYTYPE(index_obj) ||
        ((index_len = pdf_array_length(index_obj)) % 2 ))
      goto error;

    i = 0;
    while (i < index_len) {
      pdf_obj *first = pdf_get_array(index_obj, i++);
      size_obj  = pdf_get_array(index_obj, i++);
      if (!PDF_OBJ_NUMBERTYPE(first) || !PDF_OBJ_NUMBERTYPE(size_obj) ||
          parse_xrefstm_subsec(pf, &p, &length, W, wsum,
            (int) pdf_number_value(first), (int) pdf_number_value(size_obj)))
        goto error;
    }
  } else if (parse_xrefstm_subsec(pf, &p, &length, W, wsum, 0, size))
      goto error;

  if (length)
    WARN("Garbage in xref stream.");

  pdf_release_obj(xrefstm);

  return 1;

 error:
  WARN("Cannot parse cross-reference stream.");
  if (xrefstm)
    pdf_release_obj(xrefstm);
  if (*trailer) {
    pdf_release_obj(*trailer);
    *trailer = NULL;
  }
  return 0;
}

/* TODO: parse Version entry */
static pdf_obj *
read_xref (pdf_file *pf)
{
  pdf_obj *trailer = NULL, *main_trailer = NULL;
  size_t   xref_pos;

  if (!(xref_pos = find_xref(pf->file)))
    goto error;

  while (xref_pos) {
    pdf_obj *prev;

    int res = parse_xref_table(pf, xref_pos);
    if (res > 0) {
      /* cross-reference table */
      pdf_obj *xrefstm;

      if (!(trailer = parse_trailer(pf)))
        goto error;

      if (!main_trailer)
        main_trailer = pdf_link_obj(trailer);

      if ((xrefstm = pdf_lookup_dict(trailer, "XRefStm"))) {
        pdf_obj *new_trailer = NULL;
        if (PDF_OBJ_NUMBERTYPE(xrefstm) &&
            parse_xref_stream(pf, (int) pdf_number_value(xrefstm),
			      &new_trailer))
          pdf_release_obj(new_trailer);
        else
          WARN("Skipping hybrid reference section.");
	      /* Many PDF 1.5 xref streams use DecodeParms, which we cannot
	       * parse. This way we can use at least xref tables in hybrid
	       * documents. Or should we better stop parsing the file?
	       */
      }

    } else if (!res && parse_xref_stream(pf, xref_pos, &trailer)) {
      /* cross-reference stream */
      if (!main_trailer)
        main_trailer = pdf_link_obj(trailer);
    } else
      goto error;

    if ((prev = pdf_lookup_dict(trailer, "Prev"))) {
      if (PDF_OBJ_NUMBERTYPE(prev))
        xref_pos = (size_t) pdf_number_value(prev);
      else
        goto error;
    } else
      xref_pos = 0;

    pdf_release_obj(trailer);
  }

#if 0
  if (!pdf_lookup_dict(main_trailer, "Root")) {
      WARN("Trailer doesn't have catalog. Is this a correct PDF file?");
      goto error;
    }
#endif

  return main_trailer;

 error:
  WARN("Error while parsing PDF file.");
  if (trailer)
    pdf_release_obj(trailer);
  if (main_trailer)
    pdf_release_obj(main_trailer);
  return NULL;
}

static struct ht_table *pdf_files = NULL;

static pdf_file *
pdf_file_new (FILE *file)
{
  pdf_file *pf;
  ASSERT(file);
  pf = NEW(1, pdf_file);
  pf->file    = file;
  pf->trailer = NULL;
  pf->xref_table = NULL;
  pf->catalog = NULL;
  pf->num_obj = 0;
  pf->version = 0;

  seek_end(file);
  pf->file_size = tell_position(file);

  return pf;
}

static void
pdf_file_free (pdf_file *pf)
{
  size_t i;

  if (!pf) {
    return;
  }

  for (i = 0; i < pf->num_obj; i++) {
    if (pf->xref_table[i].direct)
      pdf_release_obj(pf->xref_table[i].direct);
    if (pf->xref_table[i].indirect)
      pdf_release_obj(pf->xref_table[i].indirect);
  }

  RELEASE(pf->xref_table);
  if (pf->trailer)
    pdf_release_obj(pf->trailer);
  if (pf->catalog)
    pdf_release_obj(pf->catalog);

  RELEASE(pf);  
}

void
pdf_files_init (void)
{
  pdf_files = NEW(1, struct ht_table);
  ht_init_table(pdf_files, (void (*)(void *)) pdf_file_free);
}

int
pdf_file_get_version (pdf_file *pf)
{
  ASSERT(pf);
  return pf->version;
}

pdf_obj *
pdf_file_get_trailer (pdf_file *pf)
{
  ASSERT(pf);
  return pdf_link_obj(pf->trailer);
}

/* FIXME:
 * pdf_file_get_trailer() does pdf_link_obj() but
 * pdf_file_get_catalog() does not. Why?
 */
pdf_obj *
pdf_file_get_catalog (pdf_file *pf)
{
  ASSERT(pf);
  return pf->catalog;
}

pdf_file *
pdf_open (const char *ident, FILE *file)
{
  pdf_out  *p  = current_output();
  pdf_file *pf = NULL;

  ASSERT(pdf_files);

  if (ident)
    pf = (pdf_file *) ht_lookup_table(pdf_files, ident, strlen(ident));

  if (pf) {
    pf->file = file;
  } else {
    pdf_obj *new_version;
    int version = check_for_pdf_version(file);

    if (!dpx_conf.is_xbb) {
      int ver_major, ver_minor;

      ver_major = version / 10;
      ver_minor = version % 10;

      if (version < 10)
        WARN("Unrecognized PDF version specified for input PDF file: %d.%d",
             ver_major, ver_minor);
      else if (pdf_check_version(ver_major, ver_minor) < 0) {
        WARN("Trying to include PDF file with version (%d.%d), which is " \
             "newer than current output PDF setting (%d.%d).",
             ver_major, ver_minor, p->version.major, p->version.minor);
      }
    }
    pf = pdf_file_new(file);
    pf->version = version;

    if (!(pf->trailer = read_xref(pf)))
      goto error;

    if (pdf_lookup_dict(pf->trailer, "Encrypt")) {
      WARN("PDF document is encrypted.");
      goto error;
    }

    pf->catalog = pdf_deref_obj(pdf_lookup_dict(pf->trailer, "Root"));
    if (!PDF_OBJ_DICTTYPE(pf->catalog)) {
      WARN("Cannot read PDF document catalog. Broken PDF file?");
      goto error;
    }

    new_version = pdf_deref_obj(pdf_lookup_dict(pf->catalog, "Version"));
    if (new_version) {
      unsigned int major, minor;

      if (!PDF_OBJ_NAMETYPE(new_version) ||
          sscanf(pdf_name_value(new_version), "%u.%u", &major, &minor) != 2) {
        pdf_release_obj(new_version);
        WARN("Illegal Version entry in document catalog. Broken PDF file?");
        goto error;
      }

      if (pf->version < major*10+minor)
        pf->version = major*10+minor;

      pdf_release_obj(new_version);
    }

    if (ident)
      ht_append_table(pdf_files, ident, strlen(ident), pf);
  }

  return pf;

 error:
  pdf_file_free(pf);
  return NULL;
}

void
pdf_close (pdf_file *pf)
{
  if (pf)
    pf->file = NULL;
}

void
pdf_files_close (void)
{
  ASSERT(pdf_files);
  ht_clear_table(pdf_files);
  RELEASE(pdf_files);
}

static int
check_for_pdf_version (FILE *file) 
{
  unsigned int major, minor;

  rewind(file);

  return (ungetc(fgetc(file), file) == '%' &&
	  fscanf(file, "%%PDF-%u.%u", &major, &minor) == 2) ? (major*10+minor) : -1;
}

int
check_for_pdf (FILE *file) 
{
  int version = check_for_pdf_version(file);

  if (version < 0)  /* not a PDF file */
    return 0;

  if (version <= pdf_get_version())
    return 1;

  return 1;
}

static inline int
import_dict (pdf_obj *key, pdf_obj *value, void *pdata)
{
  pdf_obj *copy;
  pdf_obj *tmp;

  copy = (pdf_obj *) pdata;

  tmp  = pdf_import_object(value);
  if (!tmp) {
    return -1;
  }
  pdf_add_dict(copy, pdf_link_obj(key), tmp);

  return 0;
}

static pdf_obj *
pdf_import_indirect (pdf_obj *object)
{
  pdf_out  *p  = current_output();
  pdf_file *pf = OBJ_FILE(object);
  uint32_t  obj_num = OBJ_NUM(object);
  uint16_t  obj_gen = OBJ_GEN(object);
  pdf_obj  *ref;

  ASSERT(pf);

  if (!checklabel(pf, obj_num, obj_gen)) {
    WARN("Can't resolve object: %lu %u", obj_num, obj_gen);
    return pdf_new_null();
  }

  ref = pf->xref_table[obj_num].indirect;
  if (!ref) {
    pdf_obj *obj, *reserved, *imported;

    obj = pdf_get_object(pf, obj_num, obj_gen);
    if (!obj) {
      WARN("Could not read object: %lu %u", obj_num, obj_gen);
      return NULL;
    }

    /* Fix for circular reference issue
     *
     * Older version of dvipdfmx disallowed the following case of
     * circular reference:
     *   obj #1 --> << /Kids [2 0 R] >>
     *   obj #2 --> << /Parents [1 0 R] >>
     * The problem is in that dvipdfmx gives new labels to objects after they
     * are completely read.
     */
    reserved = pdf_new_null(); /* for reservation of label */
    pf->xref_table[obj_num].indirect = ref = pdf_new_ref(p, reserved);
    imported = pdf_import_object(obj);
    if (imported) {
      if (imported->label) {
        WARN("Imported object already has a label: obj_id=%lu", imported->label);
      }
      OBJ_OBJ(ref) = imported;
      imported->label = reserved->label;
      imported->generation = reserved->generation;
      reserved->label = 0;
      reserved->generation = 0;
      pdf_release_obj(imported);
    }

    pdf_release_obj(reserved);
    pdf_release_obj(obj);
  }
    
    return  pdf_link_obj(ref);
}

/*
 * pdf_import_object recursively copies the object and those
 * referenced by it and changes the indirect references so that
 * they refer to the current output file. New indirect references
 * are remembered, which avoids duplicating objects when they
 * are imported several times.
 */
pdf_obj *
pdf_import_object (pdf_obj *object)
{
  pdf_obj  *imported;
  pdf_obj  *tmp;
  size_t    i;

  switch (pdf_obj_typeof(object)) {

  case PDF_INDIRECT:
    if (OBJ_FILE(object)) {
      imported = pdf_import_indirect(object);
    } else {
      imported = pdf_link_obj(object);
    }
    break;

  case PDF_STREAM:
    {
      pdf_obj *stream_dict;

      tmp = pdf_import_object(pdf_stream_dict(object));
      if (!tmp)
	return NULL;

      imported    = pdf_new_stream(0);
      stream_dict = pdf_stream_dict(imported);
      pdf_merge_dict(stream_dict, tmp);
      pdf_release_obj(tmp);
      pdf_add_stream(imported,
		     pdf_stream_dataptr(object),
		     pdf_stream_length(object));
    }
    break;

  case PDF_DICT:

    imported = pdf_new_dict();
    if (pdf_foreach_dict(object, import_dict, imported) < 0) {
      pdf_release_obj(imported);
      return NULL;
    }

    break;

  case PDF_ARRAY:

    imported = pdf_new_array();
    for (i = 0; i < pdf_array_length(object); i++) {
      tmp = pdf_import_object(pdf_get_array(object, i));
      if (!tmp) {
        pdf_release_obj(imported);
        return NULL;
      }
      pdf_add_array(imported, tmp);
    }
    break;

  default:
    imported = pdf_link_obj(object);
  }

  return imported;
}


/* returns 0 if indirect references point to the same object */
int
pdf_compare_reference (pdf_obj *ref1, pdf_obj *ref2)
{
  pdf_indirect *data1, *data2;

  ASSERT(PDF_OBJ_INDIRECTTYPE(ref1) && PDF_OBJ_INDIRECTTYPE(ref2));

  data1 = (pdf_indirect *) ref1->data;
  data2 = (pdf_indirect *) ref2->data;

  return data1->pf != data2->pf || data1->label != data2->label
    || data1->generation != data2->generation;
}

int
pdf_compare_object (pdf_obj *obj1, pdf_obj *obj2)
{
  int r = 0;

  if (!obj1 && !obj2) {
    return 0;
  } else if (!obj1 && obj2) {
    return 1;
  } else if (obj1 && !obj2) {
    return 1;
  } else if (pdf_obj_typeof(obj1) != pdf_obj_typeof(obj2)) {
    return 1;
  }

  switch (pdf_obj_typeof(obj1)) {
  case PDF_BOOLEAN:
    r = pdf_boolean_value(obj1) - pdf_boolean_value(obj2);
    break;
  case PDF_NUMBER:
    if (pdf_number_value(obj1) < pdf_number_value(obj2)) {
      r = -1;
    } else if (pdf_number_value(obj1) > pdf_number_value(obj2)) {
      r = 1;
    } else {
      r = 0;
    }
    break;
  case PDF_STRING:
    if (pdf_string_length(obj1) < pdf_string_length(obj2)) {
      r = -1;
    } else if (pdf_string_length(obj1) > pdf_string_length(obj2)) {
      r = 1;
    } else {
      r = memcmp(pdf_string_value(obj1), pdf_string_value(obj2), pdf_string_length(obj1));
    }
    break;
  case PDF_NAME:
    r = strcmp(pdf_name_value(obj1), pdf_name_value(obj2));
    break;
  case PDF_NULL:
    /* Always same */
    r = 0;
    break;
  case PDF_INDIRECT:
    r = pdf_compare_reference(obj1, obj2);
    break;
  case PDF_ARRAY:
    if (pdf_array_length(obj1) < pdf_array_length(obj2)) {
      r = -1;
    } else if (pdf_array_length(obj1) > pdf_array_length(obj2)) {
      r = 1;
    } else {
      int i;
      for (i = 0; r == 0 && i < pdf_array_length(obj1); i++) {
        pdf_obj *v1, *v2;
        v1 = pdf_get_array(obj1, i);
        v2 = pdf_get_array(obj2, i);
        r  = pdf_compare_object(v1, v2); 
      }
    }
    break;
  case PDF_DICT:
    {
      pdf_obj *keys1, *keys2;
      keys1 = pdf_dict_keys(obj1);
      keys2 = pdf_dict_keys(obj2);
      r = pdf_compare_object(keys1, keys2);
      if (r == 0) {
        int i;
        for (i = 0; r == 0 && i < pdf_array_length(keys1); i++) {
          pdf_obj *key, *v1, *v2;
          key = pdf_get_array(keys1, i);
          v1  = pdf_lookup_dict(obj1, pdf_name_value(key));
          v2  = pdf_lookup_dict(obj2, pdf_name_value(key));
          r   = pdf_compare_object(v1, v2);
        }
      }
      pdf_release_obj(keys1);
      pdf_release_obj(keys2);
    }
    break;
  case PDF_STREAM:
    /* Not seriously testing... */
    r = pdf_compare_object(pdf_stream_dict(obj1), pdf_stream_dict(obj2));
    if (r == 0) {
      size_t len1, len2;
      len1 = pdf_stream_length(obj1);
      len2 = pdf_stream_length(obj2);
      if (len1 < len2) {
        r = -1;
      } else if (len1 > len2) {
        r = 1;
      } else {
        r = 0;
      }
    }
    break;
  default:
    r = 1;
  }

  return r;
}
