From 548ff7d1194c4022463910af27bdb067489a6ef3 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Sun, 16 May 2021 11:39:05 -0400 Subject: [PATCH] Initial writing support. --- pdfio-array.c | 2 +- pdfio-dict.c | 2 +- pdfio-file.c | 253 ++++++++++++++++++++++++++++++++++-- pdfio-object.c | 79 ++++++++++- pdfio-private.h | 8 +- pdfio-stream.c | 339 +++++++++++++++++++++++++++++++++++++++++++++--- pdfio-value.c | 5 +- pdfio.h | 5 +- 8 files changed, 652 insertions(+), 41 deletions(-) diff --git a/pdfio-array.c b/pdfio-array.c index f272edb..d63bde9 100644 --- a/pdfio-array.c +++ b/pdfio-array.c @@ -573,7 +573,7 @@ _pdfioArrayWrite(pdfio_array_t *a) // I - Array // Write each value... for (i = a->num_values, v = a->values; i > 0; i --, v ++) { - if (!_pdfioValueWrite(pdf, v)) + if (!_pdfioValueWrite(pdf, v, NULL)) return (false); } diff --git a/pdfio-dict.c b/pdfio-dict.c index 1374f63..1b58e51 100644 --- a/pdfio-dict.c +++ b/pdfio-dict.c @@ -805,7 +805,7 @@ _pdfioDictWrite(pdfio_dict_t *dict, // I - Dictionary if (!_pdfioFilePuts(pdf, " 9999999999")) return (false); } - else if (!_pdfioValueWrite(pdf, &pair->value)) + else if (!_pdfioValueWrite(pdf, &pair->value, NULL)) return (false); } diff --git a/pdfio-file.c b/pdfio-file.c index 5ccc85e..1051824 100644 --- a/pdfio-file.c +++ b/pdfio-file.c @@ -27,6 +27,8 @@ static int compare_objs(pdfio_obj_t **a, pdfio_obj_t **b); static bool load_obj_stream(pdfio_obj_t *obj); static bool load_pages(pdfio_file_t *pdf, pdfio_obj_t *obj); static bool load_xref(pdfio_file_t *pdf, off_t xref_offset); +static bool write_catalog(pdfio_file_t *pdf); +static bool write_pages(pdfio_file_t *pdf); static bool write_trailer(pdfio_file_t *pdf); @@ -89,7 +91,14 @@ pdfioFileClose(pdfio_file_t *pdf) // I - PDF file // Close the file itself... if (pdf->mode == _PDFIO_MODE_WRITE) - ret = write_trailer(pdf); + { + ret = false; + + if (write_pages(pdf)) + if (write_catalog(pdf)) + if (write_trailer(pdf)) + ret = _pdfioFileFlush(pdf); + } if (close(pdf->fd) < 0) ret = false; @@ -132,10 +141,13 @@ pdfio_file_t * // O - PDF file or `NULL` on error pdfioFileCreate( const char *filename, // I - Filename const char *version, // I - PDF version number or `NULL` for default (2.0) + pdfio_rect_t *media_box, // I - Default MediaBox for pages + pdfio_rect_t *crop_box, // I - Default CropBox for pages pdfio_error_cb_t error_cb, // I - Error callback or `NULL` for default void *error_data) // I - Error callback data, if any { pdfio_file_t *pdf; // PDF file + pdfio_dict_t *dict; // Dictionary for pages object // Range check input... @@ -169,6 +181,30 @@ pdfioFileCreate( pdf->error_cb = error_cb; pdf->error_data = error_data; + if (media_box) + { + pdf->media_box = *media_box; + } + else + { + // Default to "universal" size (intersection of A4 and US Letter) + pdf->media_box.x2 = 210.0f * 72.0f / 25.4f; + pdf->media_box.y2 = 11.0f * 72.0f; + } + + if (crop_box) + { + pdf->crop_box = *crop_box; + } + else + { + // Default to "universal" size (intersection of A4 and US Letter) with 1/4" margins + pdf->crop_box.x1 = 18.0f; + pdf->crop_box.y1 = 18.0f; + pdf->crop_box.x2 = 210.0f * 72.0f / 25.4f - 18.0f; + pdf->crop_box.y2 = 11.0f * 72.0f - 18.0f; + } + // Create the file... if ((pdf->fd = open(filename, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0666)) < 0) { @@ -187,6 +223,23 @@ pdfioFileCreate( return (NULL); } + // Create the pages object... + if ((dict = pdfioDictCreate(pdf)) == NULL) + { + pdfioFileClose(pdf); + unlink(filename); + return (NULL); + } + + pdfioDictSetName(dict, "Type", "Pages"); + + if ((pdf->pages_root = pdfioFileCreateObject(pdf, dict)) == NULL) + { + pdfioFileClose(pdf); + unlink(filename); + return (NULL); + } + return (pdf); } @@ -246,7 +299,6 @@ pdfioFileCreateObject( // Initialize the object... obj->pdf = pdf; obj->number = pdf->num_objs; - obj->offset = _pdfioFileTell(pdf); obj->value.type = PDFIO_VALTYPE_DICT; obj->value.value.dict = dict; @@ -259,14 +311,73 @@ pdfioFileCreateObject( // 'pdfioFileCreatePage()' - Create a page in a PDF file. // -pdfio_obj_t * // O - New object +pdfio_stream_t * // O - Contents stream pdfioFileCreatePage(pdfio_file_t *pdf, // I - PDF file pdfio_dict_t *dict) // I - Page dictionary { - // TODO: Implement pdfioFileCreatePage - (void)pdf; - (void)dict; - return (NULL); + pdfio_obj_t *page, // Page object + *contents; // Contents object + pdfio_dict_t *contents_dict; // Dictionary for Contents object + + + // Range check input... + if (!pdf) + return (NULL); + + // Copy the page dictionary... + if (dict) + dict = pdfioDictCopy(pdf, dict); + else + dict = pdfioDictCreate(pdf); + + // Make sure the page dictionary has all of the required keys... + if (!_pdfioDictGetValue(dict, "CropBox")) + pdfioDictSetRect(dict, "CropBox", &pdf->crop_box); + + if (!_pdfioDictGetValue(dict, "MediaBox")) + pdfioDictSetRect(dict, "MediaBox", &pdf->media_box); + + pdfioDictSetObject(dict, "Parent", pdf->pages_root); + + if (!_pdfioDictGetValue(dict, "Resources")) + pdfioDictSetDict(dict, "Resources", pdfioDictCreate(pdf)); + + if (!_pdfioDictGetValue(dict, "Type")) + pdfioDictSetName(dict, "Type", "Page"); + + // Create the page object... + page = pdfioFileCreateObject(pdf, dict); + + // Create a contents object to hold the contents of the page... + contents_dict = pdfioDictCreate(pdf); + pdfioDictSetName(contents_dict, "Filter", "FlateDecode"); + + contents = pdfioFileCreateObject(pdf, contents_dict); + + // Add the contents stream to the pages object and write it... + pdfioDictSetObject(dict, "Contents", contents); + if (!pdfioObjClose(page)) + return (NULL); + + // Add the page to the array of pages... + if (pdf->num_pages >= pdf->alloc_pages) + { + pdfio_obj_t **temp = (pdfio_obj_t **)realloc(pdf->pages, (pdf->alloc_pages + 16) * sizeof(pdfio_obj_t *)); + + if (!temp) + { + _pdfioFileError(pdf, "Unable to allocate memory for pages."); + return (NULL); + } + + pdf->alloc_pages += 16; + pdf->pages = temp; + } + + pdf->pages[pdf->num_pages ++] = page; + + // Create the contents stream... + return (pdfioObjCreateStream(contents, PDFIO_FILTER_FLATE)); } @@ -1114,6 +1225,56 @@ load_xref(pdfio_file_t *pdf, // I - PDF file } +// +// 'write_catalog()' - Write the PDF root object/catalog. +// + +static bool // O - `true` on success, `false` on failure +write_catalog(pdfio_file_t *pdf) // I - PDF file +{ + pdfio_dict_t *dict; // Dictionary for catalog... + + + if ((dict = pdfioDictCreate(pdf)) == NULL) + return (false); + + pdfioDictSetName(dict, "Type", "Catalog"); + pdfioDictSetObject(dict, "Pages", pdf->pages_root); + // TODO: Add support for all of the root object dictionary keys + + if ((pdf->root = pdfioFileCreateObject(pdf, dict)) == NULL) + return (false); + else + return (pdfioObjClose(pdf->root)); +} + + +// +// 'write_pages()' - Write the PDF pages objects. +// + +static bool // O - `true` on success, `false` on failure +write_pages(pdfio_file_t *pdf) // I - PDF file +{ + pdfio_array_t *kids; // Pages array + size_t i; // Looping var + + + // Build the "Kids" array pointing to each page... + if ((kids = pdfioArrayCreate(pdf)) == NULL) + return (false); + + for (i = 0; i < pdf->num_pages; i ++) + pdfioArrayAppendObject(kids, pdf->pages[i]); + + pdfioDictSetNumber(pdf->pages_root->value.value.dict, "Count", pdf->num_pages); + pdfioDictSetArray(pdf->pages_root->value.value.dict, "Kids", kids); + + // Write the Pages object... + return (pdfioObjClose(pdf->pages_root)); +} + + // // 'write_trailer()' - Write the PDF catalog object, xref table, and trailer. // @@ -1121,8 +1282,80 @@ load_xref(pdfio_file_t *pdf, // I - PDF file static bool // O - `true` on success, `false` on failure write_trailer(pdfio_file_t *pdf) // I - PDF file { - // TODO: Write trailer - (void)pdf; + bool ret = true; // Return value + off_t xref_offset; // Offset to xref table + size_t i; // Looping var + int fd; // File for /dev/urandom + unsigned char id_values[2][16]; // ID array values - return (false); + + // Write the xref table... + // TODO: Look at adding support for xref streams... + xref_offset = _pdfioFileTell(pdf); + + if (!_pdfioFilePrintf(pdf, "xref\n0 %lu \n0000000000 65535 f \n", (unsigned long)pdf->num_objs)) + { + _pdfioFileError(pdf, "Unable to write cross-reference table."); + ret = false; + goto done; + } + + for (i = 0; i < pdf->num_objs; i ++) + { + pdfio_obj_t *obj = pdf->objs[i]; // Current object + + if (!_pdfioFilePrintf(pdf, "%010lu %05u n \n", (unsigned long)obj->offset, obj->generation)) + { + _pdfioFileError(pdf, "Unable to write cross-reference table."); + ret = false; + goto done; + } + } + + // Write the trailer... + if (!_pdfioFilePuts(pdf, "trailer\n")) + { + _pdfioFileError(pdf, "Unable to write trailer."); + ret = false; + goto done; + } + + if ((fd = open("/dev/urandom", O_RDONLY)) >= 0) + { + // Load ID array with random values from /dev/urandom... + memset(id_values, 0, sizeof(id_values)); + read(fd, id_values[0], sizeof(id_values[0])); + read(fd, id_values[1], sizeof(id_values[1])); + close(fd); + + pdf->id_array = pdfioArrayCreate(pdf); + pdfioArrayAppendBinary(pdf->id_array, id_values[0], sizeof(id_values[0])); + pdfioArrayAppendBinary(pdf->id_array, id_values[1], sizeof(id_values[1])); + } + + pdf->trailer = pdfioDictCreate(pdf); + if (pdf->encrypt) + pdfioDictSetObject(pdf->trailer, "Encrypt", pdf->encrypt); + if (pdf->id_array) + pdfioDictSetArray(pdf->trailer, "ID", pdf->id_array); + pdfioDictSetObject(pdf->trailer, "Info", pdf->info); + pdfioDictSetObject(pdf->trailer, "Root", pdf->root); + pdfioDictSetNumber(pdf->trailer, "Size", pdf->num_objs + 1); + + if (!_pdfioDictWrite(pdf->trailer, NULL)) + { + _pdfioFileError(pdf, "Unable to write trailer."); + ret = false; + goto done; + } + + if (!_pdfioFilePrintf(pdf, "\nstartxref\n%lu\n%%EOF\n", (unsigned long)xref_offset)) + { + _pdfioFileError(pdf, "Unable to write xref offset."); + ret = false; + } + + done: + + return (ret); } diff --git a/pdfio-object.c b/pdfio-object.c index 6c8ecf1..88b89ce 100644 --- a/pdfio-object.c +++ b/pdfio-object.c @@ -14,6 +14,13 @@ #include "pdfio-private.h" +// +// Local functions... +// + +static bool write_obj_header(pdfio_obj_t *obj); + + // // 'pdfioObjClose()' - Close an object, writing any data as needed to the PDF // file. @@ -22,10 +29,20 @@ bool // O - `true` on success, `false` on failure pdfioObjClose(pdfio_obj_t *obj) // I - Object { - // TODO: Implement pdfioObjClose - (void)obj; + // Range check input + if (!obj) + return (false); - return (false); + if (obj->pdf->mode != _PDFIO_MODE_WRITE) + return (true); // Nothing to do when reading + + // Write what remains for the object... + if (!obj->offset) + return (write_obj_header(obj)); // Just write the object value + else if (obj->stream) + return (pdfioStreamClose(obj->stream)); + else + return (true); // Already closed } @@ -54,11 +71,40 @@ pdfioObjCreateStream( pdfio_obj_t *obj, // I - Object pdfio_filter_t filter) // I - Type of compression to apply { - // TODO: Implement pdfioObjCreateStream - (void)obj; - (void)filter; + // Range check input + if (!obj || obj->pdf->mode != _PDFIO_MODE_WRITE || obj->value.type != PDFIO_VALTYPE_DICT) + return (NULL); - return (NULL); + if (obj->offset) + { + _pdfioFileError(obj->pdf, "Object has already been written."); + return (NULL); + } + + if (filter != PDFIO_FILTER_NONE && filter != PDFIO_FILTER_FLATE) + { + _pdfioFileError(obj->pdf, "Unsupported filter value for pdfioObjCreateStream."); + return (NULL); + } + + // Write the header... + if (!_pdfioDictGetValue(obj->value.value.dict, "Length")) + { + // Need a Length key for the stream, add a placeholder that we can fill in + // later... + pdfioDictSetNumber(obj->value.value.dict, "Length", 0.0f); + } + + if (!write_obj_header(obj)) + return (NULL); + + if (!_pdfioFilePuts(obj->pdf, "stream\n")) + return (NULL); + + obj->stream_offset = _pdfioFileTell(obj->pdf); + + // Return the new stream... + return (_pdfioStreamCreate(obj, filter)); } @@ -295,3 +341,22 @@ pdfioObjOpenStream(pdfio_obj_t *obj, // I - Object // Open the stream... return (_pdfioStreamOpen(obj, decode)); } + + +// +// 'write_obj_header()' - Write the object header... +// + +static bool // O - `true` on success, `false` on failure +write_obj_header(pdfio_obj_t *obj) // I - Object +{ + obj->offset = _pdfioFileTell(obj->pdf); + + if (!_pdfioFilePrintf(obj->pdf, "%lu %u obj\n", (unsigned long)obj->number, obj->generation)) + return (false); + + if (!_pdfioValueWrite(obj->pdf, &obj->value, &obj->length_offset)) + return (false); + + return (_pdfioFilePuts(obj->pdf, "\n")); +} diff --git a/pdfio-private.h b/pdfio-private.h index f7100bd..6489fc4 100644 --- a/pdfio-private.h +++ b/pdfio-private.h @@ -77,7 +77,8 @@ typedef enum _pdfio_predictor_e // PNG predictor constants _PDFIO_PREDICTOR_PNG_SUB = 11, // PNG Sub predictor _PDFIO_PREDICTOR_PNG_UP = 12, // PNG Up predictor _PDFIO_PREDICTOR_PNG_AVERAGE = 13, // PNG Average predictor - _PDFIO_PREDICTOR_PNG_PAETH = 14 // PNG Paeth predictor + _PDFIO_PREDICTOR_PNG_PAETH = 14, // PNG Paeth predictor + _PDFIO_PREDICTOR_PNG_AUTO = 15 // PNG "auto" predictor (currently mapped to Paeth) } _pdfio_predictor_t; typedef ssize_t (*_pdfio_tconsume_cb_t)(void *data, size_t bytes); @@ -154,6 +155,8 @@ struct _pdfio_file_s // PDF file structure { char *filename; // Filename char *version; // Version number + pdfio_rect_t media_box, // Default MediaBox value + crop_box; // Default CropBox value _pdfio_mode_t mode; // Read/write mode pdfio_error_cb_t error_cb; // Error callback void *error_data; // Data for error callback @@ -167,6 +170,7 @@ struct _pdfio_file_s // PDF file structure pdfio_dict_t *trailer; // Trailer dictionary pdfio_obj_t *root; // Root object/dictionary pdfio_obj_t *info; // Information object/dictionary + pdfio_obj_t *pages_root; // Root pages object pdfio_obj_t *encrypt; // Encryption object/dictionary pdfio_array_t *id_array; // ID array @@ -275,6 +279,6 @@ extern _pdfio_value_t *_pdfioValueCopy(pdfio_file_t *pdfdst, _pdfio_value_t *vds extern void _pdfioValueDebug(_pdfio_value_t *v, FILE *fp) PDFIO_INTERNAL; extern void _pdfioValueDelete(_pdfio_value_t *v) PDFIO_INTERNAL; extern _pdfio_value_t *_pdfioValueRead(pdfio_file_t *pdf, _pdfio_token_t *ts, _pdfio_value_t *v) PDFIO_INTERNAL; -extern bool _pdfioValueWrite(pdfio_file_t *pdf, _pdfio_value_t *v) PDFIO_INTERNAL; +extern bool _pdfioValueWrite(pdfio_file_t *pdf, _pdfio_value_t *v, off_t *length) PDFIO_INTERNAL; #endif // !PDFIO_PRIVATE_H diff --git a/pdfio-stream.c b/pdfio-stream.c index cfcdd4d..e2f6201 100644 --- a/pdfio-stream.c +++ b/pdfio-stream.c @@ -20,6 +20,7 @@ static unsigned char stream_paeth(unsigned char a, unsigned char b, unsigned char c); static ssize_t stream_read(pdfio_stream_t *st, char *buffer, size_t bytes); +static bool stream_write(pdfio_stream_t *st, const void *buffer, size_t bytes); // @@ -29,6 +30,9 @@ static ssize_t stream_read(pdfio_stream_t *st, char *buffer, size_t bytes); bool // O - `true` on success, `false` on failure pdfioStreamClose(pdfio_stream_t *st) // I - Stream { + bool ret = true; // Return value + + // Range check input... if (!st) return (false); @@ -41,15 +45,85 @@ pdfioStreamClose(pdfio_stream_t *st) // I - Stream } else { - // TODO: Implement close for writing - return (false); + // Close stream for writing... + if (st->filter == PDFIO_FILTER_FLATE) + { + // Finalize flate compression stream... + int status; // Deflate status + + while ((status = deflate(&st->flate, Z_FINISH)) != Z_STREAM_END) + { + if (status < Z_OK && status != Z_BUF_ERROR) + { + _pdfioFileError(st->pdf, "Flate compression failed (%d)", status); + ret = false; + goto done; + } + + if (!_pdfioFileWrite(st->pdf, st->cbuffer, sizeof(st->cbuffer) - st->flate.avail_out)) + { + ret = false; + goto done; + } + + st->flate.next_out = (Bytef *)st->cbuffer; + st->flate.avail_out = (uInt)sizeof(st->cbuffer); + } + + if (st->flate.avail_out < (uInt)sizeof(st->cbuffer)) + { + // Write any residuals... + if (!_pdfioFileWrite(st->pdf, st->cbuffer, sizeof(st->cbuffer) - st->flate.avail_out)) + { + ret = false; + goto done; + } + } + } + + // Save the length of this stream... + st->obj->stream_length = (size_t)(_pdfioFileTell(st->pdf) - st->obj->stream_offset); + + // End of stream marker... + if (!_pdfioFilePuts(st->pdf, "\nendstream\n")) + { + ret = false; + goto done; + } + + // Update the length as needed... + if (st->obj->length_offset) + { + // Seek back to the "/Length 9999999999" we wrote... + if (_pdfioFileSeek(st->pdf, st->obj->length_offset, SEEK_SET) < 0) + { + ret = false; + goto done; + } + + // Write the updated length value... + if (!_pdfioFilePrintf(st->pdf, "%-10lu", (unsigned long)st->obj->stream_length)) + { + ret = false; + goto done; + } + + // Seek to the end of the PDF file... + if (_pdfioFileSeek(st->pdf, 0, SEEK_END) < 0) + { + ret = false; + goto done; + } + } } + done: + free(st->pbuffers[0]); free(st->pbuffers[1]); free(st); - return (true); + return (ret); } @@ -64,11 +138,110 @@ _pdfioStreamCreate( pdfio_obj_t *obj, // I - Object pdfio_filter_t compression) // I - Compression to apply { - // TODO: Implement _pdfioStreamCreate - (void)obj; - (void)compression; + pdfio_stream_t *st; // Stream - return (NULL); + + // Allocate a new stream object... + if ((st = (pdfio_stream_t *)calloc(1, sizeof(pdfio_stream_t))) == NULL) + { + _pdfioFileError(obj->pdf, "Unable to allocate memory for a stream."); + return (NULL); + } + + st->pdf = obj->pdf; + st->obj = obj; + st->filter = compression; + + if (compression == PDFIO_FILTER_FLATE) + { + // Flate compression + pdfio_dict_t *params = pdfioDictGetDict(obj->value.value.dict, "DecodeParms"); + // Decoding parameters + int bpc = (int)pdfioDictGetNumber(params, "BitsPerComponent"); + // Bits per component + int colors = (int)pdfioDictGetNumber(params, "Colors"); + // Number of colors + int columns = (int)pdfioDictGetNumber(params, "Columns"); + // Number of columns + int predictor = (int)pdfioDictGetNumber(params, "Predictor"); + // Predictory value, if any + + PDFIO_DEBUG("_pdfioStreamCreate: FlateDecode - BitsPerComponent=%d, Colors=%d, Columns=%d, Predictor=%d\n", bpc, colors, columns, predictor); + + if (bpc == 0) + { + bpc = 8; + } + else if (bpc < 1 || bpc == 3 || (bpc > 4 && bpc < 8) || (bpc > 8 && bpc < 16) || bpc > 16) + { + _pdfioFileError(st->pdf, "Unsupported BitsPerColor value %d.", bpc); + free(st); + return (NULL); + } + + if (colors == 0) + { + colors = 1; + } + else if (colors < 0 || colors > 4) + { + _pdfioFileError(st->pdf, "Unsupported Colors value %d.", colors); + free(st); + return (NULL); + } + + if (columns == 0) + { + columns = 1; + } + else if (columns < 0) + { + _pdfioFileError(st->pdf, "Unsupported Columns value %d.", columns); + free(st); + return (NULL); + } + + if ((predictor > 1 && predictor < 10) || predictor > 15) + { + _pdfioFileError(st->pdf, "Unsupported Predictor function %d.", predictor); + free(st); + return (NULL); + } + else if (predictor) + { + // Using a PNG predictor function + st->predictor = (_pdfio_predictor_t)predictor; + st->pbpixel = (size_t)(bpc * colors + 7) / 8; + st->pbsize = (size_t)(bpc * colors * columns + 7) / 8; + if (predictor >= 10) + st->pbsize ++; // Add PNG predictor byte + + if ((st->pbuffers[0] = calloc(1, st->pbsize)) == NULL || (st->pbuffers[1] = calloc(1, st->pbsize)) == NULL) + { + _pdfioFileError(st->pdf, "Unable to allocate %lu bytes for Predictor buffers.", (unsigned long)st->pbsize); + free(st->pbuffers[0]); + free(st->pbuffers[1]); + free(st); + return (NULL); + } + } + else + st->predictor = _PDFIO_PREDICTOR_NONE; + + st->flate.next_out = (Bytef *)st->cbuffer; + st->flate.avail_out = (uInt)sizeof(st->cbuffer); + + if (deflateInit(&(st->flate), 9) != Z_OK) + { + _pdfioFileError(st->pdf, "Unable to start Flate filter."); + free(st->pbuffers[0]); + free(st->pbuffers[1]); + free(st); + return (NULL); + } + } + + return (st); } @@ -268,13 +441,8 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object else st->predictor = _PDFIO_PREDICTOR_NONE; - st->flate.zalloc = (alloc_func)0; - st->flate.zfree = (free_func)0; - st->flate.opaque = (voidpf)0; - st->flate.next_in = (Bytef *)st->cbuffer; - st->flate.next_out = NULL; - st->flate.avail_in = (uInt)_pdfioFileRead(st->pdf, st->cbuffer, sizeof(st->cbuffer)); - st->flate.avail_out = 0; + st->flate.next_in = (Bytef *)st->cbuffer; + st->flate.avail_in = (uInt)_pdfioFileRead(st->pdf, st->cbuffer, sizeof(st->cbuffer)); if (inflateInit(&(st->flate)) != Z_OK) { @@ -474,12 +642,110 @@ pdfioStreamWrite( const void *buffer, // I - Data to write size_t bytes) // I - Number of bytes to write { + size_t pbpixel = st->pbpixel, + // Size of pixel in bytes + pbline = st->pbsize - 1, + // Bytes per line + remaining, // Remaining bytes on this line + firstcol = pbline - pbpixel; + // First column bytes remaining + const unsigned char *bufptr = (const unsigned char *)buffer; + // Pointer into buffer + unsigned char *thisptr, // Current raw buffer + *prevptr; // Previous raw buffer + + // Range check input... if (!st || st->pdf->mode != _PDFIO_MODE_WRITE || !buffer || !bytes) return (false); - // TODO: Implement pdfioStreamWrite - return (false); + // Write it... + if (st->filter == PDFIO_FILTER_NONE) + { + // No filtering so just write it... + return (_pdfioFileWrite(st->pdf, buffer, bytes)); + } + + if (st->predictor == _PDFIO_PREDICTOR_NONE) + { + // No predictor, just write it out straight... + return (stream_write(st, buffer, bytes)); + } + else if ((bytes % pbline) != 0) + { + _pdfioFileError(st->pdf, "Write buffer size must be a multiple of a complete row."); + return (false); + } + + while (bytes > 0) + { + // Store the PNG predictor in the first byte of the buffer... + if (st->predictor == _PDFIO_PREDICTOR_PNG_AUTO) + st->pbuffers[st->pbcurrent][0] = 4; + else + st->pbuffers[st->pbcurrent][0] = (unsigned char)(st->predictor - 10); + + // Then process the current line using the specified PNG predictor... + thisptr = st->pbuffers[st->pbcurrent] + 1; + prevptr = st->pbuffers[!st->pbcurrent] + 1; + + switch (st->predictor) + { + default : + // Anti-compiler-warning code (NONE is handled above, TIFF is not supported for writing) + return (false); + + case _PDFIO_PREDICTOR_PNG_SUB : + // Encode the difference from the previous column + for (remaining = pbline; remaining > 0; remaining --, bufptr ++, thisptr ++) + { + if (remaining < firstcol) + *thisptr = *bufptr - thisptr[-pbpixel]; + else + *thisptr = *bufptr; + } + break; + + case _PDFIO_PREDICTOR_PNG_UP : + // Encode the difference from the previous line + for (remaining = pbline; remaining > 0; remaining --, bufptr ++, thisptr ++, prevptr ++) + { + *thisptr = *bufptr - *prevptr; + } + break; + + case _PDFIO_PREDICTOR_PNG_AVERAGE : + // Encode the difference with the average of the previous column and line + for (remaining = pbline; remaining > 0; remaining --, bufptr ++, thisptr ++, prevptr ++) + { + if (remaining < firstcol) + *thisptr = *bufptr - (thisptr[-pbpixel] + *prevptr) / 2; + else + *thisptr = *bufptr - *prevptr / 2; + } + break; + + case _PDFIO_PREDICTOR_PNG_PAETH : + case _PDFIO_PREDICTOR_PNG_AUTO : + // Encode the difference with a linear transform function + for (remaining = pbline; remaining > 0; remaining --, bufptr ++, thisptr ++, prevptr ++) + { + if (remaining < firstcol) + *thisptr = *bufptr - stream_paeth(thisptr[-pbpixel], *prevptr, prevptr[-pbpixel]); + else + *thisptr = *bufptr - stream_paeth(0, *prevptr, 0); + } + break; + } + + // Write the encoded line... + if (!stream_write(st, st->pbuffers[st->pbcurrent], st->pbsize)) + return (false); + + st->pbcurrent = !st->pbcurrent; + } + + return (true); } @@ -674,3 +940,44 @@ stream_read(pdfio_stream_t *st, // I - Stream return (-1); } + +// +// 'stream_write()' - Write flate-compressed data... +// + +static bool // O - `true` on success, `false` on failure +stream_write(pdfio_stream_t *st, // I - Stream + const void *buffer, // I - Buffer to write + size_t bytes) // I - Number of bytes to write +{ + int status; // Compression status + + + // Flate-compress the buffer... + st->flate.avail_in = (uInt)bytes; + st->flate.next_in = (Bytef *)buffer; + + while (st->flate.avail_in > 0) + { + if (st->flate.avail_out < (sizeof(st->cbuffer) / 8)) + { + // Flush the compression buffer... + if (!_pdfioFileWrite(st->pdf, st->cbuffer, sizeof(st->cbuffer) - st->flate.avail_out)) + return (false); + + st->flate.next_out = (Bytef *)st->cbuffer; + st->flate.avail_out = sizeof(st->cbuffer); + } + + // Deflate what we can this time... + status = deflate(&st->flate, Z_NO_FLUSH); + + if (status < Z_OK && status != Z_BUF_ERROR) + { + _pdfioFileError(st->pdf, "Flate compression failed (%d)", status); + return (false); + } + } + + return (true); +} diff --git a/pdfio-value.c b/pdfio-value.c index 9bbfd8b..9d6e47d 100644 --- a/pdfio-value.c +++ b/pdfio-value.c @@ -349,7 +349,8 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file bool // O - `true` on success, `false` on failure _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file - _pdfio_value_t *v) // I - Value + _pdfio_value_t *v, // I - Value + off_t *length)// O - Offset to /Length value, if any { switch (v->type) { @@ -394,7 +395,7 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file } case PDFIO_VALTYPE_DICT : - return (_pdfioDictWrite(v->value.dict, NULL)); + return (_pdfioDictWrite(v->value.dict, length)); case PDFIO_VALTYPE_INDIRECT : return (_pdfioFilePrintf(pdf, " %lu %u R", (unsigned long)v->value.indirect.number, v->value.indirect.generation)); diff --git a/pdfio.h b/pdfio.h index 6052470..b822da7 100644 --- a/pdfio.h +++ b/pdfio.h @@ -147,9 +147,10 @@ extern bool pdfioDictSetString(pdfio_dict_t *dict, const char *key, const char extern bool pdfioDictSetStringf(pdfio_dict_t *dict, const char *key, const char *format, ...) PDFIO_PUBLIC PDFIO_FORMAT(3,4); extern bool pdfioFileClose(pdfio_file_t *pdf) PDFIO_PUBLIC; -extern pdfio_file_t *pdfioFileCreate(const char *filename, const char *version, pdfio_error_cb_t error_cb, void *error_data) PDFIO_PUBLIC; +extern pdfio_file_t *pdfioFileCreate(const char *filename, const char *version, pdfio_rect_t *media_box, pdfio_rect_t *crop_box, pdfio_error_cb_t error_cb, void *error_data) PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateObject(pdfio_file_t *pdf, pdfio_dict_t *dict) PDFIO_PUBLIC; -extern pdfio_obj_t *pdfioFileCreatePage(pdfio_file_t *pdf, pdfio_dict_t *dict) PDFIO_PUBLIC; +// TODO: Add number, array, string, etc. versions of pdfioFileCreateObject? +extern pdfio_stream_t *pdfioFileCreatePage(pdfio_file_t *pdf, pdfio_dict_t *dict) PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileFindObject(pdfio_file_t *pdf, size_t number) PDFIO_PUBLIC; extern pdfio_array_t *pdfioFileGetID(pdfio_file_t *pdf) PDFIO_PUBLIC; extern const char *pdfioFileGetName(pdfio_file_t *pdf) PDFIO_PUBLIC;