From 26a92febc687acc9074440ece1ee15cfdf64d7b1 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Sun, 5 Oct 2025 15:19:15 -0400 Subject: [PATCH] Cleanup PR for PDF/A support and add documentation (Issue #122) --- CHANGES.md | 1 + pdfio-content.c | 20 +-- pdfio-file.c | 147 +++++++++++-------- pdfio-private.h | 31 ++-- testfiles/pdfio-rgba.png | Bin 0 -> 15711 bytes testpdfio.c | 308 +++++++++++++++++++++++++-------------- 6 files changed, 316 insertions(+), 191 deletions(-) create mode 100644 testfiles/pdfio-rgba.png diff --git a/CHANGES.md b/CHANGES.md index 1b4bc24..12b4e25 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ v1.6.0 - YYYY-MM-DD - Added explicit support for warning messages (Issue #118) - Added `pdfioFileCreateFontObjFromData` function for embedding fonts in memory (Issue #120) +- Added support for specifying PDF/A versions for created PDF files (Issue #122) - Added `pdfioContentBeginMarked` and `pdfioContentEndMarked` functions for creating tagged PDF content (Issue #123) - Added `pdfioFileGetLanguage` and `pdfioFileSetLanguage` functions for getting diff --git a/pdfio-content.c b/pdfio-content.c index bf4df9e..f29feae 100644 --- a/pdfio-content.c +++ b/pdfio-content.c @@ -1700,6 +1700,8 @@ pdfioFileAddOutputIntent( // (ISO-8859-1 with additional characters such as the Euro symbol) subset of // Unicode. // +// > Note: This function cannot be used when producing PDF/A files. +// pdfio_obj_t * // O - Font object pdfioFileCreateFontObjFromBase( @@ -1709,7 +1711,8 @@ pdfioFileCreateFontObjFromBase( pdfio_dict_t *dict; // Font dictionary pdfio_obj_t *obj; // Font object - if (pdf && pdf->pdfa != _PDFIO_PDFA_NONE) + + if (pdf && pdf->profile >= _PDFIO_PROFILE_PDFA_1A && pdf->profile <= _PDFIO_PROFILE_PDFA_4) { _pdfioFileError(pdf, "Base fonts are not allowed in PDF/A files; use pdfioFileCreateFontObjFromFile to embed a font."); return (NULL); @@ -2052,8 +2055,9 @@ pdfioFileCreateICCObjFromFile( // "interpolate" parameter specifies whether to interpolate when scaling the // image on the page. // -// Note: When creating an image object with alpha, a second image object is -// created to hold the "soft mask" data for the primary image. +// > Note: When creating an image object with alpha, a second image object is +// > created to hold the "soft mask" data for the primary image. PDF/A-1 +// > files do not support alpha-based transparency. // pdfio_obj_t * // O - Object @@ -2078,7 +2082,7 @@ pdfioFileCreateImageObjFromData( }; - if (pdf && (pdf->pdfa == _PDFIO_PDFA_1A || pdf->pdfa == _PDFIO_PDFA_1B) && alpha) + if (pdf && pdf->profile >= _PDFIO_PROFILE_PDFA_1A && pdf->profile <= _PDFIO_PROFILE_PDFA_1B && alpha) { _pdfioFileError(pdf, "Images with transparency (alpha channels) are not allowed in PDF/A-1 files."); return (NULL); @@ -2117,9 +2121,8 @@ pdfioFileCreateImageObjFromData( // the "interpolate" parameter specifies whether to interpolate when scaling the // image on the page. // -// > Note: Currently PNG support is limited to grayscale, RGB, or indexed files -// > without interlacing or alpha. Transparency (masking) based on color/index -// > is supported. +// > Note: PNG files containing transparency cannot be used when producing +// > PDF/A files. // pdfio_obj_t * // O - Object @@ -2750,7 +2753,7 @@ copy_png(pdfio_dict_t *dict, // I - Dictionary depth = png_get_bit_depth(pp, info); color_type = png_get_color_type(pp, info); - if ((dict->pdf->pdfa == _PDFIO_PDFA_1A || dict->pdf->pdfa == _PDFIO_PDFA_1B) && (color_type & PNG_COLOR_MASK_ALPHA)) + if (dict->pdf->profile >= _PDFIO_PROFILE_PDFA_1A && dict->pdf->profile <= _PDFIO_PROFILE_PDFA_1B && (color_type & PNG_COLOR_MASK_ALPHA)) { _pdfioFileError(dict->pdf, "PNG images with transparency (alpha channels) are not allowed in PDF/A-1 files."); goto finish_png; @@ -2874,7 +2877,6 @@ copy_png(pdfio_dict_t *dict, // I - Dictionary if (pp && info) { - png_read_end(pp, info); png_destroy_read_struct(&pp, &info, NULL); pp = NULL; diff --git a/pdfio-file.c b/pdfio-file.c index 75532e7..e3f87f2 100644 --- a/pdfio-file.c +++ b/pdfio-file.c @@ -181,8 +181,20 @@ pdfioFileClose(pdfio_file_t *pdf) // I - PDF file // name of the PDF file to create. // // The "version" argument specifies the PDF version number for the file or -// `NULL` for the default ("2.0"). The value "PCLm-1.0" can be specified to -// produce the PCLm subset of PDF. +// `NULL` for the default ("2.0"). The following values are recognized: +// +// - "1.3", "1.4", "1.5", "1.6", "1.7", "2.0": Generic PDF files of the +// specified versions. +// - "PCLm-1.0": The PCLm (raster) subset of PDF supported by some printers. +// - "PDF/A-1a": PDF/A-1a:2005 +// - "PDF/A-1b": PDF/A-1b:2005 +// - "PDF/A-2a": PDF/A-2a:2011 +// - "PDF/A-2b": PDF/A-2b:2011 +// - "PDF/A-2u": PDF/A-2u:2011 +// - "PDF/A-3a": PDF/A-3a:2012 +// - "PDF/A-3b": PDF/A-3b:2012 +// - "PDF/A-3u": PDF/A-3u:2012 +// - "PDF/A-4": PDF/A-4:2020 // // The "media_box" and "crop_box" arguments specify the default MediaBox and // CropBox for pages in the PDF file - if `NULL` then a default "Universal" size @@ -433,9 +445,22 @@ _pdfioFileCreateObj( // ``` // // The "version" argument specifies the PDF version number for the file or -// `NULL` for the default ("2.0"). Unlike @link pdfioFileCreate@ and -// @link pdfioFileCreateTemporary@, it is generally not safe to pass the -// "PCLm-1.0" version string. +// `NULL` for the default ("2.0"). The following values are recognized: +// +// - "1.3", "1.4", "1.5", "1.6", "1.7", "2.0": Generic PDF files of the +// specified versions. +// - "PDF/A-1a": PDF/A-1a:2005 +// - "PDF/A-1b": PDF/A-1b:2005 +// - "PDF/A-2a": PDF/A-2a:2011 +// - "PDF/A-2b": PDF/A-2b:2011 +// - "PDF/A-2u": PDF/A-2u:2011 +// - "PDF/A-3a": PDF/A-3a:2012 +// - "PDF/A-3b": PDF/A-3b:2012 +// - "PDF/A-3u": PDF/A-3u:2012 +// - "PDF/A-4": PDF/A-4:2020 +// +// Unlike @link pdfioFileCreate@ and @link pdfioFileCreateTemporary@, it is +// generally not safe to pass the "PCLm-1.0" version string. // // The "media_box" and "crop_box" arguments specify the default MediaBox and // CropBox for pages in the PDF file - if `NULL` then a default "Universal" size @@ -591,8 +616,20 @@ pdfioFileCreateStringObj( // will have a ".pdf" extension. // // The "version" argument specifies the PDF version number for the file or -// `NULL` for the default ("2.0"). The value "PCLm-1.0" can be specified to -// produce the PCLm subset of PDF. +// `NULL` for the default ("2.0"). The following values are recognized: +// +// - "1.3", "1.4", "1.5", "1.6", "1.7", "2.0": Generic PDF files of the +// specified versions. +// - "PCLm-1.0": The PCLm (raster) subset of PDF supported by some printers. +// - "PDF/A-1a": PDF/A-1a:2005 +// - "PDF/A-1b": PDF/A-1b:2005 +// - "PDF/A-2a": PDF/A-2a:2011 +// - "PDF/A-2b": PDF/A-2b:2011 +// - "PDF/A-2u": PDF/A-2u:2011 +// - "PDF/A-3a": PDF/A-3a:2012 +// - "PDF/A-3b": PDF/A-3b:2012 +// - "PDF/A-3u": PDF/A-3u:2012 +// - "PDF/A-4": PDF/A-4:2020 // // The "media_box" and "crop_box" arguments specify the default MediaBox and // CropBox for pages in the PDF file - if `NULL` then a default "Universal" size @@ -1329,7 +1366,7 @@ pdfioFileSetPermissions( if (!pdf) return (false); - if (pdf->pdfa != _PDFIO_PDFA_NONE && encryption != PDFIO_ENCRYPTION_NONE) + if (pdf->profile >= _PDFIO_PROFILE_PDFA_1A && pdf->profile <= _PDFIO_PROFILE_PDFA_4 && encryption != PDFIO_ENCRYPTION_NONE) { _pdfioFileError(pdf, "Encryption is not allowed for PDF/A files."); return (false); @@ -1520,7 +1557,8 @@ create_common( unsigned char id_value[16]; // File ID value time_t curtime; // Creation date/time _pdfio_sha256_t ctx; // Hashing context - const char *file_version; // Actual PDF version string + const char *file_version; // PDF file version string + PDFIO_DEBUG("create_common(filename=\"%s\", fd=%d, output_cb=%p, output_cbdata=%p, version=\"%s\", media_box=%p, crop_box=%p, error_cb=%p, error_cbdata=%p)\n", filename, fd, (void *)output_cb, (void *)output_cbdata, version, (void *)media_box, (void *)crop_box, (void *)error_cb, (void *)error_cbdata); @@ -1528,12 +1566,10 @@ create_common( if (!filename || (fd < 0 && !output_cb)) return (NULL); - if (!error_cb) { error_cb = _pdfioFileDefaultError; error_cbdata = NULL; - } // Allocate a PDF file structure... @@ -1557,47 +1593,53 @@ create_common( pdf->filename = strdup(filename); if (!version) - { version = "2.0"; - } if (!strncmp(version, "PDF/A-1", 7)) { file_version = "1.4"; + if (version[7] == 'a') - pdf->pdfa = _PDFIO_PDFA_1A; + pdf->profile = _PDFIO_PROFILE_PDFA_1A; else - pdf->pdfa = _PDFIO_PDFA_1B; // Default to 'b' + pdf->profile = _PDFIO_PROFILE_PDFA_1B; // Default to 'b' } else if (!strncmp(version, "PDF/A-2", 7)) { file_version = "1.7"; + if (version[7] == 'a') - pdf->pdfa = _PDFIO_PDFA_2A; + pdf->profile = _PDFIO_PROFILE_PDFA_2A; else if (version[7] == 'u') - pdf->pdfa = _PDFIO_PDFA_2U; + pdf->profile = _PDFIO_PROFILE_PDFA_2U; else - pdf->pdfa = _PDFIO_PDFA_2B; // Default to 'b' + pdf->profile = _PDFIO_PROFILE_PDFA_2B; // Default to 'b' } else if (!strncmp(version, "PDF/A-3", 7)) { file_version = "1.7"; + if (version[7] == 'a') - pdf->pdfa = _PDFIO_PDFA_3A; + pdf->profile = _PDFIO_PROFILE_PDFA_3A; else if (version[7] == 'u') - pdf->pdfa = _PDFIO_PDFA_3U; - else - pdf->pdfa = _PDFIO_PDFA_3B; // Default to 'b' + pdf->profile = _PDFIO_PROFILE_PDFA_3U; + else + pdf->profile = _PDFIO_PROFILE_PDFA_3B; // Default to 'b' } else if (!strncmp(version, "PDF/A-4", 7)) { file_version = "2.0"; - pdf->pdfa = _PDFIO_PDFA_4; + pdf->profile = _PDFIO_PROFILE_PDFA_4; + } + else if (!strncmp(version, "PCLm-", 5)) + { + file_version = "1.4"; + pdf->profile = _PDFIO_PROFILE_PCLM; } else { file_version = version; - pdf->pdfa = _PDFIO_PDFA_NONE; + pdf->profile = _PDFIO_PROFILE_NONE; } pdf->version = strdup(file_version); @@ -1630,19 +1672,17 @@ create_common( pdf->crop_box.y2 = 11.0f * 72.0f; } - // Write the PDF header (special case for PCLm, otherwise standard/PDF-A header) - if (!strncmp(version, "PCLm-", 5)) + // Write the PDF header (special case for PCLm, otherwise standard header) + if (pdf->profile == _PDFIO_PROFILE_PCLM) { if (!_pdfioFilePrintf(pdf, "%%PDF-1.4\n%%%s\n", version)) goto error; } - else + else if (!_pdfioFilePrintf(pdf, "%%PDF-%s\n%%\342\343\317\323\n", pdf->version)) { - if (!_pdfioFilePrintf(pdf, "%%PDF-%s\n%%\342\343\317\323\n", pdf->version)) - goto error; + goto error; } - // Create the pages object... if ((dict = pdfioDictCreate(pdf)) == NULL) goto error; @@ -2735,43 +2775,30 @@ write_metadata(pdfio_file_t *pdf) // I - PDF file status &= pdfioStreamPrintf(st, " %H\n", value); status &= pdfioStreamPuts(st, " \n"); - // TODO: Need a better way to choose the output profile - something that lets - // us choose the base PDF version and PDF/A, PDF/E, PDF/X, etc. -#if 0 - status &= pdfioStreamPuts(st, " \n"); - status &= pdfioStreamPuts(st, " A\n"); - status &= pdfioStreamPuts(st, " 1\n"); - status &= pdfioStreamPuts(st, " \n"); -#endif // 0 - - if (pdf->pdfa != _PDFIO_PDFA_NONE) + if (pdf->profile >= _PDFIO_PROFILE_PDFA_1A && pdf->profile <= _PDFIO_PROFILE_PDFA_4) { - static const char * const pdfa_versions[] = - { - "1A", // _PDFIO_PDFA_1A - "1B", // _PDFIO_PDFA_1B - "2A", // _PDFIO_PDFA_2A - "2B", // _PDFIO_PDFA_2B - "2U", // _PDFIO_PDFA_2U - "3A", // _PDFIO_PDFA_3A - "3B", // _PDFIO_PDFA_3B - "3U", // _PDFIO_PDFA_3U - "4", // _PDFIO_PDFA_4 + static const char * const pdfa_versions[] = + { + "1A", // _PDFIO_PROFILE_PDFA_1A + "1B", // _PDFIO_PROFILE_PDFA_1B + "2A", // _PDFIO_PROFILE_PDFA_2A + "2B", // _PDFIO_PROFILE_PDFA_2B + "2U", // _PDFIO_PROFILE_PDFA_2U + "3A", // _PDFIO_PROFILE_PDFA_3A + "3B", // _PDFIO_PROFILE_PDFA_3B + "3U", // _PDFIO_PROFILE_PDFA_3U + "4", // _PDFIO_PROFILE_PDFA_4 }; - const char *version_info = pdfa_versions[pdf->pdfa - _PDFIO_PDFA_1A]; - const char *conformance; - conformance = version_info + 1; + + const char *info = pdfa_versions[pdf->profile - _PDFIO_PROFILE_PDFA_1A]; status &= pdfioStreamPuts(st, " \n"); - status &= pdfioStreamPrintf(st, " %c\n",version_info[0]); - if (*conformance) - status &= pdfioStreamPrintf(st, " %s\n", conformance); + status &= pdfioStreamPrintf(st, " %c\n", *info); + if (info[1]) + status &= pdfioStreamPrintf(st, " %s\n", info + 1); status &= pdfioStreamPuts(st, " \n"); } - - - status &= pdfioStreamPuts(st, " \n"); status &= pdfioStreamPuts(st, "\n"); status &= pdfioStreamPuts(st, "\n"); diff --git a/pdfio-private.h b/pdfio-private.h index a82e46f..78753d9 100644 --- a/pdfio-private.h +++ b/pdfio-private.h @@ -105,20 +105,6 @@ typedef enum _pdfio_mode_e // Read/write mode _PDFIO_MODE_WRITE // Write a PDF file } _pdfio_mode_t; -typedef enum _pdfio_pdfa_e // PDF/A version constants -{ - _PDFIO_PDFA_NONE = 0, // Not a PDF/A file - _PDFIO_PDFA_1A, // PDF/A-1a:2005 - _PDFIO_PDFA_1B, // PDF/A-1b:2005 - _PDFIO_PDFA_2A, // PDF/A-2a:2011 - _PDFIO_PDFA_2B, // PDF/A-2b:20011 - _PDFIO_PDFA_2U, // PDF/A-2u:2011 - _PDFIO_PDFA_3A, // PDF/A-3a:2012 - _PDFIO_PDFA_3B, // PDF/A-3b:2012 - _PDFIO_PDFA_3U, // PDF/A-3u:2012 - _PDFIO_PDFA_4, // PDF/A-4:2020 -} _pdfio_pdfa_t; - typedef enum _pdfio_predictor_e // PNG predictor constants { _PDFIO_PREDICTOR_NONE = 1, // No predictor (default) @@ -131,6 +117,21 @@ typedef enum _pdfio_predictor_e // PNG predictor constants _PDFIO_PREDICTOR_PNG_AUTO = 15 // PNG "auto" predictor (currently mapped to Paeth) } _pdfio_predictor_t; +typedef enum _pdfio_profile_e // PDF profile constants +{ + _PDFIO_PROFILE_NONE = 0, // Base PDF file + _PDFIO_PROFILE_PCLM, // PCLm (PDF raster) file + _PDFIO_PROFILE_PDFA_1A, // PDF/A-1a:2005 + _PDFIO_PROFILE_PDFA_1B, // PDF/A-1b:2005 + _PDFIO_PROFILE_PDFA_2A, // PDF/A-2a:2011 + _PDFIO_PROFILE_PDFA_2B, // PDF/A-2b:2011 + _PDFIO_PROFILE_PDFA_2U, // PDF/A-2u:2011 + _PDFIO_PROFILE_PDFA_3A, // PDF/A-3a:2012 + _PDFIO_PROFILE_PDFA_3B, // PDF/A-3b:2012 + _PDFIO_PROFILE_PDFA_3U, // PDF/A-3u:2012 + _PDFIO_PROFILE_PDFA_4 // PDF/A-4:2020 +} _pdfio_profile_t; + typedef ssize_t (*_pdfio_tconsume_cb_t)(void *data, size_t bytes); typedef ssize_t (*_pdfio_tpeek_cb_t)(void *data, void *buffer, size_t bytes); @@ -253,7 +254,7 @@ struct _pdfio_file_s // PDF file structure unsigned char file_id[32]; // File identifier bytes struct lconv *loc; // Locale data char *version; // Version number - _pdfio_pdfa_t pdfa; // PDF/A conformance + _pdfio_profile_t profile; // PDF profile, if any pdfio_rect_t media_box, // Default MediaBox value crop_box; // Default CropBox value _pdfio_mode_t mode; // Read/write mode diff --git a/testfiles/pdfio-rgba.png b/testfiles/pdfio-rgba.png new file mode 100644 index 0000000000000000000000000000000000000000..6b39aa9ebd828535eeb4bf657d06ad8913ffd5ca GIT binary patch literal 15711 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 Dpzk|| literal 0 HcmV?d00001 diff --git a/testpdfio.c b/testpdfio.c index 60035d8..e9c907e 100644 --- a/testpdfio.c +++ b/testpdfio.c @@ -28,6 +28,7 @@ // static int do_crypto_tests(void); +static int do_pdfa_tests(void); static int do_test_file(const char *filename, int objnum, const char *password, 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); @@ -48,11 +49,11 @@ static int write_header_footer(pdfio_stream_t *st, const char *title, int number static pdfio_obj_t *write_image_object(pdfio_file_t *pdf, _pdfio_predictor_t predictor); 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_pdfa_file(const char *filename, const char *pdfa_version); static int write_png_tests(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, const char *outname, pdfio_file_t *outpdf, size_t *num_pages, size_t *first_image); -static int do_pdfa_tests(void); -static int create_pdfa_test_file(const char *filename, const char *pdfa_version); + // // 'main()' - Main entry for test program. @@ -129,108 +130,6 @@ main(int argc, // I - Number of command-line arguments return (ret); } -// -// 'create_pdfa_test_file()' - A helper function to generate a simple PDF/A file. -// -static int // O - 0 on success, 1 on error -create_pdfa_test_file( - const char *filename, // I - Name of the PDF file to create - const char *pdfa_version) // I - PDF/A version string (e.g., "PDF/A-1b") -{ - pdfio_file_t *pdf; // Output PDF file - pdfio_rect_t media_box = { 0.0, 0.0, 612.0, 792.0 }; - // Media box for US Letter - pdfio_obj_t *font; // Font object - pdfio_dict_t *page_dict; // Page dictionary - pdfio_stream_t *st; // Page content stream - bool error = false; // Error flag - - - testBegin("pdfioFileCreate(%s)", pdfa_version); - - if ((pdf = pdfioFileCreate(filename, pdfa_version, &media_box, NULL, (pdfio_error_cb_t)error_cb, &error)) == NULL) - { - testEnd(false); - return (1); - } - - // Embed a font, which is required for PDF/A - if ((font = pdfioFileCreateFontObjFromFile(pdf, "testfiles/OpenSans-Regular.ttf", false)) == NULL) - { - pdfioFileClose(pdf); - testEnd(false); - return (1); - } - - page_dict = pdfioDictCreate(pdf); - pdfioPageDictAddFont(page_dict, "F1", font); - st = pdfioFileCreatePage(pdf, page_dict); - - pdfioContentSetTextFont(st, "F1", 12.0); - pdfioContentTextBegin(st); - pdfioContentTextMoveTo(st, 72.0, 720.0); - pdfioContentTextShowf(st, false, "This is a compliance test for %s.", pdfa_version); - pdfioContentTextEnd(st); - - pdfioStreamClose(st); - - if (pdfioFileClose(pdf)) - { - testEnd(true); - return (0); - } - else - { - testEnd(false); - return (1); - } -} - - -// -// 'do_pdfa_tests()' - Run PDF/A generation and compliance tests. -// -static int // O - 0 on success, 1 on error -do_pdfa_tests(void) -{ - int status = 0; // Overall status - pdfio_file_t *fail_pdf; // PDF file for failure test - pdfio_rect_t media_box = { 0.0, 0.0, 612.0, 792.0 }; - // US Letter media box - bool error = false; // Error flag - - // Test creation of various PDF/A standards - status |= create_pdfa_test_file("testpdfio-pdfa-1b.pdf", "PDF/A-1b"); - status |= create_pdfa_test_file("testpdfio-pdfa-2b.pdf", "PDF/A-2b"); - status |= create_pdfa_test_file("testpdfio-pdfa-2u.pdf", "PDF/A-2u"); - status |= create_pdfa_test_file("testpdfio-pdfa-3b.pdf", "PDF/A-3b"); - status |= create_pdfa_test_file("testpdfio-pdfa-3u.pdf", "PDF/A-3u"); - status |= create_pdfa_test_file("testpdfio-pdfa-4.pdf", "PDF/A-4"); - - // Test that encryption is not allowed for PDF/A files - testBegin("pdfioFileCreate(testpdfio-pdfa-rc4.pdf)"); - if ((fail_pdf = pdfioFileCreate("testpdfio-pdfa-rc4.pdf", "PDF/A-1b", &media_box, NULL, (pdfio_error_cb_t)error_cb, &error)) == NULL) - { - testEndMessage(false, "pdfioFileCreate failed for encryption test."); - return (1); - } - - if (pdfioFileSetPermissions(fail_pdf, PDFIO_PERMISSION_ALL, PDFIO_ENCRYPTION_RC4_128, "owner", "user")) - { - testEndMessage(false, "encryption allowed on PDF/A file"); - status = 1; - } - else - { - // This is the expected outcome - testEnd(true); - } - pdfioFileClose(fail_pdf); - - return (status); -} - - // // 'do_crypto_tests()' - Test the various cryptographic functions in PDFio. // @@ -474,6 +373,56 @@ do_crypto_tests(void) } +// +// 'do_pdfa_tests()' - Run PDF/A generation and compliance tests. +// + +static int // O - 0 on success, 1 on error +do_pdfa_tests(void) +{ + int status = 0; // Overall status + pdfio_file_t *pdf; // PDF file for encryption test + bool error = false; // Error flag + + + // Test creation of files using various PDF/A standards + status |= write_pdfa_file("testpdfio-pdfa-1a.pdf", "PDF/A-1a"); + status |= write_pdfa_file("testpdfio-pdfa-1b.pdf", "PDF/A-1b"); + status |= write_pdfa_file("testpdfio-pdfa-2a.pdf", "PDF/A-2a"); + status |= write_pdfa_file("testpdfio-pdfa-2b.pdf", "PDF/A-2b"); + status |= write_pdfa_file("testpdfio-pdfa-2u.pdf", "PDF/A-2u"); + status |= write_pdfa_file("testpdfio-pdfa-3a.pdf", "PDF/A-3a"); + status |= write_pdfa_file("testpdfio-pdfa-3b.pdf", "PDF/A-3b"); + status |= write_pdfa_file("testpdfio-pdfa-3u.pdf", "PDF/A-3u"); + status |= write_pdfa_file("testpdfio-pdfa-4.pdf", "PDF/A-4"); + + // Test that encryption is not allowed for PDF/A files + testBegin("pdfioFileCreate(testpdfio-pdfa-rc4.pdf)"); + if ((pdf = pdfioFileCreate("testpdfio-pdfa-rc4.pdf", "PDF/A-1b", /*media_box*/NULL, /*crop_box*/NULL, (pdfio_error_cb_t)error_cb, &error)) == NULL) + { + testEnd(false); + return (1); + } + + testEnd(true); + + testBegin("pdfioFileSetPermissions(PDFIO_ENCRYPTION_RC4_128)"); + if (pdfioFileSetPermissions(pdf, PDFIO_PERMISSION_ALL, PDFIO_ENCRYPTION_RC4_128, "owner", "user")) + { + testEndMessage(false, "incorrectly allowed encryption"); + status = 1; + } + else + { + testEndMessage(true, "correctly prevented encryption"); + } + + pdfioFileClose(pdf); + + return (status); +} + + // // 'do_test_file()' - Try loading a PDF file and listing pages and objects. // @@ -1159,7 +1108,7 @@ do_unit_tests(void) if (do_crypto_tests()) return (1); - + // Create a new PDF file... testBegin("pdfioFileCreate(\"testpdfio-out.pdf\", ...)"); @@ -1326,9 +1275,8 @@ do_unit_tests(void) if (read_unit_file(temppdf, num_pages, first_image, false)) return (1); - pdfioFileClose(inpdf); - + // Do PDF/A tests... if (do_pdfa_tests()) return (1); @@ -3483,6 +3431,152 @@ write_jpeg_test(pdfio_file_t *pdf, // I - PDF file } +// +// 'write_pdfa_file()' - Generate a simple PDF/A file. +// + +static int // O - Exit status +write_pdfa_file( + const char *filename, // I - Name of the PDF file to create + const char *pdfa_version) // I - PDF/A version string (e.g., "PDF/A-1b") +{ + int status = 1; // Exit status + pdfio_file_t *pdf; // Output PDF file + pdfio_obj_t *font; // Font object + pdfio_obj_t *color_jpg, // JPEG file + *pdfio_png; // PNG file with transparency + pdfio_dict_t *page_dict; // Page dictionary + pdfio_stream_t *st; // Page content stream + bool error = false; // Error flag + double width, // Width of image + height; // Height of image + double swidth, // Scaled width + sheight, // Scaled height + tx, // X offset + ty; // Y offset + + + testBegin("pdfioFileCreate(%s)", filename); + + if ((pdf = pdfioFileCreate(filename, pdfa_version, /*media_box*/NULL, /*crop_box*/NULL, (pdfio_error_cb_t)error_cb, &error)) == NULL) + { + testEnd(false); + return (1); + } + + testEnd(true); + + // Embed a base font, which are not allowed for PDF/A + testBegin("pdfioFileCreateFontObjFromBase(Helvetica)"); + if ((font = pdfioFileCreateFontObjFromBase(pdf, "Helvetica")) != NULL) + { + testEnd(false); + goto done; + } + + testEnd(true); + + // Embed a font, which is required for PDF/A + testBegin("pdfioFileCreateFontObjFromFile(testfiles/OpenSans-Regular.ttf)"); + if ((font = pdfioFileCreateFontObjFromFile(pdf, "testfiles/OpenSans-Regular.ttf", false)) == NULL) + { + testEnd(false); + goto done; + } + + testEnd(true); + + // Try embedding two images, one with alpha and one without... + testBegin("pdfioFileCreateImageObjFromFile(testfiles/color.jpg)"); + if ((color_jpg = pdfioFileCreateImageObjFromFile(pdf, "testfiles/color.jpg", true)) == NULL) + { + testEnd(false); + goto done; + } + + testEnd(true); + + testBegin("pdfioFileCreateImageObjFromFile(testfiles/pdfio-rgba.png)"); + pdfio_png = pdfioFileCreateImageObjFromFile(pdf, "testfiles/pdfio-rgba.png", false); + + if ((pdfio_png != NULL && !strncmp(pdfa_version, "PDF/A-1", 7)) || (pdfio_png == NULL && strncmp(pdfa_version, "PDF/A-1", 7))) + { + testEnd(false); + goto done; + } + + testEnd(true); + + if (!pdfio_png) + { + testBegin("pdfioFileCreateImageObjFromFile(testfiles/pdfio-color.png)"); + if ((pdfio_png = pdfioFileCreateImageObjFromFile(pdf, "testfiles/pdfio-color.png", false)) == NULL) + { + testEnd(false); + goto done; + } + + testEnd(true); + } + + // Create a page... + page_dict = pdfioDictCreate(pdf); + pdfioPageDictAddFont(page_dict, "F1", font); + pdfioPageDictAddImage(page_dict, "I1", pdfio_png); + pdfioPageDictAddImage(page_dict, "I2", color_jpg); + + testBegin("pdfioFileCreatePage()"); + if ((st = pdfioFileCreatePage(pdf, page_dict)) == NULL) + { + testEnd(false); + goto done; + } + + testEnd(true); + + pdfioContentSetTextFont(st, "F1", 18.0); + pdfioContentTextBegin(st); + pdfioContentTextMoveTo(st, 72.0, 720.0); + pdfioContentTextShowf(st, false, "This is a %s compliance test page.", pdfa_version); + pdfioContentDrawImage(st, "IM1", 36.0, 720.0, 18.0, 18.0); + + width = pdfioImageGetWidth(color_jpg); + height = pdfioImageGetHeight(color_jpg); + + swidth = 400.0; + sheight = swidth * height / width; + if (sheight > 500.0) + { + sheight = 500.0; + swidth = sheight * width / height; + } + + tx = 0.5 * (595.28 - swidth); + ty = 0.5 * (720.0 - sheight); + + pdfioContentDrawImage(st, "IM2", tx, ty, swidth, sheight); + pdfioContentTextEnd(st); + + pdfioStreamClose(st); + + status = 0; + + done: + + testBegin("pdfioFileClose()"); + if (pdfioFileClose(pdf)) + { + testEnd(true); + return (status); + } + else + { + testEnd(false); + return (1); + } +} + + // // 'write_png_tests()' - Write pages of PNG test images. //