From d6746c08a477dfc8ca3fa15d52795a2cc8a9fc51 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Mon, 27 Sep 2021 07:41:50 -0400 Subject: [PATCH] Add pdfioFileCreateOutput API (Issue #21) --- Makefile | 2 +- pdfio-common.c | 45 ++- pdfio-file.c | 123 +++++++- pdfio-object.c | 25 +- pdfio-private.h | 5 +- pdfio-stream.c | 15 +- pdfio.h | 3 + pdfio.vcxproj | 8 +- pdfio.xcodeproj/project.pbxproj | 10 +- testpdfio.c | 524 ++++++++++++++++++-------------- 10 files changed, 503 insertions(+), 257 deletions(-) diff --git a/Makefile b/Makefile index dc1d760..cbec156 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ DSONAME = LDFLAGS = LIBS = -lm -lz RANLIB = ranlib -VERSION = 1.0b1 +VERSION = 1.0.0 prefix = /usr/local diff --git a/pdfio-common.c b/pdfio-common.c index 55e247e..5f54d70 100644 --- a/pdfio-common.c +++ b/pdfio-common.c @@ -377,6 +377,11 @@ _pdfioFileSeek(pdfio_file_t *pdf, // I - PDF file // No, reset the read buffer pdf->bufptr = pdf->bufend = NULL; } + else if (pdf->output_cb) + { + _pdfioFileError(pdf, "Unable to seek within output stream."); + return (-1); + } else { // Writing, make sure we write any buffered data... @@ -530,25 +535,37 @@ write_buffer(pdfio_file_t *pdf, // I - PDF file ssize_t wbytes; // Bytes written... - // Write to the file... - while (bytes > 0) + if (pdf->output_cb) { - while ((wbytes = write(pdf->fd, bufptr, bytes)) < 0) + // Write to a stream... + if ((pdf->output_cb)(pdf->output_ctx, buffer, bytes) < 0) { - // Stop if we have an error that shouldn't be retried... - if (errno != EINTR && errno != EAGAIN) - break; - } - - if (wbytes < 0) - { - // Hard error... - _pdfioFileError(pdf, "Unable to write to file - %s", strerror(errno)); + _pdfioFileError(pdf, "Unable to write to output callback."); return (false); } + } + else + { + // Write to the file... + while (bytes > 0) + { + while ((wbytes = write(pdf->fd, bufptr, bytes)) < 0) + { + // Stop if we have an error that shouldn't be retried... + if (errno != EINTR && errno != EAGAIN) + break; + } - bufptr += wbytes; - bytes -= (size_t)wbytes; + if (wbytes < 0) + { + // Hard error... + _pdfioFileError(pdf, "Unable to write to file - %s", strerror(errno)); + return (false); + } + + bufptr += wbytes; + bytes -= (size_t)wbytes; + } } return (true); diff --git a/pdfio-file.c b/pdfio-file.c index e4128c5..0670f50 100644 --- a/pdfio-file.c +++ b/pdfio-file.c @@ -130,7 +130,7 @@ pdfioFileClose(pdfio_file_t *pdf) // I - PDF file ret = _pdfioFileFlush(pdf); } - if (close(pdf->fd) < 0) + if (pdf->fd >= 0 && close(pdf->fd) < 0) ret = false; // Free all data... @@ -399,6 +399,127 @@ _pdfioFileCreateObj( } +// +// 'pdfioFileCreateOutput()' - Create a PDF file through an output callback. +// + +pdfio_file_t * // O - PDF file or `NULL` on error +pdfioFileCreateOutput( + pdfio_output_cb_t output_cb, // I - Output callback + void *output_ctx, // I - Output context + 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 + pdfio_dict_t *info_dict; // Dictionary for information object + + + // Range check input... + if (!output_cb) + return (NULL); + + if (!version) + version = "2.0"; + + if (!error_cb) + { + error_cb = _pdfioFileDefaultError; + error_data = NULL; + } + + // Allocate a PDF file structure... + if ((pdf = (pdfio_file_t *)calloc(1, sizeof(pdfio_file_t))) == NULL) + { + pdfio_file_t temp; // Dummy file + char message[8192]; // Message string + + temp.filename = (char *)"output.pdf"; + snprintf(message, sizeof(message), "Unable to allocate memory for PDF file - %s", strerror(errno)); + (error_cb)(&temp, message, error_data); + return (NULL); + } + + pdf->filename = strdup("output.pdf"); + pdf->version = strdup(version); + pdf->mode = _PDFIO_MODE_WRITE; + pdf->error_cb = error_cb; + pdf->error_data = error_data; + pdf->bufptr = pdf->buffer; + pdf->bufend = pdf->buffer + sizeof(pdf->buffer); + + if (media_box) + { + pdf->media_box = *media_box; + } + else + { + // Default to "universal" size (intersection of A4 and US Letter) + pdf->media_box.x2 = 210.0 * 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) + pdf->crop_box.x2 = 210.0 * 72.0f / 25.4f; + pdf->crop_box.y2 = 11.0f * 72.0f; + } + + // Save output callback... + pdf->fd = -1; + pdf->output_cb = output_cb; + pdf->output_ctx = output_ctx; + + // Write a standard PDF header... + if (!_pdfioFilePrintf(pdf, "%%PDF-%s\n%%\342\343\317\323\n", version)) + { + pdfioFileClose(pdf); + return (NULL); + } + + // Create the pages object... + if ((dict = pdfioDictCreate(pdf)) == NULL) + { + pdfioFileClose(pdf); + return (NULL); + } + + pdfioDictSetName(dict, "Type", "Pages"); + + if ((pdf->pages_root = pdfioFileCreateObj(pdf, dict)) == NULL) + { + pdfioFileClose(pdf); + return (NULL); + } + + // Create the info object... + if ((info_dict = pdfioDictCreate(pdf)) == NULL) + { + pdfioFileClose(pdf); + return (NULL); + } + + pdfioDictSetDate(info_dict, "CreationDate", time(NULL)); + pdfioDictSetString(info_dict, "Producer", "pdfio/" PDFIO_VERSION); + + if ((pdf->info = pdfioFileCreateObj(pdf, info_dict)) == NULL) + { + pdfioFileClose(pdf); + return (NULL); + } + + return (pdf); +} + + // // 'pdfioFileCreatePage()' - Create a page in a PDF file. // diff --git a/pdfio-object.c b/pdfio-object.c index fef280c..9059ff8 100644 --- a/pdfio-object.c +++ b/pdfio-object.c @@ -143,6 +143,9 @@ pdfioObjCreateStream( pdfio_obj_t *obj, // I - Object pdfio_filter_t filter) // I - Type of compression to apply { + pdfio_obj_t *length_obj = NULL; // Length object, if any + + // Range check input if (!obj || obj->pdf->mode != _PDFIO_MODE_WRITE || obj->value.type != PDFIO_VALTYPE_DICT) return (NULL); @@ -162,9 +165,23 @@ pdfioObjCreateStream( // 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.0); + if (obj->pdf->output_cb) + { + // Streaming via an output callback, so add a placeholder length object + _pdfio_value_t length_value; // Length value + + length_value.type = PDFIO_VALTYPE_NUMBER; + length_value.value.number = 0.0f; + + length_obj = _pdfioFileCreateObj(obj->pdf, obj->pdf, &length_value); + pdfioDictSetObj(obj->value.value.dict, "Length", length_obj); + } + else + { + // Need a Length key for the stream, add a placeholder that we can fill in + // later... + pdfioDictSetNumber(obj->value.value.dict, "Length", 0.0); + } } if (!write_obj_header(obj)) @@ -176,7 +193,7 @@ pdfioObjCreateStream( obj->stream_offset = _pdfioFileTell(obj->pdf); // Return the new stream... - return (_pdfioStreamCreate(obj, filter)); + return (_pdfioStreamCreate(obj, length_obj, filter)); } diff --git a/pdfio-private.h b/pdfio-private.h index d2c76f0..34d41c3 100644 --- a/pdfio-private.h +++ b/pdfio-private.h @@ -210,6 +210,8 @@ struct _pdfio_file_s // PDF file structure pdfio_rect_t media_box, // Default MediaBox value crop_box; // Default CropBox value _pdfio_mode_t mode; // Read/write mode + pdfio_output_cb_t output_cb; // Output callback + void *output_ctx; // Context for output callback pdfio_error_cb_t error_cb; // Error callback void *error_data; // Data for error callback @@ -266,6 +268,7 @@ struct _pdfio_stream_s // Stream { pdfio_file_t *pdf; // PDF file pdfio_obj_t *obj; // Object + pdfio_obj_t *length_obj; // Length object, if any pdfio_filter_t filter; // Compression/decompression filter size_t remaining; // Remaining bytes in stream char buffer[8192], // Read/write buffer @@ -319,7 +322,7 @@ extern bool _pdfioFileWrite(pdfio_file_t *pdf, const void *buffer, size_t bytes extern void _pdfioObjDelete(pdfio_obj_t *obj) _PDFIO_INTERNAL; extern bool _pdfioObjLoad(pdfio_obj_t *obj) _PDFIO_INTERNAL; -extern pdfio_stream_t *_pdfioStreamCreate(pdfio_obj_t *obj, pdfio_filter_t compression) _PDFIO_INTERNAL; +extern pdfio_stream_t *_pdfioStreamCreate(pdfio_obj_t *obj, pdfio_obj_t *length_obj, pdfio_filter_t compression) _PDFIO_INTERNAL; extern pdfio_stream_t *_pdfioStreamOpen(pdfio_obj_t *obj, bool decode) _PDFIO_INTERNAL; extern bool _pdfioStringIsAllocated(pdfio_file_t *pdf, const char *s) _PDFIO_INTERNAL; diff --git a/pdfio-stream.c b/pdfio-stream.c index 96492e3..bd6f832 100644 --- a/pdfio-stream.c +++ b/pdfio-stream.c @@ -93,7 +93,12 @@ pdfioStreamClose(pdfio_stream_t *st) // I - Stream } // Update the length as needed... - if (st->obj->length_offset) + if (st->length_obj) + { + st->length_obj->value.value.number = st->obj->stream_length; + pdfioObjClose(st->length_obj); + } + else if (st->obj->length_offset) { // Seek back to the "/Length 9999999999" we wrote... if (_pdfioFileSeek(st->pdf, st->obj->length_offset, SEEK_SET) < 0) @@ -137,6 +142,7 @@ pdfioStreamClose(pdfio_stream_t *st) // I - Stream pdfio_stream_t * // O - Stream or `NULL` on error _pdfioStreamCreate( pdfio_obj_t *obj, // I - Object + pdfio_obj_t *length_obj, // I - Length object, if any pdfio_filter_t compression) // I - Compression to apply { pdfio_stream_t *st; // Stream @@ -149,9 +155,10 @@ _pdfioStreamCreate( return (NULL); } - st->pdf = obj->pdf; - st->obj = obj; - st->filter = compression; + st->pdf = obj->pdf; + st->obj = obj; + st->length_obj = length_obj; + st->filter = compression; if (compression == PDFIO_FILTER_FLATE) { diff --git a/pdfio.h b/pdfio.h index c228ad4..d5de27c 100644 --- a/pdfio.h +++ b/pdfio.h @@ -74,6 +74,8 @@ typedef enum pdfio_filter_e // Compression/decompression filters for streams PDFIO_FILTER_RUNLENGTH, // RunLengthDecode filter (reading only) } pdfio_filter_t; typedef struct _pdfio_obj_s pdfio_obj_t;// Numbered object in PDF file +typedef ssize_t (*pdfio_output_cb_t)(void *ctx, const void *data, size_t datalen); + // Output callback for pdfioFileCreateOutput typedef struct pdfio_rect_s // PDF rectangle { double x1; // Lower-left X coordinate @@ -156,6 +158,7 @@ extern bool pdfioFileClose(pdfio_file_t *pdf) _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 *pdfioFileCreateArrayObj(pdfio_file_t *pdf, pdfio_array_t *array) _PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateObj(pdfio_file_t *pdf, pdfio_dict_t *dict) _PDFIO_PUBLIC; +extern pdfio_file_t *pdfioFileCreateOutput(pdfio_output_cb_t output_cb, void *output_ctx, const char *version, pdfio_rect_t *media_box, pdfio_rect_t *crop_box, pdfio_error_cb_t error_cb, void *error_data) _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 *pdfioFileFindObj(pdfio_file_t *pdf, size_t number) _PDFIO_PUBLIC; diff --git a/pdfio.vcxproj b/pdfio.vcxproj index 257b9b4..aa935ff 100644 --- a/pdfio.vcxproj +++ b/pdfio.vcxproj @@ -87,7 +87,7 @@ Level3 true - PDFIO_VERSION="1.0b1";WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + PDFIO_VERSION="1.0.0";WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true @@ -101,7 +101,7 @@ true true true - PDFIO_VERSION="1.0b1";WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + PDFIO_VERSION="1.0.0";WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true @@ -115,7 +115,7 @@ Level3 true - PDFIO_VERSION="1.0b1";_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + PDFIO_VERSION="1.0.0";_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true @@ -130,7 +130,7 @@ true true true - PDFIO_VERSION="1.0b1";NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + PDFIO_VERSION="1.0.0";NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true diff --git a/pdfio.xcodeproj/project.pbxproj b/pdfio.xcodeproj/project.pbxproj index 16bad3f..a80d998 100644 --- a/pdfio.xcodeproj/project.pbxproj +++ b/pdfio.xcodeproj/project.pbxproj @@ -241,7 +241,7 @@ 273440A8263D6FE200FBFD63 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1250; + LastUpgradeCheck = 1300; TargetAttributes = { 273440AF263D6FE200FBFD63 = { CreatedOnToolsVersion = 12.5; @@ -350,9 +350,9 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_IDENTITY = "Apple Development"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1.0; + CURRENT_PROJECT_VERSION = 1.0.0; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -428,9 +428,9 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_IDENTITY = "Apple Development"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1.0; + CURRENT_PROJECT_VERSION = 1.0.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_HARDENED_RUNTIME = YES; ENABLE_NS_ASSERTIONS = NO; diff --git a/testpdfio.c b/testpdfio.c index 94ce9f3..25494bf 100644 --- a/testpdfio.c +++ b/testpdfio.c @@ -33,6 +33,8 @@ static int do_test_file(const char *filename, int objnum, bool verbose); static int do_unit_tests(void); static int draw_image(pdfio_stream_t *st, const char *name, double x, double y, double w, double h, const char *label); static bool error_cb(pdfio_file_t *pdf, const char *message, bool *error); +static ssize_t output_cb(int *fd, const void *buffer, size_t bytes); +static int read_unit_file(const char *filename, size_t num_pages, size_t first_image, bool is_output); static ssize_t token_consume_cb(const char **s, size_t bytes); static ssize_t token_peek_cb(const char **s, char *buffer, size_t bytes); static int verify_image(pdfio_file_t *pdf, size_t number); @@ -46,6 +48,7 @@ static int write_images_test(pdfio_file_t *pdf, int number, pdfio_obj_t *font); static int write_jpeg_test(pdfio_file_t *pdf, const char *title, int number, pdfio_obj_t *font, pdfio_obj_t *image); static int write_png_test(pdfio_file_t *pdf, int number, pdfio_obj_t *font); static int write_text_test(pdfio_file_t *pdf, int first_page, pdfio_obj_t *font, const char *filename); +static int write_unit_file(pdfio_file_t *inpdf, pdfio_file_t *outpdf, size_t *num_pages, size_t *first_image); // @@ -249,17 +252,13 @@ do_test_file(const char *filename, // I - PDF filename static int // O - Exit status do_unit_tests(void) { - int i; // Looping var - pdfio_file_t *pdf, // Test PDF file + pdfio_file_t *inpdf, // Input PDF file *outpdf; // Output PDF file + int outfd; // Output file descriptor bool error = false; // Error callback data _pdfio_token_t tb; // Token buffer const char *s; // String buffer _pdfio_value_t value; // Value - pdfio_obj_t *color_jpg, // color.jpg image - *gray_jpg, // gray.jpg image - *helvetica, // Helvetica font - *page; // Page from test PDF file size_t first_image, // First image object num_pages; // Number of pages written static const char *complex_dict = // Complex dictionary value @@ -716,7 +715,7 @@ do_unit_tests(void) // First open the test PDF file... fputs("pdfioFileOpen(\"testfiles/testpdfio.pdf\"): ", stdout); - if ((pdf = pdfioFileOpen("testfiles/testpdfio.pdf", (pdfio_error_cb_t)error_cb, &error)) != NULL) + if ((inpdf = pdfioFileOpen("testfiles/testpdfio.pdf", (pdfio_error_cb_t)error_cb, &error)) != NULL) puts("PASS"); else return (1); @@ -726,8 +725,8 @@ do_unit_tests(void) // Test the value parsers for edge cases... fputs("_pdfioValueRead(complex_dict): ", stdout); s = complex_dict; - _pdfioTokenInit(&tb, pdf, (_pdfio_tconsume_cb_t)token_consume_cb, (_pdfio_tpeek_cb_t)token_peek_cb, (void *)&s); - if (_pdfioValueRead(pdf, &tb, &value)) + _pdfioTokenInit(&tb, inpdf, (_pdfio_tconsume_cb_t)token_consume_cb, (_pdfio_tpeek_cb_t)token_peek_cb, (void *)&s); + if (_pdfioValueRead(inpdf, &tb, &value)) { // TODO: Check value... fputs("PASS: ", stdout); @@ -740,8 +739,8 @@ do_unit_tests(void) // Test the value parsers for edge cases... fputs("_pdfioValueRead(cid_dict): ", stdout); s = cid_dict; - _pdfioTokenInit(&tb, pdf, (_pdfio_tconsume_cb_t)token_consume_cb, (_pdfio_tpeek_cb_t)token_peek_cb, (void *)&s); - if (_pdfioValueRead(pdf, &tb, &value)) + _pdfioTokenInit(&tb, inpdf, (_pdfio_tconsume_cb_t)token_consume_cb, (_pdfio_tpeek_cb_t)token_peek_cb, (void *)&s); + if (_pdfioValueRead(inpdf, &tb, &value)) { // TODO: Check value... fputs("PASS: ", stdout); @@ -758,230 +757,31 @@ do_unit_tests(void) else return (1); - // Set info values... - fputs("pdfioFileGet/SetAuthor: ", stdout); - pdfioFileSetAuthor(outpdf, "Michael R Sweet"); - if ((s = pdfioFileGetAuthor(outpdf)) != NULL && !strcmp(s, "Michael R Sweet")) - { - puts("PASS"); - } - else if (s) - { - printf("FAIL (got '%s', expected 'Michael R Sweet')\n", s); + if (write_unit_file(inpdf, outpdf, &num_pages, &first_image)) return (1); - } - else + + if (read_unit_file("testpdfio-out.pdf", num_pages, first_image, false)) + return (1); + + // Create a new PDF file... + if ((outfd = open("testpdfio-out2.pdf", O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0666)) < 0) { - puts("FAIL (got NULL, expected 'Michael R Sweet')"); + perror("Unable to open \"testpdfio-out2.pdf\""); return (1); } - fputs("pdfioFileGet/SetCreator: ", stdout); - pdfioFileSetCreator(outpdf, "testpdfio"); - if ((s = pdfioFileGetCreator(outpdf)) != NULL && !strcmp(s, "testpdfio")) - { - puts("PASS"); - } - else if (s) - { - printf("FAIL (got '%s', expected 'testpdfio')\n", s); - return (1); - } - else - { - puts("FAIL (got NULL, expected 'testpdfio')"); - return (1); - } - - fputs("pdfioFileGet/SetKeywords: ", stdout); - pdfioFileSetKeywords(outpdf, "one fish,two fish,red fish,blue fish"); - if ((s = pdfioFileGetKeywords(outpdf)) != NULL && !strcmp(s, "one fish,two fish,red fish,blue fish")) - { - puts("PASS"); - } - else if (s) - { - printf("FAIL (got '%s', expected 'one fish,two fish,red fish,blue fish')\n", s); - return (1); - } - else - { - puts("FAIL (got NULL, expected 'one fish,two fish,red fish,blue fish')"); - return (1); - } - - fputs("pdfioFileGet/SetSubject: ", stdout); - pdfioFileSetSubject(outpdf, "Unit test document"); - if ((s = pdfioFileGetSubject(outpdf)) != NULL && !strcmp(s, "Unit test document")) - { - puts("PASS"); - } - else if (s) - { - printf("FAIL (got '%s', expected 'Unit test document')\n", s); - return (1); - } - else - { - puts("FAIL (got NULL, expected 'Unit test document')"); - return (1); - } - - fputs("pdfioFileGet/SetTitle: ", stdout); - pdfioFileSetTitle(outpdf, "Test Document"); - if ((s = pdfioFileGetTitle(outpdf)) != NULL && !strcmp(s, "Test Document")) - { - puts("PASS"); - } - else if (s) - { - printf("FAIL (got '%s', expected 'Test Document')\n", s); - return (1); - } - else - { - puts("FAIL (got NULL, expected 'Test Document')"); - return (1); - } - - // Create some image objects... - fputs("pdfioFileCreateImageObjFromFile(\"testfiles/color.jpg\"): ", stdout); - if ((color_jpg = pdfioFileCreateImageObjFromFile(outpdf, "testfiles/color.jpg", true)) != NULL) + fputs("pdfioFileCreateOutput(...): ", stdout); + if ((outpdf = pdfioFileCreateOutput((pdfio_output_cb_t)output_cb, &outfd, NULL, NULL, NULL, (pdfio_error_cb_t)error_cb, &error)) != NULL) puts("PASS"); else return (1); - fputs("pdfioFileCreateImageObjFromFile(\"testfiles/gray.jpg\"): ", stdout); - if ((gray_jpg = pdfioFileCreateImageObjFromFile(outpdf, "testfiles/gray.jpg", true)) != NULL) - puts("PASS"); - else + if (write_unit_file(inpdf, outpdf, &num_pages, &first_image)) return (1); - // Create fonts... - fputs("pdfioFileCreateFontObjFromBase(\"Helvetica\"): ", stdout); - if ((helvetica = pdfioFileCreateFontObjFromBase(outpdf, "Helvetica")) != NULL) - puts("PASS"); - else - return (1); + close(outfd); - // Copy the first page from the test PDF file... - fputs("pdfioFileGetPage(0): ", stdout); - if ((page = pdfioFileGetPage(pdf, 0)) != NULL) - puts("PASS"); - else - return (1); - - fputs("pdfioPageCopy(first page): ", stdout); - if (pdfioPageCopy(outpdf, page)) - puts("PASS"); - else - return (1); - - // Write a page with a color image... - if (write_jpeg_test(outpdf, "Color JPEG Test", 2, helvetica, color_jpg)) - return (1); - - // Copy the third page from the test PDF file... - fputs("pdfioFileGetPage(2): ", stdout); - if ((page = pdfioFileGetPage(pdf, 2)) != NULL) - puts("PASS"); - else - return (1); - - fputs("pdfioPageCopy(third page): ", stdout); - if (pdfioPageCopy(outpdf, page)) - puts("PASS"); - else - return (1); - - // Write a page with a grayscale image... - if (write_jpeg_test(outpdf, "Grayscale JPEG Test", 4, helvetica, gray_jpg)) - return (1); - - // Write a page with PNG images... - if (write_png_test(outpdf, 5, helvetica)) - return (1); - - // Write a page that tests multiple color spaces... - if (write_color_test(outpdf, 6, helvetica)) - return (1); - - // Write a page with test images... - first_image = pdfioFileGetNumObjs(outpdf) + 1; - if (write_images_test(outpdf, 7, helvetica)) - return (1); - - // Write a page width alpha (soft masks)... - if (write_alpha_test(outpdf, 8, helvetica)) - return (1); - - // Test TrueType fonts... - if (write_font_test(outpdf, 9, helvetica, false)) - return (1); - - if (write_font_test(outpdf, 10, helvetica, true)) - return (1); - - // Print this text file... - if (write_text_test(outpdf, 11, helvetica, "README.md")) - return (1); - - // Close the test PDF file... - fputs("pdfioFileClose(\"testfiles/testpdfio.pdf\"): ", stdout); - if (pdfioFileClose(pdf)) - puts("PASS"); - else - return (1); - - fputs("pdfioFileGetNumPages: ", stdout); - if ((num_pages = pdfioFileGetNumPages(outpdf)) > 0) - { - printf("PASS (%lu)\n", (unsigned long)num_pages); - } - else - { - puts("FAIL"); - return (1); - } - - // Close the new PDF file... - fputs("pdfioFileClose(\"testpdfio-out.pdf\"): ", stdout); - if (pdfioFileClose(outpdf)) - puts("PASS"); - else - return (1); - - // Open the new PDF file to read it... - fputs("pdfioFileOpen(\"testpdfio-out.pdf\", ...): ", stdout); - if ((pdf = pdfioFileOpen("testpdfio-out.pdf", (pdfio_error_cb_t)error_cb, &error)) != NULL) - puts("PASS"); - else - return (1); - - // Verify the number of pages is the same... - fputs("pdfioFileGetNumPages: ", stdout); - if (num_pages == pdfioFileGetNumPages(pdf)) - { - puts("PASS"); - } - else - { - printf("FAIL (%lu != %lu)\n", (unsigned long)num_pages, (unsigned long)pdfioFileGetNumPages(pdf)); - return (1); - } - - // Verify the images - for (i = 0; i < 7; i ++) - { - if (verify_image(pdf, first_image + (size_t)i)) - return (1); - } - - // Close the new PDF file... - fputs("pdfioFileClose(\"testpdfio-out.pdf\"): ", stdout); - if (pdfioFileClose(pdf)) - puts("PASS"); - else + if (read_unit_file("testpdfio-out2.pdf", num_pages, first_image, true)) return (1); return (0); @@ -1068,6 +868,76 @@ error_cb(pdfio_file_t *pdf, // I - PDF file } +// +// 'output_cb()' - Write output to a file. +// + +static ssize_t // O - Number of bytes written +output_cb(int *fd, // I - File descriptor + const void *buffer, // I - Output buffer + size_t bytes) // I - Number of bytes to write +{ + return (write(*fd, buffer, bytes)); +} + + +// +// 'read_unit_file()' - Read back a unit test file and confirm its contents. +// + +static int // O - Exit status +read_unit_file(const char *filename, // I - File to read + size_t num_pages, // I - Expected number of pages + size_t first_image, // I - First image object + bool is_output) // I - File written with output callback? +{ + pdfio_file_t *pdf; // PDF file + size_t i; // Looping var + bool error = false; // Error callback data + + + // Open the new PDF file to read it... + printf("pdfioFileOpen(\"%s\", ...): ", filename); + if ((pdf = pdfioFileOpen(filename, (pdfio_error_cb_t)error_cb, &error)) != NULL) + puts("PASS"); + else + return (1); + + // Verify the number of pages is the same... + fputs("pdfioFileGetNumPages: ", stdout); + if (num_pages == pdfioFileGetNumPages(pdf)) + { + puts("PASS"); + } + else + { + printf("FAIL (%lu != %lu)\n", (unsigned long)num_pages, (unsigned long)pdfioFileGetNumPages(pdf)); + return (1); + } + + // Verify the images + for (i = 0; i < 7; i ++) + { + if (is_output) + { + if (verify_image(pdf, first_image + (size_t)i * 2)) + return (1); + } + else if (verify_image(pdf, first_image + (size_t)i)) + return (1); + } + + // Close the new PDF file... + fputs("pdfioFileClose(\"testpdfio-out.pdf\"): ", stdout); + if (pdfioFileClose(pdf)) + puts("PASS"); + else + return (1); + + return (0); +} + + // // 'token_consume_cb()' - Consume bytes from a test string. // @@ -2843,3 +2713,211 @@ write_text_test(pdfio_file_t *pdf, // I - PDF file pdfioStreamClose(st); return (1); } + + +// +// 'write_unit_file()' - Write a unit test file. +// + +static int // O - Exit status +write_unit_file( + pdfio_file_t *inpdf, // I - Input PDF file + pdfio_file_t *outpdf, // I - Output PDF file + size_t *num_pages, // O - Number of pages + size_t *first_image) // O - First image object +{ + const char *s; // String buffer + pdfio_obj_t *color_jpg, // color.jpg image + *gray_jpg, // gray.jpg image + *helvetica, // Helvetica font + *page; // Page from test PDF file + + + // Set info values... + fputs("pdfioFileGet/SetAuthor: ", stdout); + pdfioFileSetAuthor(outpdf, "Michael R Sweet"); + if ((s = pdfioFileGetAuthor(outpdf)) != NULL && !strcmp(s, "Michael R Sweet")) + { + puts("PASS"); + } + else if (s) + { + printf("FAIL (got '%s', expected 'Michael R Sweet')\n", s); + return (1); + } + else + { + puts("FAIL (got NULL, expected 'Michael R Sweet')"); + return (1); + } + + fputs("pdfioFileGet/SetCreator: ", stdout); + pdfioFileSetCreator(outpdf, "testpdfio"); + if ((s = pdfioFileGetCreator(outpdf)) != NULL && !strcmp(s, "testpdfio")) + { + puts("PASS"); + } + else if (s) + { + printf("FAIL (got '%s', expected 'testpdfio')\n", s); + return (1); + } + else + { + puts("FAIL (got NULL, expected 'testpdfio')"); + return (1); + } + + fputs("pdfioFileGet/SetKeywords: ", stdout); + pdfioFileSetKeywords(outpdf, "one fish,two fish,red fish,blue fish"); + if ((s = pdfioFileGetKeywords(outpdf)) != NULL && !strcmp(s, "one fish,two fish,red fish,blue fish")) + { + puts("PASS"); + } + else if (s) + { + printf("FAIL (got '%s', expected 'one fish,two fish,red fish,blue fish')\n", s); + return (1); + } + else + { + puts("FAIL (got NULL, expected 'one fish,two fish,red fish,blue fish')"); + return (1); + } + + fputs("pdfioFileGet/SetSubject: ", stdout); + pdfioFileSetSubject(outpdf, "Unit test document"); + if ((s = pdfioFileGetSubject(outpdf)) != NULL && !strcmp(s, "Unit test document")) + { + puts("PASS"); + } + else if (s) + { + printf("FAIL (got '%s', expected 'Unit test document')\n", s); + return (1); + } + else + { + puts("FAIL (got NULL, expected 'Unit test document')"); + return (1); + } + + fputs("pdfioFileGet/SetTitle: ", stdout); + pdfioFileSetTitle(outpdf, "Test Document"); + if ((s = pdfioFileGetTitle(outpdf)) != NULL && !strcmp(s, "Test Document")) + { + puts("PASS"); + } + else if (s) + { + printf("FAIL (got '%s', expected 'Test Document')\n", s); + return (1); + } + else + { + puts("FAIL (got NULL, expected 'Test Document')"); + return (1); + } + + // Create some image objects... + fputs("pdfioFileCreateImageObjFromFile(\"testfiles/color.jpg\"): ", stdout); + if ((color_jpg = pdfioFileCreateImageObjFromFile(outpdf, "testfiles/color.jpg", true)) != NULL) + puts("PASS"); + else + return (1); + + fputs("pdfioFileCreateImageObjFromFile(\"testfiles/gray.jpg\"): ", stdout); + if ((gray_jpg = pdfioFileCreateImageObjFromFile(outpdf, "testfiles/gray.jpg", true)) != NULL) + puts("PASS"); + else + return (1); + + // Create fonts... + fputs("pdfioFileCreateFontObjFromBase(\"Helvetica\"): ", stdout); + if ((helvetica = pdfioFileCreateFontObjFromBase(outpdf, "Helvetica")) != NULL) + puts("PASS"); + else + return (1); + + // Copy the first page from the test PDF file... + fputs("pdfioFileGetPage(0): ", stdout); + if ((page = pdfioFileGetPage(inpdf, 0)) != NULL) + puts("PASS"); + else + return (1); + + fputs("pdfioPageCopy(first page): ", stdout); + if (pdfioPageCopy(outpdf, page)) + puts("PASS"); + else + return (1); + + // Write a page with a color image... + if (write_jpeg_test(outpdf, "Color JPEG Test", 2, helvetica, color_jpg)) + return (1); + + // Copy the third page from the test PDF file... + fputs("pdfioFileGetPage(2): ", stdout); + if ((page = pdfioFileGetPage(inpdf, 2)) != NULL) + puts("PASS"); + else + return (1); + + fputs("pdfioPageCopy(third page): ", stdout); + if (pdfioPageCopy(outpdf, page)) + puts("PASS"); + else + return (1); + + // Write a page with a grayscale image... + if (write_jpeg_test(outpdf, "Grayscale JPEG Test", 4, helvetica, gray_jpg)) + return (1); + + // Write a page with PNG images... + if (write_png_test(outpdf, 5, helvetica)) + return (1); + + // Write a page that tests multiple color spaces... + if (write_color_test(outpdf, 6, helvetica)) + return (1); + + // Write a page with test images... + *first_image = pdfioFileGetNumObjs(outpdf) + 1; + if (write_images_test(outpdf, 7, helvetica)) + return (1); + + // Write a page width alpha (soft masks)... + if (write_alpha_test(outpdf, 8, helvetica)) + return (1); + + // Test TrueType fonts... + if (write_font_test(outpdf, 9, helvetica, false)) + return (1); + + if (write_font_test(outpdf, 10, helvetica, true)) + return (1); + + // Print this text file... + if (write_text_test(outpdf, 11, helvetica, "README.md")) + return (1); + + fputs("pdfioFileGetNumPages: ", stdout); + if ((*num_pages = pdfioFileGetNumPages(outpdf)) > 0) + { + printf("PASS (%lu)\n", (unsigned long)*num_pages); + } + else + { + puts("FAIL"); + return (1); + } + + // Close the new PDF file... + fputs("pdfioFileClose(...): ", stdout); + if (pdfioFileClose(outpdf)) + puts("PASS"); + else + return (1); + + return (0); +}