Compare commits

...

13 Commits

Author SHA1 Message Date
Michael R Sweet
130cef8702
Update pdfioinfo example to support Acrobat Form dictionaries as well as indirect references (Issue #114) 2025-04-04 21:24:42 -04:00
Michael R Sweet
0bd9edc845
Move token buffers off the stack (Issue #117) 2025-04-04 21:20:23 -04:00
Michael R Sweet
fe755eac3d
Add PDFIO_MAX_STRING constant to control maximum allowed PDF strings (Issue #117) 2025-04-04 19:27:03 -04:00
Michael R Sweet
8cca645835
Update date/time parsing (Issue #115) 2025-04-04 19:12:16 -04:00
Michael R Sweet
b8ea9ea064
Bump version. 2025-04-04 19:11:54 -04:00
Michael R Sweet
2874022aa4
Allow empty name tokens (Issue #116) 2025-04-04 18:26:35 -04:00
Michael R Sweet
3befcf2fd5
Fix warning about shadowed loop variable. 2025-04-04 18:17:04 -04:00
Michael R Sweet
3b2f7e21d9
Prep for 1.5.1 release. 2025-03-28 14:39:59 -04:00
Michael R Sweet
7e01069c5a
Fix UTF-16 LE support (Issue #112) 2025-03-28 14:29:24 -04:00
Michael R Sweet
88839ccb56
Fix UTF-16 LE support (Issue #112) 2025-03-28 14:28:43 -04:00
Michael R Sweet
ebd5aab39b
Fix handling of 0-length streams (Issue #111) 2025-03-27 12:44:42 -04:00
Michael R Sweet
71d33c03ff
Add PDF merge example. 2025-03-27 11:48:41 -04:00
Michael R Sweet
cfe91b4ea2
Fix output of special characters in name values (Issue #106)
Fix output of special characters in string values (Issue #107)
Fi output of large integers in dictionaries (Issue #108)

Bump version to 1.5.1.
2025-03-24 18:33:24 -04:00
23 changed files with 1109 additions and 252 deletions

1
.gitignore vendored
View File

@ -16,6 +16,7 @@
/examples/md2pdf
/examples/pdf2text
/examples/pdfioinfo
/examples/pdfiomerge
/Makefile
/packages
/pdfio.pc

View File

@ -1,6 +1,26 @@
Changes in PDFio
================
v1.5.2 - YYYY-MM-DD
-------------------
- Updated maximum allowed PDF string size to 64k (Issue #117)
- Fixed form detection in `pdfioinfo` example code (Issue #114)
- Fixed parsing of certain date/time values (Issue #115)
- Fixed support for empty name values (Issue #116)
v1.5.1 - 2025-03-28
-------------------
- Fixed output of special characters in name values (Issue #106)
- Fixed output of special characters in string values (Issue #107)
- Fixed output of large integers in dictionaries (Issue #108)
- Fixed handling of 0-length streams (Issue #111)
- Fixed detection of UTF-16 Big-Endian strings (Issue #112)
v1.5.0 - 2025-03-06
-------------------

203
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.5.0.
# Generated by GNU Autoconf 2.71 for pdfio 1.5.2.
#
# 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.5.0'
PACKAGE_STRING='pdfio 1.5.0'
PACKAGE_VERSION='1.5.2'
PACKAGE_STRING='pdfio 1.5.2'
PACKAGE_BUGREPORT='https://github.com/michaelrsweet/pdfio/issues'
PACKAGE_URL='https://www.msweet.org/pdfio'
@ -1295,7 +1295,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.5.0 to adapt to many kinds of systems.
\`configure' configures pdfio 1.5.2 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@ -1361,7 +1361,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of pdfio 1.5.0:";;
short | recursive ) echo "Configuration of pdfio 1.5.2:";;
esac
cat <<\_ACEOF
@ -1460,7 +1460,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
pdfio configure 1.5.0
pdfio configure 1.5.2
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@ -1513,39 +1513,6 @@ fi
} # ac_fn_c_try_compile
# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
# -------------------------------------------------------
# Tests whether HEADER exists and can be compiled using the include files in
# INCLUDES, setting the cache variable VAR accordingly.
ac_fn_c_check_header_compile ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
printf %s "checking for $2... " >&6; }
if eval test \${$3+y}
then :
printf %s "(cached) " >&6
else $as_nop
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
$4
#include <$2>
_ACEOF
if ac_fn_c_try_compile "$LINENO"
then :
eval "$3=yes"
else $as_nop
eval "$3=no"
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
fi
eval ac_res=\$$3
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
printf "%s\n" "$ac_res" >&6; }
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
} # ac_fn_c_check_header_compile
# ac_fn_c_try_link LINENO
# -----------------------
# Try to link conftest.$ac_ext, and return whether this succeeded.
@ -1592,6 +1559,101 @@ fi
as_fn_set_status $ac_retval
} # ac_fn_c_try_link
# ac_fn_c_check_func LINENO FUNC VAR
# ----------------------------------
# Tests whether FUNC exists, setting the cache variable VAR accordingly
ac_fn_c_check_func ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
printf %s "checking for $2... " >&6; }
if eval test \${$3+y}
then :
printf %s "(cached) " >&6
else $as_nop
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
For example, HP-UX 11i <limits.h> declares gettimeofday. */
#define $2 innocuous_$2
/* System header to define __stub macros and hopefully few prototypes,
which can conflict with char $2 (); below. */
#include <limits.h>
#undef $2
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char $2 ();
/* The GNU C library defines this for functions which it implements
to always fail with ENOSYS. Some functions are actually named
something starting with __ and the normal name is an alias. */
#if defined __stub_$2 || defined __stub___$2
choke me
#endif
int
main (void)
{
return $2 ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
eval "$3=yes"
else $as_nop
eval "$3=no"
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
fi
eval ac_res=\$$3
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
printf "%s\n" "$ac_res" >&6; }
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
} # ac_fn_c_check_func
# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
# -------------------------------------------------------
# Tests whether HEADER exists and can be compiled using the include files in
# INCLUDES, setting the cache variable VAR accordingly.
ac_fn_c_check_header_compile ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
printf %s "checking for $2... " >&6; }
if eval test \${$3+y}
then :
printf %s "(cached) " >&6
else $as_nop
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
$4
#include <$2>
_ACEOF
if ac_fn_c_try_compile "$LINENO"
then :
eval "$3=yes"
else $as_nop
eval "$3=no"
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
fi
eval ac_res=\$$3
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
printf "%s\n" "$ac_res" >&6; }
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
} # ac_fn_c_check_header_compile
ac_configure_args_raw=
for ac_arg
do
@ -1616,7 +1678,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.5.0, which was
It was created by pdfio $as_me 1.5.2, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@ -2372,9 +2434,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
PDFIO_VERSION="1.5.0"
PDFIO_VERSION_MAJOR="`echo 1.5.0 | awk -F. '{print $1}'`"
PDFIO_VERSION_MINOR="`echo 1.5.0 | awk -F. '{printf("%d\n",$2);}'`"
PDFIO_VERSION="1.5.2"
PDFIO_VERSION_MAJOR="`echo 1.5.2 | awk -F. '{print $1}'`"
PDFIO_VERSION_MINOR="`echo 1.5.2 | awk -F. '{printf("%d\n",$2);}'`"
@ -3877,6 +3939,56 @@ INSTALL="$(pwd)/install-sh"
printf "%s\n" "using $INSTALL" >&6; }
ac_fn_c_check_func "$LINENO" "timegm" "ac_cv_func_timegm"
if test "x$ac_cv_func_timegm" = xyes
then :
printf "%s\n" "#define HAVE_TIMEGM 1" >>confdefs.h
CPPFLAGS="-DHAVE_TIMEGM=1 $CPPFLAGS"
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for tm_gmtoff member in tm structure" >&5
printf %s "checking for tm_gmtoff member in tm structure... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <time.h>
int
main (void)
{
struct tm t;
int o = t.tm_gmtoff;
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"
then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
printf "%s\n" "yes" >&6; }
printf "%s\n" "#define HAVE_TM_GMTOFF 1" >>confdefs.h
CPPFLAGS="-DHAVE_TM_GMTOFF=1 $CPPFLAGS"
else $as_nop
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
printf "%s\n" "no" >&6; }
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
if test -n "$ac_tool_prefix"; then
# Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
@ -3998,7 +4110,6 @@ PKGCONFIG_REQUIRES="zlib"
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for zlib via pkg-config" >&5
printf %s "checking for zlib via pkg-config... " >&6; }
ac_header= ac_cache=
for ac_item in $ac_header_c_list
do
@ -4988,7 +5099,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.5.0, which was
This file was extended by pdfio $as_me 1.5.2, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@ -5044,7 +5155,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.5.0
pdfio config.status 1.5.2
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.5.0], [https://github.com/michaelrsweet/pdfio/issues], [pdfio], [https://www.msweet.org/pdfio])
AC_INIT([pdfio], [1.5.2], [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}'`"
@ -88,6 +88,27 @@ AC_SUBST([INSTALL])
AC_MSG_RESULT([using $INSTALL])
dnl Check for date/time functionality...
AC_CHECK_FUNC([timegm], [
AC_DEFINE([HAVE_TIMEGM], [1], [Do we have the timegm function?])
CPPFLAGS="-DHAVE_TIMEGM=1 $CPPFLAGS"
])
AC_MSG_CHECKING([for tm_gmtoff member in tm structure])
AC_COMPILE_IFELSE([
AC_LANG_PROGRAM([[#include <time.h>]], [[
struct tm t;
int o = t.tm_gmtoff;
]])
], [
AC_MSG_RESULT([yes])
AC_DEFINE([HAVE_TM_GMTOFF], [1], [Have tm_gmtoff member in struct tm?])
CPPFLAGS="-DHAVE_TM_GMTOFF=1 $CPPFLAGS"
], [
AC_MSG_RESULT([no])
])
dnl Check for pkg-config, which is used for some other tests later on...
AC_PATH_TOOL([PKGCONFIG], [pkg-config])

View File

@ -1,4 +1,4 @@
.TH pdfio 3 "pdf read/write library" "2025-03-06" "pdf read/write library"
.TH pdfio 3 "pdf read/write library" "2025-04-04" "pdf read/write library"
.SH NAME
pdfio \- pdf read/write library
.SH Introduction
@ -1047,11 +1047,26 @@ The pdfioinfo.c example program opens a PDF file and prints the title, author, c
{
const char *filename; // PDF filename
pdfio_file_t *pdf; // PDF file
const char *author; // Author name
time_t creation_date; // Creation date
struct tm *creation_tm; // Creation date/time information
char creation_text[256]; // Creation date/time as a string
const char *title; // Title
pdfio_dict_t *catalog; // Catalog dictionary
const char *author, // Author name
*creator, // Creator name
*producer, // Producer name
*title; // Title
time_t creation_date, // Creation date
modification_date; // Modification date
struct tm *creation_tm, // Creation date/time information
*modification_tm; // Modification date/time information
char creation_text[256], // Creation date/time as a string
modification_text[256], // Modification date/time human fmt string
range_text[255]; // Page range text
size_t num_pages; // PDF number of pages
bool has_acroform; // Does the file have an AcroForm?
pdfio_obj_t *page; // Object
pdfio_dict_t *page_dict; // Object dictionary
size_t cur, // Current page index
prev; // Previous page index
pdfio_rect_t cur_box, // Current MediaBox
prev_box; // Previous MediaBox
// Get the filename from the command\-line...
@ -1064,14 +1079,20 @@ The pdfioinfo.c example program opens a PDF file and prints the title, author, c
filename = argv[1];
// Open the PDF file with the default callbacks...
pdf = pdfioFileOpen(filename, /*password_cb*/NULL, /*password_cbdata*/NULL,
/*error_cb*/NULL, /*error_cbdata*/NULL);
pdf = pdfioFileOpen(filename, /*password_cb*/NULL,
/*password_cbdata*/NULL, /*error_cb*/NULL,
/*error_cbdata*/NULL);
if (pdf == NULL)
return (1);
// Get the title and author...
author = pdfioFileGetAuthor(pdf);
title = pdfioFileGetTitle(pdf);
// Get the title, author, etc...
catalog = pdfioFileGetCatalog(pdf);
author = pdfioFileGetAuthor(pdf);
creator = pdfioFileGetCreator(pdf);
has_acroform = pdfioDictGetType(catalog, "AcroForm") != PDFIO_VALTYPE_NONE;
num_pages = pdfioFileGetNumPages(pdf);
producer = pdfioFileGetProducer(pdf);
title = pdfioFileGetTitle(pdf);
// Get the creation date and convert to a string...
if ((creation_date = pdfioFileGetCreationDate(pdf)) > 0)
@ -1084,12 +1105,76 @@ The pdfioinfo.c example program opens a PDF file and prints the title, author, c
snprintf(creation_text, sizeof(creation_text), "\-\- not set \-\-");
}
// Get the modification date and convert to a string...
if ((modification_date = pdfioFileGetModificationDate(pdf)) > 0)
{
modification_tm = localtime(&modification_date);
strftime(modification_text, sizeof(modification_text), "%c", modification_tm);
}
else
{
snprintf(modification_text, sizeof(modification_text), "\-\- not set \-\-");
}
// Print file information to stdout...
printf("%s:\\n", filename);
printf(" Title: %s\\n", title ? title : "\-\- not set \-\-");
printf(" Author: %s\\n", author ? author : "\-\- not set \-\-");
printf(" Created On: %s\\n", creation_text);
printf(" Number Pages: %u\\n", (unsigned)pdfioFileGetNumPages(pdf));
printf(" Title: %s\\n", title ? title : "\-\- not set \-\-");
printf(" Author: %s\\n", author ? author : "\-\- not set \-\-");
printf(" Creator: %s\\n", creator ? creator : "\-\- not set \-\-");
printf(" Producer: %s\\n", producer ? producer : "\-\- not set \-\-");
printf(" Created On: %s\\n", creation_text);
printf(" Modified On: %s\\n", modification_text);
printf(" Version: %s\\n", pdfioFileGetVersion(pdf));
printf(" AcroForm: %s\\n", has_acroform ? "Yes" : "No");
printf(" Number of Pages: %u\\n", (unsigned)num_pages);
// Report the MediaBox for all of the pages
prev_box.x1 = prev_box.x2 = prev_box.y1 = prev_box.y2 = 0.0;
for (cur = 0, prev = 0; cur < num_pages; cur ++)
{
// Find the MediaBox for this page in the page tree...
for (page = pdfioFileGetPage(pdf, cur);
page != NULL;
page = pdfioDictGetObj(page_dict, "Parent"))
{
cur_box.x1 = cur_box.x2 = cur_box.y1 = cur_box.y2 = 0.0;
page_dict = pdfioObjGetDict(page);
if (pdfioDictGetRect(page_dict, "MediaBox", &cur_box))
break;
}
// If this MediaBox is different from the previous one, show the range of
// pages that have that size...
if (cur == 0 ||
fabs(cur_box.x1 \- prev_box.x1) > 0.01 ||
fabs(cur_box.y1 \- prev_box.y1) > 0.01 ||
fabs(cur_box.x2 \- prev_box.x2) > 0.01 ||
fabs(cur_box.y2 \- prev_box.y2) > 0.01)
{
if (cur > prev)
{
snprintf(range_text, sizeof(range_text), "Pages %u\-%u",
(unsigned)(prev + 1), (unsigned)cur);
printf("%16s: [%g %g %g %g]\\n", range_text,
prev_box.x1, prev_box.y1, prev_box.x2, prev_box.y2);
}
// Start a new series of pages with the new size...
prev = cur;
prev_box = cur_box;
}
}
// Show the last range as needed...
if (cur > prev)
{
snprintf(range_text, sizeof(range_text), "Pages %u\-%u",
(unsigned)(prev + 1), (unsigned)cur);
printf("%16s: [%g %g %g %g]\\n", range_text,
prev_box.x1, prev_box.y1, prev_box.x2, prev_box.y2);
}
// Close the PDF file...
pdfioFileClose(pdf);
@ -4590,6 +4675,10 @@ bool pdfioStreamPrintf (
...
);
.fi
.PP
This function writes a formatted string to a stream. In addition to the
standard \fBprintf\fR format characters, you can use "%N" to format a PDF name
value ("/Name") and "%S" to format a PDF string ("(String)") value.
.SS pdfioStreamPutChar
Write a single character to a stream.
.PP

View File

@ -1,13 +1,13 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>PDFio Programming Manual v1.5.0</title>
<title>PDFio Programming Manual v1.5.2</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="generator" content="codedoc v3.8">
<meta name="author" content="Michael R Sweet">
<meta name="language" content="en-US">
<meta name="copyright" content="Copyright © 2021-2025 by Michael R Sweet">
<meta name="version" content="1.5.0">
<meta name="version" content="1.5.2">
<style type="text/css"><!--
body {
background: white;
@ -251,7 +251,7 @@ span.string {
<body>
<div class="header">
<p><img class="title" src="pdfio-512.png"></p>
<h1 class="title">PDFio Programming Manual v1.5.0</h1>
<h1 class="title">PDFio Programming Manual v1.5.2</h1>
<p>Michael R Sweet</p>
<p>Copyright © 2021-2025 by Michael R Sweet</p>
</div>
@ -1165,11 +1165,26 @@ main(<span class="reserved">int</span> argc, <span clas
{
<span class="reserved">const</span> <span class="reserved">char</span> *filename; <span class="comment">// PDF filename</span>
pdfio_file_t *pdf; <span class="comment">// PDF file</span>
<span class="reserved">const</span> <span class="reserved">char</span> *author; <span class="comment">// Author name</span>
time_t creation_date; <span class="comment">// Creation date</span>
<span class="reserved">struct</span> tm *creation_tm; <span class="comment">// Creation date/time information</span>
<span class="reserved">char</span> creation_text[<span class="number">256</span>]; <span class="comment">// Creation date/time as a string</span>
<span class="reserved">const</span> <span class="reserved">char</span> *title; <span class="comment">// Title</span>
pdfio_dict_t *catalog; <span class="comment">// Catalog dictionary</span>
<span class="reserved">const</span> <span class="reserved">char</span> *author, <span class="comment">// Author name</span>
*creator, <span class="comment">// Creator name</span>
*producer, <span class="comment">// Producer name</span>
*title; <span class="comment">// Title</span>
time_t creation_date, <span class="comment">// Creation date</span>
modification_date; <span class="comment">// Modification date</span>
<span class="reserved">struct</span> tm *creation_tm, <span class="comment">// Creation date/time information</span>
*modification_tm; <span class="comment">// Modification date/time information</span>
<span class="reserved">char</span> creation_text[<span class="number">256</span>], <span class="comment">// Creation date/time as a string</span>
modification_text[<span class="number">256</span>], <span class="comment">// Modification date/time human fmt string</span>
range_text[<span class="number">255</span>]; <span class="comment">// Page range text</span>
size_t num_pages; <span class="comment">// PDF number of pages</span>
<span class="reserved">bool</span> has_acroform; <span class="comment">// Does the file have an AcroForm?</span>
pdfio_obj_t *page; <span class="comment">// Object</span>
pdfio_dict_t *page_dict; <span class="comment">// Object dictionary</span>
size_t cur, <span class="comment">// Current page index</span>
prev; <span class="comment">// Previous page index</span>
pdfio_rect_t cur_box, <span class="comment">// Current MediaBox</span>
prev_box; <span class="comment">// Previous MediaBox</span>
<span class="comment">// Get the filename from the command-line...</span>
@ -1182,14 +1197,20 @@ main(<span class="reserved">int</span> argc, <span clas
filename = argv[<span class="number">1</span>];
<span class="comment">// Open the PDF file with the default callbacks...</span>
pdf = pdfioFileOpen(filename, <span class="comment">/*password_cb*/</span>NULL, <span class="comment">/*password_cbdata*/</span>NULL,
<span class="comment">/*error_cb*/</span>NULL, <span class="comment">/*error_cbdata*/</span>NULL);
pdf = pdfioFileOpen(filename, <span class="comment">/*password_cb*/</span>NULL,
<span class="comment">/*password_cbdata*/</span>NULL, <span class="comment">/*error_cb*/</span>NULL,
<span class="comment">/*error_cbdata*/</span>NULL);
<span class="reserved">if</span> (pdf == NULL)
<span class="reserved">return</span> (<span class="number">1</span>);
<span class="comment">// Get the title and author...</span>
author = pdfioFileGetAuthor(pdf);
title = pdfioFileGetTitle(pdf);
<span class="comment">// Get the title, author, etc...</span>
catalog = pdfioFileGetCatalog(pdf);
author = pdfioFileGetAuthor(pdf);
creator = pdfioFileGetCreator(pdf);
has_acroform = pdfioDictGetType(catalog, <span class="string">&quot;AcroForm&quot;</span>) != PDFIO_VALTYPE_NONE;
num_pages = pdfioFileGetNumPages(pdf);
producer = pdfioFileGetProducer(pdf);
title = pdfioFileGetTitle(pdf);
<span class="comment">// Get the creation date and convert to a string...</span>
<span class="reserved">if</span> ((creation_date = pdfioFileGetCreationDate(pdf)) &gt; <span class="number">0</span>)
@ -1202,12 +1223,76 @@ main(<span class="reserved">int</span> argc, <span clas
snprintf(creation_text, <span class="reserved">sizeof</span>(creation_text), <span class="string">&quot;-- not set --&quot;</span>);
}
<span class="comment">// Get the modification date and convert to a string...</span>
<span class="reserved">if</span> ((modification_date = pdfioFileGetModificationDate(pdf)) &gt; <span class="number">0</span>)
{
modification_tm = localtime(&amp;modification_date);
strftime(modification_text, <span class="reserved">sizeof</span>(modification_text), <span class="string">&quot;%c&quot;</span>, modification_tm);
}
<span class="reserved">else</span>
{
snprintf(modification_text, <span class="reserved">sizeof</span>(modification_text), <span class="string">&quot;-- not set --&quot;</span>);
}
<span class="comment">// Print file information to stdout...</span>
printf(<span class="string">&quot;%s:\n&quot;</span>, filename);
printf(<span class="string">&quot; Title: %s\n&quot;</span>, title ? title : <span class="string">&quot;-- not set --&quot;</span>);
printf(<span class="string">&quot; Author: %s\n&quot;</span>, author ? author : <span class="string">&quot;-- not set --&quot;</span>);
printf(<span class="string">&quot; Created On: %s\n&quot;</span>, creation_text);
printf(<span class="string">&quot; Number Pages: %u\n&quot;</span>, (<span class="reserved">unsigned</span>)pdfioFileGetNumPages(pdf));
printf(<span class="string">&quot; Title: %s\n&quot;</span>, title ? title : <span class="string">&quot;-- not set --&quot;</span>);
printf(<span class="string">&quot; Author: %s\n&quot;</span>, author ? author : <span class="string">&quot;-- not set --&quot;</span>);
printf(<span class="string">&quot; Creator: %s\n&quot;</span>, creator ? creator : <span class="string">&quot;-- not set --&quot;</span>);
printf(<span class="string">&quot; Producer: %s\n&quot;</span>, producer ? producer : <span class="string">&quot;-- not set --&quot;</span>);
printf(<span class="string">&quot; Created On: %s\n&quot;</span>, creation_text);
printf(<span class="string">&quot; Modified On: %s\n&quot;</span>, modification_text);
printf(<span class="string">&quot; Version: %s\n&quot;</span>, pdfioFileGetVersion(pdf));
printf(<span class="string">&quot; AcroForm: %s\n&quot;</span>, has_acroform ? <span class="string">&quot;Yes&quot;</span> : <span class="string">&quot;No&quot;</span>);
printf(<span class="string">&quot; Number of Pages: %u\n&quot;</span>, (<span class="reserved">unsigned</span>)num_pages);
<span class="comment">// Report the MediaBox for all of the pages</span>
prev_box.x1 = prev_box.x2 = prev_box.y1 = prev_box.y2 = <span class="number">0.0</span>;
<span class="reserved">for</span> (cur = <span class="number">0</span>, prev = <span class="number">0</span>; cur &lt; num_pages; cur ++)
{
<span class="comment">// Find the MediaBox for this page in the page tree...</span>
<span class="reserved">for</span> (page = pdfioFileGetPage(pdf, cur);
page != NULL;
page = pdfioDictGetObj(page_dict, <span class="string">&quot;Parent&quot;</span>))
{
cur_box.x1 = cur_box.x2 = cur_box.y1 = cur_box.y2 = <span class="number">0.0</span>;
page_dict = pdfioObjGetDict(page);
<span class="reserved">if</span> (pdfioDictGetRect(page_dict, <span class="string">&quot;MediaBox&quot;</span>, &amp;cur_box))
<span class="reserved">break</span>;
}
<span class="comment">// If this MediaBox is different from the previous one, show the range of</span>
<span class="comment">// pages that have that size...</span>
<span class="reserved">if</span> (cur == <span class="number">0</span> ||
fabs(cur_box.x1 - prev_box.x1) &gt; <span class="number">0.01</span> ||
fabs(cur_box.y1 - prev_box.y1) &gt; <span class="number">0.01</span> ||
fabs(cur_box.x2 - prev_box.x2) &gt; <span class="number">0.01</span> ||
fabs(cur_box.y2 - prev_box.y2) &gt; <span class="number">0.01</span>)
{
<span class="reserved">if</span> (cur &gt; prev)
{
snprintf(range_text, <span class="reserved">sizeof</span>(range_text), <span class="string">&quot;Pages %u-%u&quot;</span>,
(<span class="reserved">unsigned</span>)(prev + <span class="number">1</span>), (<span class="reserved">unsigned</span>)cur);
printf(<span class="string">&quot;%16s: [%g %g %g %g]\n&quot;</span>, range_text,
prev_box.x1, prev_box.y1, prev_box.x2, prev_box.y2);
}
<span class="comment">// Start a new series of pages with the new size...</span>
prev = cur;
prev_box = cur_box;
}
}
<span class="comment">// Show the last range as needed...</span>
<span class="reserved">if</span> (cur &gt; prev)
{
snprintf(range_text, <span class="reserved">sizeof</span>(range_text), <span class="string">&quot;Pages %u-%u&quot;</span>,
(<span class="reserved">unsigned</span>)(prev + <span class="number">1</span>), (<span class="reserved">unsigned</span>)cur);
printf(<span class="string">&quot;%16s: [%g %g %g %g]\n&quot;</span>, range_text,
prev_box.x1, prev_box.y1, prev_box.x2, prev_box.y2);
}
<span class="comment">// Close the PDF file...</span>
pdfioFileClose(pdf);
@ -5081,6 +5166,10 @@ ssize_t pdfioStreamPeek(<a href="#pdfio_stream_t">pdfio_stream_t</a> *st, <span
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description"><code>true</code> on success, <code>false</code> on failure</p>
<h4 class="discussion">Discussion</h4>
<p class="discussion">This function writes a formatted string to a stream. In addition to the
standard <code>printf</code> format characters, you can use &quot;%N&quot; to format a PDF name
value (&quot;/Name&quot;) and &quot;%S&quot; to format a PDF string (&quot;(String)&quot;) value.</p>
<h3 class="function"><a id="pdfioStreamPutChar">pdfioStreamPutChar</a></h3>
<p class="description">Write a single character to a stream.</p>
<p class="code">

View File

@ -889,11 +889,26 @@ main(int argc, // I - Number of command-line arguments
{
const char *filename; // PDF filename
pdfio_file_t *pdf; // PDF file
const char *author; // Author name
time_t creation_date; // Creation date
struct tm *creation_tm; // Creation date/time information
char creation_text[256]; // Creation date/time as a string
const char *title; // Title
pdfio_dict_t *catalog; // Catalog dictionary
const char *author, // Author name
*creator, // Creator name
*producer, // Producer name
*title; // Title
time_t creation_date, // Creation date
modification_date; // Modification date
struct tm *creation_tm, // Creation date/time information
*modification_tm; // Modification date/time information
char creation_text[256], // Creation date/time as a string
modification_text[256], // Modification date/time human fmt string
range_text[255]; // Page range text
size_t num_pages; // PDF number of pages
bool has_acroform; // Does the file have an AcroForm?
pdfio_obj_t *page; // Object
pdfio_dict_t *page_dict; // Object dictionary
size_t cur, // Current page index
prev; // Previous page index
pdfio_rect_t cur_box, // Current MediaBox
prev_box; // Previous MediaBox
// Get the filename from the command-line...
@ -906,14 +921,20 @@ main(int argc, // I - Number of command-line arguments
filename = argv[1];
// Open the PDF file with the default callbacks...
pdf = pdfioFileOpen(filename, /*password_cb*/NULL, /*password_cbdata*/NULL,
/*error_cb*/NULL, /*error_cbdata*/NULL);
pdf = pdfioFileOpen(filename, /*password_cb*/NULL,
/*password_cbdata*/NULL, /*error_cb*/NULL,
/*error_cbdata*/NULL);
if (pdf == NULL)
return (1);
// Get the title and author...
author = pdfioFileGetAuthor(pdf);
title = pdfioFileGetTitle(pdf);
// Get the title, author, etc...
catalog = pdfioFileGetCatalog(pdf);
author = pdfioFileGetAuthor(pdf);
creator = pdfioFileGetCreator(pdf);
has_acroform = pdfioDictGetType(catalog, "AcroForm") != PDFIO_VALTYPE_NONE;
num_pages = pdfioFileGetNumPages(pdf);
producer = pdfioFileGetProducer(pdf);
title = pdfioFileGetTitle(pdf);
// Get the creation date and convert to a string...
if ((creation_date = pdfioFileGetCreationDate(pdf)) > 0)
@ -926,12 +947,76 @@ main(int argc, // I - Number of command-line arguments
snprintf(creation_text, sizeof(creation_text), "-- not set --");
}
// Get the modification date and convert to a string...
if ((modification_date = pdfioFileGetModificationDate(pdf)) > 0)
{
modification_tm = localtime(&modification_date);
strftime(modification_text, sizeof(modification_text), "%c", modification_tm);
}
else
{
snprintf(modification_text, sizeof(modification_text), "-- not set --");
}
// Print file information to stdout...
printf("%s:\n", filename);
printf(" Title: %s\n", title ? title : "-- not set --");
printf(" Author: %s\n", author ? author : "-- not set --");
printf(" Created On: %s\n", creation_text);
printf(" Number Pages: %u\n", (unsigned)pdfioFileGetNumPages(pdf));
printf(" Title: %s\n", title ? title : "-- not set --");
printf(" Author: %s\n", author ? author : "-- not set --");
printf(" Creator: %s\n", creator ? creator : "-- not set --");
printf(" Producer: %s\n", producer ? producer : "-- not set --");
printf(" Created On: %s\n", creation_text);
printf(" Modified On: %s\n", modification_text);
printf(" Version: %s\n", pdfioFileGetVersion(pdf));
printf(" AcroForm: %s\n", has_acroform ? "Yes" : "No");
printf(" Number of Pages: %u\n", (unsigned)num_pages);
// Report the MediaBox for all of the pages
prev_box.x1 = prev_box.x2 = prev_box.y1 = prev_box.y2 = 0.0;
for (cur = 0, prev = 0; cur < num_pages; cur ++)
{
// Find the MediaBox for this page in the page tree...
for (page = pdfioFileGetPage(pdf, cur);
page != NULL;
page = pdfioDictGetObj(page_dict, "Parent"))
{
cur_box.x1 = cur_box.x2 = cur_box.y1 = cur_box.y2 = 0.0;
page_dict = pdfioObjGetDict(page);
if (pdfioDictGetRect(page_dict, "MediaBox", &cur_box))
break;
}
// If this MediaBox is different from the previous one, show the range of
// pages that have that size...
if (cur == 0 ||
fabs(cur_box.x1 - prev_box.x1) > 0.01 ||
fabs(cur_box.y1 - prev_box.y1) > 0.01 ||
fabs(cur_box.x2 - prev_box.x2) > 0.01 ||
fabs(cur_box.y2 - prev_box.y2) > 0.01)
{
if (cur > prev)
{
snprintf(range_text, sizeof(range_text), "Pages %u-%u",
(unsigned)(prev + 1), (unsigned)cur);
printf("%16s: [%g %g %g %g]\n", range_text,
prev_box.x1, prev_box.y1, prev_box.x2, prev_box.y2);
}
// Start a new series of pages with the new size...
prev = cur;
prev_box = cur_box;
}
}
// Show the last range as needed...
if (cur > prev)
{
snprintf(range_text, sizeof(range_text), "Pages %u-%u",
(unsigned)(prev + 1), (unsigned)cur);
printf("%16s: [%g %g %g %g]\n", range_text,
prev_box.x1, prev_box.y1, prev_box.x2, prev_box.y2);
}
// Close the PDF file...
pdfioFileClose(pdf);

View File

@ -24,7 +24,8 @@ TARGETS = \
image2pdf \
md2pdf \
pdf2text \
pdfioinfo
pdfioinfo \
pdfiomerge
# Make everything
@ -61,5 +62,10 @@ pdfioinfo: pdfioinfo.c
$(CC) $(CFLAGS) -o $@ pdfioinfo.c $(LIBS)
# pdfiomerge
pdfiomerge: pdfiomerge.c
$(CC) $(CFLAGS) -o $@ pdfiomerge.c $(LIBS)
# Common dependencies...
$(TARGETS): Makefile ../pdfio.h ../pdfio-content.h

View File

@ -68,7 +68,7 @@ main(int argc, // I - Number of command-line arguments
catalog = pdfioFileGetCatalog(pdf);
author = pdfioFileGetAuthor(pdf);
creator = pdfioFileGetCreator(pdf);
has_acroform = pdfioDictGetObj(catalog, "AcroForm") != NULL ? true : false;
has_acroform = pdfioDictGetType(catalog, "AcroForm") != PDFIO_VALTYPE_NONE;
num_pages = pdfioFileGetNumPages(pdf);
producer = pdfioFileGetProducer(pdf);
title = pdfioFileGetTitle(pdf);

146
examples/pdfiomerge.c Normal file
View File

@ -0,0 +1,146 @@
//
// PDF merge program for PDFio.
//
// Copyright © 2025 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
//
// Usage:
//
// ./pdfmerge [-o OUTPUT.pdf] INPUT.pdf [... INPUT.pdf]
// ./pdfmerge INPUT.pdf [... INPUT.pdf] >OUTPUT.pdf
//
#include <pdfio.h>
#include <string.h>
//
// Local functions...
//
static ssize_t output_cb(void *output_cbdata, const void *buffer, size_t bytes);
static int usage(FILE *out);
//
// 'main()' - Main entry.
//
int // O - Exit status
main(int argc, // I - Number of command-line arguments
char *argv[]) // I - Command-line arguments
{
int i; // Looping var
const char *opt; // Current option
pdfio_file_t *inpdf, // Input PDF file
*outpdf = NULL; // Output PDF file
// Parse command-line...
for (i = 1; i < argc; i ++)
{
if (!strcmp(argv[i], "--help"))
{
return (usage(stdout));
}
else if (!strncmp(argv[i], "--", 2))
{
fprintf(stderr, "pdfmerge: Unknown option '%s'.\n", argv[i]);
return (usage(stderr));
}
else if (argv[i][0] == '-')
{
for (opt = argv[i] + 1; *opt; opt ++)
{
switch (*opt)
{
case 'o' : // -o OUTPUT.pdf
if (outpdf)
{
fputs("pdfmerge: Only one output file can be specified.\n", stderr);
return (usage(stderr));
}
i ++;
if (i >= argc)
{
fputs("pdfmerge: Missing output filename after '-o'.\n", stderr);
return (usage(stderr));
}
if ((outpdf = pdfioFileCreate(argv[i], /*version*/NULL, /*media_box*/NULL, /*crop_box*/NULL, /*error_cb*/NULL, /*error_data*/NULL)) == NULL)
return (1);
break;
default :
fprintf(stderr, "pdfmerge: Unknown option '-%c'.\n", *opt);
return (usage(stderr));
}
}
}
else if ((inpdf = pdfioFileOpen(argv[i], /*password_cb*/NULL, /*password_data*/NULL, /*error_cb*/NULL, /*error_data*/NULL)) == NULL)
{
return (1);
}
else
{
// Copy PDF file...
size_t p, // Current page
nump; // Number of pages
if (!outpdf)
{
if ((outpdf = pdfioFileCreateOutput(output_cb, /*output_cbdata*/NULL, /*version*/NULL, /*media_box*/NULL, /*crop_box*/NULL, /*error_cb*/NULL, /*error_data*/NULL)) == NULL)
return (1);
}
for (p = 0, nump = pdfioFileGetNumPages(inpdf); p < nump; p ++)
{
if (!pdfioPageCopy(outpdf, pdfioFileGetPage(inpdf, p)))
return (1);
}
pdfioFileClose(inpdf);
}
}
if (!outpdf)
return (usage(stderr));
pdfioFileClose(outpdf);
return (0);
}
//
// 'output_cb()' - Write PDF data to the standard output...
//
static ssize_t // O - Number of bytes written
output_cb(void *output_cbdata, // I - Callback data (not used)
const void *buffer, // I - Buffer to write
size_t bytes) // I - Number of bytes to write
{
(void)output_cbdata;
return ((ssize_t)fwrite(buffer, 1, bytes, stdout));
}
//
// 'usage()' - Show program usage.
//
static int // O - Exit status
usage(FILE *out) // I - stdout or stderr
{
fputs("Usage: pdfmerge [OPTIONS] INPUT.pdf [... INPUT.pdf] >OUTPUT.pdf\n", out);
fputs("Options:\n", out);
fputs(" --help Show help.\n", out);
fputs(" -o OUTPUT.pdf Send output to filename instead of stdout.\n", out);
return (out == stdout ? 0 : 1);
}

View File

@ -476,7 +476,7 @@ pdfioContentDrawImage(
double width, // I - Width of image
double height) // I - Height of image
{
return (pdfioStreamPrintf(st, "q %.6f 0 0 %.6f %.6f %.6f cm/%s Do Q\n", width, height, x, y, name));
return (pdfioStreamPrintf(st, "q %.6f 0 0 %.6f %.6f %.6f cm%N Do Q\n", width, height, x, y, name));
}
@ -811,7 +811,7 @@ pdfioContentSetFillColorSpace(
pdfio_stream_t *st, // I - Stream
const char *name) // I - Color space name
{
return (pdfioStreamPrintf(st, "/%s cs\n", name));
return (pdfioStreamPrintf(st, "%N cs\n", name));
}
@ -961,7 +961,7 @@ pdfioContentSetStrokeColorSpace(
pdfio_stream_t *st, // I - Stream
const char *name) // I - Color space name
{
return (pdfioStreamPrintf(st, "/%s CS\n", name));
return (pdfioStreamPrintf(st, "%N CS\n", name));
}
@ -988,7 +988,7 @@ pdfioContentSetTextFont(
const char *name, // I - Font name
double size) // I - Font size
{
return (pdfioStreamPrintf(st, "/%s %.6f Tf\n", name, size));
return (pdfioStreamPrintf(st, "%N %.6f Tf\n", name, size));
}
@ -1833,8 +1833,6 @@ pdfioFileCreateFontObjFromFile(
{
if ((w1 = ttfGetWidth(font, (int)i)) == w0 && i < 65530)
{
size_t j; // Look-ahead
for (j = 1; j < 4; j ++)
{
if (ttfGetWidth(font, (int)(i + j)) != w0)

View File

@ -120,10 +120,10 @@ extern bool pdfioContentTextMoveLine(pdfio_stream_t *st, double tx, double ty)
extern bool pdfioContentTextMoveTo(pdfio_stream_t *st, double tx, double ty) _PDFIO_PUBLIC;
extern bool pdfioContentTextNewLine(pdfio_stream_t *st) _PDFIO_PUBLIC;
extern bool pdfioContentTextNewLineShow(pdfio_stream_t *st, double ws, double cs, bool unicode, const char *s) _PDFIO_PUBLIC;
extern bool pdfioContentTextNewLineShowf(pdfio_stream_t *st, double ws, double cs, bool unicode, const char *format, ...) _PDFIO_PUBLIC _PDFIO_FORMAT(5,6);
extern bool pdfioContentTextNewLineShowf(pdfio_stream_t *st, double ws, double cs, bool unicode, const char *format, ...) _PDFIO_PUBLIC;
extern bool pdfioContentTextNextLine(pdfio_stream_t *st) _PDFIO_PUBLIC;
extern bool pdfioContentTextShow(pdfio_stream_t *st, bool unicode, const char *s) _PDFIO_PUBLIC;
extern bool pdfioContentTextShowf(pdfio_stream_t *st, bool unicode, const char *format, ...) _PDFIO_PUBLIC _PDFIO_FORMAT(3,4);
extern bool pdfioContentTextShowf(pdfio_stream_t *st, bool unicode, const char *format, ...) _PDFIO_PUBLIC;
extern bool pdfioContentTextShowJustified(pdfio_stream_t *st, bool unicode, size_t num_fragments, const double *offsets, const char * const *fragments) _PDFIO_PUBLIC;
// Resource helpers...

View File

@ -1,7 +1,7 @@
//
// PDF dictionary functions for PDFio.
//
// Copyright © 2021-2024 by Michael R Sweet.
// Copyright © 2021-2025 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -469,7 +469,7 @@ pdfioDictGetString(pdfio_dict_t *dict, // I - Dictionary
*tempptr; // Pointer into temporary string
unsigned char *dataptr; // Pointer into the data string
if (!(value->value.binary.datalen & 1) && !memcmp(value->value.binary.data, "\377\376", 2))
if (!(value->value.binary.datalen & 1) && !memcmp(value->value.binary.data, "\376\377", 2))
{
// Copy UTF-16 BE
int ch; // Unicode character
@ -528,7 +528,7 @@ pdfioDictGetString(pdfio_dict_t *dict, // I - Dictionary
*tempptr = '\0';
}
else if (!(value->value.binary.datalen & 1) && !memcmp(value->value.binary.data, "\376\377", 2))
else if (!(value->value.binary.datalen & 1) && !memcmp(value->value.binary.data, "\377\376", 2))
{
// Copy UTF-16 LE
int ch; // Unicode character
@ -1168,7 +1168,7 @@ _pdfioDictWrite(pdfio_dict_t *dict, // I - Dictionary
// Write all of the key/value pairs...
for (i = dict->num_pairs, pair = dict->pairs; i > 0; i --, pair ++)
{
if (!_pdfioFilePrintf(pdf, "/%s", pair->key))
if (!_pdfioFilePrintf(pdf, "%N", pair->key))
return (false);
if (length && !strcmp(pair->key, "Length") && pair->value.type == PDFIO_VALTYPE_NUMBER && pair->value.value.number <= 0.0)

View File

@ -110,6 +110,8 @@ pdfioFileClose(pdfio_file_t *pdf) // I - PDF file
{
bool ret = true; // Return value
size_t i; // Looping var
_pdfio_strbuf_t *current, // Current string buffer
*next; // Next string buffer
// Range check input
@ -152,6 +154,12 @@ pdfioFileClose(pdfio_file_t *pdf) // I - PDF file
free(pdf->strings[i]);
free(pdf->strings);
for (current = pdf->strbuffers; current; current = next)
{
next = current->next;
free(current);
}
free(pdf);
return (ret);

View File

@ -307,7 +307,8 @@ pdfioObjGetLength(pdfio_obj_t *obj) // I - Object
if ((lenobj = pdfioDictGetObj(obj->value.value.dict, "Length")) == NULL)
{
_pdfioFileError(obj->pdf, "Unable to get length of stream.");
if (!_pdfioDictGetValue(obj->value.value.dict, "Length"))
_pdfioFileError(obj->pdf, "Unable to get length of stream.");
return (0);
}

View File

@ -94,6 +94,7 @@
//
# define PDFIO_MAX_DEPTH 32 // Maximum nesting depth for values
# define PDFIO_MAX_STRING 65536 // Maximum length of string
typedef void (*_pdfio_extfree_t)(void *);
// Extension data free function
@ -224,6 +225,14 @@ typedef struct _pdfio_objmap_s // PDF object map
size_t src_number; // Source object number
} _pdfio_objmap_t;
typedef struct _pdfio_strbuf_s // PDF string buffer
{
struct _pdfio_strbuf_s *next; // Next string buffer
bool bufused; // Is this string buffer being used?
char buffer[PDFIO_MAX_STRING + 32];
// String buffer
} _pdfio_strbuf_t;
struct _pdfio_file_s // PDF file structure
{
char *filename; // Filename
@ -283,6 +292,7 @@ struct _pdfio_file_s // PDF file structure
size_t num_strings, // Number of strings
alloc_strings; // Allocated strings
char **strings; // Nul-terminated strings
_pdfio_strbuf_t *strbuffers; // String buffers
};
struct _pdfio_obj_s // Object
@ -327,6 +337,7 @@ struct _pdfio_stream_s // Stream
// Functions...
//
extern size_t _pdfio_strlcpy(char *dst, const char *src, size_t dstsize) _PDFIO_INTERNAL;
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;
@ -367,13 +378,13 @@ extern bool _pdfioFileAddPage(pdfio_file_t *pdf, pdfio_obj_t *obj) _PDFIO_INTER
extern bool _pdfioFileConsume(pdfio_file_t *pdf, size_t bytes) _PDFIO_INTERNAL;
extern pdfio_obj_t *_pdfioFileCreateObj(pdfio_file_t *pdf, pdfio_file_t *srcpdf, _pdfio_value_t *value) _PDFIO_INTERNAL;
extern bool _pdfioFileDefaultError(pdfio_file_t *pdf, const char *message, void *data) _PDFIO_INTERNAL;
extern bool _pdfioFileError(pdfio_file_t *pdf, const char *format, ...) _PDFIO_FORMAT(2,3) _PDFIO_INTERNAL;
extern bool _pdfioFileError(pdfio_file_t *pdf, const char *format, ...) _PDFIO_INTERNAL;
extern pdfio_obj_t *_pdfioFileFindMappedObj(pdfio_file_t *pdf, pdfio_file_t *src_pdf, size_t src_number) _PDFIO_INTERNAL;
extern bool _pdfioFileFlush(pdfio_file_t *pdf) _PDFIO_INTERNAL;
extern int _pdfioFileGetChar(pdfio_file_t *pdf) _PDFIO_INTERNAL;
extern bool _pdfioFileGets(pdfio_file_t *pdf, char *buffer, size_t bufsize) _PDFIO_INTERNAL;
extern ssize_t _pdfioFilePeek(pdfio_file_t *pdf, void *buffer, size_t bytes) _PDFIO_INTERNAL;
extern bool _pdfioFilePrintf(pdfio_file_t *pdf, const char *format, ...) _PDFIO_FORMAT(2,3) _PDFIO_INTERNAL;
extern bool _pdfioFilePrintf(pdfio_file_t *pdf, const char *format, ...) _PDFIO_INTERNAL;
extern bool _pdfioFilePuts(pdfio_file_t *pdf, const char *s) _PDFIO_INTERNAL;
extern ssize_t _pdfioFileRead(pdfio_file_t *pdf, void *buffer, size_t bytes) _PDFIO_INTERNAL;
extern off_t _pdfioFileSeek(pdfio_file_t *pdf, off_t offset, int whence) _PDFIO_INTERNAL;
@ -389,6 +400,8 @@ extern bool _pdfioObjWriteHeader(pdfio_obj_t *obj) _PDFIO_INTERNAL;
extern pdfio_stream_t *_pdfioStreamCreate(pdfio_obj_t *obj, pdfio_obj_t *length_obj, size_t cbsize, pdfio_filter_t compression) _PDFIO_INTERNAL;
extern pdfio_stream_t *_pdfioStreamOpen(pdfio_obj_t *obj, bool decode) _PDFIO_INTERNAL;
extern char *_pdfioStringAllocBuffer(pdfio_file_t *pdf);
extern void _pdfioStringFreeBuffer(pdfio_file_t *pdf, char *buffer);
extern bool _pdfioStringIsAllocated(pdfio_file_t *pdf, const char *s) _PDFIO_INTERNAL;
extern void _pdfioTokenClear(_pdfio_token_t *tb) _PDFIO_INTERNAL;

View File

@ -439,7 +439,7 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
st->pdf = obj->pdf;
st->obj = obj;
if ((st->remaining = pdfioObjGetLength(obj)) == 0)
if ((st->remaining = pdfioObjGetLength(obj)) == 0 && !_pdfioDictGetValue(pdfioObjGetDict(obj), "Length"))
{
_pdfioFileError(obj->pdf, "No stream data.");
goto error;
@ -616,7 +616,7 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
else
{
// Something else we don't support
_pdfioFileError(st->pdf, "Unsupported stream filter '/%s'.", filter);
_pdfioFileError(st->pdf, "Unsupported stream filter '%N'.", filter);
goto error;
}
}
@ -689,6 +689,10 @@ pdfioStreamPeek(pdfio_stream_t *st, // I - Stream
//
// 'pdfioStreamPrintf()' - Write a formatted string to a stream.
//
// This function writes a formatted string to a stream. In addition to the
// standard `printf` format characters, you can use "%N" to format a PDF name
// value ("/Name") and "%S" to format a PDF string ("(String)") value.
//
bool // O - `true` on success, `false` on failure
pdfioStreamPrintf(

View File

@ -1,7 +1,7 @@
//
// PDF string functions for PDFio.
//
// Copyright © 2021-2024 by Michael R Sweet.
// Copyright © 2021-2025 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -17,6 +17,83 @@
static size_t find_string(pdfio_file_t *pdf, const char *s, int *rdiff);
//
// '_pdfio_strlcpy()' - Safe string copy.
//
size_t // O - Length of source string
_pdfio_strlcpy(char *dst, // I - Destination string buffer
const char *src, // I - Source string
size_t dstsize) // I - Size of destination
{
size_t srclen; // Length of source string
// Range check input...
if (!dst || !src || dstsize == 0)
{
if (dst)
*dst = '\0';
return (0);
}
// Figure out how much room is needed...
dstsize --;
srclen = strlen(src);
// Copy the appropriate amount...
if (srclen <= dstsize)
{
// Source string will fit...
memmove(dst, src, srclen);
dst[srclen] = '\0';
}
else
{
// Source string too big, copy what we can and clean up the end...
char *ptr = dst + dstsize - 1, // Pointer into string
*end = ptr + 1; // Pointer to end of string
memmove(dst, src, dstsize);
dst[dstsize] = '\0';
// Validate last character in destination buffer...
if (ptr > dst && *ptr & 0x80)
{
while ((*ptr & 0xc0) == 0x80 && ptr > dst)
ptr --;
if ((*ptr & 0xe0) == 0xc0)
{
// Verify 2-byte UTF-8 sequence...
if ((end - ptr) != 2)
*ptr = '\0';
}
else if ((*ptr & 0xf0) == 0xe0)
{
// Verify 3-byte UTF-8 sequence...
if ((end - ptr) != 3)
*ptr = '\0';
}
else if ((*ptr & 0xf8) == 0xf0)
{
// Verify 4-byte UTF-8 sequence...
if ((end - ptr) != 4)
*ptr = '\0';
}
else if (*ptr & 0x80)
{
// Invalid sequence at end...
*ptr = '\0';
}
}
}
return (srclen);
}
//
// '_pdfio_strtod()' - Convert a string to a double value.
//
@ -112,10 +189,9 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
// Loop through the format string, formatting as needed...
bufptr = buffer;
bufend = buffer + bufsize - 1;
*bufend = '\0';
bytes = 0;
bufptr = buffer;
bufend = buffer + bufsize - 1;
bytes = 0;
while (*format)
{
@ -178,14 +254,12 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
}
else
{
prec = 0;
while (isdigit(*format & 255))
{
if (tptr < (tformat + sizeof(tformat) - 1))
*tptr++ = *format;
prec = prec * 10 + *format++ - '0';
format ++;
}
}
}
@ -259,7 +333,7 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
if (bufptr < bufend)
{
strncpy(bufptr, temp, (size_t)(bufend - bufptr - 1));
_pdfio_strlcpy(bufptr, temp, (size_t)(bufend - bufptr + 1));
bufptr += strlen(bufptr);
}
break;
@ -289,7 +363,7 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
if (bufptr < bufend)
{
strncpy(bufptr, temp, (size_t)(bufend - bufptr - 1));
_pdfio_strlcpy(bufptr, temp, (size_t)(bufend - bufptr + 1));
bufptr += strlen(bufptr);
}
break;
@ -304,7 +378,7 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
if (bufptr < bufend)
{
strncpy(bufptr, temp, (size_t)(bufend - bufptr - 1));
_pdfio_strlcpy(bufptr, temp, (size_t)(bufend - bufptr + 1));
bufptr += strlen(bufptr);
}
break;
@ -329,19 +403,111 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
}
break;
case 's' : // String
case 'S' : // PDF string
if ((s = va_arg(ap, char *)) == NULL)
s = "(null)";
// PDF strings start with "("...
if (bufptr < bufend)
*bufptr++ = '(';
bytes ++;
// Loop through the literal string...
while (*s)
{
// Escape special characters
if (*s == '\\' || *s == '(' || *s == ')')
{
// Simple escape...
if (bufptr < bufend)
*bufptr++ = '\\';
if (bufptr < bufend)
*bufptr++ = *s;
bytes += 2;
}
else if (*s < ' ')
{
// Octal escape...
snprintf(bufptr, (size_t)(bufend - bufptr + 1), "\\%03o", *s & 255);
bufptr += strlen(bufptr);
bytes += 4;
}
else
{
// Literal character...
if (bufptr < bufend)
*bufptr++ = *s;
bytes ++;
}
s ++;
}
// PDF strings end with ")"...
if (bufptr < bufend)
*bufptr++ = ')';
bytes ++;
break;
case 's' : // Literal string
if ((s = va_arg(ap, char *)) == NULL)
s = "(null)";
if (width != 0)
{
// Format string to fit inside the specified width...
if ((size_t)(width + 1) > sizeof(temp))
break;
snprintf(temp, sizeof(temp), tformat, s);
s = temp;
}
bytes += strlen(s);
if (bufptr < bufend)
{
strncpy(bufptr, s, (size_t)(bufend - bufptr - 1));
_pdfio_strlcpy(bufptr, s, (size_t)(bufend - bufptr + 1));
bufptr += strlen(bufptr);
}
break;
case 'N' : // Output name string with proper escaping
if ((s = va_arg(ap, char *)) == NULL)
s = "(null)";
// PDF names start with "/"...
if (bufptr < bufend)
*bufptr++ = '/';
bytes ++;
// Loop through the name string...
while (*s)
{
if (*s < 0x21 || *s > 0x7e || *s == '#')
{
// Output #XX for character...
snprintf(bufptr, (size_t)(bufend - bufptr + 1), "#%02X", *s & 255);
bufptr += strlen(bufptr);
bytes += 3;
}
else
{
// Output literal character...
if (bufptr < bufend)
*bufptr++ = *s;
bytes ++;
}
s ++;
}
break;
case 'n' : // Output number of chars so far
*(va_arg(ap, int *)) = (int)bytes;
break;
@ -358,11 +524,7 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
}
// Nul-terminate the string and return the number of characters needed.
if (bufptr < bufend)
{
// Everything fit in the buffer...
*bufptr = '\0';
}
*bufptr = '\0';
PDFIO_DEBUG("_pdfio_vsnprintf: Returning %ld \"%s\"\n", (long)bytes, buffer);
@ -370,6 +532,41 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
}
//
// '_pdfioStringAllocBuffer()' - Allocate a string buffer.
//
char * // O - Buffer or `NULL` on error
_pdfioStringAllocBuffer(
pdfio_file_t *pdf) // I - PDF file
{
_pdfio_strbuf_t *current; // Current string buffer
// See if we have an available string buffer...
for (current = pdf->strbuffers; current; current = current->next)
{
if (!current->bufused)
{
current->bufused = true;
return (current->buffer);
}
}
// Didn't find one, allocate a new one...
if ((current = calloc(1, sizeof(_pdfio_strbuf_t))) == NULL)
return (NULL);
// Add to the linked list of string buffers...
current->next = pdf->strbuffers;
current->bufused = true;
pdf->strbuffers = current;
return (current->buffer);
}
//
// 'pdfioStringCreate()' - Create a durable literal string.
//
@ -480,6 +677,29 @@ pdfioStringCreatef(
}
//
// '_pdfioStringFreeBuffer()' - Free a string buffer.
//
void
_pdfioStringFreeBuffer(
pdfio_file_t *pdf, // I - PDF file
char *buffer) // I - String buffer
{
_pdfio_strbuf_t *current; // Current string buffer
for (current = pdf->strbuffers; current; current = current->next)
{
if (current->buffer == buffer)
{
current->bufused = false;
break;
}
}
}
//
// '_pdfioStringIsAllocated()' - Check whether a string has been allocated.
//

View File

@ -1,7 +1,7 @@
//
// PDF token parsing functions for PDFio.
//
// Copyright © 2021-2023 by Michael R Sweet.
// Copyright © 2021-2025 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -528,13 +528,6 @@ _pdfioTokenRead(_pdfio_token_t *tb, // I - Token buffer/stack
return (false);
}
}
if (bufptr == (buffer + 1))
{
_pdfioFileError(tb->pdf, "Empty name.");
*bufptr = '\0';
return (false);
}
break;
case '<' : // Potential hex string

View File

@ -1,7 +1,7 @@
//
// PDF value functions for PDFio.
//
// Copyright © 2021-2024 by Michael R Sweet.
// Copyright © 2021-2025 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -125,7 +125,7 @@ _pdfioValueDecrypt(pdfio_file_t *pdf, // I - PDF file
_pdfio_crypto_ctx_t ctx; // Decryption context
_pdfio_crypto_cb_t cb; // Decryption callback
size_t ivlen; // Number of initialization vector bytes
uint8_t temp[32768]; // Temporary buffer for decryption
uint8_t *temp = NULL; // Temporary buffer for decryption
size_t templen; // Number of actual data bytes
time_t timeval; // Date/time value
@ -152,11 +152,16 @@ _pdfioValueDecrypt(pdfio_file_t *pdf, // I - PDF file
case PDFIO_VALTYPE_BINARY :
// Decrypt the binary string...
if (v->value.binary.datalen > (sizeof(temp) - 32))
if (v->value.binary.datalen > PDFIO_MAX_STRING)
{
_pdfioFileError(pdf, "Unable to read encrypted binary string - too long.");
return (false);
}
else if ((temp = (uint8_t *)_pdfioStringAllocBuffer(pdf)) == NULL)
{
_pdfioFileError(pdf, "Unable to read encrypted binary string - out of memory.");
return (false);
}
ivlen = v->value.binary.datalen;
if ((cb = _pdfioCryptoMakeReader(pdf, obj, &ctx, v->value.binary.data, &ivlen)) == NULL)
@ -171,6 +176,8 @@ _pdfioValueDecrypt(pdfio_file_t *pdf, // I - PDF file
v->value.binary.datalen = templen - temp[templen - 1];
else
v->value.binary.datalen = templen;
_pdfioStringFreeBuffer(pdf, (char *)temp);
break;
case PDFIO_VALTYPE_STRING :
@ -300,7 +307,9 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file
_pdfio_value_t *v, // I - Value
size_t depth) // I - Depth of value
{
char token[32768]; // Token buffer
_pdfio_value_t *ret = NULL; // Return value
char *token = _pdfioStringAllocBuffer(pdf);
// Token buffer
time_t timeval; // Date/time value
#ifdef DEBUG
static const char * const valtypes[] =
@ -322,8 +331,11 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file
PDFIO_DEBUG("_pdfioValueRead(pdf=%p, obj=%p, v=%p)\n", pdf, obj, v);
if (!_pdfioTokenGet(tb, token, sizeof(token)))
return (NULL);
if (!token)
goto done;
if (!_pdfioTokenGet(tb, token, PDFIO_MAX_STRING))
goto done;
if (!strcmp(token, "["))
{
@ -331,12 +343,14 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file
if (depth >= PDFIO_MAX_DEPTH)
{
_pdfioFileError(pdf, "Too many nested arrays.");
return (NULL);
goto done;
}
v->type = PDFIO_VALTYPE_ARRAY;
if ((v->value.array = _pdfioArrayRead(pdf, obj, tb, depth + 1)) == NULL)
return (NULL);
goto done;
ret = v;
}
else if (!strcmp(token, "<<"))
{
@ -344,29 +358,34 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file
if (depth >= PDFIO_MAX_DEPTH)
{
_pdfioFileError(pdf, "Too many nested dictionaries.");
return (NULL);
goto done;
}
v->type = PDFIO_VALTYPE_DICT;
if ((v->value.dict = _pdfioDictRead(pdf, obj, tb, depth + 1)) == NULL)
return (NULL);
goto done;
ret = v;
}
else if (!strncmp(token, "(D:", 3) && (timeval = get_date_time(token + 1)) != 0)
else if ((timeval = get_date_time(token + 1)) != 0)
{
v->type = PDFIO_VALTYPE_DATE;
v->value.date = timeval;
ret = v;
}
else if (token[0] == '(')
{
// String
v->type = PDFIO_VALTYPE_STRING;
v->value.string = pdfioStringCreate(pdf, token + 1);
ret = v;
}
else if (token[0] == '/')
{
// Name
v->type = PDFIO_VALTYPE_NAME;
v->value.name = pdfioStringCreate(pdf, token + 1);
ret = v;
}
else if (token[0] == '<')
{
@ -379,7 +398,7 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file
if ((v->value.binary.data = (unsigned char *)malloc(v->value.binary.datalen)) == NULL)
{
_pdfioFileError(pdf, "Out of memory for hex string.");
return (NULL);
goto done;
}
// Convert hex to binary...
@ -406,6 +425,8 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file
*dataptr++ = (unsigned char)d;
}
ret = v;
}
else if (strchr("0123456789-+.", token[0]) != NULL)
{
@ -493,7 +514,8 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file
PDFIO_DEBUG("_pdfioValueRead: Returning indirect value %lu %u R.\n", (unsigned long)v->value.indirect.number, v->value.indirect.generation);
return (v);
ret = v;
goto done;
}
}
}
@ -501,27 +523,41 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file
// If we get here, we have a number...
v->type = PDFIO_VALTYPE_NUMBER;
v->value.number = _pdfio_strtod(pdf, token);
ret = v;
}
else if (!strcmp(token, "true") || !strcmp(token, "false"))
{
// Boolean value
v->type = PDFIO_VALTYPE_BOOLEAN;
v->value.boolean = !strcmp(token, "true");
ret = v;
}
else if (!strcmp(token, "null"))
{
// null value
v->type = PDFIO_VALTYPE_NULL;
ret = v;
}
else
{
_pdfioFileError(pdf, "Unexpected '%s' token seen.", token);
return (NULL);
}
PDFIO_DEBUG("_pdfioValueRead: Returning %s value.\n", valtypes[v->type]);
done:
return (v);
if (token)
_pdfioStringFreeBuffer(pdf, token);
if (ret)
{
PDFIO_DEBUG("_pdfioValueRead: Returning %s value.\n", valtypes[ret->type]);
return (ret);
}
else
{
PDFIO_DEBUG("_pdfioValueRead: Returning NULL.\n");
return (NULL);
}
}
@ -546,8 +582,10 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
case PDFIO_VALTYPE_BINARY :
{
size_t databytes; // Bytes to write
uint8_t temp[32768], // Temporary buffer for encryption
uint8_t *temp = NULL, // Temporary buffer for encryption
*dataptr; // Pointer into data
bool ret = false; // Return value
if (obj && pdf->encryption)
{
@ -556,11 +594,16 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
_pdfio_crypto_cb_t cb; // Encryption callback
size_t ivlen; // Number of initialization vector bytes
if (v->value.binary.datalen > (sizeof(temp) - 32))
if (v->value.binary.datalen > PDFIO_MAX_STRING)
{
_pdfioFileError(pdf, "Unable to write encrypted binary string - too long.");
return (false);
}
else if ((temp = (uint8_t *)_pdfioStringAllocBuffer(pdf)) == NULL)
{
_pdfioFileError(pdf, "Unable to write encrypted binary string - out of memory.");
return (false);
}
cb = _pdfioCryptoMakeWriter(pdf, obj, &ctx, temp, &ivlen);
databytes = (cb)(&ctx, temp + ivlen, v->value.binary.data, v->value.binary.datalen) + ivlen;
@ -573,18 +616,25 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
}
if (!_pdfioFilePuts(pdf, "<"))
return (false);
goto bindone;
for (; databytes > 1; databytes -= 2, dataptr += 2)
{
if (!_pdfioFilePrintf(pdf, "%02X%02X", dataptr[0], dataptr[1]))
return (false);
goto bindone;
}
if (databytes > 0)
return (_pdfioFilePrintf(pdf, "%02X>", dataptr[0]));
else
return (_pdfioFilePuts(pdf, ">"));
if (databytes > 0 && !_pdfioFilePrintf(pdf, "%02X", dataptr[0]))
goto bindone;
ret = _pdfioFilePuts(pdf, ">");
bindone:
if (temp)
_pdfioStringFreeBuffer(pdf, (char *)temp);
return (ret);
}
case PDFIO_VALTYPE_BOOLEAN :
@ -609,7 +659,7 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
if (obj && pdf->encryption)
{
// Write encrypted string...
uint8_t temp[32768], // Encrypted bytes
uint8_t temp[64], // Encrypted bytes
*tempptr; // Pointer into encrypted bytes
_pdfio_crypto_ctx_t ctx; // Encryption context
_pdfio_crypto_cb_t cb; // Encryption callback
@ -637,7 +687,7 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
}
else
{
return (_pdfioFilePrintf(pdf, "(%s)", datestr));
return (_pdfioFilePrintf(pdf, "%S", datestr));
}
}
@ -648,19 +698,19 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
return (_pdfioFilePrintf(pdf, " %lu %u R", (unsigned long)v->value.indirect.number, v->value.indirect.generation));
case PDFIO_VALTYPE_NAME :
return (_pdfioFilePrintf(pdf, "/%s", v->value.name));
return (_pdfioFilePrintf(pdf, "%N", v->value.name));
case PDFIO_VALTYPE_NULL :
return (_pdfioFilePuts(pdf, " null"));
case PDFIO_VALTYPE_NUMBER :
return (_pdfioFilePrintf(pdf, " %g", v->value.number));
return (_pdfioFilePrintf(pdf, " %.6f", v->value.number));
case PDFIO_VALTYPE_STRING :
if (obj && pdf->encryption)
{
// Write encrypted string...
uint8_t temp[32768], // Encrypted bytes
uint8_t *temp = NULL, // Encrypted bytes
*tempptr; // Pointer into encrypted bytes
_pdfio_crypto_ctx_t ctx; // Encryption context
_pdfio_crypto_cb_t cb; // Encryption callback
@ -668,74 +718,46 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
// Length of value
ivlen, // Number of initialization vector bytes
tempbytes; // Number of output bytes
bool ret = false; // Return value
if (len > (sizeof(temp) - 32))
if (len > PDFIO_MAX_STRING)
{
_pdfioFileError(pdf, "Unable to write encrypted string - too long.");
return (false);
}
else if ((temp = (uint8_t *)_pdfioStringAllocBuffer(pdf)) == NULL)
{
_pdfioFileError(pdf, "Unable to write encrypted string - out of memory.");
return (false);
}
cb = _pdfioCryptoMakeWriter(pdf, obj, &ctx, temp, &ivlen);
tempbytes = (cb)(&ctx, temp + ivlen, (const uint8_t *)v->value.string, len) + ivlen;
if (!_pdfioFilePuts(pdf, "<"))
return (false);
goto strdone;
for (tempptr = temp; tempbytes > 1; tempbytes -= 2, tempptr += 2)
{
if (!_pdfioFilePrintf(pdf, "%02X%02X", tempptr[0], tempptr[1]))
return (false);
goto strdone;
}
if (tempbytes > 0)
return (_pdfioFilePrintf(pdf, "%02X>", *tempptr));
else
return (_pdfioFilePuts(pdf, ">"));
if (tempbytes > 0 && !_pdfioFilePrintf(pdf, "%02X", *tempptr))
goto strdone;
ret = _pdfioFilePuts(pdf, ">");
strdone :
_pdfioStringFreeBuffer(pdf, (char *)temp);
return (ret);
}
else
{
// Write unencrypted string...
const char *start, // Start of fragment
*end; // End of fragment
if (!_pdfioFilePuts(pdf, "("))
return (false);
// Write a quoted string value...
for (start = v->value.string; *start; start = end)
{
// Find the next character that needs to be quoted...
for (end = start; *end; end ++)
{
if (*end == '\\' || *end == ')' || (*end & 255) < ' ')
break;
}
if (end > start)
{
// Write unquoted (safe) characters...
if (!_pdfioFileWrite(pdf, start, (size_t)(end - start)))
return (false);
}
if (*end)
{
// Quote this character...
bool success; // Did the write work?
if (*end == '\\' || *end == ')')
success = _pdfioFilePrintf(pdf, "\\%c", *end);
else
success = _pdfioFilePrintf(pdf, "\\%03o", *end);
if (!success)
return (false);
end ++;
}
}
return (_pdfioFilePuts(pdf, ")"));
return (_pdfioFilePrintf(pdf, "%S", v->value.string));
}
}
@ -747,31 +769,59 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
// 'get_date_time()' - Convert PDF date/time value to time_t.
//
static time_t // O - Time in seconds
static time_t // O - Time in seconds or `0` for none
get_date_time(const char *s) // I - PDF date/time value
{
int i; // Looping var
struct tm dateval; // Date value
int offset; // Date offset
int offset = 0; // Date offset in seconds
time_t t; // Time value
PDFIO_DEBUG("get_date_time(s=\"%s\")\n", s);
// Possible date value of the form:
//
// (D:YYYYMMDDhhmmssZ)
// (D:YYYYMMDDhhmmss+HH'mm)
// (D:YYYYMMDDhhmmss-HH'mm)
// D:YYYYMMDDhhmmssZ
// D:YYYYMMDDhhmmss+HH'mm
// D:YYYYMMDDhhmmss-HH'mm
//
if (strncmp(s, "D:", 2))
return (0);
for (i = 2; i < 16; i ++)
{
// Look for date/time digits...
if (!isdigit(s[i] & 255) || !s[i])
break;
}
if (i >= 16)
if (i < 6 || (i & 1))
{
// Short year or missing digit...
return (0);
}
memset(&dateval, 0, sizeof(dateval));
dateval.tm_year = (s[2] - '0') * 1000 + (s[3] - '0') * 100 + (s[4] - '0') * 10 + s[5] - '0' - 1900;
if (i > 6)
dateval.tm_mon = (s[6] - '0') * 10 + s[7] - '0' - 1;
if (i > 8)
dateval.tm_mday = (s[8] - '0') * 10 + s[9] - '0';
else
dateval.tm_mday = 1;
if (i > 10)
dateval.tm_hour = (s[10] - '0') * 10 + s[11] - '0';
if (i > 12)
dateval.tm_min = (s[12] - '0') * 10 + s[13] - '0';
if (i > 14)
dateval.tm_sec = (s[14] - '0') * 10 + s[15] - '0';
if (i >= 16 && s[i])
{
// Get zone info...
if (s[i] == 'Z')
{
// UTC...
@ -782,14 +832,20 @@ get_date_time(const char *s) // I - PDF date/time value
// Timezone offset from UTC...
if (isdigit(s[i + 1] & 255) && isdigit(s[i + 2] & 255) && s[i + 3] == '\'' && isdigit(s[i + 4] & 255) && isdigit(s[i + 5] & 255))
{
offset = (s[i + 1] - '0') * 36000 + (s[i + 2] - '0') * 3600 + (s[i + 4] - '0') * 600 + (s[i + 5] - '0') * 60;
if (s[i] == '-')
offset = -offset;
i += 6;
// Accept trailing quote, per PDF spec...
if (s[i] == '\'')
i ++;
}
}
else if (!s[i])
else
{
// Missing zone info, invalid date string...
// Random zone info, invalid date string...
return (0);
}
}
@ -800,26 +856,24 @@ get_date_time(const char *s) // I - PDF date/time value
return (0);
}
// Date value...
memset(&dateval, 0, sizeof(dateval));
// Convert date value to time_t...
#if _WIN32
if ((t = _mkgmtime(&dateval)) < 0)
return (0);
#elif defined(HAVE_TIMEGM)
if ((t = timegm(&dateval)) < 0)
return (0);
#else
if ((t = mktime(&dateval)) < 0)
return (0);
dateval.tm_year = (s[2] - '0') * 1000 + (s[3] - '0') * 100 + (s[4] - '0') * 10 + s[5] - '0' - 1900;
dateval.tm_mon = (s[6] - '0') * 10 + s[7] - '0' - 1;
dateval.tm_mday = (s[8] - '0') * 10 + s[9] - '0';
dateval.tm_hour = (s[10] - '0') * 10 + s[11] - '0';
dateval.tm_min = (s[12] - '0') * 10 + s[13] - '0';
dateval.tm_sec = (s[14] - '0') * 10 + s[15] - '0';
# if defined(HAVE_TM_GMTOFF)
localtime_r(&t, &dateval);
t -= dateval.tm_gmtoff;
# else
t -= timezone;
# endif // HAVE_TM_GMTOFF
#endif // _WIN32
if (s[16] == 'Z')
{
offset = 0;
}
else
{
offset = (s[17] - '0') * 600 + (s[18] - '0') * 60 + (s[19] - '0') * 10 + s[20] - '0';
if (s[16] == '-')
offset = -offset;
}
return (mktime(&dateval) + offset);
return (t + offset);
}

10
pdfio.h
View File

@ -23,7 +23,7 @@ extern "C" {
// Version numbers...
//
# define PDFIO_VERSION "1.5.0"
# define PDFIO_VERSION "1.5.2"
# define PDFIO_VERSION_MAJOR 1
# define PDFIO_VERSION_MINOR 5
@ -34,11 +34,9 @@ extern "C" {
# if defined(__has_extension) || defined(__GNUC__)
# define _PDFIO_PUBLIC __attribute__ ((visibility("default")))
# define _PDFIO_FORMAT(a,b) __attribute__ ((__format__(__printf__, a,b)))
# define _PDFIO_DEPRECATED __attribute__ ((deprecated)) _PDFIO_PUBLIC
# else
# define _PDFIO_PUBLIC
# define _PDFIO_FORMAT(a,b)
# define _PDFIO_DEPRECATED
# endif // __has_extension || __GNUC__
@ -183,7 +181,7 @@ extern bool pdfioDictSetNumber(pdfio_dict_t *dict, const char *key, double valu
extern bool pdfioDictSetObj(pdfio_dict_t *dict, const char *key, pdfio_obj_t *value) _PDFIO_PUBLIC;
extern bool pdfioDictSetRect(pdfio_dict_t *dict, const char *key, pdfio_rect_t *value) _PDFIO_PUBLIC;
extern bool pdfioDictSetString(pdfio_dict_t *dict, const char *key, const char *value) _PDFIO_PUBLIC;
extern bool pdfioDictSetStringf(pdfio_dict_t *dict, const char *key, const char *format, ...) _PDFIO_PUBLIC _PDFIO_FORMAT(3,4);
extern bool pdfioDictSetStringf(pdfio_dict_t *dict, const char *key, const char *format, ...) _PDFIO_PUBLIC;
extern bool pdfioFileClose(pdfio_file_t *pdf) _PDFIO_PUBLIC;
extern pdfio_file_t *pdfioFileCreate(const char *filename, const char *version, pdfio_rect_t *media_box, pdfio_rect_t *crop_box, pdfio_error_cb_t error_cb, void *error_data) _PDFIO_PUBLIC;
@ -245,14 +243,14 @@ extern bool pdfioStreamClose(pdfio_stream_t *st) _PDFIO_PUBLIC;
extern bool pdfioStreamConsume(pdfio_stream_t *st, size_t bytes) _PDFIO_PUBLIC;
extern bool pdfioStreamGetToken(pdfio_stream_t *st, char *buffer, size_t bufsize) _PDFIO_PUBLIC;
extern ssize_t pdfioStreamPeek(pdfio_stream_t *st, void *buffer, size_t bytes) _PDFIO_PUBLIC;
extern bool pdfioStreamPrintf(pdfio_stream_t *st, const char *format, ...) _PDFIO_PUBLIC _PDFIO_FORMAT(2,3);
extern bool pdfioStreamPrintf(pdfio_stream_t *st, const char *format, ...) _PDFIO_PUBLIC;
extern bool pdfioStreamPutChar(pdfio_stream_t *st, int ch) _PDFIO_PUBLIC;
extern bool pdfioStreamPuts(pdfio_stream_t *st, const char *s) _PDFIO_PUBLIC;
extern ssize_t pdfioStreamRead(pdfio_stream_t *st, void *buffer, size_t bytes) _PDFIO_PUBLIC;
extern bool pdfioStreamWrite(pdfio_stream_t *st, const void *buffer, size_t bytes) _PDFIO_PUBLIC;
extern char *pdfioStringCreate(pdfio_file_t *pdf, const char *s) _PDFIO_PUBLIC;
extern char *pdfioStringCreatef(pdfio_file_t *pdf, const char *format, ...) _PDFIO_FORMAT(2,3) _PDFIO_PUBLIC;
extern char *pdfioStringCreatef(pdfio_file_t *pdf, const char *format, ...) _PDFIO_PUBLIC;
# ifdef __cplusplus

View File

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

View File

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