13 Commits

Author SHA1 Message Date
0c1122b689 Prep for release. 2024-06-28 19:06:44 -04:00
d4f8dd46b5 Add Windows test script. 2024-06-28 19:00:51 -04:00
986c5f0438 Update docos. 2024-06-24 11:51:50 -04:00
a81907bdb9 Refactor get_info_string to rely on pdfioDictGetString to convert binary strings to regular ones. 2024-06-24 11:49:38 -04:00
63a7a2cdbd Add unit tests for new pdfioFileGetCatalog API (Issue #67)
Fix pdfioDictGetString to convert (formerly) encrypted binary strings to
regular strings.
2024-06-24 11:46:15 -04:00
f040cc41c2 Add #define guard to allow MingW to build PDFio; note that MingW is NOT a supported toolchain for PDFio (Issue #66) 2024-06-24 09:03:46 -04:00
23883268e3 Add pdfioFileGetCatalog function (Issue #67)
Refactor the pdfioFileCreateXxx functions to use a common (private) function to
handle creating/initializing the pdfio_file_t object and base file objects.

Update unit tests to display the filename for the pdfioFileClose test.
2024-06-24 08:56:16 -04:00
a1e14503fd Bump version in other files, update makesrcdist to support checking. 2024-06-24 07:28:54 -04:00
0766869ad1 Bump version to 1.3.0. 2024-06-24 07:12:01 -04:00
6c1db141a1 Switch string pool code to an insertion sort - provides a modest 25% improvement
to open speeds on typical files.
2024-01-27 20:58:50 -05:00
b117959725 Make sure all output code paths set the locale information (Issue #61) 2024-01-27 19:23:51 -05:00
e882622233 Fix locale support (Issue #61) 2024-01-27 18:22:16 -05:00
c13b5a5e90 Bump version. 2024-01-27 18:20:36 -05:00
20 changed files with 1125 additions and 633 deletions

View File

@ -60,3 +60,5 @@ jobs:
run: nuget restore pdfio.sln
- name: Build PDFio
run: msbuild pdfio.sln
- name: Test PDFio
run: .\runtests.bat x64\Debug

View File

@ -2,6 +2,17 @@ Changes in PDFio
================
v1.3.0 (June 28, 2024)
----------------------
- Added `pdfioFileGetCatalog` API for accessing the root/catalog object of a
PDF file (Issue #67)
- Updated number support to avoid locale issues (Issue #61)
- Updated the PDFio private header to allow compilation with MingW; note that
MingW is NOT a supported toolchain for PDFio (Issue #66)
- Optimized string pool code.
v1.2.0 (January 24, 2024)
-------------------------

View File

@ -1,7 +1,7 @@
#
# Makefile for PDFio.
#
# Copyright © 2021-2023 by Michael R Sweet.
# Copyright © 2021-2024 by Michael R Sweet.
#
# Licensed under Apache License v2.0. See the file "LICENSE" for more
# information.
@ -163,7 +163,9 @@ install: $(TARGETS)
# Test everything
test: testpdfio testttf
./testttf 2>test.log
./testpdfio 2>test.log
./testpdfio 2>>test.log
LANG=fr_FR.UTF-8 ./testpdfio 2>>test.log
valgrind: testpdfio
valgrind --leak-check=full ./testpdfio

27
configure vendored
View File

@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for pdfio 1.2.0.
# Generated by GNU Autoconf 2.71 for pdfio 1.3.0.
#
# Report bugs to <https://github.com/michaelrsweet/pdfio/issues>.
#
@ -610,8 +610,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='pdfio'
PACKAGE_TARNAME='pdfio'
PACKAGE_VERSION='1.2.0'
PACKAGE_STRING='pdfio 1.2.0'
PACKAGE_VERSION='1.3.0'
PACKAGE_STRING='pdfio 1.3.0'
PACKAGE_BUGREPORT='https://github.com/michaelrsweet/pdfio/issues'
PACKAGE_URL='https://www.msweet.org/pdfio'
@ -1293,7 +1293,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures pdfio 1.2.0 to adapt to many kinds of systems.
\`configure' configures pdfio 1.3.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@ -1359,7 +1359,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of pdfio 1.2.0:";;
short | recursive ) echo "Configuration of pdfio 1.3.0:";;
esac
cat <<\_ACEOF
@ -1456,7 +1456,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
pdfio configure 1.2.0
pdfio configure 1.3.0
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@ -1612,7 +1612,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by pdfio $as_me 1.2.0, which was
It was created by pdfio $as_me 1.3.0, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@ -2368,9 +2368,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
PDFIO_VERSION="1.2.0"
PDFIO_VERSION_MAJOR="`echo 1.2.0 | awk -F. '{print $1}'`"
PDFIO_VERSION_MINOR="`echo 1.2.0 | awk -F. '{printf("%d\n",$2);}'`"
PDFIO_VERSION="1.3.0"
PDFIO_VERSION_MAJOR="`echo 1.3.0 | awk -F. '{print $1}'`"
PDFIO_VERSION_MINOR="`echo 1.3.0 | awk -F. '{printf("%d\n",$2);}'`"
@ -4029,8 +4029,8 @@ then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
printf "%s\n" "yes" >&6; }
LIBS="$($PKGCONFIG --libs zlib) $LIBS"
CPPFLAGS="$($PKGCONFIG --cflags zlib) $CPPFLAGS"
LIBS="$($PKGCONFIG --libs zlib) $LIBS"
else $as_nop
@ -4093,6 +4093,7 @@ then :
fi
PKGCONFIG_REQUIRES=""
PKGCONFIG_LIBS_PRIVATE="-lz $PKGCONFIG_LIBS_PRIVATE"
fi
@ -4934,7 +4935,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by pdfio $as_me 1.2.0, which was
This file was extended by pdfio $as_me 1.3.0, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@ -4990,7 +4991,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
pdfio config.status 1.2.0
pdfio config.status 1.3.0
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"

View File

@ -21,7 +21,7 @@ AC_PREREQ([2.70])
dnl Package name and version...
AC_INIT([pdfio], [1.2.0], [https://github.com/michaelrsweet/pdfio/issues], [pdfio], [https://www.msweet.org/pdfio])
AC_INIT([pdfio], [1.3.0], [https://github.com/michaelrsweet/pdfio/issues], [pdfio], [https://www.msweet.org/pdfio])
PDFIO_VERSION="AC_PACKAGE_VERSION"
PDFIO_VERSION_MAJOR="`echo AC_PACKAGE_VERSION | awk -F. '{print $1}'`"

View File

@ -1,4 +1,4 @@
.TH pdfio 3 "pdf read/write library" "2024-01-24" "pdf read/write library"
.TH pdfio 3 "pdf read/write library" "2024-06-24" "pdf read/write library"
.SH NAME
pdfio \- pdf read/write library
.SH Introduction
@ -2148,7 +2148,7 @@ pdfio_file_t * pdfioFileCreate (
pdfio_rect_t *media_box,
pdfio_rect_t *crop_box,
pdfio_error_cb_t error_cb,
void *error_data
void *error_cbdata
);
.fi
.PP
@ -2162,7 +2162,7 @@ The "media_box" and "crop_box" arguments specify the default MediaBox and
CropBox for pages in the PDF file - if \fBNULL\fR then a default "Universal" size
of 8.27x11in (the intersection of US Letter and ISO A4) is used.
.PP
The "error_cb" and "error_data" arguments specify an error handler callback
The "error_cb" and "error_cbdata" arguments specify an error handler callback
and its data pointer - if \fBNULL\fR the default error handler is used that
writes error messages to \fBstderr\fR.
.SS pdfioFileCreateArrayObj
@ -2325,23 +2325,23 @@ Create a PDF file through an output callback.
.nf
pdfio_file_t * pdfioFileCreateOutput (
pdfio_output_cb_t output_cb,
void *output_ctx,
void *output_cbdata,
const char *version,
pdfio_rect_t *media_box,
pdfio_rect_t *crop_box,
pdfio_error_cb_t error_cb,
void *error_data
void *error_cbdata
);
.fi
.PP
This function creates a new PDF file that is streamed though an output
callback. The "output_cb" and "output_ctx" arguments specify the output
callback and its context pointer which is called whenever data needs to be
callback. The "output_cb" and "output_cbdata" arguments specify the output
callback and its data pointer which is called whenever data needs to be
written:
.PP
.nf
ssize_t
output_cb(void *output_ctx, const void *buffer, size_t bytes)
output_cb(void *output_cbdata, const void *buffer, size_t bytes)
{
// Write buffer to output and return the number of bytes written
}
@ -2355,7 +2355,7 @@ The "media_box" and "crop_box" arguments specify the default MediaBox and
CropBox for pages in the PDF file - if \fBNULL\fR then a default "Universal" size
of 8.27x11in (the intersection of US Letter and ISO A4) is used.
.PP
The "error_cb" and "error_data" arguments specify an error handler callback
The "error_cb" and "error_cbdata" arguments specify an error handler callback
and its data pointer - if \fBNULL\fR the default error handler is used that
writes error messages to \fBstderr\fR.
.PP
@ -2397,7 +2397,7 @@ pdfio_file_t * pdfioFileCreateTemporary (
pdfio_rect_t *media_box,
pdfio_rect_t *crop_box,
pdfio_error_cb_t error_cb,
void *error_data
void *error_cbdata
);
.fi
.SS pdfioFileFindObj
@ -2420,6 +2420,16 @@ const char * pdfioFileGetAuthor (
pdfio_file_t *pdf
);
.fi
.SS pdfioFileGetCatalog
Get the document catalog dictionary.
.PP
.nf
pdfio_dict_t * pdfioFileGetCatalog (
pdfio_file_t *pdf
);
.fi
.PP
.SS pdfioFileGetCreationDate
Get the creation date for a PDF file.
.PP
@ -2545,22 +2555,22 @@ Open a PDF file for reading.
pdfio_file_t * pdfioFileOpen (
const char *filename,
pdfio_password_cb_t password_cb,
void *password_data,
void *password_cbdata,
pdfio_error_cb_t error_cb,
void *error_data
void *error_cbdata
);
.fi
.PP
This function opens an existing PDF file. The "filename" argument specifies
the name of the PDF file to create.
.PP
The "password_cb" and "password_data" arguments specify a password callback
The "password_cb" and "password_cbdata" arguments specify a password callback
and its data pointer for PDF files that use one of the standard Adobe
"security" handlers. The callback returns a password string or \fBNULL\fR to
cancel the open. If \fBNULL\fR is specified for the callback function and the
PDF file requires a password, the open will always fail.
.PP
The "error_cb" and "error_data" arguments specify an error handler callback
The "error_cb" and "error_cbdata" arguments specify an error handler callback
and its data pointer - if \fBNULL\fR the default error handler is used that
writes error messages to \fBstderr\fR.
.SS pdfioFileSetAuthor

File diff suppressed because it is too large Load Diff

View File

@ -2,18 +2,61 @@
#
# makesrcdist - make a source distribution of pdfio.
#
# Usage:
#
# ./makesrcdist [--snapshot] VERSION
#
# Support "--snapshot" option...
if test "$1" == "--snapshot"; then
shift
snapshot=1
else
snapshot=0
fi
# Get version...
if test $# != 1; then
echo "Usage: ./makesrcdist version"
exit 1
echo "Usage: ./makesrcdist [--snapshot] VERSION"
exit 1
fi
version=$1
echo Creating tag for release...
git tag -m "Tag $version" v$version
git push origin v$version
# Check that version number has been updated everywhere...
if test $(grep AC_INIT configure.ac | awk '{print $2}') != "[$version],"; then
echo "Still need to update AC_INIT version in 'configure.ac'."
exit 1
fi
if test $(grep PDFIO_VERSION= configure | awk -F \" '{print $2}') != "$version"; then
echo "Still need to run 'autoconf -f'."
exit 1
fi
if test $(grep '<version>' pdfio_native.nuspec | sed -E -e '1,$s/^.*<version>([0-9.]+).*$/\1/') != "$version"; then
echo "Still need to update version in 'pdfio_native.nuspec'."
exit 1
fi
if test $(grep '<version>' pdfio_native.redist.nuspec | sed -E -e '1,$s/^.*<version>([0-9.]+).*$/\1/') != "$version"; then
echo "Still need to update version in 'pdfio_native.redist.nuspec'."
exit 1
fi
if test $(grep PDFIO_VERSION pdfio.h | awk -F \" '{print $2}') != "$version"; then
echo "Still need to update PDFIO_VERSION in 'pdfio.h'."
exit 1
fi
# Tag release...
if test $snapshot = 0; then
echo Creating tag for release...
git tag -m "Tag $version" v$version
git push origin v$version
fi
# Make source archives...
echo Creating pdfio-$version.tar.gz...
git archive --format tar --prefix=pdfio-$version/ HEAD | gzip -v9 >pdfio-$version.tar.gz
gpg --detach-sign pdfio-$version.tar.gz

View File

@ -1,7 +1,7 @@
//
// Common support functions for pdfio.
//
// Copyright © 2021-2023 by Michael R Sweet.
// Copyright © 2021-2024 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -261,7 +261,7 @@ _pdfioFilePrintf(pdfio_file_t *pdf, // I - PDF file
// Format the string...
va_start(ap, format);
vsnprintf(buffer, sizeof(buffer), format, ap);
_pdfio_vsnprintf(pdf, buffer, sizeof(buffer), format, ap);
va_end(ap);
// Write it...

View File

@ -426,9 +426,27 @@ pdfioDictGetString(pdfio_dict_t *dict, // I - Dictionary
if (value && value->type == PDFIO_VALTYPE_STRING)
{
return (value->value.string);
}
else if (value && value->type == PDFIO_VALTYPE_BINARY && value->value.binary.datalen < 4096)
{
// Convert binary string to regular string...
char temp[4096]; // Temporary string
memcpy(temp, value->value.binary.data, value->value.binary.datalen);
temp[value->value.binary.datalen] = '\0';
free(value->value.binary.data);
value->type = PDFIO_VALTYPE_STRING;
value->value.string = pdfioStringCreate(dict->pdf, temp);
return (value->value.string);
}
else
{
return (NULL);
}
}

View File

@ -1,7 +1,7 @@
//
// PDF file functions for PDFio.
//
// Copyright © 2021-2023 by Michael R Sweet.
// Copyright © 2021-2024 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -19,11 +19,12 @@
static pdfio_obj_t *add_obj(pdfio_file_t *pdf, size_t number, unsigned short generation, off_t offset);
static int compare_objmaps(_pdfio_objmap_t *a, _pdfio_objmap_t *b);
static pdfio_file_t *create_common(const char *filename, int fd, pdfio_output_cb_t output_cb, void *output_cbdata, const char *version, pdfio_rect_t *media_box, pdfio_rect_t *crop_box, pdfio_error_cb_t error_cb, void *error_cbdata);
static const char *get_info_string(pdfio_file_t *pdf, const char *key);
static struct lconv *get_lconv(void);
static bool load_obj_stream(pdfio_obj_t *obj);
static bool load_pages(pdfio_file_t *pdf, pdfio_obj_t *obj, size_t depth);
static bool load_xref(pdfio_file_t *pdf, off_t xref_offset, pdfio_password_cb_t password_cb, void *password_data);
static bool write_catalog(pdfio_file_t *pdf);
static bool write_pages(pdfio_file_t *pdf);
static bool write_trailer(pdfio_file_t *pdf);
@ -119,11 +120,8 @@ pdfioFileClose(pdfio_file_t *pdf) // I - PDF file
{
ret = false;
if (pdfioObjClose(pdf->info_obj))
if (write_pages(pdf))
if (write_catalog(pdf))
if (write_trailer(pdf))
ret = _pdfioFileFlush(pdf);
if (pdfioObjClose(pdf->info_obj) && write_pages(pdf) && pdfioObjClose(pdf->root_obj) && write_trailer(pdf))
ret = _pdfioFileFlush(pdf);
}
if (pdf->fd >= 0 && close(pdf->fd) < 0)
@ -172,7 +170,7 @@ pdfioFileClose(pdfio_file_t *pdf) // I - PDF file
// CropBox for pages in the PDF file - if `NULL` then a default "Universal" size
// of 8.27x11in (the intersection of US Letter and ISO A4) is used.
//
// The "error_cb" and "error_data" arguments specify an error handler callback
// The "error_cb" and "error_cbdata" arguments specify an error handler callback
// and its data pointer - if `NULL` the default error handler is used that
// writes error messages to `stderr`.
//
@ -184,122 +182,37 @@ pdfioFileCreate(
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
void *error_cbdata) // 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
unsigned char id_value[16]; // File ID value
int fd; // File descriptor
// Range check input...
if (!filename)
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)
// Create the file...
if ((fd = open(filename, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0666)) < 0)
{
pdfio_file_t temp; // Dummy file
char message[8192]; // Message string
temp.filename = (char *)filename;
snprintf(message, sizeof(message), "Unable to allocate memory for PDF file - %s", strerror(errno));
(error_cb)(&temp, message, error_data);
snprintf(message, sizeof(message), "Unable to create '%s': %s", filename, strerror(errno));
(error_cb)(&temp, message, error_cbdata);
return (NULL);
}
pdf->filename = strdup(filename);
pdf->version = strdup(version);
pdf->mode = _PDFIO_MODE_WRITE;
pdf->error_cb = error_cb;
pdf->error_data = error_data;
pdf->permissions = PDFIO_PERMISSION_ALL;
pdf->bufptr = pdf->buffer;
pdf->bufend = pdf->buffer + sizeof(pdf->buffer);
if (media_box)
if ((pdf = create_common(filename, fd, /*output_cb*/NULL, /*output_cbdata*/NULL, version, media_box, crop_box, error_cb, error_cbdata)) == NULL)
{
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;
}
// Create the file...
if ((pdf->fd = open(filename, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0666)) < 0)
{
_pdfioFileError(pdf, "Unable to create file - %s", strerror(errno));
free(pdf->filename);
free(pdf->version);
free(pdf);
return (NULL);
}
// Write a standard PDF header...
if (!_pdfioFilePrintf(pdf, "%%PDF-%s\n%%\342\343\317\323\n", version))
goto error;
// Create the pages object...
if ((dict = pdfioDictCreate(pdf)) == NULL)
goto error;
pdfioDictSetName(dict, "Type", "Pages");
if ((pdf->pages_obj = pdfioFileCreateObj(pdf, dict)) == NULL)
goto error;
// Create the info object...
if ((info_dict = pdfioDictCreate(pdf)) == NULL)
goto error;
pdfioDictSetDate(info_dict, "CreationDate", time(NULL));
pdfioDictSetString(info_dict, "Producer", "pdfio/" PDFIO_VERSION);
if ((pdf->info_obj = pdfioFileCreateObj(pdf, info_dict)) == NULL)
goto error;
// Create random file ID values...
_pdfioCryptoMakeRandom(id_value, sizeof(id_value));
if ((pdf->id_array = pdfioArrayCreate(pdf)) != NULL)
{
pdfioArrayAppendBinary(pdf->id_array, id_value, sizeof(id_value));
pdfioArrayAppendBinary(pdf->id_array, id_value, sizeof(id_value));
// Remove the newly created file if we can't create the PDF file object...
close(fd);
unlink(filename);
}
return (pdf);
// Common error handling code...
error:
pdfioFileClose(pdf);
unlink(filename);
return (NULL);
}
@ -439,13 +352,13 @@ _pdfioFileCreateObj(
// 'pdfioFileCreateOutput()' - Create a PDF file through an output callback.
//
// This function creates a new PDF file that is streamed though an output
// callback. The "output_cb" and "output_ctx" arguments specify the output
// callback and its context pointer which is called whenever data needs to be
// callback. The "output_cb" and "output_cbdata" arguments specify the output
// callback and its data pointer which is called whenever data needs to be
// written:
//
// ```
// ssize_t
// output_cb(void *output_ctx, const void *buffer, size_t bytes)
// output_cb(void *output_cbdata, const void *buffer, size_t bytes)
// {
// // Write buffer to output and return the number of bytes written
// }
@ -458,7 +371,7 @@ _pdfioFileCreateObj(
// CropBox for pages in the PDF file - if `NULL` then a default "Universal" size
// of 8.27x11in (the intersection of US Letter and ISO A4) is used.
//
// The "error_cb" and "error_data" arguments specify an error handler callback
// The "error_cb" and "error_cbdata" arguments specify an error handler callback
// and its data pointer - if `NULL` the default error handler is used that
// writes error messages to `stderr`.
//
@ -469,121 +382,15 @@ _pdfioFileCreateObj(
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
pdfio_output_cb_t output_cb, // I - Output callback function
void *output_cbdata, // I - Output callback data
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
void *error_cbdata) // 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
unsigned char id_value[16]; // File ID value
// 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->permissions = PDFIO_PERMISSION_ALL;
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))
goto error;
// Create the pages object...
if ((dict = pdfioDictCreate(pdf)) == NULL)
goto error;
pdfioDictSetName(dict, "Type", "Pages");
if ((pdf->pages_obj = pdfioFileCreateObj(pdf, dict)) == NULL)
goto error;
// Create the info object...
if ((info_dict = pdfioDictCreate(pdf)) == NULL)
goto error;
pdfioDictSetDate(info_dict, "CreationDate", time(NULL));
pdfioDictSetString(info_dict, "Producer", "pdfio/" PDFIO_VERSION);
if ((pdf->info_obj = pdfioFileCreateObj(pdf, info_dict)) == NULL)
goto error;
// Create random file ID values...
_pdfioCryptoMakeRandom(id_value, sizeof(id_value));
if ((pdf->id_array = pdfioArrayCreate(pdf)) != NULL)
{
pdfioArrayAppendBinary(pdf->id_array, id_value, sizeof(id_value));
pdfioArrayAppendBinary(pdf->id_array, id_value, sizeof(id_value));
}
return (pdf);
// Common error handling code...
error:
pdfioFileClose(pdf);
return (NULL);
return (create_common("output.pdf", /*fd*/-1, output_cb, output_cbdata, version, media_box, crop_box, error_cb, error_cbdata));
}
@ -705,13 +512,11 @@ pdfioFileCreateTemporary(
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
void *error_cbdata) // 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
unsigned char id_value[16]; // File ID value
int i; // Looping var
int i, // Looping var
fd; // File descriptor
const char *tmpdir; // Temporary directory
#if _WIN32 || defined(__APPLE__)
char tmppath[256]; // Temporary directory path
@ -727,31 +532,7 @@ pdfioFileCreateTemporary(
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 *)"temporary.pdf";
snprintf(message, sizeof(message), "Unable to allocate memory for PDF file - %s", strerror(errno));
(error_cb)(&temp, message, error_data);
*buffer = '\0';
return (NULL);
}
// Create the file...
// Create the temporary PDF file...
#if _WIN32
if ((tmpdir = getenv("TEMP")) == NULL)
{
@ -779,98 +560,35 @@ pdfioFileCreateTemporary(
tmpdir = "/tmp";
#endif // _WIN32
for (i = 0; i < 1000; i ++)
for (i = 0, fd = -1; i < 1000; i ++)
{
_pdfioCryptoMakeRandom((uint8_t *)&tmpnum, sizeof(tmpnum));
snprintf(buffer, bufsize, "%s/%08x.pdf", tmpdir, tmpnum);
if ((pdf->fd = open(buffer, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC | O_EXCL, 0666)) >= 0)
if ((fd = open(buffer, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC | O_EXCL, 0666)) >= 0)
break;
}
pdf->filename = strdup(buffer);
if (i >= 1000)
if (fd < 0)
{
_pdfioFileError(pdf, "Unable to create file - %s", strerror(errno));
free(pdf->filename);
free(pdf);
*buffer = '\0';
pdfio_file_t temp; // Dummy file
char message[8192]; // Message string
temp.filename = (char *)"<temporary>";
snprintf(message, sizeof(message), "Unable to create temporary PDF file: %s", strerror(errno));
(error_cb)(&temp, message, error_cbdata);
return (NULL);
}
pdf->version = strdup(version);
pdf->mode = _PDFIO_MODE_WRITE;
pdf->error_cb = error_cb;
pdf->error_data = error_data;
pdf->permissions = PDFIO_PERMISSION_ALL;
pdf->bufptr = pdf->buffer;
pdf->bufend = pdf->buffer + sizeof(pdf->buffer);
if (media_box)
if ((pdf = create_common(buffer, fd, /*output_cb*/NULL, /*output_cbdata*/NULL, version, media_box, crop_box, error_cb, error_cbdata)) == NULL)
{
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;
}
// Write a standard PDF header...
if (!_pdfioFilePrintf(pdf, "%%PDF-%s\n%%\342\343\317\323\n", version))
goto error;
// Create the pages object...
if ((dict = pdfioDictCreate(pdf)) == NULL)
goto error;
pdfioDictSetName(dict, "Type", "Pages");
if ((pdf->pages_obj = pdfioFileCreateObj(pdf, dict)) == NULL)
goto error;
// Create the info object...
if ((info_dict = pdfioDictCreate(pdf)) == NULL)
goto error;
pdfioDictSetDate(info_dict, "CreationDate", time(NULL));
pdfioDictSetString(info_dict, "Producer", "pdfio/" PDFIO_VERSION);
if ((pdf->info_obj = pdfioFileCreateObj(pdf, info_dict)) == NULL)
goto error;
// Create random file ID values...
_pdfioCryptoMakeRandom(id_value, sizeof(id_value));
if ((pdf->id_array = pdfioArrayCreate(pdf)) != NULL)
{
pdfioArrayAppendBinary(pdf->id_array, id_value, sizeof(id_value));
pdfioArrayAppendBinary(pdf->id_array, id_value, sizeof(id_value));
// Remove the temporary file if we can't create the PDF file object...
close(fd);
unlink(buffer);
*buffer = '\0';
}
return (pdf);
// Common error handling code...
error:
pdfioFileClose(pdf);
unlink(buffer);
*buffer = '\0';
return (NULL);
}
@ -980,6 +698,19 @@ pdfioFileGetAuthor(pdfio_file_t *pdf) // I - PDF file
}
//
// 'pdfioFileGetCatalog()' - Get the document catalog dictionary.
//
// @since PDFio 1.3@
//
pdfio_dict_t * // O - Catalog dictionary
pdfioFileGetCatalog(pdfio_file_t *pdf) // I - PDF file
{
return (pdf ? pdfioObjGetDict(pdf->root_obj) : NULL);
}
//
// 'pdfioFileGetCreationDate()' - Get the creation date for a PDF file.
//
@ -1169,13 +900,13 @@ pdfioFileGetVersion(
// This function opens an existing PDF file. The "filename" argument specifies
// the name of the PDF file to create.
//
// The "password_cb" and "password_data" arguments specify a password callback
// The "password_cb" and "password_cbdata" arguments specify a password callback
// and its data pointer for PDF files that use one of the standard Adobe
// "security" handlers. The callback returns a password string or `NULL` to
// cancel the open. If `NULL` is specified for the callback function and the
// PDF file requires a password, the open will always fail.
//
// The "error_cb" and "error_data" arguments specify an error handler callback
// The "error_cb" and "error_cbdata" arguments specify an error handler callback
// and its data pointer - if `NULL` the default error handler is used that
// writes error messages to `stderr`.
//
@ -1184,9 +915,10 @@ pdfio_file_t * // O - PDF file
pdfioFileOpen(
const char *filename, // I - Filename
pdfio_password_cb_t password_cb, // I - Password callback or `NULL` for none
void *password_data, // I - Password callback data, if any
void *password_cbdata,
// I - Password callback data, if any
pdfio_error_cb_t error_cb, // I - Error callback or `NULL` for default
void *error_data) // I - Error callback data, if any
void *error_cbdata) // I - Error callback data, if any
{
pdfio_file_t *pdf; // PDF file
char line[1025], // Line from file
@ -1202,8 +934,8 @@ pdfioFileOpen(
if (!error_cb)
{
error_cb = _pdfioFileDefaultError;
error_data = NULL;
error_cb = _pdfioFileDefaultError;
error_cbdata = NULL;
}
// Allocate a PDF file structure...
@ -1214,14 +946,15 @@ pdfioFileOpen(
temp.filename = (char *)filename;
snprintf(message, sizeof(message), "Unable to allocate memory for PDF file - %s", strerror(errno));
(error_cb)(&temp, message, error_data);
(error_cb)(&temp, message, error_cbdata);
return (NULL);
}
pdf->loc = get_lconv();
pdf->filename = strdup(filename);
pdf->mode = _PDFIO_MODE_READ;
pdf->error_cb = error_cb;
pdf->error_data = error_data;
pdf->error_data = error_cbdata;
pdf->permissions = PDFIO_PERMISSION_ALL;
// Open the file...
@ -1277,7 +1010,7 @@ pdfioFileOpen(
xref_offset = (off_t)strtol(ptr + 9, NULL, 10);
if (!load_xref(pdf, xref_offset, password_cb, password_data))
if (!load_xref(pdf, xref_offset, password_cb, password_cbdata))
goto error;
return (pdf);
@ -1369,7 +1102,7 @@ pdfioFileSetPermissions(
if (!pdf)
return (false);
if (pdf->num_objs > 2) // First two objects are pages and info
if (pdf->num_objs > 3) // First three objects are pages, info, and root
{
_pdfioFileError(pdf, "You must call pdfioFileSetPermissions before adding any objects.");
return (false);
@ -1531,6 +1264,142 @@ compare_objmaps(_pdfio_objmap_t *a, // I - First object map
}
//
// 'create_common()' - Allocate and initialize a pdfio_file_t object for writing.
//
static pdfio_file_t * // O - New PDF file
create_common(
const char *filename, // I - Filename
int fd, // I - File descriptor, if any
pdfio_output_cb_t output_cb, // I - Output callback function, if any
void *output_cbdata, // I - Output callback data, if any
const char *version, // I - PDF version
pdfio_rect_t *media_box, // I - Media box or `NULL` for default
pdfio_rect_t *crop_box, // I - Crop box of `NULL` for default
pdfio_error_cb_t error_cb, // I - Error callback function
void *error_cbdata) // I - Error callback data
{
pdfio_file_t *pdf; // New PDF file
pdfio_dict_t *dict; // Dictionary
unsigned char id_value[16]; // File ID value
// Range check input...
if (!filename || (fd < 0 && !output_cb))
return (NULL);
if (!version)
version = "2.0";
if (!error_cb)
{
error_cb = _pdfioFileDefaultError;
error_cbdata = 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 *)filename;
snprintf(message, sizeof(message), "Unable to allocate memory for PDF file: %s", strerror(errno));
(error_cb)(&temp, message, error_cbdata);
return (NULL);
}
// Initialize PDF object...
pdf->loc = get_lconv();
pdf->fd = fd;
pdf->output_cb = output_cb;
pdf->output_ctx = output_cbdata;
pdf->filename = strdup(filename);
pdf->version = strdup(version);
pdf->mode = _PDFIO_MODE_WRITE;
pdf->error_cb = error_cb;
pdf->error_data = error_cbdata;
pdf->permissions = PDFIO_PERMISSION_ALL;
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;
}
// Write a standard PDF header...
if (!_pdfioFilePrintf(pdf, "%%PDF-%s\n%%\342\343\317\323\n", version))
goto error;
// Create the pages object...
if ((dict = pdfioDictCreate(pdf)) == NULL)
goto error;
pdfioDictSetName(dict, "Type", "Pages");
if ((pdf->pages_obj = pdfioFileCreateObj(pdf, dict)) == NULL)
goto error;
// Create the info object...
if ((dict = pdfioDictCreate(pdf)) == NULL)
goto error;
pdfioDictSetDate(dict, "CreationDate", time(NULL));
pdfioDictSetString(dict, "Producer", "pdfio/" PDFIO_VERSION);
if ((pdf->info_obj = pdfioFileCreateObj(pdf, dict)) == NULL)
goto error;
// Create the root object...
if ((dict = pdfioDictCreate(pdf)) == NULL)
goto error;
pdfioDictSetName(dict, "Type", "Catalog");
pdfioDictSetObj(dict, "Pages", pdf->pages_obj);
if ((pdf->root_obj = pdfioFileCreateObj(pdf, dict)) == NULL)
goto error;
// Create random file ID values...
_pdfioCryptoMakeRandom(id_value, sizeof(id_value));
if ((pdf->id_array = pdfioArrayCreate(pdf)) != NULL)
{
pdfioArrayAppendBinary(pdf->id_array, id_value, sizeof(id_value));
pdfioArrayAppendBinary(pdf->id_array, id_value, sizeof(id_value));
}
return (pdf);
// Common error handling code...
error:
pdfioFileClose(pdf);
return (NULL);
}
//
// 'get_info_string()' - Get a string value from the Info dictionary.
//
@ -1543,36 +1412,35 @@ get_info_string(pdfio_file_t *pdf, // I - PDF file
const char *key) // I - Dictionary key
{
pdfio_dict_t *dict; // Info dictionary
_pdfio_value_t *value; // Value
// Range check input...
if (!pdf || !pdf->info_obj || (dict = pdfioObjGetDict(pdf->info_obj)) == NULL || (value = _pdfioDictGetValue(dict, key)) == NULL)
if (!pdf || !pdf->info_obj || (dict = pdfioObjGetDict(pdf->info_obj)) == NULL)
return (NULL);
// If we already have a value, return it...
if (value->type == PDFIO_VALTYPE_NAME || value->type == PDFIO_VALTYPE_STRING)
{
return (value->value.string);
}
else if (value->type == PDFIO_VALTYPE_BINARY && value->value.binary.datalen < 4096)
{
// Convert binary string to regular string...
char temp[4096]; // Temporary string
memcpy(temp, value->value.binary.data, value->value.binary.datalen);
temp[value->value.binary.datalen] = '\0';
free(value->value.binary.data);
value->type = PDFIO_VALTYPE_STRING;
value->value.string = pdfioStringCreate(pdf, temp);
return (value->value.string);
}
else
return (pdfioDictGetString(dict, key));
}
//
// 'get_lconv()' - Get any locale-specific numeric information.
//
static struct lconv * // O - Locale information or `NULL`
get_lconv(void)
{
struct lconv *loc; // Locale information
if ((loc = localeconv()) != NULL)
{
// Something else that is not a string...
return (NULL);
PDFIO_DEBUG("get_lconv: loc=%p, loc->decimal_point=\"%s\"\n", loc, loc->decimal_point);
if (!loc->decimal_point || !strcmp(loc->decimal_point, "."))
loc = NULL;
}
return (loc);
}
@ -2218,30 +2086,6 @@ load_xref(
}
//
// 'write_catalog()' - Write the PDF root object/catalog.
//
static bool // O - `true` on success, `false` on failure
write_catalog(pdfio_file_t *pdf) // I - PDF file
{
pdfio_dict_t *dict; // Dictionary for catalog...
if ((dict = pdfioDictCreate(pdf)) == NULL)
return (false);
pdfioDictSetName(dict, "Type", "Catalog");
pdfioDictSetObj(dict, "Pages", pdf->pages_obj);
// TODO: Add support for all of the root object dictionary keys
if ((pdf->root_obj = pdfioFileCreateObj(pdf, dict)) == NULL)
return (false);
else
return (pdfioObjClose(pdf->root_obj));
}
//
// 'write_pages()' - Write the PDF pages objects.
//

View File

@ -1,7 +1,7 @@
//
// Private header file for PDFio.
//
// Copyright © 2021-2023 by Michael R Sweet.
// Copyright © 2021-2024 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -20,6 +20,7 @@
# include <errno.h>
# include <inttypes.h>
# include <fcntl.h>
# include <locale.h>
# ifdef _WIN32
# include <io.h>
# include <direct.h>
@ -37,9 +38,11 @@
# define unlink _unlink
# define vsnprintf _vsnprintf
# define write _write
# define F_OK 00 // POSIX parameters/flags
# define W_OK 02
# define R_OK 04
# ifndef F_OK
# define F_OK 00 // POSIX parameters/flags
# define W_OK 02
# define R_OK 04
# endif // !F_OK
# define O_RDONLY _O_RDONLY // Map standard POSIX open flags
# define O_WRONLY _O_WRONLY
# define O_CREAT _O_CREAT
@ -224,6 +227,7 @@ typedef struct _pdfio_objmap_s // PDF object map
struct _pdfio_file_s // PDF file structure
{
char *filename; // Filename
struct lconv *loc; // Locale data
char *version; // Version number
pdfio_rect_t media_box, // Default MediaBox value
crop_box; // Default CropBox value
@ -322,6 +326,9 @@ struct _pdfio_stream_s // Stream
// Functions...
//
extern double _pdfio_strtod(pdfio_file_t *pdf, const char *s) _PDFIO_INTERNAL;
extern ssize_t _pdfio_vsnprintf(pdfio_file_t *pdf, char *buffer, size_t bufsize, const char *format, va_list ap) _PDFIO_INTERNAL;
extern bool _pdfioArrayDecrypt(pdfio_file_t *pdf, pdfio_obj_t *obj, pdfio_array_t *a, size_t depth) _PDFIO_INTERNAL;
extern void _pdfioArrayDebug(pdfio_array_t *a, FILE *fp) _PDFIO_INTERNAL;
extern void _pdfioArrayDelete(pdfio_array_t *a) _PDFIO_INTERNAL;

View File

@ -1,7 +1,7 @@
//
// PDF stream functions for PDFio.
//
// Copyright © 2021-2023 by Michael R Sweet.
// Copyright © 2021-2024 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -687,7 +687,7 @@ pdfioStreamPrintf(
// Format the string...
va_start(ap, format);
vsnprintf(buffer, sizeof(buffer), format, ap);
_pdfio_vsnprintf(st->pdf, buffer, sizeof(buffer), format, ap);
va_end(ap);
// Write the string...

View File

@ -1,7 +1,7 @@
//
// PDF dictionary functions for PDFio.
// PDF string functions for PDFio.
//
// Copyright © 2021 by Michael R Sweet.
// Copyright © 2021-2024 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -14,7 +14,360 @@
// Local functions...
//
static int compare_strings(char **a, char **b);
static size_t find_string(pdfio_file_t *pdf, const char *s, int *rdiff);
//
// '_pdfio_strtod()' - Convert a string to a double value.
//
// This function wraps strtod() to avoid locale issues.
//
double // O - Double value
_pdfio_strtod(pdfio_file_t *pdf, // I - PDF file
const char *s) // I - String
{
char temp[64], // Temporary buffer
*tempptr; // Pointer into temporary buffer
// See if the locale has a special decimal point string...
if (!pdf->loc)
return (strtod(s, NULL));
// Copy leading sign, numbers, period, and then numbers...
tempptr = temp;
temp[sizeof(temp) - 1] = '\0';
while (*s && *s != '.')
{
if (tempptr < (temp + sizeof(temp) - 1))
*tempptr++ = *s++;
else
return (0.0);
}
if (*s == '.')
{
// Convert decimal point to locale equivalent...
size_t declen = strlen(pdf->loc->decimal_point);
// Length of decimal point
s ++;
if (declen <= (sizeof(temp) - (size_t)(tempptr - temp)))
{
memcpy(tempptr, pdf->loc->decimal_point, declen);
tempptr += declen;
}
else
{
return (0.0);
}
}
// Copy any remaining characters...
while (*s)
{
if (tempptr < (temp + sizeof(temp) - 1))
*tempptr++ = *s++;
else
return (0.0);
}
// Nul-terminate the temporary string and convert the string...
*tempptr = '\0';
return (strtod(temp, NULL));
}
//
// '_pdfio_vsnprintf()' - Format a string.
//
// This function emulates vsnprintf() to avoid locale issues.
//
ssize_t // O - Number of bytes
_pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
char *buffer, // I - Output buffer
size_t bufsize, // I - Size of output buffer
const char *format, // I - printf-style format string
va_list ap) // I - Pointer to additional arguments
{
char *bufptr, // Pointer to position in buffer
*bufend, // Pointer to end of buffer
size, // Size character (h, l, L)
type; // Format type character
int width, // Width of field
prec; // Number of characters of precision
char tformat[100], // Temporary format string for snprintf()
*tptr, // Pointer into temporary format
temp[1024], // Buffer for formatted numbers
*tempptr; // Pointer into buffer
char *s; // Pointer to string
ssize_t bytes; // Total number of bytes needed
const char *dec = pdf->loc ? pdf->loc->decimal_point : ".";
// Decimal point string
char *decptr; // Pointer to decimal point
// Loop through the format string, formatting as needed...
bufptr = buffer;
bufend = buffer + bufsize - 1;
*bufend = '\0';
bytes = 0;
while (*format)
{
if (*format == '%')
{
// Format character...
tptr = tformat;
*tptr++ = *format++;
if (*format == '%')
{
if (bufptr < bufend)
*bufptr++ = *format;
bytes ++;
format ++;
continue;
}
else if (strchr(" -+#\'", *format))
{
*tptr++ = *format++;
}
if (*format == '*')
{
// Get width from argument...
format ++;
width = va_arg(ap, int);
snprintf(tptr, sizeof(tformat) - (size_t)(tptr - tformat), "%d", width);
tptr += strlen(tptr);
}
else
{
width = 0;
while (isdigit(*format & 255))
{
if (tptr < (tformat + sizeof(tformat) - 1))
*tptr++ = *format;
width = width * 10 + *format++ - '0';
}
}
if (*format == '.')
{
if (tptr < (tformat + sizeof(tformat) - 1))
*tptr++ = *format;
format ++;
if (*format == '*')
{
// Get precision from argument...
format ++;
prec = va_arg(ap, int);
snprintf(tptr, sizeof(tformat) - (size_t)(tptr - tformat), "%d", prec);
tptr += strlen(tptr);
}
else
{
prec = 0;
while (isdigit(*format & 255))
{
if (tptr < (tformat + sizeof(tformat) - 1))
*tptr++ = *format;
prec = prec * 10 + *format++ - '0';
}
}
}
if (*format == 'l' && format[1] == 'l')
{
size = 'L';
if (tptr < (tformat + sizeof(tformat) - 2))
{
*tptr++ = 'l';
*tptr++ = 'l';
}
format += 2;
}
else if (*format == 'h' || *format == 'l' || *format == 'L')
{
if (tptr < (tformat + sizeof(tformat) - 1))
*tptr++ = *format;
size = *format++;
}
else
{
size = 0;
}
if (!*format)
break;
if (tptr < (tformat + sizeof(tformat) - 1))
*tptr++ = *format;
type = *format++;
*tptr = '\0';
switch (type)
{
case 'E' : // Floating point formats
case 'G' :
case 'e' :
case 'f' :
case 'g' :
if ((size_t)(width + 2) > sizeof(temp))
break;
snprintf(temp, sizeof(temp), tformat, va_arg(ap, double));
if ((decptr = strstr(temp, dec)) != NULL)
{
// Convert locale decimal point to "."
PDFIO_DEBUG("_pdfio_vsnprintf: Before \"%s\"\n", temp);
tempptr = decptr + strlen(dec);
if (tempptr > (decptr + 1))
memmove(decptr + 1, tempptr, strlen(tempptr) + 1);
*decptr = '.';
// Strip trailing 0's...
for (tempptr = temp + strlen(temp) - 1; tempptr > temp && *tempptr == '0'; tempptr --)
*tempptr = '\0';
if (*tempptr == '.')
*tempptr = '\0'; // Strip trailing decimal point
PDFIO_DEBUG("_pdfio_vsnprintf: After \"%s\"\n", temp);
}
// Copy to the output buffer
bytes += (int)strlen(temp);
if (bufptr < bufend)
{
strncpy(bufptr, temp, (size_t)(bufend - bufptr - 1));
bufptr += strlen(bufptr);
}
break;
case 'B' : // Integer formats
case 'X' :
case 'b' :
case 'd' :
case 'i' :
case 'o' :
case 'u' :
case 'x' :
if ((size_t)(width + 2) > sizeof(temp))
break;
# ifdef HAVE_LONG_LONG
if (size == 'L')
snprintf(temp, sizeof(temp), tformat, va_arg(ap, long long));
else
# endif // HAVE_LONG_LONG
if (size == 'l')
snprintf(temp, sizeof(temp), tformat, va_arg(ap, long));
else
snprintf(temp, sizeof(temp), tformat, va_arg(ap, int));
bytes += (int)strlen(temp);
if (bufptr < bufend)
{
strncpy(bufptr, temp, (size_t)(bufend - bufptr - 1));
bufptr += strlen(bufptr);
}
break;
case 'p' : // Pointer value
if ((size_t)(width + 2) > sizeof(temp))
break;
snprintf(temp, sizeof(temp), tformat, va_arg(ap, void *));
bytes += (int)strlen(temp);
if (bufptr < bufend)
{
strncpy(bufptr, temp, (size_t)(bufend - bufptr - 1));
bufptr += strlen(bufptr);
}
break;
case 'c' : // Character or character array
bytes += width;
if (bufptr < bufend)
{
if (width <= 1)
{
*bufptr++ = (char)va_arg(ap, int);
}
else
{
if ((bufptr + width) > bufend)
width = (int)(bufend - bufptr);
memcpy(bufptr, va_arg(ap, char *), (size_t)width);
bufptr += width;
}
}
break;
case 's' : // String
if ((s = va_arg(ap, char *)) == NULL)
s = "(null)";
bytes += strlen(s);
if (bufptr < bufend)
{
strncpy(bufptr, s, (size_t)(bufend - bufptr - 1));
bufptr += strlen(bufptr);
}
break;
case 'n' : // Output number of chars so far
*(va_arg(ap, int *)) = (int)bytes;
break;
}
}
else
{
// Literal character...
bytes ++;
if (bufptr < bufend)
*bufptr++ = *format++;
}
}
// Nul-terminate the string and return the number of characters needed.
if (bufptr < bufend)
{
// Everything fit in the buffer...
*bufptr = '\0';
}
PDFIO_DEBUG("_pdfio_vsnprintf: Returning %ld \"%s\"\n", (long)bytes, buffer);
return (bytes);
}
//
@ -32,8 +385,9 @@ pdfioStringCreate(
pdfio_file_t *pdf, // I - PDF file
const char *s) // I - Nul-terminated string
{
char *news; // New string
char **match; // Matching string
char *news; // New string
size_t idx; // Index into strings
int diff; // Different
PDFIO_DEBUG("pdfioStringCreate(pdf=%p, s=\"%s\")\n", pdf, s);
@ -43,8 +397,17 @@ pdfioStringCreate(
return (NULL);
// See if the string has already been added...
if (pdf->num_strings > 0 && (match = (char **)bsearch(&s, pdf->strings, pdf->num_strings, sizeof(char *), (int (*)(const void *, const void *))compare_strings)) != NULL)
return (*match);
if (pdf->num_strings > 0)
{
idx = find_string(pdf, s, &diff);
if (diff == 0)
return (pdf->strings[idx]);
}
else
{
idx = 0;
diff = -1;
}
// Not already added, so add it...
if ((news = strdup(s)) == NULL)
@ -65,11 +428,17 @@ pdfioStringCreate(
pdf->alloc_strings += 128;
}
// TODO: Change to insertion sort as needed...
pdf->strings[pdf->num_strings ++] = news;
// Insert the string...
if (diff > 0)
idx ++;
if (pdf->num_strings > 1)
qsort(pdf->strings, pdf->num_strings, sizeof(char *), (int (*)(const void *, const void *))compare_strings);
PDFIO_DEBUG("pdfioStringCreate: Inserting \"%s\" at %u\n", news, (unsigned)idx);
if (idx < pdf->num_strings)
memmove(pdf->strings + idx + 1, pdf->strings + idx, (pdf->num_strings - idx) * sizeof(char *));
pdf->strings[idx] = news;
pdf->num_strings ++;
PDFIO_DEBUG("pdfioStringCreate: %lu strings\n", (unsigned long)pdf->num_strings);
@ -120,17 +489,67 @@ _pdfioStringIsAllocated(
pdfio_file_t *pdf, // I - PDF file
const char *s) // I - String
{
return (pdf->num_strings > 0 && bsearch(&s, pdf->strings, pdf->num_strings, sizeof(char *), (int (*)(const void *, const void *))compare_strings) != NULL);
int diff; // Difference
if (pdf->num_strings == 0)
return (false);
find_string(pdf, s, &diff);
return (diff == 0);
}
//
// 'compare_strings()' - Compare two strings.
// 'find_string()' - Find an element in the array.
//
static int // O - Result of comparison
compare_strings(char **a, // I - First string
char **b) // I - Second string
static size_t // O - Index of match
find_string(pdfio_file_t *pdf, // I - PDF file
const char *s, // I - String to find
int *rdiff) // O - Difference of match
{
return (strcmp(*a, *b));
size_t left, // Left side of search
right, // Right side of search
current; // Current element
int diff; // Comparison with current element
// Do a binary search for the string...
left = 0;
right = pdf->num_strings - 1;
do
{
current = (left + right) / 2;
diff = strcmp(s, pdf->strings[current]);
if (diff == 0)
break;
else if (diff < 0)
right = current;
else
left = current;
}
while ((right - left) > 1);
if (diff != 0)
{
// Check the last 1 or 2 elements...
if ((diff = strcmp(s, pdf->strings[left])) <= 0)
{
current = left;
}
else
{
diff = strcmp(s, pdf->strings[right]);
current = right;
}
}
// Return the closest string and the difference...
*rdiff = diff;
return (current);
}

View File

@ -1,7 +1,7 @@
//
// PDF value functions for PDFio.
//
// Copyright © 2021-2023 by Michael R Sweet.
// Copyright © 2021-2024 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -497,7 +497,7 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file
// If we get here, we have a number...
v->type = PDFIO_VALTYPE_NUMBER;
v->value.number = (double)strtod(token, NULL);
v->value.number = _pdfio_strtod(pdf, token);
}
else if (!strcmp(token, "true") || !strcmp(token, "false"))
{

View File

@ -1,7 +1,7 @@
//
// Public header file for PDFio.
//
// Copyright © 2021-2023 by Michael R Sweet.
// Copyright © 2021-2024 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -23,7 +23,7 @@ extern "C" {
// Version number...
//
# define PDFIO_VERSION "1.2.0"
# define PDFIO_VERSION "1.3.0"
//
@ -191,6 +191,7 @@ extern pdfio_obj_t *pdfioFileCreateStringObj(pdfio_file_t *pdf, const char *s) _
extern pdfio_file_t *pdfioFileCreateTemporary(char *buffer, size_t bufsize, 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 *pdfioFileFindObj(pdfio_file_t *pdf, size_t number) _PDFIO_PUBLIC;
extern const char *pdfioFileGetAuthor(pdfio_file_t *pdf) _PDFIO_PUBLIC;
extern pdfio_dict_t *pdfioFileGetCatalog(pdfio_file_t *pdf) _PDFIO_PUBLIC;
extern time_t pdfioFileGetCreationDate(pdfio_file_t *pdf) _PDFIO_PUBLIC;
extern const char *pdfioFileGetCreator(pdfio_file_t *pdf) _PDFIO_PUBLIC;
extern pdfio_array_t *pdfioFileGetID(pdfio_file_t *pdf) _PDFIO_PUBLIC;

View File

@ -3,7 +3,7 @@
<metadata>
<id>pdfio_native</id>
<title>PDFio Library for VS2019+</title>
<version>1.2.0</version>
<version>1.3.0</version>
<authors>Michael R Sweet</authors>
<owners>michaelrsweet</owners>
<projectUrl>https://github.com/michaelrsweet/pappl</projectUrl>
@ -16,7 +16,7 @@
<copyright>Copyright © 2019-2024 by Michael R Sweet</copyright>
<tags>pdf file native</tags>
<dependencies>
<dependency id="pdfio_native.redist" version="1.2.0" />
<dependency id="pdfio_native.redist" version="1.3.0" />
<dependency id="zlib_native.redist" version="1.2.11" />
</dependencies>
</metadata>

View File

@ -3,7 +3,7 @@
<metadata>
<id>pdfio_native.redist</id>
<title>PDFio Library for VS2019+</title>
<version>1.2.0</version>
<version>1.3.0</version>
<authors>Michael R Sweet</authors>
<owners>michaelrsweet</owners>
<projectUrl>https://github.com/michaelrsweet/pappl</projectUrl>

16
runtests.bat Normal file
View File

@ -0,0 +1,16 @@
:: Script to run unit test program
::
:: Usage:
::
:: .\runtests.bat x64\{Debug|Release}
::
:: Copy dependent DLLs to the named build directory
echo Copying DLLs
copy packages\zlib_native.redist.1.2.11\build\native\bin\x64\Debug\*.dll %1
copy packages\zlib_native.redist.1.2.11\build\native\bin\x64\Release\*.dll %1
:: Run unit test program
echo Running %1\testpdfio.exe
cd %1
testpdfio.exe

View File

@ -1,7 +1,7 @@
//
// Test program for PDFio.
//
// Copyright © 2021-2023 by Michael R Sweet.
// Copyright © 2021-2024 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -16,6 +16,7 @@
#include "pdfio-private.h"
#include "pdfio-content.h"
#include <math.h>
#include <locale.h>
#ifndef M_PI
# define M_PI 3.14159265358979323846264338327950288
#endif // M_PI
@ -47,7 +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);
static int write_unit_file(pdfio_file_t *inpdf, const char *outname, pdfio_file_t *outpdf, size_t *num_pages, size_t *first_image);
//
@ -61,6 +62,8 @@ main(int argc, // I - Number of command-line arguments
int ret = 0; // Return value
fprintf(stderr, "testpdfio: Test locale is \"%s\".\n", setlocale(LC_ALL, getenv("LANG")));
if (argc > 1)
{
int i; // Looping var
@ -1041,7 +1044,7 @@ do_unit_tests(void)
else
goto fail;
if (write_unit_file(inpdf, outpdf, &num_pages, &first_image))
if (write_unit_file(inpdf, "testpdfio-out.pdf", outpdf, &num_pages, &first_image))
goto fail;
if (read_unit_file("testpdfio-out.pdf", num_pages, first_image, false))
@ -1060,7 +1063,7 @@ do_unit_tests(void)
else
goto fail;
if (write_unit_file(inpdf, outpdf, &num_pages, &first_image))
if (write_unit_file(inpdf, "testpdfio-out2.pdf", outpdf, &num_pages, &first_image))
goto fail;
close(outfd);
@ -1081,7 +1084,7 @@ do_unit_tests(void)
else
return (1);
if (write_unit_file(inpdf, outpdf, &num_pages, &first_image))
if (write_unit_file(inpdf, "testpdfio-rc4.pdf", outpdf, &num_pages, &first_image))
return (1);
if (read_unit_file("testpdfio-rc4.pdf", num_pages, first_image, false))
@ -1100,7 +1103,7 @@ do_unit_tests(void)
else
return (1);
if (write_unit_file(inpdf, outpdf, &num_pages, &first_image))
if (write_unit_file(inpdf, "testpdfio-rc4p.pdf", outpdf, &num_pages, &first_image))
return (1);
if (read_unit_file("testpdfio-rc4p.pdf", num_pages, first_image, false))
@ -1118,7 +1121,7 @@ do_unit_tests(void)
else
return (1);
if (write_unit_file(inpdf, outpdf, &num_pages, &first_image))
if (write_unit_file(inpdf, "testpdfio-aes.pdf", outpdf, &num_pages, &first_image))
return (1);
if (read_unit_file("testpdfio-aes.pdf", num_pages, first_image, false))
@ -1136,7 +1139,7 @@ do_unit_tests(void)
else
return (1);
if (write_unit_file(inpdf, outpdf, &num_pages, &first_image))
if (write_unit_file(inpdf, "testpdfio-aesp.pdf", outpdf, &num_pages, &first_image))
return (1);
if (read_unit_file("testpdfio-aesp.pdf", num_pages, first_image, false))
@ -1148,7 +1151,7 @@ do_unit_tests(void)
else
return (1);
if (write_unit_file(inpdf, outpdf, &num_pages, &first_image))
if (write_unit_file(inpdf, "<temporary>", outpdf, &num_pages, &first_image))
return (1);
if (read_unit_file(temppdf, num_pages, first_image, false))
@ -1305,6 +1308,7 @@ read_unit_file(const char *filename, // I - File to read
bool is_output) // I - File written with output callback?
{
pdfio_file_t *pdf; // PDF file
pdfio_dict_t *catalog; // Catalog dictionary
size_t i; // Looping var
const char *s; // String
bool error = false; // Error callback data
@ -1317,6 +1321,83 @@ read_unit_file(const char *filename, // I - File to read
else
return (1);
// Get the root object/catalog dictionary
fputs("pdfioFileGetCatalog: ", stdout);
if ((catalog = pdfioFileGetCatalog(pdf)) != NULL)
{
puts("PASS");
}
else
{
puts("FAIL (got NULL, expected dictionary)");
return (1);
}
// Verify some catalog values...
fputs("pdfioDictGetName(PageLayout): ", stdout);
if ((s = pdfioDictGetName(catalog, "PageLayout")) != NULL && !strcmp(s, "SinglePage"))
{
puts("PASS");
}
else if (s)
{
printf("FAIL (got '%s', expected 'SinglePage')\n", s);
return (1);
}
else
{
puts("FAIL (got NULL, expected 'SinglePage')");
return (1);
}
fputs("pdfioDictGetName(PageLayout): ", stdout);
if ((s = pdfioDictGetName(catalog, "PageLayout")) != NULL && !strcmp(s, "SinglePage"))
{
puts("PASS");
}
else if (s)
{
printf("FAIL (got '%s', expected 'SinglePage')\n", s);
return (1);
}
else
{
puts("FAIL (got NULL, expected 'SinglePage')");
return (1);
}
fputs("pdfioDictGetName(PageMode): ", stdout);
if ((s = pdfioDictGetName(catalog, "PageMode")) != NULL && !strcmp(s, "UseThumbs"))
{
puts("PASS");
}
else if (s)
{
printf("FAIL (got '%s', expected 'UseThumbs')\n", s);
return (1);
}
else
{
puts("FAIL (got NULL, expected 'UseThumbs')");
return (1);
}
fputs("pdfioDictGetString(Lang): ", stdout);
if ((s = pdfioDictGetString(catalog, "Lang")) != NULL && !strcmp(s, "en"))
{
puts("PASS");
}
else if (s)
{
printf("FAIL (got '%s', expected 'en')\n", s);
return (1);
}
else
{
puts("FAIL (got NULL, expected 'en')");
return (1);
}
// Verify metadata...
fputs("pdfioFileGetAuthor: ", stdout);
if ((s = pdfioFileGetAuthor(pdf)) != NULL && !strcmp(s, "Michael R Sweet"))
@ -3243,6 +3324,7 @@ write_text_test(pdfio_file_t *pdf, // I - PDF file
static int // O - Exit status
write_unit_file(
pdfio_file_t *inpdf, // I - Input PDF file
const char *outname, // I - Output PDF file name
pdfio_file_t *outpdf, // I - Output PDF file
size_t *num_pages, // O - Number of pages
size_t *first_image) // O - First image object
@ -3252,8 +3334,26 @@ write_unit_file(
*gray_jpg, // gray.jpg image
*helvetica, // Helvetica font
*page; // Page from test PDF file
pdfio_dict_t *catalog; // Catalog dictionary
// Get the root object/catalog dictionary
fputs("pdfioFileGetCatalog: ", stdout);
if ((catalog = pdfioFileGetCatalog(outpdf)) != NULL)
{
puts("PASS");
}
else
{
puts("FAIL (got NULL, expected dictionary)");
return (1);
}
// Set some catalog values...
pdfioDictSetName(catalog, "PageLayout", "SinglePage");
pdfioDictSetName(catalog, "PageMode", "UseThumbs");
pdfioDictSetString(catalog, "Lang", "en");
// Set info values...
fputs("pdfioFileGet/SetAuthor: ", stdout);
pdfioFileSetAuthor(outpdf, "Michael R Sweet");
@ -3437,7 +3537,7 @@ write_unit_file(
}
// Close the new PDF file...
fputs("pdfioFileClose(...): ", stdout);
printf("pdfioFileClose(\"%s\"): ", outname);
if (pdfioFileClose(outpdf))
puts("PASS");
else