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 0000000..6b39aa9 Binary files /dev/null and b/testfiles/pdfio-rgba.png differ 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. //