diff options
Diffstat (limited to 'Build/source/texk/dvipdfm-x/pdfobj.c')
-rw-r--r-- | Build/source/texk/dvipdfm-x/pdfobj.c | 4624 |
1 files changed, 4624 insertions, 0 deletions
diff --git a/Build/source/texk/dvipdfm-x/pdfobj.c b/Build/source/texk/dvipdfm-x/pdfobj.c new file mode 100644 index 00000000000..804466378bc --- /dev/null +++ b/Build/source/texk/dvipdfm-x/pdfobj.c @@ -0,0 +1,4624 @@ +/* This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks. + + Copyright (C) 2002-2020 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 = p + (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; +} |