Cleanup PR for PDF/A support and add documentation (Issue #122)

This commit is contained in:
Michael R Sweet
2025-10-05 15:19:15 -04:00
parent d82da750d9
commit 26a92febc6
6 changed files with 316 additions and 191 deletions

View File

@@ -15,6 +15,7 @@ v1.6.0 - YYYY-MM-DD
- Added explicit support for warning messages (Issue #118) - Added explicit support for warning messages (Issue #118)
- Added `pdfioFileCreateFontObjFromData` function for embedding fonts in - Added `pdfioFileCreateFontObjFromData` function for embedding fonts in
memory (Issue #120) memory (Issue #120)
- Added support for specifying PDF/A versions for created PDF files (Issue #122)
- Added `pdfioContentBeginMarked` and `pdfioContentEndMarked` functions for - Added `pdfioContentBeginMarked` and `pdfioContentEndMarked` functions for
creating tagged PDF content (Issue #123) creating tagged PDF content (Issue #123)
- Added `pdfioFileGetLanguage` and `pdfioFileSetLanguage` functions for getting - Added `pdfioFileGetLanguage` and `pdfioFileSetLanguage` functions for getting

View File

@@ -1700,6 +1700,8 @@ pdfioFileAddOutputIntent(
// (ISO-8859-1 with additional characters such as the Euro symbol) subset of // (ISO-8859-1 with additional characters such as the Euro symbol) subset of
// Unicode. // Unicode.
// //
// > Note: This function cannot be used when producing PDF/A files.
//
pdfio_obj_t * // O - Font object pdfio_obj_t * // O - Font object
pdfioFileCreateFontObjFromBase( pdfioFileCreateFontObjFromBase(
@@ -1709,7 +1711,8 @@ pdfioFileCreateFontObjFromBase(
pdfio_dict_t *dict; // Font dictionary pdfio_dict_t *dict; // Font dictionary
pdfio_obj_t *obj; // Font object 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."); _pdfioFileError(pdf, "Base fonts are not allowed in PDF/A files; use pdfioFileCreateFontObjFromFile to embed a font.");
return (NULL); return (NULL);
@@ -2052,8 +2055,9 @@ pdfioFileCreateICCObjFromFile(
// "interpolate" parameter specifies whether to interpolate when scaling the // "interpolate" parameter specifies whether to interpolate when scaling the
// image on the page. // image on the page.
// //
// Note: When creating an image object with alpha, a second image object is // > Note: When creating an image object with alpha, a second image object is
// created to hold the "soft mask" data for the primary image. // > 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 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."); _pdfioFileError(pdf, "Images with transparency (alpha channels) are not allowed in PDF/A-1 files.");
return (NULL); return (NULL);
@@ -2117,9 +2121,8 @@ pdfioFileCreateImageObjFromData(
// the "interpolate" parameter specifies whether to interpolate when scaling the // the "interpolate" parameter specifies whether to interpolate when scaling the
// image on the page. // image on the page.
// //
// > Note: Currently PNG support is limited to grayscale, RGB, or indexed files // > Note: PNG files containing transparency cannot be used when producing
// > without interlacing or alpha. Transparency (masking) based on color/index // > PDF/A files.
// > is supported.
// //
pdfio_obj_t * // O - Object pdfio_obj_t * // O - Object
@@ -2750,7 +2753,7 @@ copy_png(pdfio_dict_t *dict, // I - Dictionary
depth = png_get_bit_depth(pp, info); depth = png_get_bit_depth(pp, info);
color_type = png_get_color_type(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."); _pdfioFileError(dict->pdf, "PNG images with transparency (alpha channels) are not allowed in PDF/A-1 files.");
goto finish_png; goto finish_png;
@@ -2874,7 +2877,6 @@ copy_png(pdfio_dict_t *dict, // I - Dictionary
if (pp && info) if (pp && info)
{ {
png_read_end(pp, info);
png_destroy_read_struct(&pp, &info, NULL); png_destroy_read_struct(&pp, &info, NULL);
pp = NULL; pp = NULL;

View File

@@ -181,8 +181,20 @@ pdfioFileClose(pdfio_file_t *pdf) // I - PDF file
// name of the PDF file to create. // name of the PDF file to create.
// //
// The "version" argument specifies the PDF version number for the file or // 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 // `NULL` for the default ("2.0"). The following values are recognized:
// produce the PCLm subset of PDF. //
// - "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 // 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 // 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 // The "version" argument specifies the PDF version number for the file or
// `NULL` for the default ("2.0"). Unlike @link pdfioFileCreate@ and // `NULL` for the default ("2.0"). The following values are recognized:
// @link pdfioFileCreateTemporary@, it is generally not safe to pass the //
// "PCLm-1.0" version string. // - "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 // 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 // CropBox for pages in the PDF file - if `NULL` then a default "Universal" size
@@ -591,8 +616,20 @@ pdfioFileCreateStringObj(
// will have a ".pdf" extension. // will have a ".pdf" extension.
// //
// The "version" argument specifies the PDF version number for the file or // 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 // `NULL` for the default ("2.0"). The following values are recognized:
// produce the PCLm subset of PDF. //
// - "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 // 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 // CropBox for pages in the PDF file - if `NULL` then a default "Universal" size
@@ -1329,7 +1366,7 @@ pdfioFileSetPermissions(
if (!pdf) if (!pdf)
return (false); 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."); _pdfioFileError(pdf, "Encryption is not allowed for PDF/A files.");
return (false); return (false);
@@ -1520,7 +1557,8 @@ create_common(
unsigned char id_value[16]; // File ID value unsigned char id_value[16]; // File ID value
time_t curtime; // Creation date/time time_t curtime; // Creation date/time
_pdfio_sha256_t ctx; // Hashing context _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); 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)) if (!filename || (fd < 0 && !output_cb))
return (NULL); return (NULL);
if (!error_cb) if (!error_cb)
{ {
error_cb = _pdfioFileDefaultError; error_cb = _pdfioFileDefaultError;
error_cbdata = NULL; error_cbdata = NULL;
} }
// Allocate a PDF file structure... // Allocate a PDF file structure...
@@ -1557,47 +1593,53 @@ create_common(
pdf->filename = strdup(filename); pdf->filename = strdup(filename);
if (!version) if (!version)
{
version = "2.0"; version = "2.0";
}
if (!strncmp(version, "PDF/A-1", 7)) if (!strncmp(version, "PDF/A-1", 7))
{ {
file_version = "1.4"; file_version = "1.4";
if (version[7] == 'a') if (version[7] == 'a')
pdf->pdfa = _PDFIO_PDFA_1A; pdf->profile = _PDFIO_PROFILE_PDFA_1A;
else 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)) else if (!strncmp(version, "PDF/A-2", 7))
{ {
file_version = "1.7"; file_version = "1.7";
if (version[7] == 'a') if (version[7] == 'a')
pdf->pdfa = _PDFIO_PDFA_2A; pdf->profile = _PDFIO_PROFILE_PDFA_2A;
else if (version[7] == 'u') else if (version[7] == 'u')
pdf->pdfa = _PDFIO_PDFA_2U; pdf->profile = _PDFIO_PROFILE_PDFA_2U;
else 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)) else if (!strncmp(version, "PDF/A-3", 7))
{ {
file_version = "1.7"; file_version = "1.7";
if (version[7] == 'a') if (version[7] == 'a')
pdf->pdfa = _PDFIO_PDFA_3A; pdf->profile = _PDFIO_PROFILE_PDFA_3A;
else if (version[7] == 'u') else if (version[7] == 'u')
pdf->pdfa = _PDFIO_PDFA_3U; pdf->profile = _PDFIO_PROFILE_PDFA_3U;
else else
pdf->pdfa = _PDFIO_PDFA_3B; // Default to 'b' pdf->profile = _PDFIO_PROFILE_PDFA_3B; // Default to 'b'
} }
else if (!strncmp(version, "PDF/A-4", 7)) else if (!strncmp(version, "PDF/A-4", 7))
{ {
file_version = "2.0"; 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 else
{ {
file_version = version; file_version = version;
pdf->pdfa = _PDFIO_PDFA_NONE; pdf->profile = _PDFIO_PROFILE_NONE;
} }
pdf->version = strdup(file_version); pdf->version = strdup(file_version);
@@ -1630,19 +1672,17 @@ create_common(
pdf->crop_box.y2 = 11.0f * 72.0f; pdf->crop_box.y2 = 11.0f * 72.0f;
} }
// Write the PDF header (special case for PCLm, otherwise standard/PDF-A header) // Write the PDF header (special case for PCLm, otherwise standard header)
if (!strncmp(version, "PCLm-", 5)) if (pdf->profile == _PDFIO_PROFILE_PCLM)
{ {
if (!_pdfioFilePrintf(pdf, "%%PDF-1.4\n%%%s\n", version)) if (!_pdfioFilePrintf(pdf, "%%PDF-1.4\n%%%s\n", version))
goto error; 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... // Create the pages object...
if ((dict = pdfioDictCreate(pdf)) == NULL) if ((dict = pdfioDictCreate(pdf)) == NULL)
goto error; goto error;
@@ -2735,43 +2775,30 @@ write_metadata(pdfio_file_t *pdf) // I - PDF file
status &= pdfioStreamPrintf(st, " <dc:description><rdf:Alt><rdf:li xml:lang=\"x-default\">%H</rdf:li></rdf:Alt></dc:description>\n", value); status &= pdfioStreamPrintf(st, " <dc:description><rdf:Alt><rdf:li xml:lang=\"x-default\">%H</rdf:li></rdf:Alt></dc:description>\n", value);
status &= pdfioStreamPuts(st, " </rdf:Description>\n"); status &= pdfioStreamPuts(st, " </rdf:Description>\n");
// TODO: Need a better way to choose the output profile - something that lets if (pdf->profile >= _PDFIO_PROFILE_PDFA_1A && pdf->profile <= _PDFIO_PROFILE_PDFA_4)
// us choose the base PDF version and PDF/A, PDF/E, PDF/X, etc.
#if 0
status &= pdfioStreamPuts(st, " <rdf:Description rdf:about=\"\" xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n");
status &= pdfioStreamPuts(st, " <pdfaid:conformance>A</pdfaid:conformance>\n");
status &= pdfioStreamPuts(st, " <pdfaid:part>1</pdfaid:part>\n");
status &= pdfioStreamPuts(st, " </rdf:Description>\n");
#endif // 0
if (pdf->pdfa != _PDFIO_PDFA_NONE)
{ {
static const char * const pdfa_versions[] = static const char * const pdfa_versions[] =
{ {
"1A", // _PDFIO_PDFA_1A "1A", // _PDFIO_PROFILE_PDFA_1A
"1B", // _PDFIO_PDFA_1B "1B", // _PDFIO_PROFILE_PDFA_1B
"2A", // _PDFIO_PDFA_2A "2A", // _PDFIO_PROFILE_PDFA_2A
"2B", // _PDFIO_PDFA_2B "2B", // _PDFIO_PROFILE_PDFA_2B
"2U", // _PDFIO_PDFA_2U "2U", // _PDFIO_PROFILE_PDFA_2U
"3A", // _PDFIO_PDFA_3A "3A", // _PDFIO_PROFILE_PDFA_3A
"3B", // _PDFIO_PDFA_3B "3B", // _PDFIO_PROFILE_PDFA_3B
"3U", // _PDFIO_PDFA_3U "3U", // _PDFIO_PROFILE_PDFA_3U
"4", // _PDFIO_PDFA_4 "4", // _PDFIO_PROFILE_PDFA_4
}; };
const char *version_info = pdfa_versions[pdf->pdfa - _PDFIO_PDFA_1A];
const char *conformance; const char *info = pdfa_versions[pdf->profile - _PDFIO_PROFILE_PDFA_1A];
conformance = version_info + 1;
status &= pdfioStreamPuts(st, " <rdf:Description rdf:about=\"\" xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n"); status &= pdfioStreamPuts(st, " <rdf:Description rdf:about=\"\" xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n");
status &= pdfioStreamPrintf(st, " <pdfaid:part>%c</pdfaid:part>\n",version_info[0]); status &= pdfioStreamPrintf(st, " <pdfaid:part>%c</pdfaid:part>\n", *info);
if (*conformance) if (info[1])
status &= pdfioStreamPrintf(st, " <pdfaid:conformance>%s</pdfaid:conformance>\n", conformance); status &= pdfioStreamPrintf(st, " <pdfaid:conformance>%s</pdfaid:conformance>\n", info + 1);
status &= pdfioStreamPuts(st, " </rdf:Description>\n"); status &= pdfioStreamPuts(st, " </rdf:Description>\n");
} }
status &= pdfioStreamPuts(st, " </rdf:RDF>\n"); status &= pdfioStreamPuts(st, " </rdf:RDF>\n");
status &= pdfioStreamPuts(st, "</x:xmpmeta>\n"); status &= pdfioStreamPuts(st, "</x:xmpmeta>\n");
status &= pdfioStreamPuts(st, "<?xpacket end=\"r\"?>\n"); status &= pdfioStreamPuts(st, "<?xpacket end=\"r\"?>\n");

View File

@@ -105,20 +105,6 @@ typedef enum _pdfio_mode_e // Read/write mode
_PDFIO_MODE_WRITE // Write a PDF file _PDFIO_MODE_WRITE // Write a PDF file
} _pdfio_mode_t; } _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 typedef enum _pdfio_predictor_e // PNG predictor constants
{ {
_PDFIO_PREDICTOR_NONE = 1, // No predictor (default) _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_PNG_AUTO = 15 // PNG "auto" predictor (currently mapped to Paeth)
} _pdfio_predictor_t; } _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_tconsume_cb_t)(void *data, size_t bytes);
typedef ssize_t (*_pdfio_tpeek_cb_t)(void *data, void *buffer, 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 unsigned char file_id[32]; // File identifier bytes
struct lconv *loc; // Locale data struct lconv *loc; // Locale data
char *version; // Version number 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 pdfio_rect_t media_box, // Default MediaBox value
crop_box; // Default CropBox value crop_box; // Default CropBox value
_pdfio_mode_t mode; // Read/write mode _pdfio_mode_t mode; // Read/write mode

BIN
testfiles/pdfio-rgba.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -28,6 +28,7 @@
// //
static int do_crypto_tests(void); 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_test_file(const char *filename, int objnum, const char *password, bool verbose);
static int do_unit_tests(void); 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 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 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_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_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_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_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 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. // 'main()' - Main entry for test program.
@@ -129,108 +130,6 @@ main(int argc, // I - Number of command-line arguments
return (ret); 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. // '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. // '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()) if (do_crypto_tests())
return (1); return (1);
// Create a new PDF file... // Create a new PDF file...
testBegin("pdfioFileCreate(\"testpdfio-out.pdf\", ...)"); testBegin("pdfioFileCreate(\"testpdfio-out.pdf\", ...)");
@@ -1326,9 +1275,8 @@ do_unit_tests(void)
if (read_unit_file(temppdf, num_pages, first_image, false)) if (read_unit_file(temppdf, num_pages, first_image, false))
return (1); return (1);
pdfioFileClose(inpdf); pdfioFileClose(inpdf);
// Do PDF/A tests... // Do PDF/A tests...
if (do_pdfa_tests()) if (do_pdfa_tests())
return (1); 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. // 'write_png_tests()' - Write pages of PNG test images.
// //