I've updated the token writer based on Jose's comments, and made a few other minor changes. This version is ready for merging.
I'm working on updating the architecture guide, and will document the new constants there. -- Michael
# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: [email protected] # target_branch: file:///home/michael/src/%2Blocal/gnupdf/trunk/ # testament_sha1: fb266394f7c26838f80b89354244f5ab02057eea # timestamp: 2009-10-29 15:41:19 -0400 # base_revision_id: [email protected] # # Begin patch === modified file 'ChangeLog' --- ChangeLog 2009-10-28 22:23:59 +0000 +++ ChangeLog 2009-10-29 08:13:10 +0000 @@ -1,3 +1,21 @@ +2009-10-29 Michael Gold <[email protected]> + + * src/base/pdf-token-writer.c: New file. + + * src/base/pdf-token-writer.h: New file. + + * src/Makefile.am: Build the token writer. + + * src/base/pdf-stm.c (pdf_stm_write): Mark 'buf' as const. + + * src/base/pdf-stm.h: Likewise. + + * doc/gnupdf.texi: Update tokeniser documentation. + + * utils/pdf-tokeniser.c: Optionally use the token writer for output. + + * utils/pdf-tokeniser.h: Likewise. + 2009-10-28 Jose E. Marchesi <[email protected]> * gnulib modules updated and unistr/u8-check imported. === modified file 'doc/gnupdf.texi' --- doc/gnupdf.texi 2009-10-28 20:45:55 +0000 +++ doc/gnupdf.texi 2009-10-29 08:13:10 +0000 @@ -4506,7 +4506,7 @@ @end table @end deftypefun -...@deftypefun pdf_status_t pdf_stm_write (pdf_stm_t @var{stm}, pdf_char_t *...@var{buf}, pdf_size_t @var{bytes}, pdf_size_t *...@var{written_bytes}) +...@deftypefun pdf_status_t pdf_stm_write (pdf_stm_t @var{stm}, const pdf_char_t *...@var{buf}, pdf_size_t @var{bytes}, pdf_size_t *...@var{written_bytes}) Write a chunk of data into a given stream. @@ -10113,6 +10113,8 @@ @table @code @item PDF_OK The token reader was destroyed. +...@item PDF_EBADDATA +...@var{reader} was NULL. @end table @item Usage example @example @@ -10141,6 +10143,8 @@ @table @code @item PDF_OK The token writer was destroyed. +...@item PDF_EBADDATA +...@var{writer} was NULL. @end table @item Usage example @example @@ -10276,6 +10280,25 @@ @end table @end deftypefun +...@deftypefun pdf_status_t pdf_token_writer_reset (pdf_token_writer_t @var{writer}) + +Reset the state of the token writer. + +...@table @strong +...@item Parameters +...@table @var +...@item reader +A token writer. +...@end table +...@item Returns +A PDF status value: +...@table @code +...@item PDF_OK +The operation succeeded. +...@end table +...@end table +...@end deftypefun + @node Creating and destroying tokens @subsection Creating and destroying tokens === modified file 'src/Makefile.am' --- src/Makefile.am 2009-06-27 23:19:51 +0000 +++ src/Makefile.am 2009-10-25 18:06:21 +0000 @@ -84,7 +84,8 @@ base/pdf-fp-func.h base/pdf-fp-func.c TOKEN_MODULE_SOURCES = base/pdf-token.c base/pdf-token.h \ - base/pdf-token-reader.c base/pdf-token-reader.h + base/pdf-token-reader.c base/pdf-token-reader.h \ + base/pdf-token-writer.c base/pdf-token-writer.h BASE_LAYER_SOURCES = base/pdf-base.c base/pdf-base.h \ $(ALLOC_MODULE_SOURCES) \ @@ -143,7 +144,8 @@ base/pdf-crypt.h \ base/pdf-fp-func.h \ base/pdf-token.h \ - base/pdf-token-reader.h + base/pdf-token-reader.h \ + base/pdf-token-writer.h pdf.h : $(PUBLIC_HDRS) chmod +x $(top_builddir)/src/extract-public-hdr === modified file 'src/base/pdf-stm.c' --- src/base/pdf-stm.c 2009-10-28 20:45:55 +0000 +++ src/base/pdf-stm.c 2009-10-29 08:13:10 +0000 @@ -1,4 +1,4 @@ -/* -*- mode: C -*- Time-stamp: "09/10/28 21:28:26 jemarch" +/* -*- mode: C -*- Time-stamp: "2009-10-29 03:45:34 mgold" * * File: pdf-stm.c * Date: Fri Jul 6 18:43:15 2007 @@ -212,7 +212,7 @@ pdf_status_t pdf_stm_write (pdf_stm_t stm, - pdf_char_t *buf, + const pdf_char_t *buf, pdf_size_t bytes, pdf_size_t *written_bytes) { === modified file 'src/base/pdf-stm.h' --- src/base/pdf-stm.h 2009-10-28 20:45:55 +0000 +++ src/base/pdf-stm.h 2009-10-29 08:13:10 +0000 @@ -1,4 +1,4 @@ -/* -*- mode: C -*- Time-stamp: "09/10/28 21:26:53 jemarch" +/* -*- mode: C -*- Time-stamp: "2009-10-29 03:45:34 mgold" * * File: pdf-stm.h * Date: Fri Jul 6 18:37:57 2007 @@ -111,7 +111,7 @@ pdf_size_t bytes, pdf_size_t *read_bytes); pdf_status_t pdf_stm_write (pdf_stm_t stm, - pdf_char_t *buf, + const pdf_char_t *buf, pdf_size_t bytes, pdf_size_t *written_bytes); pdf_status_t pdf_stm_read_char (pdf_stm_t stm, pdf_char_t *c); === added file 'src/base/pdf-token-writer.c' --- src/base/pdf-token-writer.c 1970-01-01 00:00:00 +0000 +++ src/base/pdf-token-writer.c 2009-10-29 16:52:54 +0000 @@ -0,0 +1,887 @@ +/* -*- mode: C -*- Time-stamp: "2009-10-29 16:51:18 mgold" + * + * File: pdf-token-writer.c + * Date: Wed Sep 23 04:37:51 2009 + * + * GNU PDF Library - Stream token writer + * + */ + +/* Copyright (C) 2009 Free Software Foundation, Inc. */ + +/* 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <locale.h> +#include <inttypes.h> +#include <math.h> + +#include <pdf-token-writer.h> +#include <unistr.h> + +pdf_status_t +pdf_token_writer_new (pdf_stm_t stm, pdf_token_writer_t *writer) +{ + pdf_status_t err; + pdf_token_writer_t new_tokw; + + err = PDF_ENOMEM; + new_tokw = pdf_alloc (sizeof (*new_tokw)); + if (!new_tokw) + goto fail; + + /* determine the current locale's decimal point + * (avoid using localeconv since it may not be thread-safe) */ + new_tokw->decimal_point = NULL; + { + int len; + char decpt[16]; + + err = PDF_ERROR; + len = snprintf (decpt, sizeof (decpt), "%#.0f", 1.0); + if (len <= 0 || (pdf_size_t)len >= sizeof (decpt)) /* shouldn't happen */ + goto fail; + + err = PDF_ENOMEM; + new_tokw->decimal_point = pdf_alloc (len); + if (!new_tokw->decimal_point) + goto fail; + + /* this copies the trailing '\0' due to the starting offset */ + memcpy (new_tokw->decimal_point, &decpt[1], len); + } + + /* set max_line_length to 0 for no maximum */ + new_tokw->max_line_length = PDF_TOKW_MAX_LINE_LENGTH; + + err = PDF_ENOMEM; + new_tokw->buffer = pdf_buffer_new (PDF_TOKW_BUFFER_SIZE); + if (!new_tokw->buffer) + goto fail; + + err = PDF_EBADDATA; + if (!stm || pdf_stm_get_mode (stm) != PDF_STM_WRITE) + goto fail; + new_tokw->stream = stm; + + pdf_token_writer_reset (new_tokw); + + *writer = new_tokw; + return PDF_OK; + +fail: + if (new_tokw) + pdf_dealloc (new_tokw->decimal_point); + pdf_dealloc (new_tokw); + + return err; +} + +pdf_status_t +pdf_token_writer_reset (pdf_token_writer_t writer) +{ + writer->stage = 0; + writer->in_keyword = PDF_FALSE; + writer->line_length = 0; + return PDF_OK; +} + +pdf_status_t +pdf_token_writer_destroy (pdf_token_writer_t writer) +{ + if (!writer) return PDF_EBADDATA; + + assert (writer->buffer); + if (writer->buffer) + pdf_buffer_destroy (writer->buffer); + pdf_dealloc (writer->decimal_point); + pdf_dealloc (writer); + + return PDF_OK; +} + + +/***** Unbuffered output *****/ + +/* Write data to the stream. All output passes through this function. */ +static pdf_status_t +write_data (pdf_token_writer_t writer, const pdf_char_t *data, + pdf_size_t len, pdf_size_t *written) +{ + pdf_size_t i; + pdf_status_t rv; + + rv = pdf_stm_write (writer->stream, data, len, written); + if (rv != PDF_OK) + return rv; + + for (i = 0; i < *written; ++i) + { + pdf_char_t ch = data[i]; + ++writer->line_length; + if (pdf_is_eol_char (ch)) + writer->line_length = 0; + + writer->in_keyword = pdf_is_regular_char (ch); + } + return PDF_OK; +} + +/* Write a single character. */ +static INLINE pdf_status_t +write_char (pdf_token_writer_t writer, pdf_char_t ch) +{ + pdf_size_t written; + return write_data (writer, &ch, 1, &written); +} + +/* Write data starting at writer->pos, incrementing writer->pos as needed. */ +static INLINE pdf_status_t +write_data_using_pos (pdf_token_writer_t writer, + const pdf_char_t *data, pdf_size_t len) +{ + pdf_status_t rv; + pdf_size_t written; + if (writer->pos > len) + return PDF_EBADDATA; + + while (writer->pos < len) + { + rv = write_data (writer, data + writer->pos, len - writer->pos, + &written); + if (rv != PDF_OK) + return rv; + + writer->pos += written; + } + return PDF_OK; +} + + +/***** Buffered output, buffer management *****/ + +/* Write all buffered data to the stream. */ +static pdf_status_t +flush_buffer (pdf_token_writer_t writer) +{ + pdf_buffer_t buf = writer->buffer; + pdf_size_t len; + while ( (len = buf->wp - buf->rp) > 0 ) + { + pdf_size_t written; + pdf_status_t rv = write_data (writer, + buf->data + buf->rp, + len, &written); + if (rv != PDF_OK) + return rv; + + buf->rp += written; + } + return pdf_buffer_rewind (buf); +} + +/* Flush the buffer if there are less than 'len' bytes free. */ +static INLINE pdf_status_t +reserve_buffer_space (pdf_token_writer_t writer, pdf_size_t len) +{ + if (writer->buffer->wp + len > writer->buffer->size) + { + pdf_status_t rv = flush_buffer (writer); + if (rv != PDF_OK) + return rv; + + assert (len < writer->buffer->size); + assert (writer->buffer->wp == 0); + } + return PDF_OK; +} + +/* Write a character into the buffer; this assumes it will fit. */ +static INLINE void +write_buffered_char_nocheck (pdf_token_writer_t writer, pdf_char_t ch) +{ + writer->buffer->data[writer->buffer->wp++] = ch; + if (pdf_is_eol_char(ch)) + writer->buffered_line_length = 0; + else + ++writer->buffered_line_length; +} + +/* Write a character into the buffer. The buffer is flushed only if + * there's no room to write the character. */ +static INLINE pdf_status_t +write_buffered_char (pdf_token_writer_t writer, pdf_char_t ch) +{ + pdf_status_t rv = reserve_buffer_space (writer, 1); + if (rv == PDF_OK) + write_buffered_char_nocheck (writer, ch); + return rv; +} + + +/***** Misc. utility functions *****/ + +/* Takes a number from 0 to 15 and returns the ASCII code for the + * corresponding hexadecimal digit. */ +static INLINE pdf_char_t +hexchar (pdf_char_t value) +{ + if (value < 10) + return 48 + value; /* '0'--'9' */ + else if (value < 16) + return 65 + value - 10; /* 'A'--'F' */ + return 255; +} + +/* Prepare to write a new token, adding some whitespace if necessary. */ +static pdf_status_t +start_token (pdf_token_writer_t writer, pdf_bool_t need_wspace, + pdf_size_t len) +{ + pdf_bool_t add_wspace = (need_wspace && writer->in_keyword); + pdf_char_t wspace_char = 32; /* space */ + + if (add_wspace) + ++len; + + /* If the token would make this line too long, start a new line. */ + if (writer->line_length + len > writer->max_line_length + && writer->max_line_length > 0) + { + add_wspace = PDF_TRUE; + wspace_char = 10; /* newline */ + } + + if (add_wspace) + { + pdf_status_t rv = write_char (writer, wspace_char); + if (rv != PDF_OK) return rv; + } + + return PDF_OK; +} + + +/***** Numeric tokens *****/ + +/* Encode snprintf output for PDF. 'len' is the return value of snprintf. + * Re-encodes bytes 0 to 'len' of writer->buffer and resets buffer->rp/wp. */ +static pdf_bool_t +encode_buffer_number (pdf_token_writer_t writer, int len) +{ + pdf_buffer_t buf = writer->buffer; + if (len < 0 || len >= buf->size) + return PDF_FALSE; /* snprintf failed, or truncated its output. */ + + buf->wp = buf->rp = 0; + while (buf->rp < len) + { + char ch = (char)buf->data[buf->rp]; + if (ch == '-') + { + ++buf->rp; + buf->data[buf->wp++] = 45; /* '-' */ + } + else if (ch >= '0' && ch <= '9') + { + ++buf->rp; + buf->data[buf->wp++] = 48 + (ch - '0'); + } + else + { + /* This should be a decimal point; check it. */ + pdf_size_t declen = strlen (writer->decimal_point); + int cmp = memcmp (buf->data + buf->rp, + writer->decimal_point, declen); + if (cmp != 0) + return PDF_FALSE; /* unexpected char */ + + buf->rp += declen; + buf->data[buf->wp++] = 46; /* '.' */ + } + } + buf->rp = 0; + return PDF_TRUE; /* success */ +} + +static INLINE pdf_status_t +write_integer_token (pdf_token_writer_t writer, pdf_token_t token) +{ + pdf_status_t rv; + switch (writer->stage) + { + case 0: + { + pdf_i32_t value = pdf_token_get_integer_value (token); + int len = snprintf ((char*)writer->buffer->data, + writer->buffer->size, "%"PRId32, value); + if (!encode_buffer_number (writer, len)) return PDF_ERROR; + } + ++writer->stage; /* fall through */ + case 1: + rv = start_token (writer, PDF_TRUE /*need_wspace*/, + writer->buffer->wp); + if (rv != PDF_OK) return rv; + ++writer->stage; /* fall through */ + case 2: + return flush_buffer (writer); + default: + return PDF_EBADDATA; + } +} + +static INLINE pdf_status_t +write_real_token (pdf_token_writer_t writer, pdf_token_t token) +{ + pdf_status_t rv; + switch (writer->stage) + { + case 0: + { + pdf_buffer_t buf = writer->buffer; + pdf_real_t value = pdf_token_get_real_value (token); + if (isnan(value) || isinf(value)) + return PDF_EBADDATA; + + /* The '#' flag forces snprintf to write a decimal point. */ + int len = snprintf ((char*)buf->data, + buf->size, "%#f", (double)value); + if (!encode_buffer_number (writer, len)) return PDF_ERROR; + + /* strip trailing zeroes */ + while (buf->wp && buf->data[buf->wp-1] == 48 /* '0' */) + --buf->wp; + } + ++writer->stage; /* fall through */ + case 1: + rv = start_token (writer, PDF_TRUE /*need_wspace*/, + writer->buffer->wp); + if (rv != PDF_OK) return rv; + ++writer->stage; /* fall through */ + case 2: + return flush_buffer (writer); + default: + return PDF_EBADDATA; + } +} + + +/***** String tokens *****/ + +static INLINE pdf_bool_t +should_escape_strchar (pdf_u32_t flags, pdf_char_t ch, + pdf_bool_t quote_parens, pdf_bool_t is_utf8) +{ + if (ch == 92 /* '\\' */ || ch == 13 /* CR */) + return PDF_TRUE; + if (ch == 40 /* '(' */ || ch == 41 /* ')' */) + return quote_parens; + + if (flags & PDF_TOKEN_READABLE_STRINGS) + { + if (ch == 127 || (ch < 32 && ch != 10) + || (ch >= 128 && !is_utf8)) + return PDF_TRUE; + } + + return PDF_FALSE; +} + +static INLINE int +str_escape_len (const pdf_char_t *data, pdf_size_t len, pdf_size_t pos) +{ + switch (data[pos]) + { + /* characters with two-character escape codes */ + case 8: + case 9: + case 10: + case 12: + case 13: + case 40: + case 41: + case 92: + return 2; + } + + if (data[pos] >= 0100) + return 4; /* escaped using a backslash and 3 octal characters */ + + if (pos+1 < len) + { + if (data[pos+1] >= 48 && data[pos+1] <= 57) /* '0'..'9' */ + return 4; /* need to write a 3-character octal number */ + } + + return (data[pos] >= 010) ? 3 : 2; +} + +static void +scan_string (pdf_token_writer_t writer, pdf_u32_t flags, + const pdf_char_t *data, pdf_size_t len, pdf_bool_t *use_hex) +{ + /* Match parentheses, and determine the portion of the string + * in which they should be quoted. */ + writer->paren_quoting_start = 0; + writer->paren_quoting_end = len; + pdf_size_t i, j; + for (i = 0; i < len; ++i) + { + if (data[i] == 40) /* '(' */ + { + for (j = writer->paren_quoting_end - 1; j > i; --j) + { + /* find a matching ')' */ + if (data[j] == 41) + { + writer->paren_quoting_end = j; + writer->paren_quoting_start = i + 1; + break; + } + } + } + } + + /* Determine the size of the escaped string. */ + writer->utf8 = (flags & PDF_TOKEN_READABLE_STRINGS) + && (u8_check (data, len) == NULL); + pdf_size_t enc_bytes = 0; + for (i = 0; i < len; ++i) + { + pdf_bool_t quote_parens = (i >= writer->paren_quoting_start + && i < writer->paren_quoting_end); + if (should_escape_strchar (flags, data[i], quote_parens, writer->utf8)) + enc_bytes += str_escape_len (data, len, i); + else + ++enc_bytes; + } + *use_hex = (enc_bytes > len*2); +} + +static INLINE pdf_status_t +write_string_char (pdf_token_writer_t writer, pdf_u32_t flags, + const pdf_char_t *data, pdf_char_t len, + pdf_size_t pos) +{ + assert (len > 0); + pdf_status_t rv; + const pdf_char_t *output = data + pos; + pdf_size_t outlen = 1; + pdf_char_t esc[4] = {92 /* '\\' */, 0, 0, 0}; + + pdf_char_t ch = data[pos]; + pdf_bool_t quote_parens = (pos >= writer->paren_quoting_start + && pos < writer->paren_quoting_end); + if (should_escape_strchar (flags, ch, quote_parens, writer->utf8)) + { + /* escape the character */ + output = esc; + outlen = 2; + switch (ch) + { + case 8: esc[1] = 98; break; /* 'b' */ + case 9: esc[1] = 116; break; /* 't' */ + case 10: esc[1] = 110; break; /* 'n' */ + case 12: esc[1] = 102; break; /* 'f' */ + case 13: esc[1] = 114; break; /* 'r' */ + case 40: /* '('; fall through */ + case 41: /* ')'; fall through */ + case 92: /* '\\' */ + esc[1] = ch; + break; + default: /* use an octal escape */ + { + pdf_size_t digits; + pdf_char_t nextch = (pos+1 < len) ? data[pos+1] : 0; + if (nextch >= 48 && nextch <= 57) /* '0'..'9' */ + digits = 3; /* must use 3 octal characters */ + else if (ch > 0100) digits = 3; + else if (ch > 010) digits = 2; + else digits = 1; + + outlen = 1; + switch (digits) + { + /* fall through each case */ + case 3: esc[outlen++] = hexchar (ch / 0100); + case 2: esc[outlen++] = hexchar ((ch % 0100) / 010); + case 1: esc[outlen++] = hexchar (ch % 010); + } + } + } + } + + /* If the line will be too long, split it (the length cannot be equal to + * the maximum, since this would leave no room for the backslash). */ + if (writer->max_line_length > 0 && !pdf_is_eol_char (output[0]) + && writer->buffered_line_length + outlen >= writer->max_line_length) + { + rv = reserve_buffer_space (writer, 2); + if (rv != PDF_OK) return rv; + write_buffered_char_nocheck (writer, 92); /* '\\' */ + write_buffered_char_nocheck (writer, 10); /* newline */ + assert (writer->buffered_line_length == 0); + } + + rv = reserve_buffer_space (writer, outlen); + if (rv == PDF_OK) + { + pdf_size_t i; + for (i = 0; i < outlen; ++i) + write_buffered_char_nocheck (writer, output[i]); + } + return rv; +} + +static INLINE pdf_status_t +write_string_token (pdf_token_writer_t writer, pdf_u32_t flags, + pdf_token_t token) +{ + pdf_status_t rv; + const pdf_char_t *data = pdf_token_get_string_data (token); + pdf_size_t size = pdf_token_get_string_size (token); + + switch (writer->stage) + { + case 0: + { + pdf_bool_t use_hex = (flags & PDF_TOKEN_HEX_STRINGS); + if (!use_hex) + scan_string (writer, flags, data, size, &use_hex); + + if (use_hex) + goto hexstring_start; + } + ++writer->stage; /* fall through */ + case 1: + { + /* Passing a correct length to start_token isn't important + * since we can split the string across multiple lines. */ + pdf_size_t dummy_len = PDF_MIN(20, 2 + size); + rv = start_token (writer, PDF_FALSE /*need_wspace*/, dummy_len); + if (rv != PDF_OK) return rv; + } + + pdf_buffer_rewind (writer->buffer); + writer->buffered_line_length = writer->line_length; + write_buffered_char_nocheck (writer, 40 /* '(' */); + writer->pos = 0; + ++writer->stage; /* fall through */ + case 2: + while (writer->pos < size) + { + rv = write_string_char (writer, flags, data, size, writer->pos); + if (rv != PDF_OK) return rv; + + ++writer->pos; + } + ++writer->stage; /* fall through */ + case 3: + rv = write_buffered_char (writer, 41 /* ')' */); + if (rv != PDF_OK) return rv; + ++writer->stage; /* fall through */ + case 4: + return flush_buffer (writer); + + /*** hex strings ***/ +hexstring_start: + writer->stage = 101; + case 101: + { + pdf_size_t dummy_len = PDF_MIN(20, 2 + size*2); + rv = start_token (writer, PDF_FALSE /*need_wspace*/, dummy_len); + if (rv != PDF_OK) return rv; + } + + pdf_buffer_rewind (writer->buffer); + writer->buffered_line_length = writer->line_length; + write_buffered_char_nocheck (writer, 60 /* '<' */); + writer->pos = 0; + ++writer->stage; /* fall through */ + case 102: + while (writer->pos < size) + { + /* If this line would be too long, start a new one. */ + if (writer->buffered_line_length + 2 > writer->max_line_length + && writer->max_line_length > 0) + { + rv = write_buffered_char (writer, 10); /* newline */ + if (rv != PDF_OK) return rv; + assert (writer->buffered_line_length == 0); + } + + pdf_char_t ch = data[writer->pos]; + rv = reserve_buffer_space (writer, 2); + if (rv != PDF_OK) return rv; + + write_buffered_char_nocheck (writer, hexchar (ch / 16)); + if (writer->pos == size-1 && (ch%16) == 0) + ; /* don't write a final 0 */ + else + write_buffered_char_nocheck (writer, hexchar (ch % 16)); + ++writer->pos; + } + ++writer->stage; /* fall through */ + case 103: + rv = write_buffered_char (writer, 62 /* '>' */); + if (rv != PDF_OK) return rv; + ++writer->stage; /* fall through */ + case 104: + return flush_buffer (writer); + default: + return PDF_EBADDATA; + } +} + + +/***** Other tokens *****/ + +static INLINE pdf_status_t +should_escape_namechar (pdf_u32_t flags, pdf_char_t ch, pdf_bool_t *escape) +{ + if (!ch) + return PDF_EBADDATA; + + *escape = !pdf_is_regular_char (ch); + if (flags & PDF_TOKEN_NO_NAME_ESCAPES) + { + if (*escape) + return PDF_EBADDATA; + } + else + { + *escape = *escape || ch == 35 /* '#' */ + || ch < 33 || ch >= 127; + } + return PDF_OK; +} + +static INLINE pdf_status_t +write_name_token (pdf_token_writer_t writer, pdf_u32_t flags, + pdf_token_t token) +{ + pdf_status_t rv; + pdf_size_t size = pdf_token_get_name_size (token); + const pdf_char_t *data = pdf_token_get_name_data (token); + switch (writer->stage) + { + case 0: + /* Validate the name; also calculate the encoded size + * and store it in ->pos temporarily. */ + writer->pos = 1 + size; + { + pdf_size_t i; + for (i = 0; i < size; ++i) + { + pdf_bool_t escape; + rv = should_escape_namechar (flags, data[i], &escape); + if (rv != PDF_OK) return rv; /* bad name */ + + if (escape) + writer->pos += 2; /* 2 hex characters */ + } + } + pdf_buffer_rewind (writer->buffer); + write_buffered_char_nocheck (writer, 47 /* '/' */); + ++writer->stage; /* fall through */ + case 1: + rv = start_token (writer, PDF_FALSE /*need_wspace*/, + writer->pos /* encoded token length */); + if (rv != PDF_OK) return rv; + + writer->pos = 0; + ++writer->stage; /* fall through */ + case 2: + while (writer->pos < size) + { + pdf_bool_t escape; + pdf_char_t ch = data[writer->pos]; + rv = should_escape_namechar (flags, ch, &escape); + if (rv != PDF_OK) return rv; /* bad name */ + + if (escape) + { + rv = reserve_buffer_space (writer, 3); + if (rv != PDF_OK) return rv; + + write_buffered_char_nocheck (writer, 35 /* '#' */); + write_buffered_char_nocheck (writer, hexchar (ch / 16)); + write_buffered_char_nocheck (writer, hexchar (ch % 16)); + } + else + { + rv = write_buffered_char (writer, ch); + if (rv != PDF_OK) return rv; + } + ++writer->pos; + } + ++writer->stage; /* fall through */ + case 3: + return flush_buffer (writer); + default: + return PDF_EBADDATA; + } +} + +static INLINE pdf_status_t +write_keyword_token (pdf_token_writer_t writer, pdf_token_t token) +{ + const pdf_char_t *data = pdf_token_get_keyword_data (token); + pdf_size_t size = pdf_token_get_keyword_size (token); + pdf_status_t rv; + switch (writer->stage) + { + case 0: + if (memchr (data, 0, size)) + return PDF_EBADDATA; /* data contains a null byte */ + ++writer->stage; /* fall through */ + case 1: + rv = start_token (writer, PDF_TRUE /*need_wspace*/, size); + if (rv != PDF_OK) return rv; + + writer->pos = 0; + ++writer->stage; /* fall through */ + case 2: + return write_data_using_pos (writer, + pdf_token_get_keyword_data (token), + size); + default: + return PDF_EBADDATA; + } +} + +static INLINE pdf_status_t +write_comment_token (pdf_token_writer_t writer, pdf_token_t token) +{ + const pdf_char_t *data = pdf_token_get_comment_data (token); + pdf_size_t size = pdf_token_get_comment_size (token); + pdf_status_t rv; + switch (writer->stage) + { + case 0: + { + /* A comment can't span multiple lines. */ + pdf_size_t i; + for (i = 0; i < size; ++i) + { + if (pdf_is_eol_char(data[i])) + return PDF_EBADDATA; + } + } + ++writer->stage; /* fall through */ + case 1: + rv = start_token (writer, PDF_FALSE /*need_wspace*/, size+1); + if (rv != PDF_OK) return rv; + ++writer->stage; /* fall through */ + case 2: + rv = write_char (writer, 37 /* '%' */); + if (rv != PDF_OK) return rv; + + writer->pos = 0; + ++writer->stage; /* fall through */ + case 3: + rv = write_data_using_pos (writer, data, size); + if (rv != PDF_OK) return rv; + ++writer->stage; /* fall through */ + case 4: + return write_char (writer, 10 /* '\n' */); + default: + return PDF_EBADDATA; + } +} + +static INLINE pdf_status_t +write_valueless_token (pdf_token_writer_t writer, + pdf_char_t ch, pdf_size_t len) +{ + pdf_char_t buf[2] = {ch,ch}; + pdf_status_t rv; + assert (len == 1 || len == 2); + + switch (writer->stage) + { + case 0: + rv = start_token (writer, PDF_FALSE /*need_wspace*/, len); + if (rv != PDF_OK) return rv; + + writer->pos = 0; + ++writer->stage; /* fall through */ + case 1: + return write_data_using_pos (writer, buf, len); + default: + return PDF_EBADDATA; + } +} + + +/***** Token dispatching *****/ + +pdf_status_t +pdf_token_write (pdf_token_writer_t writer, pdf_u32_t flags, pdf_token_t token) +{ + pdf_status_t rv; + switch (pdf_token_get_type (token)) + { + case PDF_TOKEN_INTEGER: + rv = write_integer_token (writer, token); + break; + case PDF_TOKEN_REAL: + rv = write_real_token (writer, token); + break; + case PDF_TOKEN_STRING: + rv = write_string_token (writer, flags, token); + break; + case PDF_TOKEN_NAME: + rv = write_name_token (writer, flags, token); + break; + case PDF_TOKEN_KEYWORD: + rv = write_keyword_token (writer, token); + break; + case PDF_TOKEN_COMMENT: + rv = write_comment_token (writer, token); + break; + case PDF_TOKEN_DICT_START: + rv = write_valueless_token (writer, 60 /* '<' */, 2); + break; + case PDF_TOKEN_DICT_END: + rv = write_valueless_token (writer, 62 /* '>' */, 2); + break; + case PDF_TOKEN_ARRAY_START: + rv = write_valueless_token (writer, 91 /* '[' */, 1); + break; + case PDF_TOKEN_ARRAY_END: + rv = write_valueless_token (writer, 93 /* ']' */, 1); + break; + case PDF_TOKEN_PROC_START: + rv = write_valueless_token (writer, 123 /* '{' */, 1); + break; + case PDF_TOKEN_PROC_END: + rv = write_valueless_token (writer, 125 /* '}' */, 1); + break; + default: + assert (0); + return PDF_ERROR; + } + + if (rv == PDF_OK) + writer->stage = 0; + return rv; +} + +/* End of pdf-token-writer.c */ === added file 'src/base/pdf-token-writer.h' --- src/base/pdf-token-writer.h 1970-01-01 00:00:00 +0000 +++ src/base/pdf-token-writer.h 2009-10-29 17:07:37 +0000 @@ -0,0 +1,76 @@ +/* -*- mode: C -*- Time-stamp: "2009-10-25 18:06:21 mgold" + * + * File: pdf-token-writer.h + * Date: Wed Sep 23 04:30:26 2009 + * + * GNU PDF Library - Stream token writer + * + */ + +/* Copyright (C) 2009 Free Software Foundation, Inc. */ + +/* 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PDF_TOKEN_WRITER_H +#define PDF_TOKEN_WRITER_H + +#include <config.h> + +#include <pdf-types.h> +#include <pdf-stm.h> +#include <pdf-token.h> +#include <pdf-token-reader.h> + +/* BEGIN PUBLIC */ +/* pdf-token-writer.h */ + +struct pdf_token_writer_s; /* opaque type */ +typedef struct pdf_token_writer_s *pdf_token_writer_t; + +pdf_status_t pdf_token_writer_new (pdf_stm_t stm, pdf_token_writer_t *writer); +pdf_status_t pdf_token_writer_destroy (pdf_token_writer_t writer); +pdf_status_t pdf_token_writer_reset (pdf_token_writer_t writer); +pdf_status_t pdf_token_write (pdf_token_writer_t writer, pdf_u32_t flags, + pdf_token_t token); + +/* END PUBLIC */ + +/* Internal state */ +struct pdf_token_writer_s { + pdf_stm_t stream; /* stream to read bytes from */ + char *decimal_point; + + pdf_bool_t in_keyword; + pdf_size_t line_length, buffered_line_length, max_line_length; + + int stage; + pdf_size_t pos; + pdf_size_t paren_quoting_start, paren_quoting_end; + pdf_bool_t utf8; + pdf_buffer_t buffer; +}; + +/* PDF32000 7.5.1: "lines that are not part of stream object data + * are limited to no more than 255 characters"... */ +#define PDF_TOKW_MAX_LINE_LENGTH 255 + +/* The buffer size is mostly arbitrary, but the buffer must be large + * enough for snprintf to write any possible floating point value. + * Any number over 50 should be fine. */ +#define PDF_TOKW_BUFFER_SIZE 32768 + +#endif + +/* End of pdf-token-writer.h */ === modified file 'utils/pdf-tokeniser.c' --- utils/pdf-tokeniser.c 2009-10-21 12:59:20 +0000 +++ utils/pdf-tokeniser.c 2009-10-29 16:52:54 +0000 @@ -1,4 +1,4 @@ -/* -*- mode: C -*- Time-stamp: "09/10/21 14:57:10 jemarch" +/* -*- mode: C -*- Time-stamp: "2009-10-29 15:43:35 mgold" * * File: pdf-tokeniser.c * Date: Wed May 20 05:25:40 2009 @@ -30,6 +30,7 @@ #include <stdlib.h> #include <string.h> #include <getopt.h> +#include <errno.h> #include <pdf.h> #include <pdf-tokeniser.h> @@ -43,6 +44,9 @@ {"help", no_argument, NULL, HELP_ARG}, {"usage", no_argument, NULL, USAGE_ARG}, {"version", no_argument, NULL, VERSION_ARG}, + {"token-writer", no_argument, NULL, TOKW_ARG}, + {"reader-flags", required_argument, NULL, READER_FLAGS_ARG}, + {"writer-flags", required_argument, NULL, WRITER_FLAGS_ARG}, {NULL, 0, NULL, 0} }; @@ -59,6 +63,9 @@ --help print a help message and exit\n\ --usage print a usage message and exit\n\ --version show pdf-tokeniser version and exit\n\ + --token-writer generate output using the token writer\n\ + --reader-flags=INTEGER specify token reader flags\n\ + --writer-flags=INTEGER specify token writer flags\n\ "; char *pdf_tokeniser_help_msg = ""; @@ -183,30 +190,62 @@ }; void -print_file (FILE *file) +print_file (FILE *file, pdf_bool_t use_tokw, + pdf_u32_t reader_flags, pdf_u32_t writer_flags) { pdf_status_t rv; pdf_token_reader_t reader = NULL; + pdf_token_writer_t writer = NULL; pdf_token_t token; - pdf_stm_t stm = NULL; + pdf_stm_t stm_in = NULL; + pdf_stm_t stm_out = NULL; - rv = pdf_stm_cfile_new (file, 0, 0 /*cache_size*/, PDF_STM_READ, &stm); + rv = pdf_stm_cfile_new (file, 0, 0 /*cache_size*/, PDF_STM_READ, &stm_in); if (rv != PDF_OK) { - fprintf(stderr, "failed to create stream\n"); + fprintf(stderr, "failed to create input stream\n"); goto out; } - rv = pdf_token_reader_new(stm, &reader); + rv = pdf_token_reader_new(stm_in, &reader); if (rv != PDF_OK) { fprintf(stderr, "failed to create reader\n"); goto out; } - while (( rv = pdf_token_read(reader, 0, &token) ) == PDF_OK) - { - print_tok(token); + if (use_tokw) + { + rv = pdf_stm_cfile_new (stdout, 0, 0 /*cache_size*/, + PDF_STM_WRITE, &stm_out); + if (rv != PDF_OK) + { + fprintf(stderr, "failed to create output stream\n"); + goto out; + } + + rv = pdf_token_writer_new(stm_out, &writer); + if (rv != PDF_OK) + { + fprintf(stderr, "failed to create writer\n"); + goto out; + } + } + + while (( rv = pdf_token_read(reader, reader_flags, &token) ) == PDF_OK) + { + if (use_tokw) + { + rv = pdf_token_write(writer, writer_flags, token); + if (rv != PDF_OK) + { + fprintf(stderr, "pdf_token_write error %d\n", rv); + goto out; + } + } + else + print_tok(token); + pdf_token_destroy(token); } @@ -218,14 +257,37 @@ fprintf(stderr, "done\n"); out: + if (writer) pdf_token_writer_destroy(writer); + if (stm_out) pdf_stm_destroy(stm_out); if (reader) pdf_token_reader_destroy(reader); - if (stm) pdf_stm_destroy(stm); + if (stm_in) pdf_stm_destroy(stm_in); +} + +pdf_u32_t +parse_u32_arg (const char *argvalue, const char *argname, const char *appname) +{ + char *end; + pdf_u32_t ret; + unsigned long int tmp; + + errno = 0; + tmp = strtoul (argvalue, &end, 0); + ret = (pdf_u32_t)tmp; + if (errno || *end != '\0' || tmp != ret) + { + fprintf (stderr, "%s: invalid argument `%s' for `--%s'\n", + appname, argvalue, argname); + exit (1); + } + return ret; } int main (int argc, char **argv) { char c; + pdf_bool_t use_tokw = PDF_FALSE; + pdf_u32_t reader_flags = 0, writer_flags = 0; /* set_program_name (argv[0]); */ @@ -256,6 +318,21 @@ exit (0); break; } + case TOKW_ARG: + { + use_tokw = 1; + break; + } + case READER_FLAGS_ARG: + { + reader_flags = parse_u32_arg (optarg, "reader-flags", argv[0]); + break; + } + case WRITER_FLAGS_ARG: + { + writer_flags = parse_u32_arg (optarg, "writer-flags", argv[0]); + break; + } default: { break; @@ -264,6 +341,6 @@ } setlocale(LC_ALL, ""); - print_file(stdin); + print_file(stdin, use_tokw, reader_flags, writer_flags); return 0; } === modified file 'utils/pdf-tokeniser.h' --- utils/pdf-tokeniser.h 2009-06-28 09:20:53 +0000 +++ utils/pdf-tokeniser.h 2009-10-29 08:13:10 +0000 @@ -1,4 +1,4 @@ -/* -*- mode: C -*- Time-stamp: "09/06/24 20:56:43 jemarch" +/* -*- mode: C -*- Time-stamp: "2009-10-25 18:06:21 mgold" * * File: pdf-tokeniser.h * Date: Wed Jun 24 20:54:49 2009 @@ -35,7 +35,10 @@ { HELP_ARG, USAGE_ARG, - VERSION_ARG + VERSION_ARG, + TOKW_ARG, + READER_FLAGS_ARG, + WRITER_FLAGS_ARG }; #endif /* pdf-tokeniser.h */ # Begin bundle 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
signature.asc
Description: Digital signature
