diff options
Diffstat (limited to 'Build/source/texk/xdvipdfmx/src/pdfobj.c')
-rw-r--r-- | Build/source/texk/xdvipdfmx/src/pdfobj.c | 2542 |
1 files changed, 2542 insertions, 0 deletions
diff --git a/Build/source/texk/xdvipdfmx/src/pdfobj.c b/Build/source/texk/xdvipdfmx/src/pdfobj.c new file mode 100644 index 00000000000..7e6930bc667 --- /dev/null +++ b/Build/source/texk/xdvipdfmx/src/pdfobj.c @@ -0,0 +1,2542 @@ +/* $Header: /home/cvsroot/dvipdfmx/src/pdfobj.c,v 1.39 2005/08/30 07:53:37 chofchof Exp $ + + This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks. + + Copyright (C) 2002 by Jin-Hwan Cho and Shunsaku Hirata, + the dvipdfmx project team <dvipdfmx@project.ktug.or.kr> + + 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. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <ctype.h> +#include <string.h> + +#include "system.h" +#include "mem.h" +#include "error.h" +#include "mfileio.h" +#include "pdflimits.h" +#include "pdfencrypt.h" +#include "pdfparse.h" + +#ifdef HAVE_ZLIB +#include <zlib.h> +#endif /* HAVE_ZLIB */ + +#include "pdfobj.h" + +#define STREAM_ALLOC_SIZE 4096u +#define ARRAY_ALLOC_SIZE 256 +#define IND_OBJECTS_ALLOC_SIZE 512 + +/* Any of these types can be represented as follows */ +struct pdf_obj +{ + int type; + + unsigned long label; /* Only used for indirect objects + all other "label" to zero */ + unsigned short generation; /* Only used if "label" is used */ + unsigned refcount; /* Number of links to this object */ + void *data; +}; + +struct pdf_boolean +{ + char value; +}; + +struct pdf_number +{ + double value; +}; + +struct pdf_string +{ + unsigned char *string; + unsigned short length; +}; + +struct pdf_name +{ + char *name; +}; + +struct pdf_array +{ + unsigned long max; + unsigned long size; + struct pdf_obj **values; +}; + +struct pdf_dict +{ + struct pdf_obj *key; + struct pdf_obj *value; + struct pdf_dict *next; +}; + +struct pdf_stream +{ + struct pdf_obj *dict; + unsigned char *stream; + unsigned long stream_length; + unsigned long max_length; + unsigned char _flags; +}; + +struct pdf_indirect +{ + unsigned label; + unsigned generation; + int dirty; /* Dirty objects came from an input file and were not + generated by this program. They have a label in a + different numbering sequence. These are translated + when the object is written out */ + FILE *dirty_file; +}; + +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; + +static FILE *pdf_output_file = NULL; +static FILE *pdf_input_file = NULL; + +static long pdf_output_file_position = 0; +static long pdf_output_line_position = 0; +static long compression_saved = 0; + +#define FORMAT_BUF_SIZE 4096 +static char format_buffer[FORMAT_BUF_SIZE]; + +static struct xref_entry +{ + long file_position; + pdf_obj *object; + int used; /* Used by PDF import */ +} *output_xref = NULL; + +static unsigned long pdf_max_ind_objects = 0; +static unsigned long next_label = 1; + +static unsigned long startxref; + +static unsigned pdf_root_obj = 0; +static unsigned pdf_info_obj = 0; +static unsigned pdf_encrypt_obj = 0; + +/* Internal static routines */ + +static void pdf_flush_obj (pdf_obj *object, FILE *file); +static void pdf_label_obj (pdf_obj *object); +static void pdf_write_obj (pdf_obj *object, FILE *file); + +static void pdf_out_char (FILE *file, char c); +static void pdf_out (FILE *file, const void *buffer, long length); + +static void release_indirect (pdf_indirect *data); +static void write_indirect (pdf_indirect *indirect, FILE *file); + +static void release_boolean (pdf_obj *data); +static void write_boolean (pdf_boolean *data, FILE *file); + +static void release_null (pdf_null *data); +static void write_null (pdf_null *data, FILE *file); + +static void release_number (pdf_number *number); +static void write_number (pdf_number *number, FILE *file); + +static void write_string (pdf_string *str, FILE *file); +static void release_string (pdf_string *str); + +static void write_name (pdf_name *name, FILE *file); +static void release_name (pdf_name *name); + +static void write_array (pdf_array *array, FILE *file); +static void release_array (pdf_array *array); + +static void write_dict (pdf_dict *dict, FILE *file); +static void release_dict (pdf_dict *dict); + +static void write_stream (pdf_stream *stream, FILE *file); +static void release_stream (pdf_stream *stream); + +static pdf_obj *pdf_ref_file_obj (unsigned long obj_num, unsigned obj_gen); +static pdf_obj *pdf_read_object (unsigned long obj_num, unsigned obj_gen); + +static int verbose = 0; +static char compression_level = 9; + +void +pdf_set_compression (int level) +{ +#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) + compression_level = level; + else { + ERROR("set_compression: invalid compression level: %d", level); + } +#endif /* !HAVE_ZLIB */ + + return; +} + +static unsigned pdf_version = 3; + +void +pdf_set_version (unsigned version) +{ + /* Don't forget to update CIDFont_stdcc_def[] in cid.c too! */ + if (version >= 3 && version <= 6) { + pdf_version = version; + } +} + +unsigned +pdf_get_version (void) +{ + return pdf_version; +} + +void +pdf_obj_set_verbose(void) +{ + verbose++; +} + +#define BINARY_MARKER "%\344\360\355\370\n" +void +pdf_out_init (const char *filename) +{ + char v; + + pdf_output_file = MFOPEN(filename, FOPEN_WBIN_MODE); + if (!pdf_output_file) { + if (strlen(filename) < 128) + ERROR("Unable to open \"%s\".", filename); + else + ERROR("Unable to open file."); + } + pdf_out(pdf_output_file, "%PDF-1.", strlen("%PDF-1.")); + v = '0' + pdf_version; + pdf_out(pdf_output_file, &v, 1); + pdf_out(pdf_output_file, "\n", 1); + pdf_out(pdf_output_file, BINARY_MARKER, strlen(BINARY_MARKER)); +} + +static void +dump_xref (void) +{ + long length; + unsigned long i; + + /* Record where this xref is for trailer */ + startxref = pdf_output_file_position; + + pdf_out(pdf_output_file, "xref\n", 5); + + length = sprintf(format_buffer, "%d %lu\n", 0, next_label); + pdf_out(pdf_output_file, format_buffer, length); + + length = sprintf(format_buffer, "%010ld %05ld f \n", 0L, 65535L); + /* + * 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. + */ + pdf_out(pdf_output_file, format_buffer, length); + for (i = 1; i < next_label; i++){ + length = sprintf(format_buffer, "%010ld %05ld n \n", + output_xref[i-1].file_position, 0L); + pdf_out(pdf_output_file, format_buffer, length); + } + /* Done with xref table */ + RELEASE(output_xref); + output_xref = NULL; +} + +static void +dump_trailer (void) +{ + long length; + unsigned long starttrailer; + + starttrailer = pdf_output_file_position; + + pdf_out(pdf_output_file, "trailer\n", 8); + pdf_out(pdf_output_file, "<<\n", 3); + + length = sprintf(format_buffer, "/Size %lu\n", next_label); + pdf_out(pdf_output_file, format_buffer, length); + + if (pdf_root_obj == 0) + ERROR ("dump_trailer: Invalid root object"); + length = sprintf(format_buffer, "/Root %u %u R\n", pdf_root_obj, 0); + pdf_out(pdf_output_file, format_buffer, length); + + if (pdf_encrypt_obj != 0) { + length = sprintf(format_buffer, "/Encrypt %u %u R\n", pdf_encrypt_obj, 0); + pdf_out(pdf_output_file, format_buffer, length); + } + if (pdf_info_obj != 0) { + length = sprintf(format_buffer, "/Info %u %u R\n", pdf_info_obj, 0); + pdf_out(pdf_output_file, format_buffer, length); + } + if (pdf_encrypt_obj != 0) { + unsigned char *id; + id = pdf_enc_id_string(); + length = sprintf(format_buffer, "/ID [<%s> <%s>]\n", id, id); + pdf_out(pdf_output_file, format_buffer, length); + } + pdf_out(pdf_output_file, ">>\n", 3); + pdf_out(pdf_output_file, "startxref\n", 10); + + length = sprintf(format_buffer, "%lu\n", startxref); + pdf_out(pdf_output_file, format_buffer, length); + + pdf_out(pdf_output_file, "%%EOF\n", 6); +} + +void +pdf_out_flush (void) +{ + if (pdf_output_file) { + dump_xref(); + dump_trailer(); + MESG("\n"); + if (verbose) { + if (compression_level > 0) { + MESG("Compression eliminated approximately %lu bytes\n", compression_saved); + } + } + MESG("%lu bytes written", pdf_output_file_position); + + MFCLOSE(pdf_output_file); + } +} + +void +pdf_error_cleanup (void) +{ + /* + * This routine is the cleanup required for an abnormal exit. + * For now, simply close the file. + */ + if (pdf_output_file) + MFCLOSE(pdf_output_file); +} + + +void +pdf_set_root (pdf_obj *object) +{ + if (pdf_root_obj != 0) { + ERROR("Root object already set!"); + } + if (object->label == 0) { /* Make sure this object has a label */ + pdf_label_obj(object); + } + pdf_root_obj = object->label; +} + +void +pdf_set_info (pdf_obj *object) +{ + if (pdf_info_obj != 0) { + ERROR ("Info object already set!"); + } + if (object->label == 0) { /* Make sure this object has a label */ + pdf_label_obj(object); + } + pdf_info_obj = object->label; +} + +void +pdf_set_encrypt (pdf_obj *object) +{ + if (pdf_encrypt_obj != 0) { + ERROR("Encrypt object already set!"); + } + if (object->label == 0) { /* Make sure this object has a label */ + pdf_label_obj(object); + } + pdf_encrypt_obj = object->label; +} + +static +void pdf_out_char (FILE *file, char c) +{ + fputc(c, file); + /* Keep tallys for xref table *only* if writing a pdf file. */ + if (file == pdf_output_file) { + pdf_output_file_position += 1; + if (c == '\n') + pdf_output_line_position = 0; + else + pdf_output_line_position += 1; + } +} + +#define pdf_out_xchar(f,c) do {\ + int __tmpnum;\ + __tmpnum = ((c) >> 4) & 0x0f;\ + pdf_out_char((f), (((__tmpnum) >= 10) ? (__tmpnum)+'W' : (__tmpnum)+'0'));\ + __tmpnum = (c) & 0x0f;\ + pdf_out_char((f), (((__tmpnum) >= 10) ? (__tmpnum)+'W' : (__tmpnum)+'0'));\ +} while (0) + +static +void pdf_out (FILE *file, const void *buffer, long length) +{ + fwrite(buffer, 1, length, file); + /* Keep tallys for xref table *only* if writing a pdf file */ + if (file == pdf_output_file) { + pdf_output_file_position += length; + pdf_output_line_position += length; + /* "foo\nbar\n "... */ + if (length > 0 && + ((char *)buffer)[length-1] == '\n') + pdf_output_line_position = 0; + } +} + +static +void pdf_out_white (FILE *file) +{ + if (file == pdf_output_file && pdf_output_line_position >= 80) { + pdf_out_char(file, '\n'); + } else { + pdf_out_char(file, ' '); + } +} + +#define TYPECHECK(o,t) if (!(o) || (o)->type != (t)) {\ + ERROR("typecheck: Invalid object type. %d %d", (o) ? (o)->type : -1, (t));\ +} + +#define INVALIDOBJ(o) ((o) == NULL || (o)->type <= 0 || (o)->type > PDF_INDIRECT) + +pdf_obj * +pdf_new_obj(int type) +{ + pdf_obj *result; + + if (type > PDF_INDIRECT || type < PDF_UNDEFINED) + 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; + + 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_obj *object) +{ + if (INVALIDOBJ(object)) + ERROR("pdf_label_obj(): passed invalid object."); + + if (next_label > pdf_max_ind_objects) { + pdf_max_ind_objects += IND_OBJECTS_ALLOC_SIZE; + output_xref = RENEW(output_xref, pdf_max_ind_objects, struct xref_entry); + } + /* + * Don't change label on an already labeled object. Ignore such calls. + */ + if (object->label == 0) { + /* Save so we can lookup this object by its number */ + output_xref[next_label-1].object = object; + output_xref[next_label-1].file_position = 0L; + object->label = next_label; + object->generation = 0; + next_label++; + } +} + +/* + * 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_obj *result; + pdf_indirect *indirect; + + if (INVALIDOBJ(object)) + ERROR("pdf_ref_obj(): passed invalid object."); + + if (object->refcount == 0) { + MESG("\nTrying to refer already released object!!!\n"); + pdf_write_obj(object, stderr); + ERROR("Cannot continue..."); + } + + result = pdf_new_obj(PDF_INDIRECT); + indirect = NEW(1, pdf_indirect); + result->data = indirect; + if (object->type == PDF_INDIRECT) { + indirect->label = ((pdf_indirect *) (object->data))->label; + indirect->generation = ((pdf_indirect *) (object->data))->generation; + indirect->dirty = ((pdf_indirect *) (object->data))->dirty; + indirect->dirty_file = ((pdf_indirect *) (object->data))->dirty_file; + } else { + if (object->label == 0) { + pdf_label_obj(object); + } + indirect->label = object->label; + indirect->generation = object->generation; + indirect->dirty = 0; + indirect->dirty_file = NULL; + } + + return result; +} + +static void +release_indirect (pdf_indirect *data) +{ + RELEASE(data); +} + +static void +write_indirect (pdf_indirect *indirect, FILE *file) +{ + long length; + + if (indirect->dirty) { + if (file == stderr) { + pdf_out(file, "{d}", 3); + length = sprintf(format_buffer, "%d %d R", indirect->label, indirect->generation); + pdf_out(stderr, format_buffer, length); + } else { + pdf_obj *clean; + + if (indirect->dirty_file != pdf_input_file) { + ERROR("Input PDF file doesn't match object: label=%d, from_file=%p, current_file=%p", + indirect->label, indirect->dirty_file, pdf_input_file); + } + clean = pdf_ref_file_obj(indirect->label, indirect->generation); + pdf_write_obj(clean, file); + pdf_release_obj(clean); + } + } else { + length = sprintf(format_buffer, "%d %d R", indirect->label, indirect->generation); + pdf_out(file, format_buffer, length); + } +} + +pdf_obj * +pdf_new_null (void) +{ + pdf_obj *result; + + result = pdf_new_obj(PDF_NULL); + result->data = NULL; + + return result; +} + +static void +release_null (pdf_null *obj) +{ + return; +} + +static void +write_null (pdf_null *obj, FILE *file) +{ + pdf_out(file, "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_boolean *data, FILE *file) +{ + if (data->value) { + pdf_out(file, "true", 4); + } else { + pdf_out(file, "false", 5); + } +} + +void +pdf_set_boolean (pdf_obj *object, char value) +{ + pdf_boolean *data; + + TYPECHECK(object, PDF_BOOLEAN); + + data = object->data; + data->value = value; +} + +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_number *number, FILE *file) +{ + int count; + + count = pdf_sprint_number(format_buffer, number->value); + + pdf_out(file, format_buffer, 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; + + result = pdf_new_obj(PDF_STRING); + data = NEW(1, pdf_string); + result->data = data; + if (length != 0) { + data->length = length; + data->string = NEW(length+1, unsigned char); + memcpy(data->string, str, length); + /* Shouldn't assume NULL terminated. */ + data->string[length] = '\0'; + } else { + data->length = 0; + 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) +{ + int result = 0; + int 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 result; +} + +static void +write_string (pdf_string *str, FILE *file) +{ + unsigned char *s; + char wbuf[FORMAT_BUF_SIZE]; /* Shouldn't use format_buffer[]. */ + int nescc = 0, i, count; + + s = str->string; + pdf_encrypt_data(s, str->length); + /* + * Count all ASCII non-printable characters. + */ + for (i = 0; i < str->length; i++) { + if (!isprint(s[i])) + nescc++; + } + /* + * If the string contains much escaped chars, then we write it as + * ASCII hex string. + */ + if (nescc > str->length / 3) { + pdf_out_char(file, '<'); + for (i = 0; i < str->length; i++) { + pdf_out_xchar(file, s[i]); + } + pdf_out_char(file, '>'); + } else { + pdf_out_char(file, '('); + /* + * 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 < str->length; i++) { + count = pdfobj_escape_str(wbuf, FORMAT_BUF_SIZE, &(s[i]), 1); + pdf_out(file, wbuf, count); + } + pdf_out_char(file, ')'); + } +} + +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; + unsigned 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_name *name, FILE *file) +{ + char *s; + int i, length; + + 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) == '%') +#endif + pdf_out_char(file, '/'); + for (i = 0; i < length; i++) { + if (s[i] < '!' || s[i] > '~' || s[i] == '#' || is_delim(s[i])) { + /* ^ "space" is here. */ + pdf_out_char (file, '#'); + pdf_out_xchar(file, s[i]); + } else { + pdf_out_char (file, s[i]); + } + } +} + +static void +release_name (pdf_name *data) +{ + if (data->name != NULL) { + RELEASE(data->name); + data->name = NULL; + } + RELEASE(data); +} + +void +pdf_set_name (pdf_obj *object, const char *name) +{ + pdf_name *data; + unsigned length; + + TYPECHECK(object, PDF_NAME); + + length = strlen(name); + data = object->data; + if (data->name != NULL) { + RELEASE(data->name); + } + if (length != 0) { + data->name = NEW(length+1, char); + memcpy(data->name, name, length); + data->name[length] = 0; + } else { + data->name = NULL; + } +} + +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_array *array, FILE *file) +{ + pdf_out_char(file, '['); + if (array->size > 0) { + unsigned long i; + + for (i = 0; i < array->size; i++) { + if (i > 0) + pdf_out_white(file); + if (!array->values[i]) + WARN("PDF array element #ld undefined.", i); + pdf_write_obj(array->values[i], file); + } + } + pdf_out_char(file, ']'); +} + +pdf_obj * +pdf_get_array (pdf_obj *array, long 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 int +pdf_array_length (pdf_obj *array) +{ + pdf_array *data; + + TYPECHECK(array, PDF_ARRAY); + + data = (pdf_array *) array->data; + + return (unsigned int) data->size; +} + +static void +release_array (pdf_array *data) +{ + unsigned long 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; +} + +void +pdf_put_array (pdf_obj *array, unsigned idx, pdf_obj *object) +{ + pdf_array *data; + long 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) { + int i; + + result = data->values[0]; + for (i = 1; i < data->size; i++) + data->values[i-1] = data->values[i]; + data->size--; + } + + return result; +} + +/* Prepend an object to an array */ +void +pdf_unshift_array (pdf_obj *array, pdf_obj *object) +{ + pdf_array *data; + int i; + + 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 *); + } + for (i = 0; i < data->size; i++) + data->values[i+1] = data->values[i]; + data->values[0] = object; + data->size++; +} + +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; +} + + +static void +write_dict (pdf_dict *dict, FILE *file) +{ + pdf_out (file, "<<\n", 3); /* dropping \n saves few kb. */ + while (dict->key != NULL) { + pdf_write_obj(dict->key, file); + if (((dict -> value)->type) == PDF_BOOLEAN || + ((dict -> value)->type) == PDF_NUMBER || + ((dict -> value)->type) == PDF_INDIRECT || + ((dict -> value)->type) == PDF_NULL) { + pdf_out_white(file); + } + pdf_write_obj(dict->value, file); + pdf_out_char (file, '\n'); /* removing this saves few kb. */ + dict = dict->next; + } + pdf_out(file, ">>", 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 */ +void +pdf_add_dict (pdf_obj *dict, pdf_obj *key, pdf_obj *value) +{ + pdf_dict *data; + + 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"); + + data = dict->data; + /* If this key already exists, simply replace the value */ + while (data->key != NULL) { + 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; + 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 = key; + data->value = value; + } +} + +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; + } +} + +/* 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 **) &(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; + } + + return; +} + +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; + + result->data = data; + + return result; +} + +static void +write_stream (pdf_stream *stream, FILE *file) +{ + unsigned char *filtered; + unsigned long filtered_length; + unsigned long buffer_length; + unsigned char *buffer; + + /* + * 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; + +#if 0 + if (stream->stream_length < 10) + stream->_flags &= ^STREAM_COMPRESS; +#endif + +#ifdef HAVE_ZLIB + /* Apply compression filter if requested */ + if (stream->stream_length > 0 && + (stream->_flags & STREAM_COMPRESS) && + compression_level > 0) { + + buffer_length = filtered_length + filtered_length/1000 + 14; + buffer = NEW(buffer_length, unsigned char); + { + pdf_obj *filters; + + filters = pdf_lookup_dict(stream->dict, "Filter"); + if (!filters) + filters = pdf_new_array(); + /* + * FlateDecode is the first filter to be applied to the stream. + */ + pdf_unshift_array(filters, pdf_new_name("FlateDecode")); + pdf_add_dict(stream->dict, pdf_new_name("Filter"), filters); + } +#ifdef HAVE_ZLIB_COMPRESS2 + if (compress2(buffer, &buffer_length, filtered, + filtered_length, compression_level)) { + ERROR("Zlib error"); + } +#else + if (compress(buffer, &buffer_length, filtered, + filtered_length)) { + ERROR ("Zlib error"); + } +#endif /* HAVE_ZLIB_COMPRESS2 */ + RELEASE(filtered); + compression_saved += filtered_length - buffer_length - strlen("/Filter [/FlateDecode]\n"); + + filtered = buffer; + filtered_length = buffer_length; + } +#endif /* HAVE_ZLIB */ + +#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(stream->dict, file); + + pdf_out(file, "\nstream\n", 8); + pdf_encrypt_data(filtered, filtered_length); + if (filtered_length > 0) { + pdf_out(file, 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(file, "\n", 1); + pdf_out(file, "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; + } + 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; +} + +long +pdf_stream_length (pdf_obj *stream) +{ + pdf_stream *data; + + TYPECHECK(stream, PDF_STREAM); + + data = stream->data; + + return (long) data->stream_length; +} + +void +pdf_add_stream (pdf_obj *stream, const void *stream_data, long 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; +} + +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; +} + +static void +pdf_write_obj (pdf_obj *object, FILE *file) +{ + if (object == NULL) { + write_null(NULL, file); + return; + } + + if (INVALIDOBJ(object)) + ERROR("pdf_write_obj: Invalid object, type = %d\n", object->type); + + if (file == stderr) + fprintf(stderr, "{%d}", object->refcount); + + switch (object->type) { + case PDF_BOOLEAN: + write_boolean(object->data, file); + break; + case PDF_NUMBER: + write_number (object->data, file); + break; + case PDF_STRING: + write_string (object->data, file); + break; + case PDF_NAME: + write_name(object->data, file); + break; + case PDF_ARRAY: + write_array(object->data, file); + break; + case PDF_DICT: + write_dict (object->data, file); + break; + case PDF_STREAM: + write_stream(object->data, file); + break; + case PDF_NULL: + write_null(NULL, file); + break; + case PDF_INDIRECT: + write_indirect(object->data, file); + break; + } +} + +/* Write the object to the file */ +static void +pdf_flush_obj (pdf_obj *object, FILE *file) +{ + long length; + + /* + * Record file position. No object is numbered 0, so subtract 1 + * when using as an array index + */ + output_xref[object->label-1].file_position = pdf_output_file_position; + length = sprintf(format_buffer, "%lu %d obj\n", object->label, object->generation); + pdf_enc_set_label(object->label); + pdf_enc_set_generation(object->generation); + pdf_out(file, format_buffer, length); + pdf_write_obj(object, file); + pdf_out(file, "\nendobj\n", 8); +} + +void +pdf_release_obj (pdf_obj *object) +{ + 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); + pdf_write_obj(object, stderr); + ERROR("pdf_release_obj: Called with invalid object."); + } + object->refcount -= 1; + if (object->refcount == 0) { + /* + * 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 && pdf_output_file != NULL) { + pdf_flush_obj(object, pdf_output_file); + } + switch (object->type) { + case PDF_BOOLEAN: + release_boolean(object->data); + break; + case PDF_NULL: + release_null(object->data); + 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); + } +} + +/* Copy object data without changing object label. */ +void +pdf_copy_object (pdf_obj *dst, pdf_obj *src) +{ + if (!dst || !src) + return; + + switch (dst->type) { + case PDF_BOOLEAN: release_boolean(dst->data); break; + case PDF_NULL: release_null(dst->data); break; + case PDF_NUMBER: release_number(dst->data); break; + case PDF_STRING: release_string(dst->data); break; + case PDF_NAME: release_name(dst->data); break; + case PDF_ARRAY: release_array(dst->data); break; + case PDF_DICT: release_dict(dst->data); break; + case PDF_STREAM: release_stream(dst->data); break; + case PDF_INDIRECT: release_indirect(dst->data); break; + } + + dst->type = src->type; + switch (src->type) { + case PDF_BOOLEAN: + dst->data = NEW(1, pdf_boolean); + pdf_set_boolean(dst, pdf_boolean_value(src)); + break; + case PDF_NULL: + dst->data = NULL; + break; + case PDF_NUMBER: + dst->data = NEW(1, pdf_number); + pdf_set_number(dst, pdf_number_value(src)); + break; + case PDF_STRING: + dst->data = NEW(1, pdf_string); + pdf_set_string(dst, + pdf_string_value(src), + pdf_string_length(src)); + break; + case PDF_NAME: + dst->data = NEW(1, pdf_name); + pdf_set_name(dst, pdf_name_value(src)); + break; + case PDF_ARRAY: + { + pdf_array *data; + unsigned long i; + + dst->data = data = NEW(1, pdf_array); + data->size = 0; + data->max = 0; + data->values = NULL; + for (i = 0; i < pdf_array_length(src); i++) { + pdf_add_array(dst, pdf_link_obj(pdf_get_array(src, i))); + } + } + break; + case PDF_DICT: + { + pdf_dict *data; + + dst->data = data = NEW(1, pdf_dict); + data->key = NULL; + data->value = NULL; + data->next = NULL; + pdf_merge_dict(dst, src); + } + break; + case PDF_STREAM: + { + pdf_stream *data; + + dst->data = data = NEW(1, pdf_stream); + data->dict = pdf_new_dict(); + data->_flags = ((pdf_stream *)src->data)->_flags; + data->stream_length = 0; + data->max_length = 0; + + pdf_add_stream(dst, pdf_stream_dataptr(src), pdf_stream_length(src)); + pdf_merge_dict(data->dict, pdf_stream_dict(src)); + } + break; + case PDF_INDIRECT: + { + pdf_indirect *data; + + dst->data = data = NEW(1, pdf_indirect); + data->label = ((pdf_indirect *) (src->data))->label; + data->generation = ((pdf_indirect *) (src->data))->generation; + data->dirty = ((pdf_indirect *) (src->data))->dirty; + data->dirty_file = ((pdf_indirect *) (src->data))->dirty_file; + } + break; + } + + return; +} + +static int +backup_line (void) +{ + 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 unsigned long pdf_file_size = 0; + +static long +find_xref (void) +{ + long xref_pos; + int tries = 10; + + seek_end(pdf_input_file); + + pdf_file_size = tell_position(pdf_input_file); + do { + long currentpos; + + if (!backup_line()) { + 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 */ + mfgets(work_buffer, WORK_BUFFER_SIZE, pdf_input_file); + + { + char *start, *end, *number; + + start = work_buffer; + end = start + strlen(work_buffer); + skip_white(&start, end); + number = parse_number(&start, end); + xref_pos = (long) 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 (void) +{ + 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, pdf_input_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 { + char *p = work_buffer + strlen("trailer"); + skip_white(&p, work_buffer + WORK_BUFFER_SIZE); + result = parse_pdf_dict(&p, work_buffer + WORK_BUFFER_SIZE); + } + + return result; +} + +struct object +{ + unsigned long file_position; + unsigned generation; + /* + * Object numbers in original file and new file must have different + * object numbers. + * new_ref provides a reference for the object in the new file object + * space. When it is first set, an object in the old file is copied + * to the new file with a new number. new_ref remains set until the + * file is closed so that future references can access the object via + * new_ref instead of copying the object again. + */ + pdf_obj *direct; + pdf_obj *indirect; + int used; +} *xref_table = NULL; +long num_input_objects; + +/* + * 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 long +next_object_offset (unsigned long obj_num) +{ + long next = pdf_file_size; /* Worst case */ + long i, curr; + + curr = xref_table[obj_num].file_position; + /* Check all other objects to find next one */ + for (i = 0; i < num_input_objects; i++) { + if (xref_table[i].used && + xref_table[i].file_position > curr && + xref_table[i].file_position < next) + next = xref_table[i].file_position; + } + + return next; +} + +#define checklabel(n,g) ((n) > 0 && (n) < num_input_objects && \ + xref_table[(n)].generation == (g)) +#define labelfreed(n,g) (!xref_table[(n)].used) + +/* + * The following routine returns a reference to an object existing + * only in the input file. It does this as follows. If the object + * has never been referenced before, it reads the object + * in and creates a reference to it. Then it writes + * the object out, keeping the existing reference. If the + * object has been read in (and written out) before, it simply + * returns the retained existing reference to that object + */ +static pdf_obj * +pdf_ref_file_obj (unsigned long obj_num, unsigned obj_gen) +{ + pdf_obj *obj, *ref; + + if (!checklabel(obj_num, obj_gen)) { + WARN("Can't resolve object: %lu %u", + obj_num, obj_gen); + return NULL; + } + ref = xref_table[obj_num].indirect; + if (ref != NULL) + return pdf_link_obj(ref); + + obj = pdf_read_object(obj_num, obj_gen); + if (!obj) { + WARN("Could not read object: %lu %u", + obj_num, obj_gen); + return NULL; + } + + ref = pdf_ref_obj(obj); + xref_table[obj_num].indirect = ref; + xref_table[obj_num].direct = obj; + /* Make sure the caller doesn't free this object */ + + return pdf_link_obj(ref); +} + + +pdf_obj * +pdf_new_ref (unsigned long obj_num, int obj_gen) +{ + pdf_obj *result; + pdf_indirect *indirect; + + if (!checklabel(obj_num, obj_gen)) { + WARN("Invalid object label: %lu %d", obj_num, obj_gen); + return NULL; + } + result = pdf_new_obj(PDF_INDIRECT); + indirect = NEW(1, pdf_indirect); + result->data = indirect; + indirect->label = obj_num; + indirect->generation = obj_gen; + indirect->dirty = 1; + indirect->dirty_file = pdf_input_file; + + return result; +} + + +/* Label without corresponding object definition should + * be replaced with "null". But we won't do that. + */ +static pdf_obj * +pdf_read_object (unsigned long obj_num, unsigned obj_gen) +{ + pdf_obj *result; + long offset, limit, length; + char *buffer, *p, *endptr; + + if (!checklabel(obj_num, obj_gen)) { + WARN("Trying to read nonexistent object: %lu %u", + obj_num, obj_gen); + return NULL; + } + if (labelfreed(obj_num, obj_gen)) { + WARN("Trying to read deleted object: %lu %u", + obj_num, obj_gen); + return NULL; + } + offset = xref_table[obj_num].file_position; + limit = next_object_offset(obj_num); + length = limit - offset; + + if (length <= 0) + return NULL; + + buffer = NEW(length + 1, char); + + seek_absolute(pdf_input_file, offset); + fread(buffer, sizeof(char), length, pdf_input_file); + + p = buffer; + endptr = p + length; + + /* Check for obj_num and obj_gen */ + { + char *q = p; /* <== p */ + char *sp; + unsigned long 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 (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); + + 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; +} + +/* pdf_deref_obj always returns a link instead of the original */ +pdf_obj * +pdf_deref_obj (pdf_obj *obj) +{ + pdf_obj *result; + + if (obj == NULL) + return NULL; + else if (pdf_obj_typeof(obj) != PDF_INDIRECT) { + return pdf_link_obj(obj); + } + +#define OBJ_NUM(o) (((pdf_indirect *)((o)->data))->label) +#define OBJ_GEN(o) (((pdf_indirect *)((o)->data))->generation) +#define ISDIRTY(o) (((pdf_indirect *)((o)->data))->dirty) +#define OBJ_SRC(o) (((pdf_indirect *)((o)->data))->dirty_file) + if (!ISDIRTY(obj)) + ERROR("Tried to deref a non-file object"); + result = pdf_read_object(OBJ_NUM(obj), OBJ_GEN(obj)); + while (result && + pdf_obj_typeof(result) == PDF_INDIRECT + ) { + pdf_obj *tmp; + + tmp = pdf_read_object(OBJ_NUM(result), OBJ_GEN(result)); + pdf_release_obj(result); + result = tmp; + } + + return result; +} + +static void +extend_xref (long new_size) +{ + unsigned long i; + + xref_table = RENEW(xref_table, new_size, struct object); + for (i = num_input_objects; i < new_size; i++) { + xref_table[i].direct = NULL; + xref_table[i].indirect = NULL; + xref_table[i].used = 0; + xref_table[i].file_position = 0L; + } + num_input_objects = new_size; +} + +static int +parse_xref (void) +{ + unsigned long first, size; + unsigned long i, offset; + unsigned int obj_gen; + char flag; + int r; + + /* + * This routine reads one xref segment. It must be called positioned + * at the beginning of an xref table. It may be called multiple times + * on the same file. xref tables sometimes come in pieces. + */ + mfgets(work_buffer, WORK_BUFFER_SIZE, pdf_input_file); + if (memcmp(work_buffer, "xref", strlen("xref"))) { + WARN("No xref. Are you sure this is a PDF file?"); + return 0; + } + /* Next line in file has first item and size of table */ + for (;;) { + unsigned long current_pos; + + current_pos = tell_position(pdf_input_file); + if (mfgets(work_buffer, WORK_BUFFER_SIZE, pdf_input_file) == NULL) + ERROR("parse_xref: premature end of PDF file while parsing xref"); + if (!strncmp(work_buffer, "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. + */ + seek_absolute(pdf_input_file, current_pos); + break; + } + sscanf(work_buffer, "%lu %lu", &first, &size); + if (num_input_objects < first + size) { + extend_xref (first + size); + } + + for (i = first; i < first + size; i++) { + fread(work_buffer, sizeof(char), 20, pdf_input_file); + /* + * 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. + */ +#define POSITION_UNSET(t,n) ((t)[(n)].file_position == 0) +#define SET_XREF_ENTRY(t,n,o,g) \ +do { \ + (t)[(n)].file_position = (o); \ + (t)[(n)].generation = (g); \ +} while (0) +#define SET_XREF_FLAG(t,n,f) ((t)[(n)].used = ((f) == 'n') ? 1 : 0) + work_buffer[19] = 0; + offset = 0UL; obj_gen = 0; flag = 0; + r = sscanf(work_buffer, "%010lu %05u %c", &offset, &obj_gen, &flag); + if ( r != 3 || + ((flag != 'n' && flag != 'f') || + (flag == 'n' && + (offset >= pdf_file_size || (offset > 0 && offset < 4))))) { + WARN("Invalid xref table entry [%lu]. PDF file is corrupt...", i); + return 0; + } + if (POSITION_UNSET(xref_table, i)) + SET_XREF_ENTRY(xref_table, i, offset, obj_gen); + SET_XREF_FLAG(xref_table, i, flag); + } + } + + return 1; +} + +static pdf_obj * +read_xref (void) +{ + pdf_obj *main_trailer; + long xref_pos; + + xref_pos = find_xref(); + if (xref_pos == 0) { + WARN("Can't find xref table."); + return NULL; + } + + /* Read primary xref table */ + seek_absolute(pdf_input_file, xref_pos); + if (!parse_xref()) { + WARN("Couldn't read xref table. Is this a correct PDF file?"); + return NULL; + } + main_trailer = parse_trailer(); + if (main_trailer == NULL) { + WARN("Couldn't read xref trailer. Is this a correct PDF file?"); + return NULL; + } + + { + pdf_obj *xref_size; + + xref_size = pdf_lookup_dict(main_trailer, "Size"); + if (xref_size == NULL || + pdf_lookup_dict(main_trailer, "Root") == NULL) { + WARN("Trailer doesn't have catalog or a size. Is this a correct PDF file?"); + return NULL; + } + if (num_input_objects < pdf_number_value(xref_size)) { + extend_xref((long)pdf_number_value(xref_size)); + } + } + + /* Read any additional xref tables */ + { + pdf_obj *prev_trailer, *prev_xref; + + prev_trailer = pdf_link_obj(main_trailer); + while ((prev_xref = pdf_lookup_dict(prev_trailer, "Prev")) != NULL) { + xref_pos = (long) pdf_number_value(prev_xref); + seek_absolute(pdf_input_file, xref_pos); + pdf_release_obj(prev_trailer); + if (!parse_xref()) { + WARN("Couldn't read xref table. Is this a correct PDF file?"); + return NULL; + } + prev_trailer = parse_trailer(); + if (prev_trailer == NULL) { + WARN("Couldn't read xref trailer. Is this a correct PDF file?"); + return NULL; + } + } + pdf_release_obj(prev_trailer); + } + + return main_trailer; +} + +static char any_open = 0; + +pdf_obj * +pdf_open (FILE *file) +{ + pdf_obj *trailer; + + if (any_open) { + WARN("Only one PDF file may be open at one time."); + exit(1); + } + pdf_input_file = file; + if (!check_for_pdf(pdf_input_file)) { + WARN("pdf_open: Not a PDF 1.[1-3] file."); + return NULL; + } + if ((trailer = read_xref()) == NULL) { + WARN("No trailer."); + pdf_close(); + return NULL; + } + any_open = 1; + + return trailer; +} + +void +pdf_close (void) +{ + unsigned long i; + int done; + + /* + * Following loop must be iterated because each write could trigger + * an additional indirect reference of an object with a lower number! + */ + do { + done = 1; + for (i = 0; i < num_input_objects; i++) { + if (xref_table[i].direct != NULL) { + pdf_release_obj(xref_table[i].direct); + xref_table[i].direct = NULL; + done = 0; + } + } + } while (!done); + /* + * Now take care of the indirect objects. They had to be left around + * until all the direct objects were flushed. + */ + for (i = 0; i < num_input_objects; i++) { + if (xref_table[i].indirect != NULL) { + pdf_release_obj(xref_table[i].indirect); + } + } + RELEASE(xref_table); + xref_table = NULL; + num_input_objects = 0; + any_open = 0; + pdf_input_file = NULL; +} + +int +check_for_pdf (FILE *file) +{ + int result = 0; + + rewind(file); + if (fread(work_buffer, sizeof(char), strlen("%PDF-1.x"), file) == + strlen("%PDF-1.x") && + !strncmp(work_buffer, "%PDF-1.", strlen("%PDF-1."))) { + if (work_buffer[7] >= '0' && work_buffer[7] <= '0' + pdf_version) + result = 1; + else { + WARN("Version of PDF file (1.%c) is newer than version limit specification.", + work_buffer[7]); + } + } + + return result; +} + +static int CDECL +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; +} + +pdf_obj * +pdf_import_object (pdf_obj *object) +{ + pdf_obj *imported; + pdf_obj *tmp; + int i; + + switch (pdf_obj_typeof(object)) { + + case PDF_INDIRECT: + if (ISDIRTY(object)) { + if (OBJ_SRC(object) != pdf_input_file) { + WARN("Different file is opened before object is imported?"); + return NULL; + } + imported = pdf_ref_file_obj(OBJ_NUM(object), OBJ_GEN(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; +} + + +int +pdf_compare_reference (pdf_obj *ref1, pdf_obj *ref2) +{ + pdf_indirect *data1, *data2; + + if (!PDF_OBJ_INDIRECTTYPE(ref1) || + !PDF_OBJ_INDIRECTTYPE(ref2)) { + ERROR("Not indirect reference..."); + } + + data1 = (pdf_indirect *) ref1->data; + data2 = (pdf_indirect *) ref2->data; + + if (data1->dirty != data2->dirty) + return (int) (data1->dirty - data2->dirty); + if (data1->dirty_file != data2->dirty_file) + return (int) (data1->dirty_file - data2->dirty_file); + if (data1->label != data2->label) + return (int) (data1->label - data2->label); + if (data1->generation != data2->generation) + return (int) (data1->generation - data2->generation); + + return 0; +} |