Compare commits

..

No commits in common. "master" and "v1.4.1" have entirely different histories.

124 changed files with 1406 additions and 5421 deletions

View File

@ -17,7 +17,7 @@ jobs:
- name: Update Build Environment - name: Update Build Environment
run: sudo apt-get update --fix-missing -y run: sudo apt-get update --fix-missing -y
- name: Install Prerequisites - name: Install Prerequisites
run: sudo apt-get install -y cppcheck zlib1g-dev libpng-dev run: sudo apt-get install -y cppcheck zlib1g-dev
- name: Configure PDFio - name: Configure PDFio
run: ./configure --enable-debug --enable-sanitizer --enable-maintainer run: ./configure --enable-debug --enable-sanitizer --enable-maintainer
- name: Build PDFio - name: Build PDFio

View File

@ -32,7 +32,7 @@ jobs:
run: sudo apt-get update --fix-missing -y run: sudo apt-get update --fix-missing -y
- name: Install Prerequisites - name: Install Prerequisites
run: sudo apt-get install -y zlib1g-dev libpng-dev run: sudo apt-get install -y zlib1g-dev
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v2

View File

@ -12,7 +12,7 @@ jobs:
- name: Update Build Environment - name: Update Build Environment
run: sudo apt-get update --fix-missing -y run: sudo apt-get update --fix-missing -y
- name: Install Prerequisites - name: Install Prerequisites
run: sudo apt-get install -y zlib1g-dev libpng-dev run: sudo apt-get install -y zlib1g-dev
- name: Download Coverity Build Tool - name: Download Coverity Build Tool
run: | run: |
wget -q https://scan.coverity.com/download/linux64 --post-data token="$TOKEN&project=$GITHUB_REPOSITORY" -O cov-analysis-linux64.tar.gz wget -q https://scan.coverity.com/download/linux64 --post-data token="$TOKEN&project=$GITHUB_REPOSITORY" -O cov-analysis-linux64.tar.gz

1
.gitignore vendored
View File

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

View File

@ -1,48 +1,6 @@
Changes in PDFio Changes in PDFio
================ ================
v1.5.2 - YYYY-MM-DD
-------------------
- Updated maximum allowed PDF string size to 64k (Issue #117)
- Updated dictionary reading code to discard duplicate key/value pairs with a
warning message (Issue #118)
- 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
-------------------
- Added support for embedded color profiles in JPEG images (Issue #7)
- Added `pdfioFileCreateICCObjFromData` API.
- Added support for writing cross-reference streams for PDF 1.5 and newer files
(Issue #10)
- Added `pdfioFileGetModDate()` API (Issue #88)
- Added support for using libpng to embed PNG images in PDF output (Issue #90)
- Added support for writing the PCLm subset of PDF (Issue #99)
- Now support opening damaged PDF files (Issue #45)
- Updated documentation (Issue #95)
- Updated the pdf2txt example to support font encodings.
- Fixed potential heap/integer overflow issues in the TrueType cmap code.
- Fixed an output issue for extremely small `double` values with the
`pdfioContent` APIs.
- Fixed a missing Widths array issue for embedded TrueType fonts.
- Fixed some Unicode font embedding issues.
v1.4.1 - 2025-01-24 v1.4.1 - 2025-01-24
------------------- -------------------

View File

@ -15,7 +15,7 @@
.SILENT: .SILENT:
# Version numbers... # Version number...
PDFIO_VERSION = @PDFIO_VERSION@ PDFIO_VERSION = @PDFIO_VERSION@
PDFIO_VERSION_MAJOR = @PDFIO_VERSION_MAJOR@ PDFIO_VERSION_MAJOR = @PDFIO_VERSION_MAJOR@
PDFIO_VERSION_MINOR = @PDFIO_VERSION_MINOR@ PDFIO_VERSION_MINOR = @PDFIO_VERSION_MINOR@

256
configure vendored
View File

@ -1,6 +1,6 @@
#! /bin/sh #! /bin/sh
# Guess values for system-dependent variables and create Makefiles. # Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for pdfio 1.5.2. # Generated by GNU Autoconf 2.71 for pdfio 1.4.1.
# #
# Report bugs to <https://github.com/michaelrsweet/pdfio/issues>. # Report bugs to <https://github.com/michaelrsweet/pdfio/issues>.
# #
@ -610,8 +610,8 @@ MAKEFLAGS=
# Identity of this package. # Identity of this package.
PACKAGE_NAME='pdfio' PACKAGE_NAME='pdfio'
PACKAGE_TARNAME='pdfio' PACKAGE_TARNAME='pdfio'
PACKAGE_VERSION='1.5.2' PACKAGE_VERSION='1.4.1'
PACKAGE_STRING='pdfio 1.5.2' PACKAGE_STRING='pdfio 1.4.1'
PACKAGE_BUGREPORT='https://github.com/michaelrsweet/pdfio/issues' PACKAGE_BUGREPORT='https://github.com/michaelrsweet/pdfio/issues'
PACKAGE_URL='https://www.msweet.org/pdfio' PACKAGE_URL='https://www.msweet.org/pdfio'
@ -653,7 +653,6 @@ WARNINGS
CSFLAGS CSFLAGS
LIBPDFIO_STATIC LIBPDFIO_STATIC
LIBPDFIO LIBPDFIO
PKGCONFIG_LIBPNG
PKGCONFIG_REQUIRES PKGCONFIG_REQUIRES
PKGCONFIG_LIBS_PRIVATE PKGCONFIG_LIBS_PRIVATE
PKGCONFIG_LIBS PKGCONFIG_LIBS
@ -730,7 +729,6 @@ SHELL'
ac_subst_files='' ac_subst_files=''
ac_user_opts=' ac_user_opts='
enable_option_checking enable_option_checking
enable_libpng
enable_static enable_static
enable_shared enable_shared
enable_debug enable_debug
@ -1295,7 +1293,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing. # 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. # This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF cat <<_ACEOF
\`configure' configures pdfio 1.5.2 to adapt to many kinds of systems. \`configure' configures pdfio 1.4.1 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]... Usage: $0 [OPTION]... [VAR=VALUE]...
@ -1361,7 +1359,7 @@ fi
if test -n "$ac_init_help"; then if test -n "$ac_init_help"; then
case $ac_init_help in case $ac_init_help in
short | recursive ) echo "Configuration of pdfio 1.5.2:";; short | recursive ) echo "Configuration of pdfio 1.4.1:";;
esac esac
cat <<\_ACEOF cat <<\_ACEOF
@ -1369,8 +1367,6 @@ Optional Features:
--disable-option-checking ignore unrecognized --enable/--with options --disable-option-checking ignore unrecognized --enable/--with options
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
--enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
--enable-libpng use libpng for pdfioFileCreateImageObjFromFile,
default=auto
--disable-static do not install static library --disable-static do not install static library
--enable-shared install shared library --enable-shared install shared library
--enable-debug turn on debugging, default=no --enable-debug turn on debugging, default=no
@ -1460,7 +1456,7 @@ fi
test -n "$ac_init_help" && exit $ac_status test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then if $ac_init_version; then
cat <<\_ACEOF cat <<\_ACEOF
pdfio configure 1.5.2 pdfio configure 1.4.1
generated by GNU Autoconf 2.71 generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc. Copyright (C) 2021 Free Software Foundation, Inc.
@ -1513,6 +1509,39 @@ fi
} # ac_fn_c_try_compile } # 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 # ac_fn_c_try_link LINENO
# ----------------------- # -----------------------
# Try to link conftest.$ac_ext, and return whether this succeeded. # Try to link conftest.$ac_ext, and return whether this succeeded.
@ -1559,101 +1588,6 @@ fi
as_fn_set_status $ac_retval as_fn_set_status $ac_retval
} # ac_fn_c_try_link } # 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= ac_configure_args_raw=
for ac_arg for ac_arg
do do
@ -1678,7 +1612,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake. running configure, to aid debugging if configure makes a mistake.
It was created by pdfio $as_me 1.5.2, which was It was created by pdfio $as_me 1.4.1, which was
generated by GNU Autoconf 2.71. Invocation command line was generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw $ $0$ac_configure_args_raw
@ -2434,9 +2368,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
PDFIO_VERSION="1.5.2" PDFIO_VERSION="1.4.1"
PDFIO_VERSION_MAJOR="`echo 1.5.2 | awk -F. '{print $1}'`" PDFIO_VERSION_MAJOR="`echo 1.4.1 | awk -F. '{print $1}'`"
PDFIO_VERSION_MINOR="`echo 1.5.2 | awk -F. '{printf("%d\n",$2);}'`" PDFIO_VERSION_MINOR="`echo 1.4.1 | awk -F. '{printf("%d\n",$2);}'`"
@ -3939,56 +3873,6 @@ INSTALL="$(pwd)/install-sh"
printf "%s\n" "using $INSTALL" >&6; } 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 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. # 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 set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
@ -4110,6 +3994,7 @@ PKGCONFIG_REQUIRES="zlib"
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for zlib via pkg-config" >&5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for zlib via pkg-config" >&5
printf %s "checking for zlib via pkg-config... " >&6; } printf %s "checking for zlib via pkg-config... " >&6; }
ac_header= ac_cache= ac_header= ac_cache=
for ac_item in $ac_header_c_list for ac_item in $ac_header_c_list
do do
@ -4214,55 +4099,6 @@ fi
fi fi
# Check whether --enable-libpng was given.
if test ${enable_libpng+y}
then :
enableval=$enable_libpng;
fi
PKGCONFIG_LIBPNG=""
if test "x$PKGCONFIG" != x -a x$enable_libpng != xno
then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libpng-1.6.x" >&5
printf %s "checking for libpng-1.6.x... " >&6; }
if $PKGCONFIG --exists libpng16
then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
printf "%s\n" "yes" >&6; };
printf "%s\n" "#define HAVE_LIBPNG 1" >>confdefs.h
CPPFLAGS="$($PKGCONFIG --cflags libpng16) -DHAVE_LIBPNG=1 $CPPFLAGS"
LIBS="$($PKGCONFIG --libs libpng16) -lz $LIBS"
PKGCONFIG_LIBS_PRIVATE="$($PKGCONFIG --libs libpng16) $PKGCONFIG_LIBS_PRIVATE"
PKGCONFIG_REQUIRES="libpng >= 1.6,$PKGCONFIG_REQUIRES"
else $as_nop
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
printf "%s\n" "no" >&6; };
if test x$enable_libpng = xyes
then :
as_fn_error $? "libpng-dev 1.6 or later required for --enable-libpng." "$LINENO" 5
fi
fi
elif test x$enable_libpng = xyes
then :
as_fn_error $? "libpng-dev 1.6 or later required for --enable-libpng." "$LINENO" 5
fi
# Check whether --enable-static was given. # Check whether --enable-static was given.
if test ${enable_static+y} if test ${enable_static+y}
then : then :
@ -5099,7 +4935,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their # report actual input values of CONFIG_FILES etc. instead of their
# values after options handling. # values after options handling.
ac_log=" ac_log="
This file was extended by pdfio $as_me 1.5.2, which was This file was extended by pdfio $as_me 1.4.1, which was
generated by GNU Autoconf 2.71. Invocation command line was generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES CONFIG_FILES = $CONFIG_FILES
@ -5155,7 +4991,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped' ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\ ac_cs_version="\\
pdfio config.status 1.5.2 pdfio config.status 1.4.1
configured by $0, generated by GNU Autoconf 2.71, configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\" with options \\"\$ac_cs_config\\"

View File

@ -1,7 +1,7 @@
dnl dnl
dnl Configuration script for PDFio dnl Configuration script for PDFio
dnl dnl
dnl Copyright © 2023-2025 by Michael R Sweet dnl Copyright © 2023-2024 by Michael R Sweet
dnl dnl
dnl Licensed under Apache License v2.0. See the file "LICENSE" for more dnl Licensed under Apache License v2.0. See the file "LICENSE" for more
dnl information. dnl information.
@ -21,7 +21,7 @@ AC_PREREQ([2.70])
dnl Package name and version... dnl Package name and version...
AC_INIT([pdfio], [1.5.2], [https://github.com/michaelrsweet/pdfio/issues], [pdfio], [https://www.msweet.org/pdfio]) AC_INIT([pdfio], [1.4.1], [https://github.com/michaelrsweet/pdfio/issues], [pdfio], [https://www.msweet.org/pdfio])
PDFIO_VERSION="AC_PACKAGE_VERSION" PDFIO_VERSION="AC_PACKAGE_VERSION"
PDFIO_VERSION_MAJOR="`echo AC_PACKAGE_VERSION | awk -F. '{print $1}'`" PDFIO_VERSION_MAJOR="`echo AC_PACKAGE_VERSION | awk -F. '{print $1}'`"
@ -88,27 +88,6 @@ AC_SUBST([INSTALL])
AC_MSG_RESULT([using $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... dnl Check for pkg-config, which is used for some other tests later on...
AC_PATH_TOOL([PKGCONFIG], [pkg-config]) AC_PATH_TOOL([PKGCONFIG], [pkg-config])
@ -142,32 +121,6 @@ AS_IF([$PKGCONFIG --exists zlib], [
]) ])
dnl libpng...
AC_ARG_ENABLE([libpng], AS_HELP_STRING([--enable-libpng], [use libpng for pdfioFileCreateImageObjFromFile, default=auto]))
PKGCONFIG_LIBPNG=""
AC_SUBST([PKGCONFIG_LIBPNG])
AS_IF([test "x$PKGCONFIG" != x -a x$enable_libpng != xno], [
AC_MSG_CHECKING([for libpng-1.6.x])
AS_IF([$PKGCONFIG --exists libpng16], [
AC_MSG_RESULT([yes]);
AC_DEFINE([HAVE_LIBPNG], 1, [Have PNG library?])
CPPFLAGS="$($PKGCONFIG --cflags libpng16) -DHAVE_LIBPNG=1 $CPPFLAGS"
LIBS="$($PKGCONFIG --libs libpng16) -lz $LIBS"
PKGCONFIG_LIBS_PRIVATE="$($PKGCONFIG --libs libpng16) $PKGCONFIG_LIBS_PRIVATE"
PKGCONFIG_REQUIRES="libpng >= 1.6,$PKGCONFIG_REQUIRES"
], [
AC_MSG_RESULT([no]);
AS_IF([test x$enable_libpng = xyes], [
AC_MSG_ERROR([libpng-dev 1.6 or later required for --enable-libpng.])
])
])
], [test x$enable_libpng = xyes], [
AC_MSG_ERROR([libpng-dev 1.6 or later required for --enable-libpng.])
])
dnl Library target... dnl Library target...
AC_ARG_ENABLE([static], AS_HELP_STRING([--disable-static], [do not install static library])) AC_ARG_ENABLE([static], AS_HELP_STRING([--disable-static], [do not install static library]))
AC_ARG_ENABLE([shared], AS_HELP_STRING([--enable-shared], [install shared library])) AC_ARG_ENABLE([shared], AS_HELP_STRING([--enable-shared], [install shared library]))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,4 +1,4 @@
.TH pdfio 3 "pdf read/write library" "2025-04-04" "pdf read/write library" .TH pdfio 3 "pdf read/write library" "2025-01-24" "pdf read/write library"
.SH NAME .SH NAME
pdfio \- pdf read/write library pdfio \- pdf read/write library
.SH Introduction .SH Introduction
@ -34,7 +34,7 @@ PDFio is
.I not .I not
concerned with rendering or viewing a PDF file, although a PDF RIP or viewer could be written using it. concerned with rendering or viewing a PDF file, although a PDF RIP or viewer could be written using it.
.PP .PP
PDFio is Copyright \[co] 2021\-2025 by Michael R Sweet and is licensed under the Apache License Version 2.0 with an (optional) exception to allow linking against GPL2/LGPL2 software. See the files "LICENSE" and "NOTICE" for more information. PDFio is Copyright \[co] 2021\-2024 by Michael R Sweet and is licensed under the Apache License Version 2.0 with an (optional) exception to allow linking against GPL2/LGPL2 software. See the files "LICENSE" and "NOTICE" for more information.
.SS Requirements .SS Requirements
.PP .PP
PDFio requires the following to build the software: PDFio requires the following to build the software:
@ -52,11 +52,9 @@ A POSIX\-compliant sh program
.IP \(bu 5 .IP \(bu 5
.PP .PP
ZLIB (https://www.zlib.net/) 1.0 or higher ZLIB (https://www.zlib.net) 1.0 or higher
.PP
PDFio will also use libpng 1.6 or higher (https://www.libpng.org/) to provide enhanced PNG image support.
.PP .PP
IDE files for Xcode (macOS/iOS) and Visual Studio (Windows) are also provided. IDE files for Xcode (macOS/iOS) and Visual Studio (Windows) are also provided.
.SS Installing PDFio .SS Installing PDFio
@ -1047,26 +1045,11 @@ The pdfioinfo.c example program opens a PDF file and prints the title, author, c
{ {
const char *filename; // PDF filename const char *filename; // PDF filename
pdfio_file_t *pdf; // PDF file pdfio_file_t *pdf; // PDF file
pdfio_dict_t *catalog; // Catalog dictionary const char *author; // Author name
const char *author, // Author name time_t creation_date; // Creation date
*creator, // Creator name struct tm *creation_tm; // Creation date/time information
*producer, // Producer name char creation_text[256]; // Creation date/time as a string
*title; // Title const char *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... // Get the filename from the command\-line...
@ -1079,20 +1062,14 @@ The pdfioinfo.c example program opens a PDF file and prints the title, author, c
filename = argv[1]; filename = argv[1];
// Open the PDF file with the default callbacks... // Open the PDF file with the default callbacks...
pdf = pdfioFileOpen(filename, /*password_cb*/NULL, pdf = pdfioFileOpen(filename, /*password_cb*/NULL, /*password_cbdata*/NULL,
/*password_cbdata*/NULL, /*error_cb*/NULL, /*error_cb*/NULL, /*error_cbdata*/NULL);
/*error_cbdata*/NULL);
if (pdf == NULL) if (pdf == NULL)
return (1); return (1);
// Get the title, author, etc... // Get the title and author...
catalog = pdfioFileGetCatalog(pdf); author = pdfioFileGetAuthor(pdf);
author = pdfioFileGetAuthor(pdf); title = pdfioFileGetTitle(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... // Get the creation date and convert to a string...
if ((creation_date = pdfioFileGetCreationDate(pdf)) > 0) if ((creation_date = pdfioFileGetCreationDate(pdf)) > 0)
@ -1105,76 +1082,12 @@ The pdfioinfo.c example program opens a PDF file and prints the title, author, c
snprintf(creation_text, sizeof(creation_text), "\-\- not set \-\-"); 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... // Print file information to stdout...
printf("%s:\\n", filename); printf("%s:\\n", filename);
printf(" Title: %s\\n", title ? title : "\-\- not set \-\-"); printf(" Title: %s\\n", title ? title : "\-\- not set \-\-");
printf(" Author: %s\\n", author ? author : "\-\- not set \-\-"); printf(" Author: %s\\n", author ? author : "\-\- not set \-\-");
printf(" Creator: %s\\n", creator ? creator : "\-\- not set \-\-"); printf(" Created On: %s\\n", creation_text);
printf(" Producer: %s\\n", producer ? producer : "\-\- not set \-\-"); printf(" Number Pages: %u\\n", (unsigned)pdfioFileGetNumPages(pdf));
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... // Close the PDF file...
pdfioFileClose(pdf); pdfioFileClose(pdf);
@ -1184,83 +1097,28 @@ The pdfioinfo.c example program opens a PDF file and prints the title, author, c
.fi .fi
.SS Extract Text from PDF File .SS Extract Text from PDF File
.PP .PP
The pdf2text.c example code extracts text from a PDF file and writes it to the standard output. Unlike some other PDF tools, it outputs the text in the order it is seen in each page stream so the output might appear "jumbled" if the PDF producer doesn't output text in reading order. The code is able to handle different font encodings and produces UTF\-8 output. The pdf2text.c example code extracts non\-Unicode text from a PDF file by scanning each page for strings and text drawing commands. Since it doesn't look at the font encoding or support Unicode text, it is really only useful to extract plain ASCII text from a PDF file. And since it writes text in the order it appears in the page stream, it may not come out in the same order as appears on the page.
.PP .PP
The pdfioStreamGetToken function is used to read individual tokens from the page streams: The pdfioStreamGetToken function is used to read individual tokens from the page streams. Tokens starting with the open parenthesis are text strings, while PDF operators are left as\-is. We use some simple logic to make sure that we include spaces between text strings and add newlines for the text operators that start a new line in a text block:
.nf .nf
pdfio_stream_t *st; // Page stream pdfio_stream_t *st; // Page stream
char buffer[1024], // Token buffer
*bufptr, // Pointer into buffer
name[256]; // Current (font) name
bool first = true; // First string on line? bool first = true; // First string on line?
int encoding[256]; // Font encoding to Unicode char buffer[1024]; // Token buffer
bool in_array = false; // Are we in an array?
// Read PDF tokens from the page stream... // Read PDF tokens from the page stream...
while (pdfioStreamGetToken(st, buffer, sizeof(buffer))) while (pdfioStreamGetToken(st, buffer, sizeof(buffer)))
{ {
.fi if (buffer[0] == '(')
.PP
Justified text can be found inside arrays ("[ ... ]"), so we look for the array delimiter tokens and any (spacing) numbers inside an array. Experimentation has shown that numbers greater than 100 can be treated as whitespace:
.nf
if (!strcmp(buffer, "["))
{
// Start of an array for justified text...
in_array = true;
}
else if (!strcmp(buffer, "]"))
{
// End of an array for justified text...
in_array = false;
}
else if (!first && in_array && (isdigit(buffer[0]) || buffer[0] == '\-') && fabs(atof(buffer)) > 100)
{
// Whitespace in a justified text block...
putchar(' ');
}
.fi
.PP
Tokens starting with \'(' or \'<' are text fragments. 8\-bit text starting with \'(' needs to be mapped to Unicode using the current font encoding while hex strings starting with \'<' are UTF\-16 (Unicode) that need to be converted to UTF\-8:
.nf
else if (buffer[0] == '(')
{ {
// Text string using an 8\-bit encoding // Text string using an 8\-bit encoding
first = false; if (first)
first = false;
else if (buffer[1] != ' ')
putchar(' ');
for (bufptr = buffer + 1; *bufptr; bufptr ++) fputs(buffer + 1, stdout);
put_utf8(encoding[*bufptr & 255]);
} }
else if (buffer[0] == '<')
{
// Unicode text string
first = false;
puts_utf16(buffer + 1);
}
.fi
.PP
Simple (8\-bit) fonts include an encoding table that maps the 8\-bit characters to one of 1051 Unicode glyph names. Since each font can use a different encoding, we look for font names starting with \'/' and the "Tf" (set text font) operator token and load that font's encoding using the load_encoding function:
.nf
else if (buffer[0] == '/')
{
// Save name...
strncpy(name, buffer + 1, sizeof(name) \- 1);
name[sizeof(name) \- 1] = '\\0';
}
else if (!strcmp(buffer, "Tf") && name[0])
{
// Set font...
load_encoding(obj, name, encoding);
}
.fi
.PP
Finally, some text operators start a new line in a text block, so when we see their tokens we output a newline:
.nf
else if (!strcmp(buffer, "Td") || !strcmp(buffer, "TD") || !strcmp(buffer, "T*") || else if (!strcmp(buffer, "Td") || !strcmp(buffer, "TD") || !strcmp(buffer, "T*") ||
!strcmp(buffer, "\\'") || !strcmp(buffer, "\\"")) !strcmp(buffer, "\\'") || !strcmp(buffer, "\\""))
{ {
@ -1269,150 +1127,9 @@ Finally, some text operators start a new line in a text block, so when we see th
first = true; first = true;
} }
} }
.fi
.PP
The load_encoding Function
.PP
The load_encoding function looks up the named font in the page's "Resources" dictionary. Every PDF simple font contains an "Encoding" dictionary with a base encoding ("WinANSI", "MacRoman", or "MacExpert") and a differences array that lists character indexes and glyph names for an 8\-bit font.
.PP
We start by initializing the encoding array to the default WinANSI encoding and looking up the font object for the named font:
.nf
static void
load_encoding(
pdfio_obj_t *page_obj, // I \- Page object
const char *name, // I \- Font name
int encoding[256]) // O \- Encoding table
{
size_t i, j; // Looping vars
pdfio_dict_t *page_dict, // Page dictionary
*resources_dict, // Resources dictionary
*font_dict; // Font dictionary
pdfio_obj_t *font_obj, // Font object
*encoding_obj; // Encoding object
static int win_ansi[32] = // WinANSI characters from 128 to 159
{
...
};
static int mac_roman[128] = // MacRoman characters from 128 to 255
{
...
};
if (!first)
// Initialize the encoding to be the "standard" WinAnsi... putchar('\\n');
for (i = 0; i < 128; i ++)
encoding[i] = i;
for (i = 160; i < 256; i ++)
encoding[i] = i;
memcpy(encoding + 128, win_ansi, sizeof(win_ansi));
// Find the named font...
if ((page_dict = pdfioObjGetDict(page_obj)) == NULL)
return;
if ((resources_dict = pdfioDictGetDict(page_dict, "Resources")) == NULL)
return;
if ((font_dict = pdfioDictGetDict(resources_dict, "Font")) == NULL)
{
// Font resources not a dictionary, see if it is an object...
if ((font_obj = pdfioDictGetObj(resources_dict, "Font")) != NULL)
font_dict = pdfioObjGetDict(font_obj);
if (!font_dict)
return;
}
if ((font_obj = pdfioDictGetObj(font_dict, name)) == NULL)
return;
.fi
.PP
Once we have found the font we see if it has an "Encoding" dictionary:
.nf
pdfio_dict_t *encoding_dict; // Encoding dictionary
if ((encoding_obj = pdfioDictGetObj(pdfioObjGetDict(font_obj), "Encoding")) == NULL)
return;
if ((encoding_dict = pdfioObjGetDict(encoding_obj)) == NULL)
return;
.fi
.PP
Once we have the encoding dictionary we can get the "BaseEncoding" and "Differences" values:
.nf
const char *base_encoding; // BaseEncoding name
pdfio_array_t *differences; // Differences array
// OK, have the encoding object, build the encoding using it...
base_encoding = pdfioDictGetName(encoding_dict, "BaseEncoding");
differences = pdfioDictGetArray(encoding_dict, "Differences");
.fi
.PP
If the base encoding is "MacRomainEncoding", we need to reset the upper 128 characters in the encoding array match it:
.nf
if (base_encoding && !strcmp(base_encoding, "MacRomanEncoding"))
{
// Map upper 128
memcpy(encoding + 128, mac_roman, sizeof(mac_roman));
}
.fi
.PP
Then we loop through the differences array, keeping track of the current index within the encoding array. A number indicates a new index while a name is the Unicode glyph for the current index:
.nf
typedef struct name_map_s
{
const char *name; // Character name
int unicode; // Unicode value
} name_map_t;
static name_map_t unicode_map[1051]; // List of glyph names
if (differences)
{
// Apply differences
size_t count = pdfioArrayGetSize(differences);
// Number of differences
const char *name; // Character name
size_t idx = 0; // Index in encoding array
for (i = 0; i < count; i ++)
{
switch (pdfioArrayGetType(differences, i))
{
case PDFIO_VALTYPE_NUMBER :
// Get the index of the next character...
idx = (size_t)pdfioArrayGetNumber(differences, i);
break;
case PDFIO_VALTYPE_NAME :
// Lookup name and apply to encoding...
if (idx < 0 || idx > 255)
break;
name = pdfioArrayGetName(differences, i);
for (j = 0; j < (sizeof(unicode_map) / sizeof(unicode_map[0])); j ++)
{
if (!strcmp(name, unicode_map[j].name))
{
encoding[idx] = unicode_map[j].unicode;
break;
}
}
idx ++;
break;
default :
// Do nothing for other values
break;
}
}
}
}
.fi .fi
.SS Create a PDF File With Text and an Image .SS Create a PDF File With Text and an Image
.PP .PP
@ -3844,8 +3561,7 @@ This function creates a new PDF file. The "filename" argument specifies the
name of the PDF file to create. name of the PDF file to create.
.PP .PP
The "version" argument specifies the PDF version number for the file or The "version" argument specifies the PDF version number for the file or
\fBNULL\fR for the default ("2.0"). The value "PCLm-1.0" can be specified to \fBNULL\fR for the default ("2.0").
produce the PCLm subset of PDF.
.PP .PP
The "media_box" and "crop_box" arguments specify the default MediaBox and The "media_box" and "crop_box" arguments specify the default MediaBox and
CropBox for pages in the PDF file - if \fBNULL\fR then a default "Universal" size CropBox for pages in the PDF file - if \fBNULL\fR then a default "Universal" size
@ -3927,19 +3643,8 @@ This function embeds a TrueType/OpenType font into a PDF file. The
characters (potentially full Unicode, but more typically a subset) characters (potentially full Unicode, but more typically a subset)
or to only support the Windows CP1252 (ISO-8859-1 with additional or to only support the Windows CP1252 (ISO-8859-1 with additional
characters such as the Euro symbol) subset of Unicode. characters such as the Euro symbol) subset of Unicode.
.SS pdfioFileCreateICCObjFromData
Add ICC profile data to a PDF file.
.PP
.nf
pdfio_obj_t * pdfioFileCreateICCObjFromData (
pdfio_file_t *pdf,
const unsigned char *data,
size_t datalen,
size_t num_colors
);
.fi
.SS pdfioFileCreateICCObjFromFile .SS pdfioFileCreateICCObjFromFile
Add an ICC profile file to a PDF file. Add an ICC profile object to a PDF file.
.PP .PP
.nf .nf
pdfio_obj_t * pdfioFileCreateICCObjFromFile ( pdfio_obj_t * pdfioFileCreateICCObjFromFile (
@ -4062,9 +3767,7 @@ written:
.fi .fi
The "version" argument specifies the PDF version number for the file or The "version" argument specifies the PDF version number for the file or
\fBNULL\fR for the default ("2.0"). Unlike \fIpdfioFileCreate\fR and \fBNULL\fR for the default ("2.0").
\fIpdfioFileCreateTemporary\fR, it is generally not safe to pass the
"PCLm-1.0" version string.
.PP .PP
The "media_box" and "crop_box" arguments specify the default MediaBox and The "media_box" and "crop_box" arguments specify the default MediaBox and
CropBox for pages in the PDF file - if \fBNULL\fR then a default "Universal" size CropBox for pages in the PDF file - if \fBNULL\fR then a default "Universal" size
@ -4177,14 +3880,6 @@ const char * pdfioFileGetKeywords (
pdfio_file_t *pdf pdfio_file_t *pdf
); );
.fi .fi
.SS pdfioFileGetModificationDate
Get the most recent modification date for a PDF file.
.PP
.nf
time_t pdfioFileGetModificationDate (
pdfio_file_t *pdf
);
.fi
.SS pdfioFileGetName .SS pdfioFileGetName
Get a PDF's filename. Get a PDF's filename.
.PP .PP
@ -4332,15 +4027,6 @@ void pdfioFileSetKeywords (
const char *value const char *value
); );
.fi .fi
.SS pdfioFileSetModificationDate
Set the modification date for a PDF file.
.PP
.nf
void pdfioFileSetModificationDate (
pdfio_file_t *pdf,
time_t value
);
.fi
.SS pdfioFileSetPermissions .SS pdfioFileSetPermissions
Set the PDF permissions, encryption mode, and passwords. Set the PDF permissions, encryption mode, and passwords.
.PP .PP
@ -4648,13 +4334,12 @@ bool pdfioStreamGetToken (
); );
.fi .fi
.PP .PP
This function reads a single PDF token from a stream, skipping all whitespace This function reads a single PDF token from a stream. Operator tokens,
and comments. Operator tokens, boolean values, and numbers are returned boolean values, and numbers are returned as-is in the provided string buffer.
as-is in the provided string buffer. String values start with the opening String values start with the opening parenthesis ('(') but have all escaping
parenthesis ('(') but have all escaping resolved and the terminating resolved and the terminating parenthesis removed. Hexadecimal string values
parenthesis removed. Hexadecimal string values start with the opening angle start with the opening angle bracket ('<') and have all whitespace and the
bracket ('<') and have all whitespace and the terminating angle bracket terminating angle bracket removed.
removed.
.SS pdfioStreamPeek .SS pdfioStreamPeek
Peek at data in a stream. Peek at data in a stream.
.PP .PP
@ -4675,10 +4360,6 @@ bool pdfioStreamPrintf (
... ...
); );
.fi .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 .SS pdfioStreamPutChar
Write a single character to a stream. Write a single character to a stream.
.PP .PP

View File

@ -1,13 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en-US"> <html lang="en-US">
<head> <head>
<title>PDFio Programming Manual v1.5.2</title> <title>PDFio Programming Manual v1.4.1</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"> <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="generator" content="codedoc v3.8"> <meta name="generator" content="codedoc v3.8">
<meta name="author" content="Michael R Sweet"> <meta name="author" content="Michael R Sweet">
<meta name="language" content="en-US"> <meta name="language" content="en-US">
<meta name="copyright" content="Copyright © 2021-2025 by Michael R Sweet"> <meta name="copyright" content="Copyright © 2021-2025 by Michael R Sweet">
<meta name="version" content="1.5.2"> <meta name="version" content="1.4.1">
<style type="text/css"><!-- <style type="text/css"><!--
body { body {
background: white; background: white;
@ -251,7 +251,7 @@ span.string {
<body> <body>
<div class="header"> <div class="header">
<p><img class="title" src="pdfio-512.png"></p> <p><img class="title" src="pdfio-512.png"></p>
<h1 class="title">PDFio Programming Manual v1.5.2</h1> <h1 class="title">PDFio Programming Manual v1.4.1</h1>
<p>Michael R Sweet</p> <p>Michael R Sweet</p>
<p>Copyright © 2021-2025 by Michael R Sweet</p> <p>Copyright © 2021-2025 by Michael R Sweet</p>
</div> </div>
@ -400,7 +400,6 @@ span.string {
<li><a href="#pdfioFileCreateArrayObj">pdfioFileCreateArrayObj</a></li> <li><a href="#pdfioFileCreateArrayObj">pdfioFileCreateArrayObj</a></li>
<li><a href="#pdfioFileCreateFontObjFromBase">pdfioFileCreateFontObjFromBase</a></li> <li><a href="#pdfioFileCreateFontObjFromBase">pdfioFileCreateFontObjFromBase</a></li>
<li><a href="#pdfioFileCreateFontObjFromFile">pdfioFileCreateFontObjFromFile</a></li> <li><a href="#pdfioFileCreateFontObjFromFile">pdfioFileCreateFontObjFromFile</a></li>
<li><a href="#pdfioFileCreateICCObjFromData">pdfioFileCreateICCObjFromData</a></li>
<li><a href="#pdfioFileCreateICCObjFromFile">pdfioFileCreateICCObjFromFile</a></li> <li><a href="#pdfioFileCreateICCObjFromFile">pdfioFileCreateICCObjFromFile</a></li>
<li><a href="#pdfioFileCreateImageObjFromData">pdfioFileCreateImageObjFromData</a></li> <li><a href="#pdfioFileCreateImageObjFromData">pdfioFileCreateImageObjFromData</a></li>
<li><a href="#pdfioFileCreateImageObjFromFile">pdfioFileCreateImageObjFromFile</a></li> <li><a href="#pdfioFileCreateImageObjFromFile">pdfioFileCreateImageObjFromFile</a></li>
@ -418,7 +417,6 @@ span.string {
<li><a href="#pdfioFileGetCreator">pdfioFileGetCreator</a></li> <li><a href="#pdfioFileGetCreator">pdfioFileGetCreator</a></li>
<li><a href="#pdfioFileGetID">pdfioFileGetID</a></li> <li><a href="#pdfioFileGetID">pdfioFileGetID</a></li>
<li><a href="#pdfioFileGetKeywords">pdfioFileGetKeywords</a></li> <li><a href="#pdfioFileGetKeywords">pdfioFileGetKeywords</a></li>
<li><a href="#pdfioFileGetModificationDate">pdfioFileGetModificationDate</a></li>
<li><a href="#pdfioFileGetName">pdfioFileGetName</a></li> <li><a href="#pdfioFileGetName">pdfioFileGetName</a></li>
<li><a href="#pdfioFileGetNumObjs">pdfioFileGetNumObjs</a></li> <li><a href="#pdfioFileGetNumObjs">pdfioFileGetNumObjs</a></li>
<li><a href="#pdfioFileGetNumPages">pdfioFileGetNumPages</a></li> <li><a href="#pdfioFileGetNumPages">pdfioFileGetNumPages</a></li>
@ -434,7 +432,6 @@ span.string {
<li><a href="#pdfioFileSetCreationDate">pdfioFileSetCreationDate</a></li> <li><a href="#pdfioFileSetCreationDate">pdfioFileSetCreationDate</a></li>
<li><a href="#pdfioFileSetCreator">pdfioFileSetCreator</a></li> <li><a href="#pdfioFileSetCreator">pdfioFileSetCreator</a></li>
<li><a href="#pdfioFileSetKeywords">pdfioFileSetKeywords</a></li> <li><a href="#pdfioFileSetKeywords">pdfioFileSetKeywords</a></li>
<li><a href="#pdfioFileSetModificationDate">pdfioFileSetModificationDate</a></li>
<li><a href="#pdfioFileSetPermissions">pdfioFileSetPermissions</a></li> <li><a href="#pdfioFileSetPermissions">pdfioFileSetPermissions</a></li>
<li><a href="#pdfioFileSetSubject">pdfioFileSetSubject</a></li> <li><a href="#pdfioFileSetSubject">pdfioFileSetSubject</a></li>
<li><a href="#pdfioFileSetTitle">pdfioFileSetTitle</a></li> <li><a href="#pdfioFileSetTitle">pdfioFileSetTitle</a></li>
@ -525,7 +522,7 @@ span.string {
</li> </li>
</ul> </ul>
<p>PDFio is <em>not</em> concerned with rendering or viewing a PDF file, although a PDF RIP or viewer could be written using it.</p> <p>PDFio is <em>not</em> concerned with rendering or viewing a PDF file, although a PDF RIP or viewer could be written using it.</p>
<p>PDFio is Copyright © 2021-2025 by Michael R Sweet and is licensed under the Apache License Version 2.0 with an (optional) exception to allow linking against GPL2/LGPL2 software. See the files &quot;LICENSE&quot; and &quot;NOTICE&quot; for more information.</p> <p>PDFio is Copyright © 2021-2024 by Michael R Sweet and is licensed under the Apache License Version 2.0 with an (optional) exception to allow linking against GPL2/LGPL2 software. See the files &quot;LICENSE&quot; and &quot;NOTICE&quot; for more information.</p>
<h3 class="title" id="requirements">Requirements</h3> <h3 class="title" id="requirements">Requirements</h3>
<p>PDFio requires the following to build the software:</p> <p>PDFio requires the following to build the software:</p>
<ul> <ul>
@ -535,10 +532,9 @@ span.string {
</li> </li>
<li><p>A POSIX-compliant <code>sh</code> program</p> <li><p>A POSIX-compliant <code>sh</code> program</p>
</li> </li>
<li><p>ZLIB (<a href="https://www.zlib.net/">https://www.zlib.net/</a>) 1.0 or higher</p> <li><p>ZLIB (<a href="https://www.zlib.net">https://www.zlib.net</a>) 1.0 or higher</p>
</li> </li>
</ul> </ul>
<p>PDFio will also use libpng 1.6 or higher (<a href="https://www.libpng.org/">https://www.libpng.org/</a>) to provide enhanced PNG image support.</p>
<p>IDE files for Xcode (macOS/iOS) and Visual Studio (Windows) are also provided.</p> <p>IDE files for Xcode (macOS/iOS) and Visual Studio (Windows) are also provided.</p>
<h3 class="title" id="installing-pdfio">Installing PDFio</h3> <h3 class="title" id="installing-pdfio">Installing PDFio</h3>
<p>PDFio comes with a configure script that creates a portable makefile that will work on any POSIX-compliant system with ZLIB installed. To make it, run:</p> <p>PDFio comes with a configure script that creates a portable makefile that will work on any POSIX-compliant system with ZLIB installed. To make it, run:</p>
@ -1165,26 +1161,11 @@ 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> <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> pdfio_file_t *pdf; <span class="comment">// PDF file</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>
<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>
*creator, <span class="comment">// Creator name</span> <span class="reserved">struct</span> tm *creation_tm; <span class="comment">// Creation date/time information</span>
*producer, <span class="comment">// Producer name</span> <span class="reserved">char</span> creation_text[<span class="number">256</span>]; <span class="comment">// Creation date/time as a string</span>
*title; <span class="comment">// Title</span> <span class="reserved">const</span> <span class="reserved">char</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> <span class="comment">// Get the filename from the command-line...</span>
@ -1197,20 +1178,14 @@ main(<span class="reserved">int</span> argc, <span clas
filename = argv[<span class="number">1</span>]; filename = argv[<span class="number">1</span>];
<span class="comment">// Open the PDF file with the default callbacks...</span> <span class="comment">// Open the PDF file with the default callbacks...</span>
pdf = pdfioFileOpen(filename, <span class="comment">/*password_cb*/</span>NULL, pdf = pdfioFileOpen(filename, <span class="comment">/*password_cb*/</span>NULL, <span class="comment">/*password_cbdata*/</span>NULL,
<span class="comment">/*password_cbdata*/</span>NULL, <span class="comment">/*error_cb*/</span>NULL, <span class="comment">/*error_cb*/</span>NULL, <span class="comment">/*error_cbdata*/</span>NULL);
<span class="comment">/*error_cbdata*/</span>NULL);
<span class="reserved">if</span> (pdf == NULL) <span class="reserved">if</span> (pdf == NULL)
<span class="reserved">return</span> (<span class="number">1</span>); <span class="reserved">return</span> (<span class="number">1</span>);
<span class="comment">// Get the title, author, etc...</span> <span class="comment">// Get the title and author...</span>
catalog = pdfioFileGetCatalog(pdf); author = pdfioFileGetAuthor(pdf);
author = pdfioFileGetAuthor(pdf); title = pdfioFileGetTitle(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="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>) <span class="reserved">if</span> ((creation_date = pdfioFileGetCreationDate(pdf)) &gt; <span class="number">0</span>)
@ -1223,76 +1198,12 @@ 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>); 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> <span class="comment">// Print file information to stdout...</span>
printf(<span class="string">&quot;%s:\n&quot;</span>, filename); 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; 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; 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; Created On: %s\n&quot;</span>, creation_text);
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; Number Pages: %u\n&quot;</span>, (<span class="reserved">unsigned</span>)pdfioFileGetNumPages(pdf));
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> <span class="comment">// Close the PDF file...</span>
pdfioFileClose(pdf); pdfioFileClose(pdf);
@ -1301,69 +1212,26 @@ main(<span class="reserved">int</span> argc, <span clas
} }
</code></pre> </code></pre>
<h3 class="title" id="extract-text-from-pdf-file">Extract Text from PDF File</h3> <h3 class="title" id="extract-text-from-pdf-file">Extract Text from PDF File</h3>
<p>The <code>pdf2text.c</code> example code extracts text from a PDF file and writes it to the standard output. Unlike some other PDF tools, it outputs the text in the order it is seen in each page stream so the output might appear &quot;jumbled&quot; if the PDF producer doesn't output text in reading order. The code is able to handle different font encodings and produces UTF-8 output.</p> <p>The <code>pdf2text.c</code> example code extracts non-Unicode text from a PDF file by scanning each page for strings and text drawing commands. Since it doesn't look at the font encoding or support Unicode text, it is really only useful to extract plain ASCII text from a PDF file. And since it writes text in the order it appears in the page stream, it may not come out in the same order as appears on the page.</p>
<p>The <a href="#pdfioStreamGetToken"><code>pdfioStreamGetToken</code></a> function is used to read individual tokens from the page streams:</p> <p>The <a href="#pdfioStreamGetToken"><code>pdfioStreamGetToken</code></a> function is used to read individual tokens from the page streams. Tokens starting with the open parenthesis are text strings, while PDF operators are left as-is. We use some simple logic to make sure that we include spaces between text strings and add newlines for the text operators that start a new line in a text block:</p>
<pre><code class="language-c">pdfio_stream_t *st; <span class="comment">// Page stream</span> <pre><code class="language-c">pdfio_stream_t *st; <span class="comment">// Page stream</span>
<span class="reserved">char</span> buffer[<span class="number">1024</span>], <span class="comment">// Token buffer</span>
*bufptr, <span class="comment">// Pointer into buffer</span>
name[<span class="number">256</span>]; <span class="comment">// Current (font) name</span>
<span class="reserved">bool</span> first = <span class="reserved">true</span>; <span class="comment">// First string on line?</span> <span class="reserved">bool</span> first = <span class="reserved">true</span>; <span class="comment">// First string on line?</span>
<span class="reserved">int</span> encoding[<span class="number">256</span>]; <span class="comment">// Font encoding to Unicode</span> <span class="reserved">char</span> buffer[<span class="number">1024</span>]; <span class="comment">// Token buffer</span>
<span class="reserved">bool</span> in_array = <span class="reserved">false</span>; <span class="comment">// Are we in an array?</span>
<span class="comment">// Read PDF tokens from the page stream...</span> <span class="comment">// Read PDF tokens from the page stream...</span>
<span class="reserved">while</span> (pdfioStreamGetToken(st, buffer, <span class="reserved">sizeof</span>(buffer))) <span class="reserved">while</span> (pdfioStreamGetToken(st, buffer, <span class="reserved">sizeof</span>(buffer)))
{ {
</code></pre> <span class="reserved">if</span> (buffer[<span class="number">0</span>] == <span class="string">'('</span>)
<p>Justified text can be found inside arrays (&quot;[ ... ]&quot;), so we look for the array delimiter tokens and any (spacing) numbers inside an array. Experimentation has shown that numbers greater than 100 can be treated as whitespace:</p>
<pre><code class="language-c"> <span class="reserved">if</span> (!strcmp(buffer, <span class="string">&quot;[&quot;</span>))
{
<span class="comment">// Start of an array for justified text...</span>
in_array = <span class="reserved">true</span>;
}
<span class="reserved">else</span> <span class="reserved">if</span> (!strcmp(buffer, <span class="string">&quot;]&quot;</span>))
{
<span class="comment">// End of an array for justified text...</span>
in_array = <span class="reserved">false</span>;
}
<span class="reserved">else</span> <span class="reserved">if</span> (!first &amp;&amp; in_array &amp;&amp; (isdigit(buffer[<span class="number">0</span>]) || buffer[<span class="number">0</span>] == <span class="string">'-'</span>) &amp;&amp; fabs(atof(buffer)) &gt; <span class="number">100</span>)
{
<span class="comment">// Whitespace in a justified text block...</span>
putchar(<span class="string">' '</span>);
}
</code></pre>
<p>Tokens starting with '(' or '&lt;' are text fragments. 8-bit text starting with '(' needs to be mapped to Unicode using the current font encoding while hex strings starting with '&lt;' are UTF-16 (Unicode) that need to be converted to UTF-8:</p>
<pre><code class="language-c"> <span class="reserved">else</span> <span class="reserved">if</span> (buffer[<span class="number">0</span>] == <span class="string">'('</span>)
{ {
<span class="comment">// Text string using an 8-bit encoding</span> <span class="comment">// Text string using an 8-bit encoding</span>
first = <span class="reserved">false</span>; <span class="reserved">if</span> (first)
first = <span class="reserved">false</span>;
<span class="reserved">else</span> <span class="reserved">if</span> (buffer[<span class="number">1</span>] != <span class="string">' '</span>)
putchar(<span class="string">' '</span>);
<span class="reserved">for</span> (bufptr = buffer + <span class="number">1</span>; *bufptr; bufptr ++) fputs(buffer + <span class="number">1</span>, stdout);
put_utf8(encoding[*bufptr &amp; <span class="number">255</span>]);
} }
<span class="reserved">else</span> <span class="reserved">if</span> (buffer[<span class="number">0</span>] == <span class="string">'&lt;'</span>) <span class="reserved">else</span> <span class="reserved">if</span> (!strcmp(buffer, <span class="string">&quot;Td&quot;</span>) || !strcmp(buffer, <span class="string">&quot;TD&quot;</span>) || !strcmp(buffer, <span class="string">&quot;T*&quot;</span>) ||
{
<span class="comment">// Unicode text string</span>
first = <span class="reserved">false</span>;
puts_utf16(buffer + <span class="number">1</span>);
}
</code></pre>
<p>Simple (8-bit) fonts include an encoding table that maps the 8-bit characters to one of 1051 Unicode glyph names. Since each font can use a different encoding, we look for font names starting with '/' and the &quot;Tf&quot; (set text font) operator token and load that font's encoding using the <a href="#the-loadencoding-function">load_encoding</a> function:</p>
<pre><code class="language-c"> <span class="reserved">else</span> <span class="reserved">if</span> (buffer[<span class="number">0</span>] == <span class="string">'/'</span>)
{
<span class="comment">// Save name...</span>
strncpy(name, buffer + <span class="number">1</span>, <span class="reserved">sizeof</span>(name) - <span class="number">1</span>);
name[<span class="reserved">sizeof</span>(name) - <span class="number">1</span>] = <span class="string">'\0'</span>;
}
<span class="reserved">else</span> <span class="reserved">if</span> (!strcmp(buffer, <span class="string">&quot;Tf&quot;</span>) &amp;&amp; name[<span class="number">0</span>])
{
<span class="comment">// Set font...</span>
load_encoding(obj, name, encoding);
}
</code></pre>
<p>Finally, some text operators start a new line in a text block, so when we see their tokens we output a newline:</p>
<pre><code class="language-c"> <span class="reserved">else</span> <span class="reserved">if</span> (!strcmp(buffer, <span class="string">&quot;Td&quot;</span>) || !strcmp(buffer, <span class="string">&quot;TD&quot;</span>) || !strcmp(buffer, <span class="string">&quot;T*&quot;</span>) ||
!strcmp(buffer, <span class="string">&quot;\'&quot;</span>) || !strcmp(buffer, <span class="string">&quot;\&quot;&quot;</span>)) !strcmp(buffer, <span class="string">&quot;\'&quot;</span>) || !strcmp(buffer, <span class="string">&quot;\&quot;&quot;</span>))
{ {
<span class="comment">// Text operators that advance to the next line in the block</span> <span class="comment">// Text operators that advance to the next line in the block</span>
@ -1371,133 +1239,9 @@ main(<span class="reserved">int</span> argc, <span clas
first = <span class="reserved">true</span>; first = <span class="reserved">true</span>;
} }
} }
</code></pre>
<h4 id="the-loadencoding-function">The <code>load_encoding</code> Function</h4>
<p>The <code>load_encoding</code> function looks up the named font in the page's &quot;Resources&quot; dictionary. Every PDF simple font contains an &quot;Encoding&quot; dictionary with a base encoding (&quot;WinANSI&quot;, &quot;MacRoman&quot;, or &quot;MacExpert&quot;) and a differences array that lists character indexes and glyph names for an 8-bit font.</p>
<p>We start by initializing the encoding array to the default WinANSI encoding and looking up the font object for the named font:</p>
<pre><code class="language-c"><span class="reserved">static</span> <span class="reserved">void</span>
load_encoding(
pdfio_obj_t *page_obj, <span class="comment">// I - Page object</span>
<span class="reserved">const</span> <span class="reserved">char</span> *name, <span class="comment">// I - Font name</span>
<span class="reserved">int</span> encoding[<span class="number">256</span>]) <span class="comment">// O - Encoding table</span>
{
size_t i, j; <span class="comment">// Looping vars</span>
pdfio_dict_t *page_dict, <span class="comment">// Page dictionary</span>
*resources_dict, <span class="comment">// Resources dictionary</span>
*font_dict; <span class="comment">// Font dictionary</span>
pdfio_obj_t *font_obj, <span class="comment">// Font object</span>
*encoding_obj; <span class="comment">// Encoding object</span>
<span class="reserved">static</span> <span class="reserved">int</span> win_ansi[<span class="number">32</span>] = <span class="comment">// WinANSI characters from 128 to 159</span>
{
...
};
<span class="reserved">static</span> <span class="reserved">int</span> mac_roman[<span class="number">128</span>] = <span class="comment">// MacRoman characters from 128 to 255</span>
{
...
};
<span class="reserved">if</span> (!first)
<span class="comment">// Initialize the encoding to be the &quot;standard&quot; WinAnsi...</span> putchar(<span class="string">'\n'</span>);
<span class="reserved">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">128</span>; i ++)
encoding[i] = i;
<span class="reserved">for</span> (i = <span class="number">160</span>; i &lt; <span class="number">256</span>; i ++)
encoding[i] = i;
memcpy(encoding + <span class="number">128</span>, win_ansi, <span class="reserved">sizeof</span>(win_ansi));
<span class="comment">// Find the named font...</span>
<span class="reserved">if</span> ((page_dict = pdfioObjGetDict(page_obj)) == NULL)
<span class="reserved">return</span>;
<span class="reserved">if</span> ((resources_dict = pdfioDictGetDict(page_dict, <span class="string">&quot;Resources&quot;</span>)) == NULL)
<span class="reserved">return</span>;
<span class="reserved">if</span> ((font_dict = pdfioDictGetDict(resources_dict, <span class="string">&quot;Font&quot;</span>)) == NULL)
{
<span class="comment">// Font resources not a dictionary, see if it is an object...</span>
<span class="reserved">if</span> ((font_obj = pdfioDictGetObj(resources_dict, <span class="string">&quot;Font&quot;</span>)) != NULL)
font_dict = pdfioObjGetDict(font_obj);
<span class="reserved">if</span> (!font_dict)
<span class="reserved">return</span>;
}
<span class="reserved">if</span> ((font_obj = pdfioDictGetObj(font_dict, name)) == NULL)
<span class="reserved">return</span>;
</code></pre>
<p>Once we have found the font we see if it has an &quot;Encoding&quot; dictionary:</p>
<pre><code class="language-c"> pdfio_dict_t *encoding_dict; <span class="comment">// Encoding dictionary</span>
<span class="reserved">if</span> ((encoding_obj = pdfioDictGetObj(pdfioObjGetDict(font_obj), <span class="string">&quot;Encoding&quot;</span>)) == NULL)
<span class="reserved">return</span>;
<span class="reserved">if</span> ((encoding_dict = pdfioObjGetDict(encoding_obj)) == NULL)
<span class="reserved">return</span>;
</code></pre>
<p>Once we have the encoding dictionary we can get the &quot;BaseEncoding&quot; and &quot;Differences&quot; values:</p>
<pre><code class="language-c"> <span class="reserved">const</span> <span class="reserved">char</span> *base_encoding; <span class="comment">// BaseEncoding name</span>
pdfio_array_t *differences; <span class="comment">// Differences array</span>
<span class="comment">// OK, have the encoding object, build the encoding using it...</span>
base_encoding = pdfioDictGetName(encoding_dict, <span class="string">&quot;BaseEncoding&quot;</span>);
differences = pdfioDictGetArray(encoding_dict, <span class="string">&quot;Differences&quot;</span>);
</code></pre>
<p>If the base encoding is &quot;MacRomainEncoding&quot;, we need to reset the upper 128 characters in the encoding array match it:</p>
<pre><code class="language-c"> <span class="reserved">if</span> (base_encoding &amp;&amp; !strcmp(base_encoding, <span class="string">&quot;MacRomanEncoding&quot;</span>))
{
<span class="comment">// Map upper 128</span>
memcpy(encoding + <span class="number">128</span>, mac_roman, <span class="reserved">sizeof</span>(mac_roman));
}
</code></pre>
<p>Then we loop through the differences array, keeping track of the current index within the encoding array. A number indicates a new index while a name is the Unicode glyph for the current index:</p>
<pre><code class="language-c"> <span class="reserved">typedef</span> <span class="reserved">struct</span> name_map_s
{
<span class="reserved">const</span> <span class="reserved">char</span> *name; <span class="comment">// Character name</span>
<span class="reserved">int</span> unicode; <span class="comment">// Unicode value</span>
} name_map_t;
<span class="reserved">static</span> name_map_t unicode_map[<span class="number">1051</span>]; <span class="comment">// List of glyph names</span>
<span class="reserved">if</span> (differences)
{
<span class="comment">// Apply differences</span>
size_t count = pdfioArrayGetSize(differences);
<span class="comment">// Number of differences</span>
<span class="reserved">const</span> <span class="reserved">char</span> *name; <span class="comment">// Character name</span>
size_t idx = <span class="number">0</span>; <span class="comment">// Index in encoding array</span>
<span class="reserved">for</span> (i = <span class="number">0</span>; i &lt; count; i ++)
{
<span class="reserved">switch</span> (pdfioArrayGetType(differences, i))
{
<span class="reserved">case</span> PDFIO_VALTYPE_NUMBER :
<span class="comment">// Get the index of the next character...</span>
idx = (size_t)pdfioArrayGetNumber(differences, i);
<span class="reserved">break</span>;
<span class="reserved">case</span> PDFIO_VALTYPE_NAME :
<span class="comment">// Lookup name and apply to encoding...</span>
<span class="reserved">if</span> (idx &lt; <span class="number">0</span> || idx &gt; <span class="number">255</span>)
<span class="reserved">break</span>;
name = pdfioArrayGetName(differences, i);
<span class="reserved">for</span> (j = <span class="number">0</span>; j &lt; (<span class="reserved">sizeof</span>(unicode_map) / <span class="reserved">sizeof</span>(unicode_map[<span class="number">0</span>])); j ++)
{
<span class="reserved">if</span> (!strcmp(name, unicode_map[j].name))
{
encoding[idx] = unicode_map[j].unicode;
<span class="reserved">break</span>;
}
}
idx ++;
<span class="reserved">break</span>;
<span class="reserved">default</span> :
<span class="comment">// Do nothing for other values</span>
<span class="reserved">break</span>;
}
}
}
}
</code></pre> </code></pre>
<h3 class="title" id="create-a-pdf-file-with-text-and-an-image">Create a PDF File With Text and an Image</h3> <h3 class="title" id="create-a-pdf-file-with-text-and-an-image">Create a PDF File With Text and an Image</h3>
<p>The <code>image2pdf.c</code> example code creates a PDF file containing a JPEG or PNG image file and optional caption on a single page. The <code>create_pdf_image_file</code> function creates the PDF file, embeds a base font and the named JPEG or PNG image file, and then creates a page with the image centered on the page with any text centered below:</p> <p>The <code>image2pdf.c</code> example code creates a PDF file containing a JPEG or PNG image file and optional caption on a single page. The <code>create_pdf_image_file</code> function creates the PDF file, embeds a base font and the named JPEG or PNG image file, and then creates a page with the image centered on the page with any text centered below:</p>
@ -4068,8 +3812,7 @@ have been iterated.</p>
name of the PDF file to create.<br> name of the PDF file to create.<br>
<br> <br>
The &quot;version&quot; argument specifies the PDF version number for the file or The &quot;version&quot; argument specifies the PDF version number for the file or
<code>NULL</code> for the default (&quot;2.0&quot;). The value &quot;PCLm-1.0&quot; can be specified to <code>NULL</code> for the default (&quot;2.0&quot;).<br>
produce the PCLm subset of PDF.<br>
<br> <br>
The &quot;media_box&quot; and &quot;crop_box&quot; arguments specify the default MediaBox and The &quot;media_box&quot; and &quot;crop_box&quot; arguments specify the default MediaBox and
CropBox for pages in the PDF file - if <code>NULL</code> then a default &quot;Universal&quot; size CropBox for pages in the PDF file - if <code>NULL</code> then a default &quot;Universal&quot; size
@ -4164,25 +3907,8 @@ Unicode.</p>
characters (potentially full Unicode, but more typically a subset) characters (potentially full Unicode, but more typically a subset)
or to only support the Windows CP1252 (ISO-8859-1 with additional or to only support the Windows CP1252 (ISO-8859-1 with additional
characters such as the Euro symbol) subset of Unicode.</p> characters such as the Euro symbol) subset of Unicode.</p>
<h3 class="function"><a id="pdfioFileCreateICCObjFromData">pdfioFileCreateICCObjFromData</a></h3>
<p class="description">Add ICC profile data to a PDF file.</p>
<p class="code">
<a href="#pdfio_obj_t">pdfio_obj_t</a> *pdfioFileCreateICCObjFromData(<a href="#pdfio_file_t">pdfio_file_t</a> *pdf, <span class="reserved">const</span> <span class="reserved">unsigned</span> <span class="reserved">char</span> *data, size_t datalen, size_t num_colors);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>pdf</th>
<td class="description">PDF file</td></tr>
<tr><th>data</th>
<td class="description">ICC profile buffer</td></tr>
<tr><th>datalen</th>
<td class="description">Length of ICC profile</td></tr>
<tr><th>num_colors</th>
<td class="description">Number of color components (1, 3, or 4)</td></tr>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Object</p>
<h3 class="function"><a id="pdfioFileCreateICCObjFromFile">pdfioFileCreateICCObjFromFile</a></h3> <h3 class="function"><a id="pdfioFileCreateICCObjFromFile">pdfioFileCreateICCObjFromFile</a></h3>
<p class="description">Add an ICC profile file to a PDF file.</p> <p class="description">Add an ICC profile object to a PDF file.</p>
<p class="code"> <p class="code">
<a href="#pdfio_obj_t">pdfio_obj_t</a> *pdfioFileCreateICCObjFromFile(<a href="#pdfio_file_t">pdfio_file_t</a> *pdf, <span class="reserved">const</span> <span class="reserved">char</span> *filename, size_t num_colors);</p> <a href="#pdfio_obj_t">pdfio_obj_t</a> *pdfioFileCreateICCObjFromFile(<a href="#pdfio_file_t">pdfio_file_t</a> *pdf, <span class="reserved">const</span> <span class="reserved">char</span> *filename, size_t num_colors);</p>
<h4 class="parameters">Parameters</h4> <h4 class="parameters">Parameters</h4>
@ -4343,9 +4069,7 @@ output_cb(void *output_cbdata, const void *buffer, size_t bytes)
</pre> </pre>
The &quot;version&quot; argument specifies the PDF version number for the file or The &quot;version&quot; argument specifies the PDF version number for the file or
<code>NULL</code> for the default (&quot;2.0&quot;). Unlike <a href="#pdfioFileCreate"><code>pdfioFileCreate</code></a> and <code>NULL</code> for the default (&quot;2.0&quot;).<br>
<a href="#pdfioFileCreateTemporary"><code>pdfioFileCreateTemporary</code></a>, it is generally not safe to pass the
&quot;PCLm-1.0&quot; version string.<br>
<br> <br>
The &quot;media_box&quot; and &quot;crop_box&quot; arguments specify the default MediaBox and The &quot;media_box&quot; and &quot;crop_box&quot; arguments specify the default MediaBox and
CropBox for pages in the PDF file - if <code>NULL</code> then a default &quot;Universal&quot; size CropBox for pages in the PDF file - if <code>NULL</code> then a default &quot;Universal&quot; size
@ -4413,19 +4137,8 @@ You must call <a href="#pdfioObjClose"><code>pdfioObjClose</code></a> to write t
<p class="description">Create a temporary PDF file.</p> <p class="description">Create a temporary PDF file.</p>
<p class="discussion">This function creates a PDF file with a unique filename in the current <p class="discussion">This function creates a PDF file with a unique filename in the current
temporary directory. The temporary file is stored in the string &quot;buffer&quot; an temporary directory. The temporary file is stored in the string &quot;buffer&quot; an
will have a &quot;.pdf&quot; extension.<br> will have a &quot;.pdf&quot; extension. Otherwise, this function works the same as
<br> the <a href="#pdfioFileCreate"><code>pdfioFileCreate</code></a> function.
The &quot;version&quot; argument specifies the PDF version number for the file or
<code>NULL</code> for the default (&quot;2.0&quot;). The value &quot;PCLm-1.0&quot; can be specified to
produce the PCLm subset of PDF.<br>
<br>
The &quot;media_box&quot; and &quot;crop_box&quot; arguments specify the default MediaBox and
CropBox for pages in the PDF file - if <code>NULL</code> then a default &quot;Universal&quot; size
of 8.27x11in (the intersection of US Letter and ISO A4) is used.<br>
<br>
The &quot;error_cb&quot; and &quot;error_cbdata&quot; arguments specify an error handler callback
and its data pointer - if <code>NULL</code> the default error handler is used that
writes error messages to <code>stderr</code>.
</p> </p>
<h3 class="function"><a id="pdfioFileFindObj">pdfioFileFindObj</a></h3> <h3 class="function"><a id="pdfioFileFindObj">pdfioFileFindObj</a></h3>
@ -4510,17 +4223,6 @@ time_t pdfioFileGetCreationDate(<a href="#pdfio_file_t">pdfio_file_t</a> *pdf);<
</tbody></table> </tbody></table>
<h4 class="returnvalue">Return Value</h4> <h4 class="returnvalue">Return Value</h4>
<p class="description">Keywords string or <code>NULL</code> for none</p> <p class="description">Keywords string or <code>NULL</code> for none</p>
<h3 class="function"><a id="pdfioFileGetModificationDate">pdfioFileGetModificationDate</a></h3>
<p class="description">Get the most recent modification date for a PDF file.</p>
<p class="code">
time_t pdfioFileGetModificationDate(<a href="#pdfio_file_t">pdfio_file_t</a> *pdf);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>pdf</th>
<td class="description">PDF file</td></tr>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Modification date or <code>0</code> for none</p>
<h3 class="function"><a id="pdfioFileGetName">pdfioFileGetName</a></h3> <h3 class="function"><a id="pdfioFileGetName">pdfioFileGetName</a></h3>
<p class="description">Get a PDF's filename.</p> <p class="description">Get a PDF's filename.</p>
<p class="code"> <p class="code">
@ -4716,17 +4418,6 @@ writes error messages to <code>stderr</code>.</p>
<tr><th>value</th> <tr><th>value</th>
<td class="description">Value</td></tr> <td class="description">Value</td></tr>
</tbody></table> </tbody></table>
<h3 class="function"><a id="pdfioFileSetModificationDate">pdfioFileSetModificationDate</a></h3>
<p class="description">Set the modification date for a PDF file.</p>
<p class="code">
<span class="reserved">void</span> pdfioFileSetModificationDate(<a href="#pdfio_file_t">pdfio_file_t</a> *pdf, time_t value);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>pdf</th>
<td class="description">PDF file</td></tr>
<tr><th>value</th>
<td class="description">Value</td></tr>
</tbody></table>
<h3 class="function"><a id="pdfioFileSetPermissions">pdfioFileSetPermissions</a></h3> <h3 class="function"><a id="pdfioFileSetPermissions">pdfioFileSetPermissions</a></h3>
<p class="description">Set the PDF permissions, encryption mode, and passwords.</p> <p class="description">Set the PDF permissions, encryption mode, and passwords.</p>
<p class="code"> <p class="code">
@ -5127,15 +4818,14 @@ size_t pdfioPageGetNumStreams(<a href="#pdfio_obj_t">pdfio_obj_t</a> *page);</p>
<td class="description">Size of string buffer</td></tr> <td class="description">Size of string buffer</td></tr>
</tbody></table> </tbody></table>
<h4 class="returnvalue">Return Value</h4> <h4 class="returnvalue">Return Value</h4>
<p class="description"><code>true</code> on success, <code>false</code> on end-of-stream or error</p> <p class="description"><code>true</code> on success, <code>false</code> on EOF</p>
<h4 class="discussion">Discussion</h4> <h4 class="discussion">Discussion</h4>
<p class="discussion">This function reads a single PDF token from a stream, skipping all whitespace <p class="discussion">This function reads a single PDF token from a stream. Operator tokens,
and comments. Operator tokens, boolean values, and numbers are returned boolean values, and numbers are returned as-is in the provided string buffer.
as-is in the provided string buffer. String values start with the opening String values start with the opening parenthesis ('(') but have all escaping
parenthesis ('(') but have all escaping resolved and the terminating resolved and the terminating parenthesis removed. Hexadecimal string values
parenthesis removed. Hexadecimal string values start with the opening angle start with the opening angle bracket ('&lt;') and have all whitespace and the
bracket ('&lt;') and have all whitespace and the terminating angle bracket terminating angle bracket removed.</p>
removed.</p>
<h3 class="function"><a id="pdfioStreamPeek">pdfioStreamPeek</a></h3> <h3 class="function"><a id="pdfioStreamPeek">pdfioStreamPeek</a></h3>
<p class="description">Peek at data in a stream.</p> <p class="description">Peek at data in a stream.</p>
<p class="code"> <p class="code">
@ -5166,10 +4856,6 @@ ssize_t pdfioStreamPeek(<a href="#pdfio_stream_t">pdfio_stream_t</a> *st, <span
</tbody></table> </tbody></table>
<h4 class="returnvalue">Return Value</h4> <h4 class="returnvalue">Return Value</h4>
<p class="description"><code>true</code> on success, <code>false</code> on failure</p> <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> <h3 class="function"><a id="pdfioStreamPutChar">pdfioStreamPutChar</a></h3>
<p class="description">Write a single character to a stream.</p> <p class="description">Write a single character to a stream.</p>
<p class="code"> <p class="code">

View File

@ -15,7 +15,7 @@ goals of PDFio are:
PDFio is *not* concerned with rendering or viewing a PDF file, although a PDF PDFio is *not* concerned with rendering or viewing a PDF file, although a PDF
RIP or viewer could be written using it. RIP or viewer could be written using it.
PDFio is Copyright © 2021-2025 by Michael R Sweet and is licensed under the PDFio is Copyright © 2021-2024 by Michael R Sweet and is licensed under the
Apache License Version 2.0 with an (optional) exception to allow linking against Apache License Version 2.0 with an (optional) exception to allow linking against
GPL2/LGPL2 software. See the files "LICENSE" and "NOTICE" for more information. GPL2/LGPL2 software. See the files "LICENSE" and "NOTICE" for more information.
@ -28,10 +28,7 @@ PDFio requires the following to build the software:
- A C99 compiler such as Clang, GCC, or MS Visual C - A C99 compiler such as Clang, GCC, or MS Visual C
- A POSIX-compliant `make` program - A POSIX-compliant `make` program
- A POSIX-compliant `sh` program - A POSIX-compliant `sh` program
- ZLIB (<https://www.zlib.net/>) 1.0 or higher - ZLIB (<https://www.zlib.net>) 1.0 or higher
PDFio will also use libpng 1.6 or higher (<https://www.libpng.org/>) to provide
enhanced PNG image support.
IDE files for Xcode (macOS/iOS) and Visual Studio (Windows) are also provided. IDE files for Xcode (macOS/iOS) and Visual Studio (Windows) are also provided.
@ -889,26 +886,11 @@ main(int argc, // I - Number of command-line arguments
{ {
const char *filename; // PDF filename const char *filename; // PDF filename
pdfio_file_t *pdf; // PDF file pdfio_file_t *pdf; // PDF file
pdfio_dict_t *catalog; // Catalog dictionary const char *author; // Author name
const char *author, // Author name time_t creation_date; // Creation date
*creator, // Creator name struct tm *creation_tm; // Creation date/time information
*producer, // Producer name char creation_text[256]; // Creation date/time as a string
*title; // Title const char *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... // Get the filename from the command-line...
@ -921,20 +903,14 @@ main(int argc, // I - Number of command-line arguments
filename = argv[1]; filename = argv[1];
// Open the PDF file with the default callbacks... // Open the PDF file with the default callbacks...
pdf = pdfioFileOpen(filename, /*password_cb*/NULL, pdf = pdfioFileOpen(filename, /*password_cb*/NULL, /*password_cbdata*/NULL,
/*password_cbdata*/NULL, /*error_cb*/NULL, /*error_cb*/NULL, /*error_cbdata*/NULL);
/*error_cbdata*/NULL);
if (pdf == NULL) if (pdf == NULL)
return (1); return (1);
// Get the title, author, etc... // Get the title and author...
catalog = pdfioFileGetCatalog(pdf); author = pdfioFileGetAuthor(pdf);
author = pdfioFileGetAuthor(pdf); title = pdfioFileGetTitle(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... // Get the creation date and convert to a string...
if ((creation_date = pdfioFileGetCreationDate(pdf)) > 0) if ((creation_date = pdfioFileGetCreationDate(pdf)) > 0)
@ -947,76 +923,12 @@ main(int argc, // I - Number of command-line arguments
snprintf(creation_text, sizeof(creation_text), "-- not set --"); 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... // Print file information to stdout...
printf("%s:\n", filename); printf("%s:\n", filename);
printf(" Title: %s\n", title ? title : "-- not set --"); printf(" Title: %s\n", title ? title : "-- not set --");
printf(" Author: %s\n", author ? author : "-- not set --"); printf(" Author: %s\n", author ? author : "-- not set --");
printf(" Creator: %s\n", creator ? creator : "-- not set --"); printf(" Created On: %s\n", creation_text);
printf(" Producer: %s\n", producer ? producer : "-- not set --"); printf(" Number Pages: %u\n", (unsigned)pdfioFileGetNumPages(pdf));
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... // Close the PDF file...
pdfioFileClose(pdf); pdfioFileClose(pdf);
@ -1029,98 +941,37 @@ main(int argc, // I - Number of command-line arguments
Extract Text from PDF File Extract Text from PDF File
-------------------------- --------------------------
The `pdf2text.c` example code extracts text from a PDF file and writes it to the The `pdf2text.c` example code extracts non-Unicode text from a PDF file by
standard output. Unlike some other PDF tools, it outputs the text in the order scanning each page for strings and text drawing commands. Since it doesn't
it is seen in each page stream so the output might appear "jumbled" if the PDF look at the font encoding or support Unicode text, it is really only useful to
producer doesn't output text in reading order. The code is able to handle extract plain ASCII text from a PDF file. And since it writes text in the order
different font encodings and produces UTF-8 output. it appears in the page stream, it may not come out in the same order as appears
on the page.
The [`pdfioStreamGetToken`](@@) function is used to read individual tokens from The [`pdfioStreamGetToken`](@@) function is used to read individual tokens from
the page streams: the page streams. Tokens starting with the open parenthesis are text strings,
while PDF operators are left as-is. We use some simple logic to make sure that
we include spaces between text strings and add newlines for the text operators
that start a new line in a text block:
```c ```c
pdfio_stream_t *st; // Page stream pdfio_stream_t *st; // Page stream
char buffer[1024], // Token buffer
*bufptr, // Pointer into buffer
name[256]; // Current (font) name
bool first = true; // First string on line? bool first = true; // First string on line?
int encoding[256]; // Font encoding to Unicode char buffer[1024]; // Token buffer
bool in_array = false; // Are we in an array?
// Read PDF tokens from the page stream... // Read PDF tokens from the page stream...
while (pdfioStreamGetToken(st, buffer, sizeof(buffer))) while (pdfioStreamGetToken(st, buffer, sizeof(buffer)))
{ {
``` if (buffer[0] == '(')
Justified text can be found inside arrays ("[ ... ]"), so we look for the array
delimiter tokens and any (spacing) numbers inside an array. Experimentation has
shown that numbers greater than 100 can be treated as whitespace:
```c
if (!strcmp(buffer, "["))
{
// Start of an array for justified text...
in_array = true;
}
else if (!strcmp(buffer, "]"))
{
// End of an array for justified text...
in_array = false;
}
else if (!first && in_array && (isdigit(buffer[0]) || buffer[0] == '-') && fabs(atof(buffer)) > 100)
{
// Whitespace in a justified text block...
putchar(' ');
}
```
Tokens starting with '(' or '<' are text fragments. 8-bit text starting with
'(' needs to be mapped to Unicode using the current font encoding while hex
strings starting with '<' are UTF-16 (Unicode) that need to be converted to
UTF-8:
```c
else if (buffer[0] == '(')
{ {
// Text string using an 8-bit encoding // Text string using an 8-bit encoding
first = false; if (first)
first = false;
else if (buffer[1] != ' ')
putchar(' ');
for (bufptr = buffer + 1; *bufptr; bufptr ++) fputs(buffer + 1, stdout);
put_utf8(encoding[*bufptr & 255]);
} }
else if (buffer[0] == '<')
{
// Unicode text string
first = false;
puts_utf16(buffer + 1);
}
```
Simple (8-bit) fonts include an encoding table that maps the 8-bit characters to
one of 1051 Unicode glyph names. Since each font can use a different encoding,
we look for font names starting with '/' and the "Tf" (set text font) operator
token and load that font's encoding using the
[load_encoding](#the-loadencoding-function) function:
```c
else if (buffer[0] == '/')
{
// Save name...
strncpy(name, buffer + 1, sizeof(name) - 1);
name[sizeof(name) - 1] = '\0';
}
else if (!strcmp(buffer, "Tf") && name[0])
{
// Set font...
load_encoding(obj, name, encoding);
}
```
Finally, some text operators start a new line in a text block, so when we see
their tokens we output a newline:
```c
else if (!strcmp(buffer, "Td") || !strcmp(buffer, "TD") || !strcmp(buffer, "T*") || else if (!strcmp(buffer, "Td") || !strcmp(buffer, "TD") || !strcmp(buffer, "T*") ||
!strcmp(buffer, "\'") || !strcmp(buffer, "\"")) !strcmp(buffer, "\'") || !strcmp(buffer, "\""))
{ {
@ -1129,160 +980,9 @@ their tokens we output a newline:
first = true; first = true;
} }
} }
```
if (!first)
### The `load_encoding` Function putchar('\n');
The `load_encoding` function looks up the named font in the page's "Resources"
dictionary. Every PDF simple font contains an "Encoding" dictionary with a base
encoding ("WinANSI", "MacRoman", or "MacExpert") and a differences array that
lists character indexes and glyph names for an 8-bit font.
We start by initializing the encoding array to the default WinANSI encoding and
looking up the font object for the named font:
```c
static void
load_encoding(
pdfio_obj_t *page_obj, // I - Page object
const char *name, // I - Font name
int encoding[256]) // O - Encoding table
{
size_t i, j; // Looping vars
pdfio_dict_t *page_dict, // Page dictionary
*resources_dict, // Resources dictionary
*font_dict; // Font dictionary
pdfio_obj_t *font_obj, // Font object
*encoding_obj; // Encoding object
static int win_ansi[32] = // WinANSI characters from 128 to 159
{
...
};
static int mac_roman[128] = // MacRoman characters from 128 to 255
{
...
};
// Initialize the encoding to be the "standard" WinAnsi...
for (i = 0; i < 128; i ++)
encoding[i] = i;
for (i = 160; i < 256; i ++)
encoding[i] = i;
memcpy(encoding + 128, win_ansi, sizeof(win_ansi));
// Find the named font...
if ((page_dict = pdfioObjGetDict(page_obj)) == NULL)
return;
if ((resources_dict = pdfioDictGetDict(page_dict, "Resources")) == NULL)
return;
if ((font_dict = pdfioDictGetDict(resources_dict, "Font")) == NULL)
{
// Font resources not a dictionary, see if it is an object...
if ((font_obj = pdfioDictGetObj(resources_dict, "Font")) != NULL)
font_dict = pdfioObjGetDict(font_obj);
if (!font_dict)
return;
}
if ((font_obj = pdfioDictGetObj(font_dict, name)) == NULL)
return;
```
Once we have found the font we see if it has an "Encoding" dictionary:
```c
pdfio_dict_t *encoding_dict; // Encoding dictionary
if ((encoding_obj = pdfioDictGetObj(pdfioObjGetDict(font_obj), "Encoding")) == NULL)
return;
if ((encoding_dict = pdfioObjGetDict(encoding_obj)) == NULL)
return;
```
Once we have the encoding dictionary we can get the "BaseEncoding" and
"Differences" values:
```c
const char *base_encoding; // BaseEncoding name
pdfio_array_t *differences; // Differences array
// OK, have the encoding object, build the encoding using it...
base_encoding = pdfioDictGetName(encoding_dict, "BaseEncoding");
differences = pdfioDictGetArray(encoding_dict, "Differences");
```
If the base encoding is "MacRomainEncoding", we need to reset the upper 128
characters in the encoding array match it:
```c
if (base_encoding && !strcmp(base_encoding, "MacRomanEncoding"))
{
// Map upper 128
memcpy(encoding + 128, mac_roman, sizeof(mac_roman));
}
```
Then we loop through the differences array, keeping track of the current index
within the encoding array. A number indicates a new index while a name is the
Unicode glyph for the current index:
```c
typedef struct name_map_s
{
const char *name; // Character name
int unicode; // Unicode value
} name_map_t;
static name_map_t unicode_map[1051]; // List of glyph names
if (differences)
{
// Apply differences
size_t count = pdfioArrayGetSize(differences);
// Number of differences
const char *name; // Character name
size_t idx = 0; // Index in encoding array
for (i = 0; i < count; i ++)
{
switch (pdfioArrayGetType(differences, i))
{
case PDFIO_VALTYPE_NUMBER :
// Get the index of the next character...
idx = (size_t)pdfioArrayGetNumber(differences, i);
break;
case PDFIO_VALTYPE_NAME :
// Lookup name and apply to encoding...
if (idx < 0 || idx > 255)
break;
name = pdfioArrayGetName(differences, i);
for (j = 0; j < (sizeof(unicode_map) / sizeof(unicode_map[0])); j ++)
{
if (!strcmp(name, unicode_map[j].name))
{
encoding[idx] = unicode_map[j].unicode;
break;
}
}
idx ++;
break;
default :
// Do nothing for other values
break;
}
}
}
}
``` ```

View File

@ -14,8 +14,8 @@
# Common options # Common options
CFLAGS = -g $(CPPFLAGS) CFLAGS = -g $(CPPFLAGS)
#CFLAGS = -g -fsanitize=address $(CPPFLAGS) #CFLAGS = -g -fsanitize=address $(CPPFLAGS)
CPPFLAGS = -I.. $(shell PKG_CONFIG_PATH="..:$(PKG_CONFIG_PATH)" pkg-config pdfio --cflags) CPPFLAGS = -I.. -I/usr/local/include
LIBS = -L.. $(shell PKG_CONFIG_PATH="..:$(PKG_CONFIG_PATH)" pkg-config pdfio --libs) LIBS = -L.. -L/usr/local/lib -lpdfio -lz -lm
# Targets # Targets
@ -24,8 +24,7 @@ TARGETS = \
image2pdf \ image2pdf \
md2pdf \ md2pdf \
pdf2text \ pdf2text \
pdfioinfo \ pdfioinfo
pdfiomerge
# Make everything # Make everything
@ -62,10 +61,5 @@ pdfioinfo: pdfioinfo.c
$(CC) $(CFLAGS) -o $@ pdfioinfo.c $(LIBS) $(CC) $(CFLAGS) -o $@ pdfioinfo.c $(LIBS)
# pdfiomerge
pdfiomerge: pdfiomerge.c
$(CC) $(CFLAGS) -o $@ pdfiomerge.c $(LIBS)
# Common dependencies... # Common dependencies...
$(TARGETS): Makefile ../pdfio.h ../pdfio-content.h $(TARGETS): Makefile ../pdfio.h ../pdfio-content.h

View File

@ -1,7 +1,7 @@
// //
// Image example for PDFio. // Image example for PDFio.
// //
// Copyright © 2023-2025 by Michael R Sweet. // Copyright © 2023-2024 by Michael R Sweet.
// //
// Licensed under Apache License v2.0. See the file "LICENSE" for more // Licensed under Apache License v2.0. See the file "LICENSE" for more
// information. // information.
@ -22,8 +22,8 @@
bool // O - True on success, false on failure bool // O - True on success, false on failure
create_pdf_image_file( create_pdf_image_file(
const char *imagename, // I - Image filename
const char *pdfname, // I - PDF filename const char *pdfname, // I - PDF filename
const char *imagename, // I - Image filename
const char *caption) // I - Caption filename const char *caption) // I - Caption filename
{ {
pdfio_file_t *pdf; // PDF file pdfio_file_t *pdf; // PDF file
@ -36,15 +36,6 @@ create_pdf_image_file(
double tx, ty; // Position on page double tx, ty; // Position on page
// Default the caption...
if (!caption)
{
if ((caption = strrchr(imagename, '/')) != NULL)
caption ++;
else
caption = imagename;
}
// Create the PDF file... // Create the PDF file...
pdf = pdfioFileCreate(pdfname, /*version*/NULL, /*media_box*/NULL, pdf = pdfioFileCreate(pdfname, /*version*/NULL, /*media_box*/NULL,
/*crop_box*/NULL, /*error_cb*/NULL, /*crop_box*/NULL, /*error_cb*/NULL,

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,6 @@
#include <pdfio.h> #include <pdfio.h>
#include <time.h> #include <time.h>
#include <math.h>
// //
@ -26,26 +25,11 @@ main(int argc, // I - Number of command-line arguments
{ {
const char *filename; // PDF filename const char *filename; // PDF filename
pdfio_file_t *pdf; // PDF file pdfio_file_t *pdf; // PDF file
pdfio_dict_t *catalog; // Catalog dictionary const char *author; // Author name
const char *author, // Author name time_t creation_date; // Creation date
*creator, // Creator name struct tm *creation_tm; // Creation date/time information
*producer, // Producer name char creation_text[256]; // Creation date/time as a string
*title; // Title const char *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... // Get the filename from the command-line...
@ -64,14 +48,9 @@ main(int argc, // I - Number of command-line arguments
if (pdf == NULL) if (pdf == NULL)
return (1); return (1);
// Get the title, author, etc... // Get the title and author...
catalog = pdfioFileGetCatalog(pdf); author = pdfioFileGetAuthor(pdf);
author = pdfioFileGetAuthor(pdf); title = pdfioFileGetTitle(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... // Get the creation date and convert to a string...
if ((creation_date = pdfioFileGetCreationDate(pdf)) > 0) if ((creation_date = pdfioFileGetCreationDate(pdf)) > 0)
@ -84,76 +63,12 @@ main(int argc, // I - Number of command-line arguments
snprintf(creation_text, sizeof(creation_text), "-- not set --"); 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... // Print file information to stdout...
printf("%s:\n", filename); printf("%s:\n", filename);
printf(" Title: %s\n", title ? title : "-- not set --"); printf(" Title: %s\n", title ? title : "-- not set --");
printf(" Author: %s\n", author ? author : "-- not set --"); printf(" Author: %s\n", author ? author : "-- not set --");
printf(" Creator: %s\n", creator ? creator : "-- not set --"); printf(" Created On: %s\n", creation_text);
printf(" Producer: %s\n", producer ? producer : "-- not set --"); printf(" Number Pages: %u\n", (unsigned)pdfioFileGetNumPages(pdf));
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... // Close the PDF file...
pdfioFileClose(pdf); pdfioFileClose(pdf);

View File

@ -1,146 +0,0 @@
//
// 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

@ -21,60 +21,40 @@ if test $# != 1; then
exit 1 exit 1
fi fi
status=0
version=$1 version=$1
version_major=$(echo $1 | awk -F. '{print $1}')
version_minor=$(echo $1 | awk -F. '{print $2}')
# Check that version number has been updated everywhere... # Check that version number has been updated everywhere...
if test $(grep AC_INIT configure.ac | awk '{print $2}') != "[$version],"; then if test $(grep AC_INIT configure.ac | awk '{print $2}') != "[$version],"; then
echo "Still need to update AC_INIT version in 'configure.ac'." echo "Still need to update AC_INIT version in 'configure.ac'."
status=1 exit 1
fi fi
if test $(head -4 CHANGES.md | tail -1 | awk '{print $1}') != "v$version"; then if test $(head -4 CHANGES.md | tail -1 | awk '{print $1}') != "v$version"; then
echo "Still need to update CHANGES.md version number." echo "Still need to update CHANGES.md version number."
status=1 exit 1
fi fi
if test $(head -4 CHANGES.md | tail -1 | awk '{print $3}') = "YYYY-MM-DD"; then if test $(head -4 CHANGES.md | tail -1 | awk '{print $3}') = "YYYY-MM-DD"; then
echo "Still need to update CHANGES.md release date." echo "Still need to update CHANGES.md release date."
status=1 exit 1
fi fi
if test $(grep PDFIO_VERSION= configure | awk -F \" '{print $2}') != "$version"; then if test $(grep PDFIO_VERSION= configure | awk -F \" '{print $2}') != "$version"; then
echo "Still need to run 'autoconf -f'." echo "Still need to run 'autoconf -f'."
status=1 exit 1
fi fi
if test $(grep '<version>' pdfio_native.nuspec | sed -E -e '1,$s/^.*<version>([0-9.]+).*$/\1/') != "$version"; then 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'." echo "Still need to update version in 'pdfio_native.nuspec'."
status=1 exit 1
fi fi
if test $(grep '<version>' pdfio_native.redist.nuspec | sed -E -e '1,$s/^.*<version>([0-9.]+).*$/\1/') != "$version"; then 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'." echo "Still need to update version in 'pdfio_native.redist.nuspec'."
status=1 exit 1
fi fi
if test $(grep PDFIO_VERSION pdfio.h | awk -F \" '{print $2}') != "$version"; then if test $(grep PDFIO_VERSION pdfio.h | awk -F \" '{print $2}') != "$version"; then
echo "Still need to update PDFIO_VERSION in 'pdfio.h'." echo "Still need to update PDFIO_VERSION in 'pdfio.h'."
status=1
fi
if test $(grep PDFIO_VERSION_MAJOR pdfio.h | awk '{print $4}') != "$version_major"; then
echo "Still need to update PDFIO_VERSION_MAJOR in 'pdfio.h'."
status=1
fi
if test $(grep PDFIO_VERSION_MINOR pdfio.h | awk '{print $4}') != "$version_minor"; then
echo "Still need to update PDFIO_VERSION_MINOR in 'pdfio.h'."
status=1
fi
if test $(grep VERSION pdfio1.def | awk '{print $2}') != "$version_major.$version_minor"; then
echo "Still need to update VERSION in 'pdfio1.def'."
status=1
fi
if test $status = 1; then
exit 1 exit 1
fi fi

View File

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="libpng_native" version="1.6.30" targetFramework="native" />
<package id="libpng_native.redist" version="1.6.30" targetFramework="native" />
<package id="zlib_native" version="1.2.11" targetFramework="native" /> <package id="zlib_native" version="1.2.11" targetFramework="native" />
<package id="zlib_native.redist" version="1.2.11" targetFramework="native" /> <package id="zlib_native.redist" version="1.2.11" targetFramework="native" />
</packages> </packages>

View File

@ -1,7 +1,7 @@
// //
// AES functions for PDFio. // AES functions for PDFio.
// //
// Copyright © 2021-2025 by Michael R Sweet. // Copyright © 2021 by Michael R Sweet.
// //
// Licensed under Apache License v2.0. See the file "LICENSE" for more // Licensed under Apache License v2.0. See the file "LICENSE" for more
// information. // information.
@ -76,18 +76,18 @@ static const uint8_t Rcon[11] = // Round constants
// Local functions... // Local functions...
// //
static void add_round_key(size_t round, state_t *state, const uint8_t *round_key); static void AddRoundKey(size_t round, state_t *state, const uint8_t *RoundKey);
static void sub_bytes(state_t *state); static void SubBytes(state_t *state);
static void shift_rows(state_t *state); static void ShiftRows(state_t *state);
static uint8_t xtime(uint8_t x); static uint8_t xtime(uint8_t x);
static void mix_columns(state_t *state); static void MixColumns(state_t *state);
static uint8_t multiply(uint8_t x, uint8_t y); static uint8_t Multiply(uint8_t x, uint8_t y);
static void inv_mix_columns(state_t *state); static void InvMixColumns(state_t *state);
static void inv_sub_bytes(state_t *state); static void InvSubBytes(state_t *state);
static void inv_shift_rows(state_t *state); static void InvShiftRows(state_t *state);
static void cipher(state_t *state, const _pdfio_aes_t *ctx); static void Cipher(state_t *state, const _pdfio_aes_t *ctx);
static void inv_cipher(state_t *state, const _pdfio_aes_t *ctx); static void InvCipher(state_t *state, const _pdfio_aes_t *ctx);
static void xor_with_iv(uint8_t *buf, const uint8_t *Iv); static void XorWithIv(uint8_t *buf, const uint8_t *Iv);
// //
@ -106,6 +106,7 @@ _pdfioCryptoAESInit(
*rkptr, // Current round_key values *rkptr, // Current round_key values
*rkend, // End of round_key values *rkend, // End of round_key values
tempa[4]; // Used for the column/row operations tempa[4]; // Used for the column/row operations
// size_t roundlen = keylen + 24; // Length of round_key
size_t nwords = keylen / 4; // Number of 32-bit words in key size_t nwords = keylen / 4; // Number of 32-bit words in key
@ -187,8 +188,8 @@ _pdfioCryptoAESDecrypt(
while (len > 15) while (len > 15)
{ {
memcpy(next_iv, outbuffer, 16); memcpy(next_iv, outbuffer, 16);
inv_cipher((state_t *)outbuffer, ctx); InvCipher((state_t *)outbuffer, ctx);
xor_with_iv(outbuffer, ctx->iv); XorWithIv(outbuffer, ctx->iv);
memcpy(ctx->iv, next_iv, 16); memcpy(ctx->iv, next_iv, 16);
outbuffer += 16; outbuffer += 16;
len -= 16; len -= 16;
@ -230,8 +231,8 @@ _pdfioCryptoAESEncrypt(
while (len > 15) while (len > 15)
{ {
xor_with_iv(outbuffer, iv); XorWithIv(outbuffer, iv);
cipher((state_t*)outbuffer, ctx); Cipher((state_t*)outbuffer, ctx);
iv = outbuffer; iv = outbuffer;
outbuffer += 16; outbuffer += 16;
len -= 16; len -= 16;
@ -241,10 +242,10 @@ _pdfioCryptoAESEncrypt(
if (len > 0) if (len > 0)
{ {
// Pad the final buffer with (16 - len)... // Pad the final buffer with (16 - len)...
memset(outbuffer + len, (int)(16 - len), 16 - len); memset(outbuffer + len, 16 - len, 16 - len);
xor_with_iv(outbuffer, iv); XorWithIv(outbuffer, iv);
cipher((state_t*)outbuffer, ctx); Cipher((state_t*)outbuffer, ctx);
iv = outbuffer; iv = outbuffer;
outbytes += 16; outbytes += 16;
} }
@ -256,32 +257,24 @@ _pdfioCryptoAESEncrypt(
} }
// // This function adds the round key to state.
// 'add_round_key()' - Add the round key to state.
//
// The round key is added to the state by an XOR function. // The round key is added to the state by an XOR function.
//
static void static void
add_round_key(size_t round, // I - Which round AddRoundKey(size_t round, state_t *state, const uint8_t *RoundKey)
state_t *state, // I - Current state
const uint8_t *round_key) // I - Key
{ {
unsigned i; // Looping var unsigned i; // Looping var
uint8_t *sptr = (*state)[0]; // Pointer into state uint8_t *sptr = (*state)[0]; // Pointer into state
for (round_key += round * 16, i = 16; i > 0; i --, sptr ++, round_key ++) for (RoundKey += round * 16, i = 16; i > 0; i --, sptr ++, RoundKey ++)
*sptr ^= *round_key; *sptr ^= *RoundKey;
} }
// // The SubBytes Function Substitutes the values in the
// 'sub_bytes()' - Substitute the values in the state matrix with values in an S-box. // state matrix with values in an S-box.
//
static void static void
sub_bytes(state_t *state) // I - Current state SubBytes(state_t *state)
{ {
unsigned i; // Looping var unsigned i; // Looping var
uint8_t *sptr = (*state)[0]; // Pointer into state uint8_t *sptr = (*state)[0]; // Pointer into state
@ -291,16 +284,11 @@ sub_bytes(state_t *state) // I - Current state
*sptr = sbox[*sptr]; *sptr = sbox[*sptr];
} }
// The ShiftRows() function shifts the rows in the state to the left.
//
// 'shift_rows()' - Shift the rows in the state to the left.
//
// Each row is shifted with different offset. // Each row is shifted with different offset.
// Offset = Row number. So the first row is not shifted. // Offset = Row number. So the first row is not shifted.
//
static void static void
shift_rows(state_t *state) // I - Current state ShiftRows(state_t *state)
{ {
uint8_t *sptr = (*state)[0]; // Pointer into state uint8_t *sptr = (*state)[0]; // Pointer into state
uint8_t temp; // Temporary value uint8_t temp; // Temporary value
@ -331,29 +319,21 @@ shift_rows(state_t *state) // I - Current state
} }
// static uint8_t
// 'xtime()' - Compute the AES xtime function. xtime(uint8_t x)
//
static uint8_t // O - xtime(x)
xtime(uint8_t x) // I - Column value
{ {
return ((uint8_t)((x << 1) ^ ((x >> 7) * 0x1b))); return ((uint8_t)((x << 1) ^ ((x >> 7) * 0x1b)));
} }
// // MixColumns function mixes the columns of the state matrix
// 'mix_columns()' - Mix the columns of the state matrix.
//
static void static void
mix_columns(state_t *state) // I - Current state MixColumns(state_t *state)
{ {
unsigned i; // Looping var unsigned i; // Looping var
uint8_t *sptr = (*state)[0]; // Pointer into state uint8_t *sptr = (*state)[0]; // Pointer into state
uint8_t Tmp, Tm, t; // Temporary values uint8_t Tmp, Tm, t; // Temporary values
for (i = 4; i > 0; i --, sptr += 4) for (i = 4; i > 0; i --, sptr += 4)
{ {
t = sptr[0]; t = sptr[0];
@ -377,15 +357,11 @@ mix_columns(state_t *state) // I - Current state
} }
// // Multiply is used to multiply numbers in the field GF(2^8)
// 'multiply()' - Multiply numbers in the field GF(2^8)
//
// Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary // Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary
// The compiler seems to be able to vectorize the operation better this way. // The compiler seems to be able to vectorize the operation better this way.
// See https://github.com/kokke/tiny-AES-c/pull/34 // See https://github.com/kokke/tiny-AES-c/pull/34
// static uint8_t Multiply(uint8_t x, uint8_t y)
static uint8_t multiply(uint8_t x, uint8_t y)
{ {
return (((y & 1) * x) ^ return (((y & 1) * x) ^
((y>>1 & 1) * xtime(x)) ^ ((y>>1 & 1) * xtime(x)) ^
@ -395,15 +371,11 @@ static uint8_t multiply(uint8_t x, uint8_t y)
} }
// // MixColumns function mixes the columns of the state matrix.
// 'mix_columns()' - Mix the columns of the state matrix.
//
// The method used to multiply may be difficult to understand for the inexperienced. // The method used to multiply may be difficult to understand for the inexperienced.
// Please use the references to gain more information. // Please use the references to gain more information.
//
static void static void
inv_mix_columns(state_t *state) // I - Current state InvMixColumns(state_t *state)
{ {
unsigned i; // Looping var unsigned i; // Looping var
uint8_t *sptr = (*state)[0]; // Pointer into state uint8_t *sptr = (*state)[0]; // Pointer into state
@ -417,20 +389,18 @@ inv_mix_columns(state_t *state) // I - Current state
c = sptr[2]; c = sptr[2];
d = sptr[3]; d = sptr[3];
*sptr++ = multiply(a, 0x0e) ^ multiply(b, 0x0b) ^ multiply(c, 0x0d) ^ multiply(d, 0x09); *sptr++ = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09);
*sptr++ = multiply(a, 0x09) ^ multiply(b, 0x0e) ^ multiply(c, 0x0b) ^ multiply(d, 0x0d); *sptr++ = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d);
*sptr++ = multiply(a, 0x0d) ^ multiply(b, 0x09) ^ multiply(c, 0x0e) ^ multiply(d, 0x0b); *sptr++ = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b);
*sptr++ = multiply(a, 0x0b) ^ multiply(b, 0x0d) ^ multiply(c, 0x09) ^ multiply(d, 0x0e); *sptr++ = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e);
} }
} }
// // The SubBytes Function Substitutes the values in the
// 'sub_bytes()' - Substitute the values in the state matrix with values in an S-box. // state matrix with values in an S-box.
//
static void static void
inv_sub_bytes(state_t *state) // I - Current state InvSubBytes(state_t *state)
{ {
unsigned i; // Looping var unsigned i; // Looping var
uint8_t *sptr = (*state)[0]; // Pointer into state uint8_t *sptr = (*state)[0]; // Pointer into state
@ -441,12 +411,8 @@ inv_sub_bytes(state_t *state) // I - Current state
} }
//
// 'inv_shift_rows()' - Shift the rows in the state to the right.
//
static void static void
inv_shift_rows(state_t *state) // I - Current state InvShiftRows(state_t *state)
{ {
uint8_t *sptr = (*state)[0]; // Pointer into state uint8_t *sptr = (*state)[0]; // Pointer into state
uint8_t temp; // Temporary value uint8_t temp; // Temporary value
@ -477,52 +443,40 @@ inv_shift_rows(state_t *state) // I - Current state
} }
// // Cipher is the main function that encrypts the PlainText.
// 'cipher()' - Encrypt the PlainText.
//
static void static void
cipher(state_t *state, // I - Current state Cipher(state_t *state, const _pdfio_aes_t *ctx)
const _pdfio_aes_t *ctx) // I - AES context
{ {
size_t round = 0; // Current round size_t round = 0;
// Add the First round key to the state before starting the rounds. // Add the First round key to the state before starting the rounds.
add_round_key(0, state, ctx->round_key); AddRoundKey(0, state, ctx->round_key);
// There will be Nr rounds. // There will be Nr rounds.
// The first Nr-1 rounds are identical. // The first Nr-1 rounds are identical.
// These Nr rounds are executed in the loop below. // These Nr rounds are executed in the loop below.
// Last one without mix_columns() // Last one without MixColumns()
for (round = 1; round < ctx->round_size; round ++) for (round = 1; round < ctx->round_size; round ++)
{ {
sub_bytes(state); SubBytes(state);
shift_rows(state); ShiftRows(state);
mix_columns(state); MixColumns(state);
add_round_key(round, state, ctx->round_key); AddRoundKey(round, state, ctx->round_key);
} }
// Add round key to last round // Add round key to last round
sub_bytes(state); SubBytes(state);
shift_rows(state); ShiftRows(state);
add_round_key(ctx->round_size, state, ctx->round_key); AddRoundKey(ctx->round_size, state, ctx->round_key);
} }
//
// 'inv_cipher()' - Decrypt the CipherText.
//
static void static void
inv_cipher(state_t *state, // I - Current state InvCipher(state_t *state, const _pdfio_aes_t *ctx)
const _pdfio_aes_t *ctx) // I - AES context
{ {
size_t round; // Current round size_t round;
// Add the First round key to the state before starting the rounds. // Add the First round key to the state before starting the rounds.
add_round_key(ctx->round_size, state, ctx->round_key); AddRoundKey(ctx->round_size, state, ctx->round_key);
// There will be Nr rounds. // There will be Nr rounds.
// The first Nr-1 rounds are identical. // The first Nr-1 rounds are identical.
@ -530,25 +484,20 @@ inv_cipher(state_t *state, // I - Current state
// Last one without InvMixColumn() // Last one without InvMixColumn()
for (round = ctx->round_size - 1; ; round --) for (round = ctx->round_size - 1; ; round --)
{ {
inv_shift_rows(state); InvShiftRows(state);
inv_sub_bytes(state); InvSubBytes(state);
add_round_key(round, state, ctx->round_key); AddRoundKey(round, state, ctx->round_key);
if (round == 0) if (round == 0)
break; break;
inv_mix_columns(state); InvMixColumns(state);
} }
} }
//
// 'xor_with_iv()' - XOR a block with the initialization vector.
//
static void static void
xor_with_iv(uint8_t *buf, // I - Block XorWithIv(uint8_t *buf, const uint8_t *Iv)
const uint8_t *Iv) // I - Initialization vector
{ {
// 16-byte block... // 16-byte block...
*buf++ ^= *Iv++; *buf++ ^= *Iv++;

View File

@ -98,7 +98,7 @@ _pdfioFileFlush(pdfio_file_t *pdf) // I - PDF file
if (!write_buffer(pdf, pdf->buffer, (size_t)(pdf->bufptr - pdf->buffer))) if (!write_buffer(pdf, pdf->buffer, (size_t)(pdf->bufptr - pdf->buffer)))
return (false); return (false);
pdf->bufpos += (off_t)(pdf->bufptr - pdf->buffer); pdf->bufpos += pdf->bufptr - pdf->buffer;
} }
pdf->bufptr = pdf->buffer; pdf->bufptr = pdf->buffer;
@ -216,7 +216,7 @@ _pdfioFilePeek(pdfio_file_t *pdf, // I - PDF file
PDFIO_DEBUG("_pdfioFilePeek: Sliding buffer, total=%ld\n", (long)total); PDFIO_DEBUG("_pdfioFilePeek: Sliding buffer, total=%ld\n", (long)total);
memmove(pdf->buffer, pdf->bufptr, total); memmove(pdf->buffer, pdf->bufptr, total);
pdf->bufpos += (off_t)(pdf->bufptr - pdf->buffer); pdf->bufpos += pdf->bufptr - pdf->buffer;
pdf->bufptr = pdf->buffer; pdf->bufptr = pdf->buffer;
pdf->bufend = pdf->buffer + total; pdf->bufend = pdf->buffer + total;
@ -317,14 +317,14 @@ _pdfioFileRead(pdfio_file_t *pdf, // I - PDF file
// Advance current position in file as needed... // Advance current position in file as needed...
if (pdf->bufend) if (pdf->bufend)
{ {
pdf->bufpos += (off_t)(pdf->bufend - pdf->buffer); pdf->bufpos += pdf->bufend - pdf->buffer;
pdf->bufptr = pdf->bufend = NULL; pdf->bufptr = pdf->bufend = NULL;
} }
// Read directly from the file... // Read directly from the file...
if ((rbytes = read_buffer(pdf, bufptr, bytes)) > 0) if ((rbytes = read_buffer(pdf, bufptr, bytes)) > 0)
{ {
pdf->bufpos += (off_t)rbytes; pdf->bufpos += rbytes;
continue; continue;
} }
else if (rbytes < 0 && (errno == EINTR || errno == EAGAIN)) else if (rbytes < 0 && (errno == EINTR || errno == EAGAIN))
@ -361,14 +361,14 @@ _pdfioFileSeek(pdfio_file_t *pdf, // I - PDF file
// Adjust offset for relative seeks... // Adjust offset for relative seeks...
if (whence == SEEK_CUR) if (whence == SEEK_CUR)
{ {
offset += pdf->bufpos + (off_t)(pdf->bufptr - pdf->buffer); offset += pdf->bufpos + (pdf->bufptr - pdf->buffer);
whence = SEEK_SET; whence = SEEK_SET;
} }
if (pdf->mode == _PDFIO_MODE_READ) if (pdf->mode == _PDFIO_MODE_READ)
{ {
// Reading, see if we already have the data we need... // Reading, see if we already have the data we need...
if (whence != SEEK_END && offset >= pdf->bufpos && pdf->bufend && offset < (off_t)(pdf->bufpos + pdf->bufend - pdf->buffer)) if (whence != SEEK_END && offset >= pdf->bufpos && pdf->bufend && offset < (pdf->bufpos + pdf->bufend - pdf->buffer))
{ {
// Yes, seek within existing buffer... // Yes, seek within existing buffer...
pdf->bufptr = pdf->buffer + (offset - pdf->bufpos); pdf->bufptr = pdf->buffer + (offset - pdf->bufpos);

File diff suppressed because it is too large Load Diff

View File

@ -120,16 +120,15 @@ 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 pdfioContentTextMoveTo(pdfio_stream_t *st, double tx, double ty) _PDFIO_PUBLIC;
extern bool pdfioContentTextNewLine(pdfio_stream_t *st) _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 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; extern bool pdfioContentTextNewLineShowf(pdfio_stream_t *st, double ws, double cs, bool unicode, const char *format, ...) _PDFIO_PUBLIC _PDFIO_FORMAT(5,6);
extern bool pdfioContentTextNextLine(pdfio_stream_t *st) _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 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; extern bool pdfioContentTextShowf(pdfio_stream_t *st, bool unicode, const char *format, ...) _PDFIO_PUBLIC _PDFIO_FORMAT(3,4);
extern bool pdfioContentTextShowJustified(pdfio_stream_t *st, bool unicode, size_t num_fragments, const double *offsets, const char * const *fragments) _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... // Resource helpers...
extern pdfio_obj_t *pdfioFileCreateFontObjFromBase(pdfio_file_t *pdf, const char *name) _PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateFontObjFromBase(pdfio_file_t *pdf, const char *name) _PDFIO_PUBLIC;
extern pdfio_obj_t *pdfioFileCreateFontObjFromFile(pdfio_file_t *pdf, const char *filename, bool unicode) _PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateFontObjFromFile(pdfio_file_t *pdf, const char *filename, bool unicode) _PDFIO_PUBLIC;
extern pdfio_obj_t *pdfioFileCreateICCObjFromData(pdfio_file_t *pdf, const unsigned char *data, size_t datalen, size_t num_colors) _PDFIO_PUBLIC;
extern pdfio_obj_t *pdfioFileCreateICCObjFromFile(pdfio_file_t *pdf, const char *filename, size_t num_colors) _PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateICCObjFromFile(pdfio_file_t *pdf, const char *filename, size_t num_colors) _PDFIO_PUBLIC;
extern pdfio_obj_t *pdfioFileCreateImageObjFromData(pdfio_file_t *pdf, const unsigned char *data, size_t width, size_t height, size_t num_colors, pdfio_array_t *color_data, bool alpha, bool interpolate) _PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateImageObjFromData(pdfio_file_t *pdf, const unsigned char *data, size_t width, size_t height, size_t num_colors, pdfio_array_t *color_data, bool alpha, bool interpolate) _PDFIO_PUBLIC;
extern pdfio_obj_t *pdfioFileCreateImageObjFromFile(pdfio_file_t *pdf, const char *filename, bool interpolate) _PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateImageObjFromFile(pdfio_file_t *pdf, const char *filename, bool interpolate) _PDFIO_PUBLIC;

View File

@ -1,7 +1,7 @@
// //
// Cryptographic support functions for PDFio. // Cryptographic support functions for PDFio.
// //
// Copyright © 2021-2025 by Michael R Sweet. // Copyright © 2021-2023 by Michael R Sweet.
// //
// Licensed under Apache License v2.0. See the file "LICENSE" for more // Licensed under Apache License v2.0. See the file "LICENSE" for more
// information. // information.
@ -466,7 +466,6 @@ _pdfioCryptoMakeReader(
if (memcmp(pdf->password, pdf_user_key, 32) && memcmp(own_user_key, pdf_user_key, 16)) if (memcmp(pdf->password, pdf_user_key, 32) && memcmp(own_user_key, pdf_user_key, 16))
{ {
_pdfioFileError(pdf, "Unable to unlock file.");
*ivlen = 0; *ivlen = 0;
return (NULL); return (NULL);
} }
@ -484,7 +483,6 @@ _pdfioCryptoMakeReader(
switch (pdf->encryption) switch (pdf->encryption)
{ {
default : default :
_pdfioFileError(pdf, "Unsupported encryption algorithm.");
*ivlen = 0; *ivlen = 0;
return (NULL); return (NULL);

View File

@ -1,7 +1,7 @@
// //
// PDF dictionary functions for PDFio. // PDF dictionary functions for PDFio.
// //
// Copyright © 2021-2025 by Michael R Sweet. // Copyright © 2021-2024 by Michael R Sweet.
// //
// Licensed under Apache License v2.0. See the file "LICENSE" for more // Licensed under Apache License v2.0. See the file "LICENSE" for more
// information. // information.
@ -469,7 +469,7 @@ pdfioDictGetString(pdfio_dict_t *dict, // I - Dictionary
*tempptr; // Pointer into temporary string *tempptr; // Pointer into temporary string
unsigned char *dataptr; // Pointer into the data string unsigned char *dataptr; // Pointer into the data string
if (!(value->value.binary.datalen & 1) && !memcmp(value->value.binary.data, "\376\377", 2)) if (!(value->value.binary.datalen & 1) && !memcmp(value->value.binary.data, "\377\376", 2))
{ {
// Copy UTF-16 BE // Copy UTF-16 BE
int ch; // Unicode character int ch; // Unicode character
@ -528,7 +528,7 @@ pdfioDictGetString(pdfio_dict_t *dict, // I - Dictionary
*tempptr = '\0'; *tempptr = '\0';
} }
else if (!(value->value.binary.datalen & 1) && !memcmp(value->value.binary.data, "\377\376", 2)) else if (!(value->value.binary.datalen & 1) && !memcmp(value->value.binary.data, "\376\377", 2))
{ {
// Copy UTF-16 LE // Copy UTF-16 LE
int ch; // Unicode character int ch; // Unicode character
@ -737,6 +737,11 @@ _pdfioDictRead(pdfio_file_t *pdf, // I - PDF file
_pdfioFileError(pdf, "Invalid dictionary contents."); _pdfioFileError(pdf, "Invalid dictionary contents.");
break; break;
} }
else if (_pdfioDictGetValue(dict, key + 1))
{
_pdfioFileError(pdf, "Duplicate dictionary key '%s'.", key + 1);
return (NULL);
}
// Then get the next value... // Then get the next value...
PDFIO_DEBUG("_pdfioDictRead: Reading value for '%s'.\n", key + 1); PDFIO_DEBUG("_pdfioDictRead: Reading value for '%s'.\n", key + 1);
@ -746,15 +751,8 @@ _pdfioDictRead(pdfio_file_t *pdf, // I - PDF file
_pdfioFileError(pdf, "Missing value for dictionary key '%s'.", key + 1); _pdfioFileError(pdf, "Missing value for dictionary key '%s'.", key + 1);
break; break;
} }
else if (_pdfioDictGetValue(dict, key + 1))
{ if (!_pdfioDictSetValue(dict, pdfioStringCreate(pdf, key + 1), &value))
// Issue 118: Discard duplicate key/value pairs, in the future this will
// be a warning message...
_pdfioFileError(pdf, "WARNING: Discarding value for duplicate dictionary key '%s'.", key + 1);
_pdfioValueDelete(&value);
continue;
}
else if (!_pdfioDictSetValue(dict, pdfioStringCreate(pdf, key + 1), &value))
break; break;
PDFIO_DEBUG("_pdfioDictRead: Set %s.\n", key); PDFIO_DEBUG("_pdfioDictRead: Set %s.\n", key);
@ -1170,7 +1168,7 @@ _pdfioDictWrite(pdfio_dict_t *dict, // I - Dictionary
// Write all of the key/value pairs... // Write all of the key/value pairs...
for (i = dict->num_pairs, pair = dict->pairs; i > 0; i --, pair ++) for (i = dict->num_pairs, pair = dict->pairs; i > 0; i --, pair ++)
{ {
if (!_pdfioFilePrintf(pdf, "%N", pair->key)) if (!_pdfioFilePrintf(pdf, "/%s", pair->key))
return (false); return (false);
if (length && !strcmp(pair->key, "Length") && pair->value.type == PDFIO_VALTYPE_NUMBER && pair->value.value.number <= 0.0) if (length && !strcmp(pair->key, "Length") && pair->value.type == PDFIO_VALTYPE_NUMBER && pair->value.value.number <= 0.0)

View File

@ -25,7 +25,6 @@ static struct lconv *get_lconv(void);
static bool load_obj_stream(pdfio_obj_t *obj); 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_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 load_xref(pdfio_file_t *pdf, off_t xref_offset, pdfio_password_cb_t password_cb, void *password_data);
static bool repair_xref(pdfio_file_t *pdf, pdfio_password_cb_t password_cb, void *password_data);
static bool write_pages(pdfio_file_t *pdf); static bool write_pages(pdfio_file_t *pdf);
static bool write_trailer(pdfio_file_t *pdf); static bool write_trailer(pdfio_file_t *pdf);
@ -110,8 +109,6 @@ pdfioFileClose(pdfio_file_t *pdf) // I - PDF file
{ {
bool ret = true; // Return value bool ret = true; // Return value
size_t i; // Looping var size_t i; // Looping var
_pdfio_strbuf_t *current, // Current string buffer
*next; // Next string buffer
// Range check input // Range check input
@ -154,12 +151,6 @@ pdfioFileClose(pdfio_file_t *pdf) // I - PDF file
free(pdf->strings[i]); free(pdf->strings[i]);
free(pdf->strings); free(pdf->strings);
for (current = pdf->strbuffers; current; current = next)
{
next = current->next;
free(current);
}
free(pdf); free(pdf);
return (ret); return (ret);
@ -173,8 +164,7 @@ pdfioFileClose(pdfio_file_t *pdf) // I - PDF file
// name of the PDF file to create. // name of the PDF file to create.
// //
// The "version" argument specifies the PDF version number for the file or // The "version" argument specifies the PDF version number for the file or
// `NULL` for the default ("2.0"). The value "PCLm-1.0" can be specified to // `NULL` for the default ("2.0").
// produce the PCLm subset of PDF.
// //
// The "media_box" and "crop_box" arguments specify the default MediaBox and // The "media_box" and "crop_box" arguments specify the default MediaBox and
// CropBox for pages in the PDF file - if `NULL` then a default "Universal" size // CropBox for pages in the PDF file - if `NULL` then a default "Universal" size
@ -406,9 +396,7 @@ _pdfioFileCreateObj(
// ``` // ```
// //
// The "version" argument specifies the PDF version number for the file or // The "version" argument specifies the PDF version number for the file or
// `NULL` for the default ("2.0"). Unlike @link pdfioFileCreate@ and // `NULL` for the default ("2.0").
// @link pdfioFileCreateTemporary@, it is generally not safe to pass the
// "PCLm-1.0" version string.
// //
// The "media_box" and "crop_box" arguments specify the default MediaBox and // The "media_box" and "crop_box" arguments specify the default MediaBox and
// CropBox for pages in the PDF file - if `NULL` then a default "Universal" size // CropBox for pages in the PDF file - if `NULL` then a default "Universal" size
@ -543,19 +531,8 @@ pdfioFileCreateStringObj(
// //
// This function creates a PDF file with a unique filename in the current // This function creates a PDF file with a unique filename in the current
// temporary directory. The temporary file is stored in the string "buffer" an // temporary directory. The temporary file is stored in the string "buffer" an
// will have a ".pdf" extension. // will have a ".pdf" extension. Otherwise, this function works the same as
// // the @link pdfioFileCreate@ function.
// The "version" argument specifies the PDF version number for the file or
// `NULL` for the default ("2.0"). The value "PCLm-1.0" can be specified to
// produce the PCLm subset of PDF.
//
// The "media_box" and "crop_box" arguments specify the default MediaBox and
// CropBox for pages in the PDF file - if `NULL` then a default "Universal" size
// of 8.27x11in (the intersection of US Letter and ISO A4) is used.
//
// 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`.
// //
// @since PDFio v1.1@ // @since PDFio v1.1@
// //
@ -824,18 +801,6 @@ pdfioFileGetKeywords(pdfio_file_t *pdf) // I - PDF file
} }
//
// 'pdfioFileGetModificationDate()' - Get the most recent modification date for a PDF file.
//
time_t // O - Modification date or `0` for none
pdfioFileGetModificationDate(
pdfio_file_t *pdf) // I - PDF file
{
return (pdf && pdf->info_obj ? pdfioDictGetDate(pdfioObjGetDict(pdf->info_obj), "ModDate") : 0);
}
// //
// 'pdfioFileGetName()' - Get a PDF's filename. // 'pdfioFileGetName()' - Get a PDF's filename.
// //
@ -1093,10 +1058,7 @@ pdfioFileOpen(
xref_offset = (off_t)strtol(ptr + 9, NULL, 10); xref_offset = (off_t)strtol(ptr + 9, NULL, 10);
if (!load_xref(pdf, xref_offset, password_cb, password_cbdata)) if (!load_xref(pdf, xref_offset, password_cb, password_cbdata))
{ goto error;
if (!repair_xref(pdf, password_cb, password_cbdata))
goto error;
}
return (pdf); return (pdf);
@ -1164,20 +1126,6 @@ pdfioFileSetKeywords(
} }
//
// 'pdfioFileSetModificationDate()' - Set the modification date for a PDF file.
//
void
pdfioFileSetModificationDate(
pdfio_file_t *pdf, // I - PDF file
time_t value) // I - Value
{
if (pdf && pdf->info_obj)
pdfioDictSetDate(pdf->info_obj->value.value.dict, "ModDate", value);
}
// //
// 'pdfioFileSetPermissions()' - Set the PDF permissions, encryption mode, and passwords. // 'pdfioFileSetPermissions()' - Set the PDF permissions, encryption mode, and passwords.
// //
@ -1418,7 +1366,7 @@ create_common(
pdf->output_cb = output_cb; pdf->output_cb = output_cb;
pdf->output_ctx = output_cbdata; pdf->output_ctx = output_cbdata;
pdf->filename = strdup(filename); pdf->filename = strdup(filename);
pdf->version = strdup(!strncmp(version, "PCLm-", 5) ? "1.4" : version); pdf->version = strdup(version);
pdf->mode = _PDFIO_MODE_WRITE; pdf->mode = _PDFIO_MODE_WRITE;
pdf->error_cb = error_cb; pdf->error_cb = error_cb;
pdf->error_data = error_cbdata; pdf->error_data = error_cbdata;
@ -1449,15 +1397,8 @@ create_common(
} }
// Write a standard PDF header... // Write a standard PDF header...
if (!strncmp(version, "PCLm-", 5)) if (!_pdfioFilePrintf(pdf, "%%PDF-%s\n%%\342\343\317\323\n", version))
{
if (!_pdfioFilePrintf(pdf, "%%PDF-1.4\n%%%s\n", version))
goto error;
}
else if (!_pdfioFilePrintf(pdf, "%%PDF-%s\n%%\342\343\317\323\n", version))
{
goto error; goto error;
}
// Create the pages object... // Create the pages object...
if ((dict = pdfioDictCreate(pdf)) == NULL) if ((dict = pdfioDictCreate(pdf)) == NULL)
@ -1807,7 +1748,7 @@ load_xref(
return (false); return (false);
} }
if (_pdfioFileSeek(pdf, line_offset + (off_t)(ptr + 3 - line), SEEK_SET) < 0) if (_pdfioFileSeek(pdf, line_offset + ptr + 3 - line, SEEK_SET) < 0)
{ {
_pdfioFileError(pdf, "Unable to seek to xref object %lu %u.", (unsigned long)number, (unsigned)generation); _pdfioFileError(pdf, "Unable to seek to xref object %lu %u.", (unsigned long)number, (unsigned)generation);
return (false); return (false);
@ -1949,7 +1890,7 @@ load_xref(
if (w[0] == 0 || buffer[0] == 1) if (w[0] == 0 || buffer[0] == 1)
{ {
// Location of object... // Location of object...
current->offset = (off_t)offset; current->offset = offset;
} }
else if (number != offset) else if (number != offset)
{ {
@ -1986,7 +1927,7 @@ load_xref(
else if (!current) else if (!current)
{ {
// Add this object... // Add this object...
if (!add_obj(pdf, (size_t)number, (unsigned short)generation, (off_t)offset)) if (!add_obj(pdf, (size_t)number, (unsigned short)generation, offset))
return (false); return (false);
} }
@ -2113,7 +2054,7 @@ load_xref(
if (pdfioFileFindObj(pdf, (size_t)number)) if (pdfioFileFindObj(pdf, (size_t)number))
continue; // Don't replace newer object... continue; // Don't replace newer object...
if (!add_obj(pdf, (size_t)number, (unsigned short)generation, (off_t)offset)) if (!add_obj(pdf, (size_t)number, (unsigned short)generation, offset))
return (false); return (false);
} }
@ -2198,162 +2139,6 @@ load_xref(
} }
//
// 'repair_xref()' - Try to "repair" a PDF file and its cross-references...
//
static bool // O - `true` on success, `false` on failure
repair_xref(
pdfio_file_t *pdf, // I - PDF file
pdfio_password_cb_t password_cb, // I - Password callback or `NULL` for none
void *password_data) // I - Password callback data, if any
{
char line[16384], // Line from file
*ptr; // Pointer into line
off_t line_offset; // Offset in file
intmax_t number; // Object number
int generation; // Generation number
size_t i; // Looping var
size_t num_sobjs = 0; // Number of object streams
pdfio_obj_t *sobjs[16384]; // Object streams to load
// Let caller know something is wrong...
_pdfioFileError(pdf, "WARNING: Cross-reference table is damaged, attempting to rebuild.");
// Read from the beginning of the file, looking for
if ((line_offset = _pdfioFileSeek(pdf, 0, SEEK_SET)) < 0)
return (false);
while (_pdfioFileGets(pdf, line, sizeof(line)))
{
// See if this is the start of an object...
if (line[0] >= '1' && line[0] <= '9')
{
// Maybe, look some more...
if ((number = strtoimax(line, &ptr, 10)) >= 1 && (generation = (int)strtol(ptr, &ptr, 10)) >= 0 && generation < 65536)
{
while (isspace(*ptr & 255))
ptr ++;
if (!strncmp(ptr, "obj", 3))
{
// Yes, start of an object...
pdfio_obj_t *obj; // Object
_pdfio_token_t tb; // Token buffer/stack
PDFIO_DEBUG("OBJECT %ld %d at offset %ld\n", (long)number, generation, (long)line_offset);
if ((obj = add_obj(pdf, (size_t)number, (unsigned short)generation, line_offset)) == NULL)
{
_pdfioFileError(pdf, "Unable to allocate memory for object.");
return (false);
}
_pdfioTokenInit(&tb, pdf, (_pdfio_tconsume_cb_t)_pdfioFileConsume, (_pdfio_tpeek_cb_t)_pdfioFilePeek, pdf);
if (!_pdfioValueRead(pdf, obj, &tb, &obj->value, 0))
{
_pdfioFileError(pdf, "Unable to read cross-reference stream dictionary.");
return (false);
}
if (_pdfioTokenGet(&tb, line, sizeof(line)) && strcmp(line, "stream"))
{
const char *type = pdfioObjGetType(obj);
// Object type
_pdfioTokenFlush(&tb);
obj->stream_offset = _pdfioFileTell(pdf);
if (type && !strcmp(type, "ObjStm") && num_sobjs < (sizeof(sobjs) / sizeof(sobjs[0])))
{
sobjs[num_sobjs] = obj;
num_sobjs ++;
}
if (type && !strcmp(type, "XRef") && !pdf->trailer_dict)
{
// Save the trailer dictionary...
pdf->trailer_dict = pdfioObjGetDict(obj);
pdf->encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt");
pdf->id_array = pdfioDictGetArray(pdf->trailer_dict, "ID");
}
}
}
}
}
else if (!strncmp(line, "trailer", 7) && (!line[7] || isspace(line[7] & 255) || line[7] == '<'))
{
// Trailer dictionary
_pdfio_token_t tb; // Token buffer/stack
_pdfio_value_t trailer; // Trailer
if (line[7])
{
// Probably the start of the trailer dictionary, rewind the file so
// we can read it...
_pdfioFileSeek(pdf, line_offset + 7, SEEK_SET);
}
PDFIO_DEBUG("TRAILER at offset %ld\n", (long)line_offset);
_pdfioTokenInit(&tb, pdf, (_pdfio_tconsume_cb_t)_pdfioFileConsume, (_pdfio_tpeek_cb_t)_pdfioFilePeek, pdf);
if (!_pdfioValueRead(pdf, NULL, &tb, &trailer, 0))
{
_pdfioFileError(pdf, "Unable to read cross-reference stream dictionary.");
return (false);
}
else if (trailer.type != PDFIO_VALTYPE_DICT)
{
_pdfioFileError(pdf, "Trailer is not a dictionary.");
return (false);
}
_pdfioTokenFlush(&tb);
if (!pdf->trailer_dict)
{
// Save the trailer dictionary and grab the root (catalog) and info
// objects...
pdf->trailer_dict = trailer.value.dict;
pdf->encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt");
pdf->id_array = pdfioDictGetArray(pdf->trailer_dict, "ID");
}
}
// Get the offset for the next line...
line_offset = _pdfioFileTell(pdf);
}
// If the trailer contains an Encrypt key, try unlocking the file...
if (pdf->encrypt_obj && !_pdfioCryptoUnlock(pdf, password_cb, password_data))
return (false);
// Load any stream objects...
for (i = 0; i < num_sobjs; i ++)
{
if (!load_obj_stream(sobjs[i]))
return (false);
}
// Once we have all of the xref tables loaded, get the important objects and
// build the pages array...
pdf->info_obj = pdfioDictGetObj(pdf->trailer_dict, "Info");
if ((pdf->root_obj = pdfioDictGetObj(pdf->trailer_dict, "Root")) == NULL)
{
_pdfioFileError(pdf, "Missing Root object.");
return (false);
}
PDFIO_DEBUG("repair_xref: Root=%p(%lu)\n", pdf->root_obj, (unsigned long)pdf->root_obj->number);
// Load pages...
return (load_pages(pdf, pdfioDictGetObj(pdfioObjGetDict(pdf->root_obj), "Pages"), 0));
}
// //
// 'write_pages()' - Write the PDF pages objects. // 'write_pages()' - Write the PDF pages objects.
// //
@ -2372,7 +2157,7 @@ write_pages(pdfio_file_t *pdf) // I - PDF file
for (i = 0; i < pdf->num_pages; i ++) for (i = 0; i < pdf->num_pages; i ++)
pdfioArrayAppendObj(kids, pdf->pages[i]); pdfioArrayAppendObj(kids, pdf->pages[i]);
pdfioDictSetNumber(pdf->pages_obj->value.value.dict, "Count", (double)pdf->num_pages); pdfioDictSetNumber(pdf->pages_obj->value.value.dict, "Count", pdf->num_pages);
pdfioDictSetArray(pdf->pages_obj->value.value.dict, "Kids", kids); pdfioDictSetArray(pdf->pages_obj->value.value.dict, "Kids", kids);
// Write the Pages object... // Write the Pages object...
@ -2390,221 +2175,59 @@ write_trailer(pdfio_file_t *pdf) // I - PDF file
bool ret = true; // Return value bool ret = true; // Return value
off_t xref_offset; // Offset to xref table off_t xref_offset; // Offset to xref table
size_t i; // Looping var size_t i; // Looping var
pdfio_obj_t *obj; // Current object
// Write the xref table... // Write the xref table...
// TODO: Look at adding support for xref streams...
xref_offset = _pdfioFileTell(pdf); xref_offset = _pdfioFileTell(pdf);
if (strcmp(pdf->version, "1.5") >= 0 && !pdf->output_cb) if (!_pdfioFilePrintf(pdf, "xref\n0 %lu \n0000000000 65535 f \n", (unsigned long)pdf->num_objs + 1))
{ {
// Write a cross-reference stream... _pdfioFileError(pdf, "Unable to write cross-reference table.");
pdfio_dict_t *xref_dict; // Object dictionary ret = false;
pdfio_array_t *w_array; // W array goto done;
pdfio_obj_t *xref_obj; // Object
pdfio_stream_t *xref_st; // Stream
int offsize; // Size of object offsets
unsigned char buffer[10]; // Buffer entry
pdfio_encryption_t encryption; // PDF encryption mode
// Disable encryption while we write the xref stream...
encryption = pdf->encryption;
pdf->encryption = PDFIO_ENCRYPTION_NONE;
// Figure out how many bytes are needed for the object numbers
if (xref_offset < 0xff)
offsize = 1;
else if (xref_offset < 0xffff)
offsize = 2;
else if (xref_offset < 0xffffff)
offsize = 3;
else if (xref_offset < 0xffffffff)
offsize = 4;
else if (xref_offset < 0xffffffffff)
offsize = 5;
else if (xref_offset < 0xffffffffffff)
offsize = 6;
else if (xref_offset < 0xffffffffffffff)
offsize = 7;
else
offsize = 8;
// Create the object...
if ((w_array = pdfioArrayCreate(pdf)) == NULL)
{
_pdfioFileError(pdf, "Unable to write cross-reference table.");
ret = false;
goto done;
}
pdfioArrayAppendNumber(w_array, 1);
pdfioArrayAppendNumber(w_array, offsize);
pdfioArrayAppendNumber(w_array, 1);
if ((xref_dict = pdfioDictCreate(pdf)) == NULL)
{
_pdfioFileError(pdf, "Unable to write cross-reference table.");
ret = false;
goto done;
}
pdfioDictSetName(xref_dict, "Type", "XRef");
pdfioDictSetNumber(xref_dict, "Size", (double)(pdf->num_objs + 2));
pdfioDictSetArray(xref_dict, "W", w_array);
pdfioDictSetName(xref_dict, "Filter", "FlateDecode");
pdfioDictSetObj(xref_dict, "Info", pdf->info_obj);
pdfioDictSetObj(xref_dict, "Root", pdf->root_obj);
if (pdf->encrypt_obj)
pdfioDictSetObj(xref_dict, "Encrypt", pdf->encrypt_obj);
if (pdf->id_array)
pdfioDictSetArray(xref_dict, "ID", pdf->id_array);
if ((xref_obj = pdfioFileCreateObj(pdf, xref_dict)) == NULL)
{
_pdfioFileError(pdf, "Unable to write cross-reference table.");
ret = false;
goto done;
}
if ((xref_st = pdfioObjCreateStream(xref_obj, PDFIO_FILTER_FLATE)) == NULL)
{
_pdfioFileError(pdf, "Unable to write cross-reference table.");
ret = false;
goto done;
}
// Write the "free" 0 object...
memset(buffer, 0, sizeof(buffer));
pdfioStreamWrite(xref_st, buffer, offsize + 2);
// Then write the "allocated" objects...
buffer[0] = 1;
for (i = 0; i < pdf->num_objs; i ++)
{
obj = pdf->objs[i]; // Current object
switch (offsize)
{
case 1 :
buffer[1] = obj->offset & 255;
break;
case 2 :
buffer[1] = (obj->offset >> 8) & 255;
buffer[2] = obj->offset & 255;
break;
case 3 :
buffer[1] = (obj->offset >> 16) & 255;
buffer[2] = (obj->offset >> 8) & 255;
buffer[3] = obj->offset & 255;
break;
case 4 :
buffer[1] = (obj->offset >> 24) & 255;
buffer[2] = (obj->offset >> 16) & 255;
buffer[3] = (obj->offset >> 8) & 255;
buffer[4] = obj->offset & 255;
break;
case 5 :
buffer[1] = (obj->offset >> 32) & 255;
buffer[2] = (obj->offset >> 24) & 255;
buffer[3] = (obj->offset >> 16) & 255;
buffer[4] = (obj->offset >> 8) & 255;
buffer[5] = obj->offset & 255;
break;
case 6 :
buffer[1] = (obj->offset >> 40) & 255;
buffer[2] = (obj->offset >> 32) & 255;
buffer[3] = (obj->offset >> 24) & 255;
buffer[4] = (obj->offset >> 16) & 255;
buffer[5] = (obj->offset >> 8) & 255;
buffer[6] = obj->offset & 255;
break;
case 7 :
buffer[1] = (obj->offset >> 48) & 255;
buffer[2] = (obj->offset >> 40) & 255;
buffer[3] = (obj->offset >> 32) & 255;
buffer[4] = (obj->offset >> 24) & 255;
buffer[5] = (obj->offset >> 16) & 255;
buffer[6] = (obj->offset >> 8) & 255;
buffer[7] = obj->offset & 255;
break;
default :
buffer[1] = (obj->offset >> 56) & 255;
buffer[2] = (obj->offset >> 48) & 255;
buffer[3] = (obj->offset >> 40) & 255;
buffer[4] = (obj->offset >> 32) & 255;
buffer[5] = (obj->offset >> 24) & 255;
buffer[6] = (obj->offset >> 16) & 255;
buffer[7] = (obj->offset >> 8) & 255;
buffer[8] = obj->offset & 255;
break;
}
if (!pdfioStreamWrite(xref_st, buffer, offsize + 2))
{
_pdfioFileError(pdf, "Unable to write cross-reference table.");
ret = false;
goto done;
}
}
pdfioStreamClose(xref_st);
pdf->encryption = encryption;
} }
else
for (i = 0; i < pdf->num_objs; i ++)
{ {
// Write a cross-reference table... pdfio_obj_t *obj = pdf->objs[i]; // Current object
if (!_pdfioFilePrintf(pdf, "xref\n0 %lu \n0000000000 65535 f \n", (unsigned long)pdf->num_objs + 1))
if (!_pdfioFilePrintf(pdf, "%010lu %05u n \n", (unsigned long)obj->offset, obj->generation))
{ {
_pdfioFileError(pdf, "Unable to write cross-reference table."); _pdfioFileError(pdf, "Unable to write cross-reference table.");
ret = false; ret = false;
goto done; goto done;
} }
}
for (i = 0; i < pdf->num_objs; i ++) // Write the trailer...
{ if (!_pdfioFilePuts(pdf, "trailer\n"))
obj = pdf->objs[i]; // Current object {
_pdfioFileError(pdf, "Unable to write trailer.");
ret = false;
goto done;
}
if (!_pdfioFilePrintf(pdf, "%010lu %05u n \n", (unsigned long)obj->offset, obj->generation)) if ((pdf->trailer_dict = pdfioDictCreate(pdf)) == NULL)
{ {
_pdfioFileError(pdf, "Unable to write cross-reference table."); _pdfioFileError(pdf, "Unable to create trailer.");
ret = false; ret = false;
goto done; goto done;
} }
}
// Write the trailer... if (pdf->encrypt_obj)
if (!_pdfioFilePuts(pdf, "trailer\n")) pdfioDictSetObj(pdf->trailer_dict, "Encrypt", pdf->encrypt_obj);
{ if (pdf->id_array)
_pdfioFileError(pdf, "Unable to write trailer."); pdfioDictSetArray(pdf->trailer_dict, "ID", pdf->id_array);
ret = false; pdfioDictSetObj(pdf->trailer_dict, "Info", pdf->info_obj);
goto done; pdfioDictSetObj(pdf->trailer_dict, "Root", pdf->root_obj);
} pdfioDictSetNumber(pdf->trailer_dict, "Size", pdf->num_objs + 1);
if ((pdf->trailer_dict = pdfioDictCreate(pdf)) == NULL) if (!_pdfioDictWrite(pdf->trailer_dict, NULL, NULL))
{ {
_pdfioFileError(pdf, "Unable to create trailer."); _pdfioFileError(pdf, "Unable to write trailer.");
ret = false; ret = false;
goto done; goto done;
}
if (pdf->encrypt_obj)
pdfioDictSetObj(pdf->trailer_dict, "Encrypt", pdf->encrypt_obj);
if (pdf->id_array)
pdfioDictSetArray(pdf->trailer_dict, "ID", pdf->id_array);
pdfioDictSetObj(pdf->trailer_dict, "Info", pdf->info_obj);
pdfioDictSetObj(pdf->trailer_dict, "Root", pdf->root_obj);
pdfioDictSetNumber(pdf->trailer_dict, "Size", (double)(pdf->num_objs + 1));
if (!_pdfioDictWrite(pdf->trailer_dict, NULL, NULL))
{
_pdfioFileError(pdf, "Unable to write trailer.");
ret = false;
goto done;
}
} }
if (!_pdfioFilePrintf(pdf, "\nstartxref\n%lu\n%%EOF\n", (unsigned long)xref_offset)) if (!_pdfioFilePrintf(pdf, "\nstartxref\n%lu\n%%EOF\n", (unsigned long)xref_offset))

View File

@ -1,7 +1,7 @@
// //
// MD5 functions for PDFio. // MD5 functions for PDFio.
// //
// Copyright © 2021-2025 by Michael R Sweet. // Copyright © 2021 by Michael R Sweet.
// Copyright © 1999 Aladdin Enterprises. All rights reserved. // Copyright © 1999 Aladdin Enterprises. All rights reserved.
// //
// This software is provided 'as-is', without any express or implied // This software is provided 'as-is', without any express or implied
@ -108,285 +108,231 @@
#define T63 0x2ad7d2bb #define T63 0x2ad7d2bb
#define T64 0xeb86d391 #define T64 0xeb86d391
//
// Use the unoptimized (big-endian) implementation if we don't know the
// endian-ness of the platform.
//
#ifdef __BYTE_ORDER__
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define ARCH_IS_BIG_ENDIAN 0 // Use little endian optimized version
# else
# define ARCH_IS_BIG_ENDIAN 1 // Use generic version
# endif // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#elif !defined(ARCH_IS_BIG_ENDIAN)
# define ARCH_IS_BIG_ENDIAN 1 // Use generic version
#endif // !ARCH_IS_BIG_ENDIAN
//
// 'md5_process()' - Hash a block of data.
//
static void static void
md5_process(_pdfio_md5_t *pms, // I - MD5 state md5_process(_pdfio_md5_t *pms, const uint8_t *data /*[64]*/)
const uint8_t *data/*[64]*/)// I - Data
{ {
uint32_t a = pms->abcd[0], // First word of state uint32_t
b = pms->abcd[1], // Second word of state a = pms->abcd[0], b = pms->abcd[1],
c = pms->abcd[2], // Third word of state c = pms->abcd[2], d = pms->abcd[3];
d = pms->abcd[3]; // Fourth word of state uint32_t t;
uint32_t t; // Temporary state
#ifndef ARCH_IS_BIG_ENDIAN
# define ARCH_IS_BIG_ENDIAN 1 /* slower, default implementation */
#endif
#if ARCH_IS_BIG_ENDIAN #if ARCH_IS_BIG_ENDIAN
// On big-endian machines, we must arrange the bytes in the right
// order. (This also works on machines of unknown byte order.)
uint32_t X[16]; // Little-endian representation
const uint8_t *xp; // Pointer into data
int i; // Looping var
for (i = 0, xp = data; i < 16; i ++, xp += 4) /*
X[i] = xp[0] + (unsigned)(xp[1] << 8) + (unsigned)(xp[2] << 16) + (unsigned)(xp[3] << 24); * On big-endian machines, we must arrange the bytes in the right
* order. (This also works on machines of unknown byte order.)
*/
uint32_t X[16];
const uint8_t *xp = data;
int i;
for (i = 0; i < 16; ++i, xp += 4)
X[i] = xp[0] + (unsigned)(xp[1] << 8) + (unsigned)(xp[2] << 16) + (unsigned)(xp[3] << 24);
#else /* !ARCH_IS_BIG_ENDIAN */ #else /* !ARCH_IS_BIG_ENDIAN */
// On little-endian machines, we can process properly aligned data without copying it.
uint32_t xbuf[16]; // Aligned buffer
const uint32_t *X; // Pointer to little-endian representation
if (!((data - (const uint8_t *)0) & 3)) /*
{ * On little-endian machines, we can process properly aligned data
// data is properly aligned, use it directly... * without copying it.
X = (const uint32_t *)data; */
} uint32_t xbuf[16];
else const uint32_t *X;
{
// data is not aligned, copy to the aligned buffer... if (!((data - (const uint8_t *)0) & 3)) {
memcpy(xbuf, data, 64); /* data are properly aligned */
X = xbuf; X = (const uint32_t *)data;
} } else {
#endif // ARCH_IS_BIG_ENDIAN /* not aligned */
memcpy(xbuf, data, 64);
X = xbuf;
}
#endif
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
// Round 1. /* Round 1. */
// Let [abcd k s i] denote the operation /* Let [abcd k s i] denote the operation
// a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) #define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
#define SET(a, b, c, d, k, s, Ti) t = a + F(b,c,d) + X[k] + Ti; a = ROTATE_LEFT(t, s) + b #define SET(a, b, c, d, k, s, Ti)\
t = a + F(b,c,d) + X[k] + Ti;\
// Do the following 16 operations. a = ROTATE_LEFT(t, s) + b
SET(a, b, c, d, 0, 7, T1); /* Do the following 16 operations. */
SET(d, a, b, c, 1, 12, T2); SET(a, b, c, d, 0, 7, T1);
SET(c, d, a, b, 2, 17, T3); SET(d, a, b, c, 1, 12, T2);
SET(b, c, d, a, 3, 22, T4); SET(c, d, a, b, 2, 17, T3);
SET(a, b, c, d, 4, 7, T5); SET(b, c, d, a, 3, 22, T4);
SET(d, a, b, c, 5, 12, T6); SET(a, b, c, d, 4, 7, T5);
SET(c, d, a, b, 6, 17, T7); SET(d, a, b, c, 5, 12, T6);
SET(b, c, d, a, 7, 22, T8); SET(c, d, a, b, 6, 17, T7);
SET(a, b, c, d, 8, 7, T9); SET(b, c, d, a, 7, 22, T8);
SET(d, a, b, c, 9, 12, T10); SET(a, b, c, d, 8, 7, T9);
SET(c, d, a, b, 10, 17, T11); SET(d, a, b, c, 9, 12, T10);
SET(b, c, d, a, 11, 22, T12); SET(c, d, a, b, 10, 17, T11);
SET(a, b, c, d, 12, 7, T13); SET(b, c, d, a, 11, 22, T12);
SET(d, a, b, c, 13, 12, T14); SET(a, b, c, d, 12, 7, T13);
SET(c, d, a, b, 14, 17, T15); SET(d, a, b, c, 13, 12, T14);
SET(b, c, d, a, 15, 22, T16); SET(c, d, a, b, 14, 17, T15);
SET(b, c, d, a, 15, 22, T16);
#undef SET #undef SET
// Round 2. /* Round 2. */
// Let [abcd k s i] denote the operation /* Let [abcd k s i] denote the operation
// a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) #define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
#define SET(a, b, c, d, k, s, Ti) t = a + G(b,c,d) + X[k] + Ti; a = ROTATE_LEFT(t, s) + b #define SET(a, b, c, d, k, s, Ti)\
t = a + G(b,c,d) + X[k] + Ti;\
// Do the following 16 operations. a = ROTATE_LEFT(t, s) + b
SET(a, b, c, d, 1, 5, T17); /* Do the following 16 operations. */
SET(d, a, b, c, 6, 9, T18); SET(a, b, c, d, 1, 5, T17);
SET(c, d, a, b, 11, 14, T19); SET(d, a, b, c, 6, 9, T18);
SET(b, c, d, a, 0, 20, T20); SET(c, d, a, b, 11, 14, T19);
SET(a, b, c, d, 5, 5, T21); SET(b, c, d, a, 0, 20, T20);
SET(d, a, b, c, 10, 9, T22); SET(a, b, c, d, 5, 5, T21);
SET(c, d, a, b, 15, 14, T23); SET(d, a, b, c, 10, 9, T22);
SET(b, c, d, a, 4, 20, T24); SET(c, d, a, b, 15, 14, T23);
SET(a, b, c, d, 9, 5, T25); SET(b, c, d, a, 4, 20, T24);
SET(d, a, b, c, 14, 9, T26); SET(a, b, c, d, 9, 5, T25);
SET(c, d, a, b, 3, 14, T27); SET(d, a, b, c, 14, 9, T26);
SET(b, c, d, a, 8, 20, T28); SET(c, d, a, b, 3, 14, T27);
SET(a, b, c, d, 13, 5, T29); SET(b, c, d, a, 8, 20, T28);
SET(d, a, b, c, 2, 9, T30); SET(a, b, c, d, 13, 5, T29);
SET(c, d, a, b, 7, 14, T31); SET(d, a, b, c, 2, 9, T30);
SET(b, c, d, a, 12, 20, T32); SET(c, d, a, b, 7, 14, T31);
SET(b, c, d, a, 12, 20, T32);
#undef SET #undef SET
// Round 3. /* Round 3. */
// Let [abcd k s t] denote the operation /* Let [abcd k s t] denote the operation
// a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
#define H(x, y, z) ((x) ^ (y) ^ (z)) #define H(x, y, z) ((x) ^ (y) ^ (z))
#define SET(a, b, c, d, k, s, Ti) t = a + H(b,c,d) + X[k] + Ti; a = ROTATE_LEFT(t, s) + b #define SET(a, b, c, d, k, s, Ti)\
t = a + H(b,c,d) + X[k] + Ti;\
// Do the following 16 operations. a = ROTATE_LEFT(t, s) + b
SET(a, b, c, d, 5, 4, T33); /* Do the following 16 operations. */
SET(d, a, b, c, 8, 11, T34); SET(a, b, c, d, 5, 4, T33);
SET(c, d, a, b, 11, 16, T35); SET(d, a, b, c, 8, 11, T34);
SET(b, c, d, a, 14, 23, T36); SET(c, d, a, b, 11, 16, T35);
SET(a, b, c, d, 1, 4, T37); SET(b, c, d, a, 14, 23, T36);
SET(d, a, b, c, 4, 11, T38); SET(a, b, c, d, 1, 4, T37);
SET(c, d, a, b, 7, 16, T39); SET(d, a, b, c, 4, 11, T38);
SET(b, c, d, a, 10, 23, T40); SET(c, d, a, b, 7, 16, T39);
SET(a, b, c, d, 13, 4, T41); SET(b, c, d, a, 10, 23, T40);
SET(d, a, b, c, 0, 11, T42); SET(a, b, c, d, 13, 4, T41);
SET(c, d, a, b, 3, 16, T43); SET(d, a, b, c, 0, 11, T42);
SET(b, c, d, a, 6, 23, T44); SET(c, d, a, b, 3, 16, T43);
SET(a, b, c, d, 9, 4, T45); SET(b, c, d, a, 6, 23, T44);
SET(d, a, b, c, 12, 11, T46); SET(a, b, c, d, 9, 4, T45);
SET(c, d, a, b, 15, 16, T47); SET(d, a, b, c, 12, 11, T46);
SET(b, c, d, a, 2, 23, T48); SET(c, d, a, b, 15, 16, T47);
SET(b, c, d, a, 2, 23, T48);
#undef SET #undef SET
// Round 4. /* Round 4. */
// Let [abcd k s t] denote the operation /* Let [abcd k s t] denote the operation
// a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
#define I(x, y, z) ((y) ^ ((x) | ~(z))) #define I(x, y, z) ((y) ^ ((x) | ~(z)))
#define SET(a, b, c, d, k, s, Ti) t = a + I(b,c,d) + X[k] + Ti; a = ROTATE_LEFT(t, s) + b #define SET(a, b, c, d, k, s, Ti)\
t = a + I(b,c,d) + X[k] + Ti;\
// Do the following 16 operations. a = ROTATE_LEFT(t, s) + b
SET(a, b, c, d, 0, 6, T49); /* Do the following 16 operations. */
SET(d, a, b, c, 7, 10, T50); SET(a, b, c, d, 0, 6, T49);
SET(c, d, a, b, 14, 15, T51); SET(d, a, b, c, 7, 10, T50);
SET(b, c, d, a, 5, 21, T52); SET(c, d, a, b, 14, 15, T51);
SET(a, b, c, d, 12, 6, T53); SET(b, c, d, a, 5, 21, T52);
SET(d, a, b, c, 3, 10, T54); SET(a, b, c, d, 12, 6, T53);
SET(c, d, a, b, 10, 15, T55); SET(d, a, b, c, 3, 10, T54);
SET(b, c, d, a, 1, 21, T56); SET(c, d, a, b, 10, 15, T55);
SET(a, b, c, d, 8, 6, T57); SET(b, c, d, a, 1, 21, T56);
SET(d, a, b, c, 15, 10, T58); SET(a, b, c, d, 8, 6, T57);
SET(c, d, a, b, 6, 15, T59); SET(d, a, b, c, 15, 10, T58);
SET(b, c, d, a, 13, 21, T60); SET(c, d, a, b, 6, 15, T59);
SET(a, b, c, d, 4, 6, T61); SET(b, c, d, a, 13, 21, T60);
SET(d, a, b, c, 11, 10, T62); SET(a, b, c, d, 4, 6, T61);
SET(c, d, a, b, 2, 15, T63); SET(d, a, b, c, 11, 10, T62);
SET(b, c, d, a, 9, 21, T64); SET(c, d, a, b, 2, 15, T63);
SET(b, c, d, a, 9, 21, T64);
#undef SET #undef SET
// Then perform the following additions. (That is increment each of the four /* Then perform the following additions. (That is increment each
// registers by the value it had before this block was started.) of the four registers by the value it had before this block
pms->abcd[0] += a; was started.) */
pms->abcd[1] += b; pms->abcd[0] += a;
pms->abcd[2] += c; pms->abcd[1] += b;
pms->abcd[3] += d; pms->abcd[2] += c;
pms->abcd[3] += d;
} }
//
// '_pdfioCryptoMD5Init()' - Initialize an MD5 hash.
//
void void
_pdfioCryptoMD5Init(_pdfio_md5_t *pms) // I - MD5 state _pdfioCryptoMD5Init(_pdfio_md5_t *pms)
{ {
pms->count[0] = pms->count[1] = 0; pms->count[0] = pms->count[1] = 0;
pms->abcd[0] = 0x67452301; pms->abcd[0] = 0x67452301;
pms->abcd[1] = 0xefcdab89; pms->abcd[1] = 0xefcdab89;
pms->abcd[2] = 0x98badcfe; pms->abcd[2] = 0x98badcfe;
pms->abcd[3] = 0x10325476; pms->abcd[3] = 0x10325476;
} }
//
// '_pdfioCryptoMD5Append()' - Append bytes to the MD5 hash.
//
void void
_pdfioCryptoMD5Append( _pdfioCryptoMD5Append(_pdfio_md5_t *pms, const uint8_t *data, size_t nbytes)
_pdfio_md5_t *pms, // I - MD5 state
const uint8_t *data, // I - Data to add
size_t nbytes) // I - Number of bytes
{ {
const uint8_t *p = data; // Pointer into data const uint8_t *p = data;
size_t left = nbytes; // Remaining bytes size_t left = nbytes;
size_t offset = (pms->count[0] >> 3) & 63; size_t offset = (pms->count[0] >> 3) & 63;
// Offset into state uint32_t nbits = (uint32_t)(nbytes << 3);
uint32_t nbits = (uint32_t)(nbytes << 3);
// Number of bits to add
if (nbytes == 0)
return;
if (nbytes == 0) /* Update the message length. */
return; pms->count[1] += (unsigned)(nbytes >> 29);
pms->count[0] += nbits;
if (pms->count[0] < nbits)
pms->count[1]++;
// Update the message length. /* Process an initial partial block. */
pms->count[1] += (unsigned)(nbytes >> 29); if (offset) {
pms->count[0] += nbits; size_t copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
if (pms->count[0] < nbits)
pms->count[1] ++;
// Process an initial partial block. memcpy(pms->buf + offset, p, copy);
if (offset) if (offset + copy < 64)
{ return;
size_t copy = ((offset + nbytes) > 64 ? 64 - offset : nbytes); p += copy;
// Number of bytes to copy left -= copy;
md5_process(pms, pms->buf);
}
memcpy(pms->buf + offset, p, copy); /* Process full blocks. */
for (; left >= 64; p += 64, left -= 64)
md5_process(pms, p);
if ((offset + copy) < 64) /* Process a final partial block. */
return; if (left)
memcpy(pms->buf, p, left);
p += copy;
left -= copy;
md5_process(pms, pms->buf);
}
// Process full blocks.
for (; left >= 64; p += 64, left -= 64)
md5_process(pms, p);
// Copy a final partial block.
if (left)
memcpy(pms->buf, p, left);
} }
//
// '_pdfioCryptoMD5Finish()' - Finalize the MD5 hash.
//
void void
_pdfioCryptoMD5Finish( _pdfioCryptoMD5Finish(_pdfio_md5_t *pms, uint8_t digest[16])
_pdfio_md5_t *pms, // I - MD5 state
uint8_t digest[16]) // O - Digest value
{ {
int i; // Looping var static const uint8_t pad[64] = {
uint8_t data[8]; // Digest length data 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
static const uint8_t pad[64] = // Padding bytes 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, uint8_t data[8];
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, int i;
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
/* Save the length before padding. */
// Save the length before padding. for (i = 0; i < 8; ++i)
for (i = 0; i < 8; ++i) data[i] = (uint8_t)(pms->count[i >> 2] >> ((i & 3) << 3));
data[i] = (uint8_t)(pms->count[i >> 2] >> ((i & 3) << 3)); /* Pad to 56 bytes mod 64. */
_pdfioCryptoMD5Append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
// Pad to 56 bytes mod 64. /* Append the length. */
_pdfioCryptoMD5Append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); _pdfioCryptoMD5Append(pms, data, 8);
for (i = 0; i < 16; ++i)
// Append the length. digest[i] = (uint8_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
_pdfioCryptoMD5Append(pms, data, 8);
// Copy the digest from the state...
for (i = 0; i < 16; ++i)
digest[i] = (uint8_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
} }

View File

@ -1,7 +1,7 @@
// //
// PDF object functions for PDFio. // PDF object functions for PDFio.
// //
// Copyright © 2021-2025 by Michael R Sweet. // Copyright © 2021-2024 by Michael R Sweet.
// //
// Licensed under Apache License v2.0. See the file "LICENSE" for more // Licensed under Apache License v2.0. See the file "LICENSE" for more
// information. // information.
@ -10,6 +10,13 @@
#include "pdfio-private.h" #include "pdfio-private.h"
//
// Local functions...
//
static bool write_obj_header(pdfio_obj_t *obj);
// //
// 'pdfioObjClose()' - Close an object, writing any data as needed to the PDF // 'pdfioObjClose()' - Close an object, writing any data as needed to the PDF
// file. // file.
@ -35,7 +42,7 @@ pdfioObjClose(pdfio_obj_t *obj) // I - Object
if (!obj->offset) if (!obj->offset)
{ {
// Write the object value // Write the object value
if (!_pdfioObjWriteHeader(obj)) if (!write_obj_header(obj))
return (false); return (false);
// Write the "endobj" line... // Write the "endobj" line...
@ -188,7 +195,7 @@ pdfioObjCreateStream(
} }
} }
if (!_pdfioObjWriteHeader(obj)) if (!write_obj_header(obj))
return (NULL); return (NULL);
if (!_pdfioFilePuts(obj->pdf, "stream\n")) if (!_pdfioFilePuts(obj->pdf, "stream\n"))
@ -198,7 +205,7 @@ pdfioObjCreateStream(
obj->pdf->current_obj = obj; obj->pdf->current_obj = obj;
// Return the new stream... // Return the new stream...
return (_pdfioStreamCreate(obj, length_obj, 0, filter)); return (_pdfioStreamCreate(obj, length_obj, filter));
} }
@ -307,8 +314,7 @@ pdfioObjGetLength(pdfio_obj_t *obj) // I - Object
if ((lenobj = pdfioDictGetObj(obj->value.value.dict, "Length")) == NULL) if ((lenobj = pdfioDictGetObj(obj->value.value.dict, "Length")) == NULL)
{ {
if (!_pdfioDictGetValue(obj->value.value.dict, "Length")) _pdfioFileError(obj->pdf, "Unable to get length of stream.");
_pdfioFileError(obj->pdf, "Unable to get length of stream.");
return (0); return (0);
} }
@ -576,11 +582,11 @@ _pdfioObjSetExtension(
// //
// '_pdfioObjWriteHeader()' - Write the object header... // 'write_obj_header()' - Write the object header...
// //
bool // O - `true` on success, `false` on failure static bool // O - `true` on success, `false` on failure
_pdfioObjWriteHeader(pdfio_obj_t *obj) // I - Object write_obj_header(pdfio_obj_t *obj) // I - Object
{ {
obj->offset = _pdfioFileTell(obj->pdf); obj->offset = _pdfioFileTell(obj->pdf);

View File

@ -1,7 +1,7 @@
// //
// Private header file for PDFio. // Private header file for PDFio.
// //
// Copyright © 2021-2025 by Michael R Sweet. // Copyright © 2021-2024 by Michael R Sweet.
// //
// Licensed under Apache License v2.0. See the file "LICENSE" for more // Licensed under Apache License v2.0. See the file "LICENSE" for more
// information. // information.
@ -94,7 +94,6 @@
// //
# define PDFIO_MAX_DEPTH 32 // Maximum nesting depth for values # 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 *); typedef void (*_pdfio_extfree_t)(void *);
// Extension data free function // Extension data free function
@ -108,7 +107,7 @@ typedef enum _pdfio_mode_e // Read/write mode
typedef enum _pdfio_predictor_e // PNG predictor constants typedef enum _pdfio_predictor_e // PNG predictor constants
{ {
_PDFIO_PREDICTOR_NONE = 1, // No predictor (default) _PDFIO_PREDICTOR_NONE = 1, // No predictor (default)
_PDFIO_PREDICTOR_TIFF2 = 2, // TIFF predictor 2 (difference from left neighbor) _PDFIO_PREDICTOR_TIFF2 = 2, // TIFF2 predictor (???)
_PDFIO_PREDICTOR_PNG_NONE = 10, // PNG None predictor (same as `_PDFIO_PREDICTOR_NONE`) _PDFIO_PREDICTOR_PNG_NONE = 10, // PNG None predictor (same as `_PDFIO_PREDICTOR_NONE`)
_PDFIO_PREDICTOR_PNG_SUB = 11, // PNG Sub predictor _PDFIO_PREDICTOR_PNG_SUB = 11, // PNG Sub predictor
_PDFIO_PREDICTOR_PNG_UP = 12, // PNG Up predictor _PDFIO_PREDICTOR_PNG_UP = 12, // PNG Up predictor
@ -225,14 +224,6 @@ typedef struct _pdfio_objmap_s // PDF object map
size_t src_number; // Source object number size_t src_number; // Source object number
} _pdfio_objmap_t; } _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 struct _pdfio_file_s // PDF file structure
{ {
char *filename; // Filename char *filename; // Filename
@ -292,7 +283,6 @@ struct _pdfio_file_s // PDF file structure
size_t num_strings, // Number of strings size_t num_strings, // Number of strings
alloc_strings; // Allocated strings alloc_strings; // Allocated strings
char **strings; // Nul-terminated strings char **strings; // Nul-terminated strings
_pdfio_strbuf_t *strbuffers; // String buffers
}; };
struct _pdfio_obj_s // Object struct _pdfio_obj_s // Object
@ -323,9 +313,8 @@ struct _pdfio_stream_s // Stream
z_stream flate; // Flate filter state z_stream flate; // Flate filter state
_pdfio_predictor_t predictor; // Predictor function, if any _pdfio_predictor_t predictor; // Predictor function, if any
size_t pbpixel, // Size of a pixel in bytes size_t pbpixel, // Size of a pixel in bytes
pbsize, // Predictor buffer size, if any pbsize; // Predictor buffer size, if any
cbsize; // Compressed data buffer size unsigned char cbuffer[4096], // Compressed data buffer
unsigned char *cbuffer, // Compressed data buffer
*prbuffer, // Raw buffer (previous line), as needed *prbuffer, // Raw buffer (previous line), as needed
*psbuffer; // PNG filter buffer, as needed *psbuffer; // PNG filter buffer, as needed
_pdfio_crypto_cb_t crypto_cb; // Encryption/descryption callback, if any _pdfio_crypto_cb_t crypto_cb; // Encryption/descryption callback, if any
@ -337,7 +326,6 @@ struct _pdfio_stream_s // Stream
// Functions... // 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 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 ssize_t _pdfio_vsnprintf(pdfio_file_t *pdf, char *buffer, size_t bufsize, const char *format, va_list ap) _PDFIO_INTERNAL;
@ -378,13 +366,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 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 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 _pdfioFileDefaultError(pdfio_file_t *pdf, const char *message, void *data) _PDFIO_INTERNAL;
extern bool _pdfioFileError(pdfio_file_t *pdf, const char *format, ...) _PDFIO_INTERNAL; extern bool _pdfioFileError(pdfio_file_t *pdf, const char *format, ...) _PDFIO_FORMAT(2,3) _PDFIO_INTERNAL;
extern pdfio_obj_t *_pdfioFileFindMappedObj(pdfio_file_t *pdf, pdfio_file_t *src_pdf, size_t src_number) _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 bool _pdfioFileFlush(pdfio_file_t *pdf) _PDFIO_INTERNAL;
extern int _pdfioFileGetChar(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 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 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_INTERNAL; extern bool _pdfioFilePrintf(pdfio_file_t *pdf, const char *format, ...) _PDFIO_FORMAT(2,3) _PDFIO_INTERNAL;
extern bool _pdfioFilePuts(pdfio_file_t *pdf, const char *s) _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 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; extern off_t _pdfioFileSeek(pdfio_file_t *pdf, off_t offset, int whence) _PDFIO_INTERNAL;
@ -395,13 +383,10 @@ extern void _pdfioObjDelete(pdfio_obj_t *obj) _PDFIO_INTERNAL;
extern void *_pdfioObjGetExtension(pdfio_obj_t *obj) _PDFIO_INTERNAL; extern void *_pdfioObjGetExtension(pdfio_obj_t *obj) _PDFIO_INTERNAL;
extern bool _pdfioObjLoad(pdfio_obj_t *obj) _PDFIO_INTERNAL; extern bool _pdfioObjLoad(pdfio_obj_t *obj) _PDFIO_INTERNAL;
extern void _pdfioObjSetExtension(pdfio_obj_t *obj, void *data, _pdfio_extfree_t datafree) _PDFIO_INTERNAL; extern void _pdfioObjSetExtension(pdfio_obj_t *obj, void *data, _pdfio_extfree_t datafree) _PDFIO_INTERNAL;
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 *_pdfioStreamCreate(pdfio_obj_t *obj, pdfio_obj_t *length_obj, pdfio_filter_t compression) _PDFIO_INTERNAL;
extern pdfio_stream_t *_pdfioStreamOpen(pdfio_obj_t *obj, bool decode) _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 bool _pdfioStringIsAllocated(pdfio_file_t *pdf, const char *s) _PDFIO_INTERNAL;
extern void _pdfioTokenClear(_pdfio_token_t *tb) _PDFIO_INTERNAL; extern void _pdfioTokenClear(_pdfio_token_t *tb) _PDFIO_INTERNAL;

View File

@ -1,7 +1,7 @@
// //
// PDF stream functions for PDFio. // PDF stream functions for PDFio.
// //
// Copyright © 2021-2025 by Michael R Sweet. // Copyright © 2021-2024 by Michael R Sweet.
// //
// Licensed under Apache License v2.0. See the file "LICENSE" for more // Licensed under Apache License v2.0. See the file "LICENSE" for more
// information. // information.
@ -50,7 +50,7 @@ pdfioStreamClose(pdfio_stream_t *st) // I - Stream
while ((status = deflate(&st->flate, Z_FINISH)) != Z_STREAM_END) while ((status = deflate(&st->flate, Z_FINISH)) != Z_STREAM_END)
{ {
size_t bytes = st->cbsize - st->flate.avail_out, size_t bytes = sizeof(st->cbuffer) - st->flate.avail_out,
// Bytes to write // Bytes to write
outbytes; // Actual bytes written outbytes; // Actual bytes written
@ -89,13 +89,13 @@ pdfioStreamClose(pdfio_stream_t *st) // I - Stream
} }
st->flate.next_out = (Bytef *)st->cbuffer + bytes; st->flate.next_out = (Bytef *)st->cbuffer + bytes;
st->flate.avail_out = (uInt)(st->cbsize - bytes); st->flate.avail_out = (uInt)(sizeof(st->cbuffer) - bytes);
} }
if (st->flate.avail_out < (uInt)st->cbsize) if (st->flate.avail_out < (uInt)sizeof(st->cbuffer))
{ {
// Write any residuals... // Write any residuals...
size_t bytes = st->cbsize - st->flate.avail_out; size_t bytes = sizeof(st->cbuffer) - st->flate.avail_out;
// Bytes to write // Bytes to write
if (st->crypto_cb) if (st->crypto_cb)
@ -140,7 +140,7 @@ pdfioStreamClose(pdfio_stream_t *st) // I - Stream
// Update the length as needed... // Update the length as needed...
if (st->length_obj) if (st->length_obj)
{ {
st->length_obj->value.value.number = (double)st->obj->stream_length; st->length_obj->value.value.number = st->obj->stream_length;
pdfioObjClose(st->length_obj); pdfioObjClose(st->length_obj);
} }
else if (st->obj->length_offset) else if (st->obj->length_offset)
@ -172,7 +172,6 @@ pdfioStreamClose(pdfio_stream_t *st) // I - Stream
st->pdf->current_obj = NULL; st->pdf->current_obj = NULL;
free(st->cbuffer);
free(st->prbuffer); free(st->prbuffer);
free(st->psbuffer); free(st->psbuffer);
free(st); free(st);
@ -191,7 +190,6 @@ pdfio_stream_t * // O - Stream or `NULL` on error
_pdfioStreamCreate( _pdfioStreamCreate(
pdfio_obj_t *obj, // I - Object pdfio_obj_t *obj, // I - Object
pdfio_obj_t *length_obj, // I - Length object, if any pdfio_obj_t *length_obj, // I - Length object, if any
size_t cbsize, // I - Size of compression buffer
pdfio_filter_t compression) // I - Compression to apply pdfio_filter_t compression) // I - Compression to apply
{ {
pdfio_stream_t *st; // Stream pdfio_stream_t *st; // Stream
@ -304,21 +302,8 @@ _pdfioStreamCreate(
else else
st->predictor = _PDFIO_PREDICTOR_NONE; st->predictor = _PDFIO_PREDICTOR_NONE;
if (cbsize == 0)
cbsize = 4096;
st->cbsize = cbsize;
if ((st->cbuffer = malloc(cbsize)) == NULL)
{
_pdfioFileError(st->pdf, "Unable to allocate %lu bytes for Flate output buffer: %s", (unsigned long)cbsize, strerror(errno));
free(st->prbuffer);
free(st->psbuffer);
free(st);
return (NULL);
}
st->flate.next_out = (Bytef *)st->cbuffer; st->flate.next_out = (Bytef *)st->cbuffer;
st->flate.avail_out = (uInt)cbsize; st->flate.avail_out = (uInt)sizeof(st->cbuffer);
if ((status = deflateInit(&(st->flate), 9)) != Z_OK) if ((status = deflateInit(&(st->flate), 9)) != Z_OK)
{ {
@ -377,16 +362,15 @@ pdfioStreamConsume(pdfio_stream_t *st, // I - Stream
// //
// 'pdfioStreamGetToken()' - Read a single PDF token from a stream. // 'pdfioStreamGetToken()' - Read a single PDF token from a stream.
// //
// This function reads a single PDF token from a stream, skipping all whitespace // This function reads a single PDF token from a stream. Operator tokens,
// and comments. Operator tokens, boolean values, and numbers are returned // boolean values, and numbers are returned as-is in the provided string buffer.
// as-is in the provided string buffer. String values start with the opening // String values start with the opening parenthesis ('(') but have all escaping
// parenthesis ('(') but have all escaping resolved and the terminating // resolved and the terminating parenthesis removed. Hexadecimal string values
// parenthesis removed. Hexadecimal string values start with the opening angle // start with the opening angle bracket ('<') and have all whitespace and the
// bracket ('<') and have all whitespace and the terminating angle bracket // terminating angle bracket removed.
// removed.
// //
bool // O - `true` on success, `false` on end-of-stream or error bool // O - `true` on success, `false` on EOF
pdfioStreamGetToken( pdfioStreamGetToken(
pdfio_stream_t *st, // I - Stream pdfio_stream_t *st, // I - Stream
char *buffer, // I - String buffer char *buffer, // I - String buffer
@ -439,16 +423,16 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
st->pdf = obj->pdf; st->pdf = obj->pdf;
st->obj = obj; st->obj = obj;
if ((st->remaining = pdfioObjGetLength(obj)) == 0 && !_pdfioDictGetValue(pdfioObjGetDict(obj), "Length")) if ((st->remaining = pdfioObjGetLength(obj)) == 0)
{ {
_pdfioFileError(obj->pdf, "No stream data."); free(st);
goto error; return (NULL);
} }
if (_pdfioFileSeek(st->pdf, obj->stream_offset, SEEK_SET) != obj->stream_offset) if (_pdfioFileSeek(st->pdf, obj->stream_offset, SEEK_SET) != obj->stream_offset)
{ {
_pdfioFileError(obj->pdf, "Unable to seek to stream data."); free(st);
goto error; return (NULL);
} }
type = pdfioObjGetType(obj); type = pdfioObjGetType(obj);
@ -461,7 +445,11 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
ivlen = (size_t)_pdfioFilePeek(st->pdf, iv, sizeof(iv)); ivlen = (size_t)_pdfioFilePeek(st->pdf, iv, sizeof(iv));
if ((st->crypto_cb = _pdfioCryptoMakeReader(st->pdf, obj, &st->crypto_ctx, iv, &ivlen)) == NULL) if ((st->crypto_cb = _pdfioCryptoMakeReader(st->pdf, obj, &st->crypto_ctx, iv, &ivlen)) == NULL)
goto error; {
// TODO: Add error message?
free(st);
return (NULL);
}
PDFIO_DEBUG("_pdfioStreamOpen: ivlen=%d\n", (int)ivlen); PDFIO_DEBUG("_pdfioStreamOpen: ivlen=%d\n", (int)ivlen);
if (ivlen > 0) if (ivlen > 0)
@ -492,7 +480,8 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
{ {
// TODO: Implement compound filters... // TODO: Implement compound filters...
_pdfioFileError(st->pdf, "Unsupported compound stream filter."); _pdfioFileError(st->pdf, "Unsupported compound stream filter.");
goto error; free(st);
return (NULL);
} }
// No filter, read as-is... // No filter, read as-is...
@ -525,7 +514,8 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
else if (bpc < 1 || bpc == 3 || (bpc > 4 && bpc < 8) || (bpc > 8 && bpc < 16) || bpc > 16) else if (bpc < 1 || bpc == 3 || (bpc > 4 && bpc < 8) || (bpc > 8 && bpc < 16) || bpc > 16)
{ {
_pdfioFileError(st->pdf, "Unsupported BitsPerColor value %d.", bpc); _pdfioFileError(st->pdf, "Unsupported BitsPerColor value %d.", bpc);
goto error; free(st);
return (NULL);
} }
if (colors == 0) if (colors == 0)
@ -535,7 +525,8 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
else if (colors < 0 || colors > 4) else if (colors < 0 || colors > 4)
{ {
_pdfioFileError(st->pdf, "Unsupported Colors value %d.", colors); _pdfioFileError(st->pdf, "Unsupported Colors value %d.", colors);
goto error; free(st);
return (NULL);
} }
if (columns == 0) if (columns == 0)
@ -545,13 +536,15 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
else if (columns < 0) else if (columns < 0)
{ {
_pdfioFileError(st->pdf, "Unsupported Columns value %d.", columns); _pdfioFileError(st->pdf, "Unsupported Columns value %d.", columns);
goto error; free(st);
return (NULL);
} }
if ((predictor > 2 && predictor < 10) || predictor > 15) if ((predictor > 2 && predictor < 10) || predictor > 15)
{ {
_pdfioFileError(st->pdf, "Unsupported Predictor function %d.", predictor); _pdfioFileError(st->pdf, "Unsupported Predictor function %d.", predictor);
goto error; free(st);
return (NULL);
} }
else if (predictor > 1) else if (predictor > 1)
{ {
@ -565,31 +558,28 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
if ((st->prbuffer = calloc(1, st->pbsize - 1)) == NULL || (st->psbuffer = calloc(1, st->pbsize)) == NULL) if ((st->prbuffer = calloc(1, st->pbsize - 1)) == NULL || (st->psbuffer = calloc(1, st->pbsize)) == NULL)
{ {
_pdfioFileError(st->pdf, "Unable to allocate %lu bytes for Predictor buffers.", (unsigned long)st->pbsize); _pdfioFileError(st->pdf, "Unable to allocate %lu bytes for Predictor buffers.", (unsigned long)st->pbsize);
goto error; free(st->prbuffer);
free(st->psbuffer);
free(st);
return (NULL);
} }
} }
else else
{
st->predictor = _PDFIO_PREDICTOR_NONE; st->predictor = _PDFIO_PREDICTOR_NONE;
}
st->cbsize = 4096;
if ((st->cbuffer = malloc(st->cbsize)) == NULL)
{
_pdfioFileError(st->pdf, "Unable to allocate %lu bytes for Flate compression buffer.", (unsigned long)st->cbsize);
goto error;
}
PDFIO_DEBUG("_pdfioStreamOpen: pos=%ld\n", (long)_pdfioFileTell(st->pdf)); PDFIO_DEBUG("_pdfioStreamOpen: pos=%ld\n", (long)_pdfioFileTell(st->pdf));
if (st->cbsize > st->remaining) if (sizeof(st->cbuffer) > st->remaining)
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining); rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining);
else else
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->cbsize); rbytes = _pdfioFileRead(st->pdf, st->cbuffer, sizeof(st->cbuffer));
if (rbytes <= 0) if (rbytes <= 0)
{ {
_pdfioFileError(st->pdf, "Unable to read bytes for stream."); _pdfioFileError(st->pdf, "Unable to read bytes for stream.");
goto error; free(st->prbuffer);
free(st->psbuffer);
free(st);
return (NULL);
} }
if (st->crypto_cb) if (st->crypto_cb)
@ -603,7 +593,10 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
if ((status = inflateInit(&(st->flate))) != Z_OK) if ((status = inflateInit(&(st->flate))) != Z_OK)
{ {
_pdfioFileError(st->pdf, "Unable to start Flate filter: %s", zstrerror(status)); _pdfioFileError(st->pdf, "Unable to start Flate filter: %s", zstrerror(status));
goto error; free(st->prbuffer);
free(st->psbuffer);
free(st);
return (NULL);
} }
st->remaining -= st->flate.avail_in; st->remaining -= st->flate.avail_in;
@ -616,8 +609,9 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
else else
{ {
// Something else we don't support // Something else we don't support
_pdfioFileError(st->pdf, "Unsupported stream filter '%N'.", filter); _pdfioFileError(st->pdf, "Unsupported stream filter '/%s'.", filter);
goto error; free(st);
return (NULL);
} }
} }
else else
@ -627,16 +621,6 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
} }
return (st); return (st);
// If we get here something went wrong...
error:
free(st->cbuffer);
free(st->prbuffer);
free(st->psbuffer);
free(st);
return (NULL);
} }
@ -689,10 +673,6 @@ pdfioStreamPeek(pdfio_stream_t *st, // I - Stream
// //
// 'pdfioStreamPrintf()' - Write a formatted string to a 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 bool // O - `true` on success, `false` on failure
pdfioStreamPrintf( pdfioStreamPrintf(
@ -1065,10 +1045,10 @@ stream_read(pdfio_stream_t *st, // I - Stream
if (st->flate.avail_in == 0) if (st->flate.avail_in == 0)
{ {
// Read more from the file... // Read more from the file...
if (st->cbsize > st->remaining) if (sizeof(st->cbuffer) > st->remaining)
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining); rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining);
else else
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->cbsize); rbytes = _pdfioFileRead(st->pdf, st->cbuffer, sizeof(st->cbuffer));
if (rbytes <= 0) if (rbytes <= 0)
return (-1); // End of file... return (-1); // End of file...
@ -1121,10 +1101,10 @@ stream_read(pdfio_stream_t *st, // I - Stream
if (st->flate.avail_in == 0) if (st->flate.avail_in == 0)
{ {
// Read more from the file... // Read more from the file...
if (st->cbsize > st->remaining) if (sizeof(st->cbuffer) > st->remaining)
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining); rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining);
else else
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->cbsize); rbytes = _pdfioFileRead(st->pdf, st->cbuffer, sizeof(st->cbuffer));
if (rbytes <= 0) if (rbytes <= 0)
return (-1); // End of file... return (-1); // End of file...
@ -1191,10 +1171,10 @@ stream_read(pdfio_stream_t *st, // I - Stream
if (st->flate.avail_in == 0) if (st->flate.avail_in == 0)
{ {
// Read more from the file... // Read more from the file...
if (st->cbsize > st->remaining) if (sizeof(st->cbuffer) > st->remaining)
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining); rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining);
else else
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->cbsize); rbytes = _pdfioFileRead(st->pdf, st->cbuffer, sizeof(st->cbuffer));
if (rbytes <= 0) if (rbytes <= 0)
return (-1); // End of file... return (-1); // End of file...
@ -1298,10 +1278,10 @@ stream_write(pdfio_stream_t *st, // I - Stream
while (st->flate.avail_in > 0) while (st->flate.avail_in > 0)
{ {
if (st->flate.avail_out < (st->cbsize / 8)) if (st->flate.avail_out < (sizeof(st->cbuffer) / 8))
{ {
// Flush the compression buffer... // Flush the compression buffer...
size_t cbytes = st->cbsize - st->flate.avail_out, size_t cbytes = sizeof(st->cbuffer) - st->flate.avail_out,
outbytes; outbytes;
if (st->crypto_cb) if (st->crypto_cb)
@ -1330,7 +1310,7 @@ stream_write(pdfio_stream_t *st, // I - Stream
} }
st->flate.next_out = (Bytef *)st->cbuffer + cbytes; st->flate.next_out = (Bytef *)st->cbuffer + cbytes;
st->flate.avail_out = (uInt)(st->cbsize - cbytes); st->flate.avail_out = (uInt)(sizeof(st->cbuffer) - cbytes);
} }
// Deflate what we can this time... // Deflate what we can this time...

View File

@ -1,7 +1,7 @@
// //
// PDF string functions for PDFio. // PDF string functions for PDFio.
// //
// Copyright © 2021-2025 by Michael R Sweet. // Copyright © 2021-2024 by Michael R Sweet.
// //
// Licensed under Apache License v2.0. See the file "LICENSE" for more // Licensed under Apache License v2.0. See the file "LICENSE" for more
// information. // information.
@ -17,83 +17,6 @@
static size_t find_string(pdfio_file_t *pdf, const char *s, int *rdiff); 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. // '_pdfio_strtod()' - Convert a string to a double value.
// //
@ -189,9 +112,10 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
// Loop through the format string, formatting as needed... // Loop through the format string, formatting as needed...
bufptr = buffer; bufptr = buffer;
bufend = buffer + bufsize - 1; bufend = buffer + bufsize - 1;
bytes = 0; *bufend = '\0';
bytes = 0;
while (*format) while (*format)
{ {
@ -254,12 +178,14 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
} }
else else
{ {
prec = 0;
while (isdigit(*format & 255)) while (isdigit(*format & 255))
{ {
if (tptr < (tformat + sizeof(tformat) - 1)) if (tptr < (tformat + sizeof(tformat) - 1))
*tptr++ = *format; *tptr++ = *format;
format ++; prec = prec * 10 + *format++ - '0';
} }
} }
} }
@ -333,7 +259,7 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
if (bufptr < bufend) if (bufptr < bufend)
{ {
_pdfio_strlcpy(bufptr, temp, (size_t)(bufend - bufptr + 1)); strncpy(bufptr, temp, (size_t)(bufend - bufptr - 1));
bufptr += strlen(bufptr); bufptr += strlen(bufptr);
} }
break; break;
@ -363,7 +289,7 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
if (bufptr < bufend) if (bufptr < bufend)
{ {
_pdfio_strlcpy(bufptr, temp, (size_t)(bufend - bufptr + 1)); strncpy(bufptr, temp, (size_t)(bufend - bufptr - 1));
bufptr += strlen(bufptr); bufptr += strlen(bufptr);
} }
break; break;
@ -378,7 +304,7 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
if (bufptr < bufend) if (bufptr < bufend)
{ {
_pdfio_strlcpy(bufptr, temp, (size_t)(bufend - bufptr + 1)); strncpy(bufptr, temp, (size_t)(bufend - bufptr - 1));
bufptr += strlen(bufptr); bufptr += strlen(bufptr);
} }
break; break;
@ -403,111 +329,19 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
} }
break; break;
case 'S' : // PDF string case 's' : // String
if ((s = va_arg(ap, char *)) == NULL) if ((s = va_arg(ap, char *)) == NULL)
s = "(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); bytes += strlen(s);
if (bufptr < bufend) if (bufptr < bufend)
{ {
_pdfio_strlcpy(bufptr, s, (size_t)(bufend - bufptr + 1)); strncpy(bufptr, s, (size_t)(bufend - bufptr - 1));
bufptr += strlen(bufptr); bufptr += strlen(bufptr);
} }
break; 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 case 'n' : // Output number of chars so far
*(va_arg(ap, int *)) = (int)bytes; *(va_arg(ap, int *)) = (int)bytes;
break; break;
@ -524,7 +358,11 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
} }
// Nul-terminate the string and return the number of characters needed. // Nul-terminate the string and return the number of characters needed.
*bufptr = '\0'; if (bufptr < bufend)
{
// Everything fit in the buffer...
*bufptr = '\0';
}
PDFIO_DEBUG("_pdfio_vsnprintf: Returning %ld \"%s\"\n", (long)bytes, buffer); PDFIO_DEBUG("_pdfio_vsnprintf: Returning %ld \"%s\"\n", (long)bytes, buffer);
@ -532,41 +370,6 @@ _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. // 'pdfioStringCreate()' - Create a durable literal string.
// //
@ -677,29 +480,6 @@ 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. // '_pdfioStringIsAllocated()' - Check whether a string has been allocated.
// //

View File

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

View File

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

16
pdfio.h
View File

@ -20,12 +20,10 @@ extern "C" {
// //
// Version numbers... // Version number...
// //
# define PDFIO_VERSION "1.5.2" # define PDFIO_VERSION "1.4.1"
# define PDFIO_VERSION_MAJOR 1
# define PDFIO_VERSION_MINOR 5
// //
@ -34,9 +32,11 @@ extern "C" {
# if defined(__has_extension) || defined(__GNUC__) # if defined(__has_extension) || defined(__GNUC__)
# define _PDFIO_PUBLIC __attribute__ ((visibility("default"))) # define _PDFIO_PUBLIC __attribute__ ((visibility("default")))
# define _PDFIO_FORMAT(a,b) __attribute__ ((__format__(__printf__, a,b)))
# define _PDFIO_DEPRECATED __attribute__ ((deprecated)) _PDFIO_PUBLIC # define _PDFIO_DEPRECATED __attribute__ ((deprecated)) _PDFIO_PUBLIC
# else # else
# define _PDFIO_PUBLIC # define _PDFIO_PUBLIC
# define _PDFIO_FORMAT(a,b)
# define _PDFIO_DEPRECATED # define _PDFIO_DEPRECATED
# endif // __has_extension || __GNUC__ # endif // __has_extension || __GNUC__
@ -181,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 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 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 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; extern bool pdfioDictSetStringf(pdfio_dict_t *dict, const char *key, const char *format, ...) _PDFIO_PUBLIC _PDFIO_FORMAT(3,4);
extern bool pdfioFileClose(pdfio_file_t *pdf) _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; 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;
@ -201,7 +201,6 @@ extern time_t pdfioFileGetCreationDate(pdfio_file_t *pdf) _PDFIO_PUBLIC;
extern const char *pdfioFileGetCreator(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; extern pdfio_array_t *pdfioFileGetID(pdfio_file_t *pdf) _PDFIO_PUBLIC;
extern const char *pdfioFileGetKeywords(pdfio_file_t *pdf) _PDFIO_PUBLIC; extern const char *pdfioFileGetKeywords(pdfio_file_t *pdf) _PDFIO_PUBLIC;
extern time_t pdfioFileGetModificationDate(pdfio_file_t *pdf) _PDFIO_PUBLIC;
extern const char *pdfioFileGetName(pdfio_file_t *pdf) _PDFIO_PUBLIC; extern const char *pdfioFileGetName(pdfio_file_t *pdf) _PDFIO_PUBLIC;
extern size_t pdfioFileGetNumObjs(pdfio_file_t *pdf) _PDFIO_PUBLIC; extern size_t pdfioFileGetNumObjs(pdfio_file_t *pdf) _PDFIO_PUBLIC;
extern size_t pdfioFileGetNumPages(pdfio_file_t *pdf) _PDFIO_PUBLIC; extern size_t pdfioFileGetNumPages(pdfio_file_t *pdf) _PDFIO_PUBLIC;
@ -217,7 +216,6 @@ extern void pdfioFileSetAuthor(pdfio_file_t *pdf, const char *value) _PDFIO_PUB
extern void pdfioFileSetCreationDate(pdfio_file_t *pdf, time_t value) _PDFIO_PUBLIC; extern void pdfioFileSetCreationDate(pdfio_file_t *pdf, time_t value) _PDFIO_PUBLIC;
extern void pdfioFileSetCreator(pdfio_file_t *pdf, const char *value) _PDFIO_PUBLIC; extern void pdfioFileSetCreator(pdfio_file_t *pdf, const char *value) _PDFIO_PUBLIC;
extern void pdfioFileSetKeywords(pdfio_file_t *pdf, const char *value) _PDFIO_PUBLIC; extern void pdfioFileSetKeywords(pdfio_file_t *pdf, const char *value) _PDFIO_PUBLIC;
extern void pdfioFileSetModificationDate(pdfio_file_t *pdf, time_t value) _PDFIO_PUBLIC;
extern bool pdfioFileSetPermissions(pdfio_file_t *pdf, pdfio_permission_t permissions, pdfio_encryption_t encryption, const char *owner_password, const char *user_password) _PDFIO_PUBLIC; extern bool pdfioFileSetPermissions(pdfio_file_t *pdf, pdfio_permission_t permissions, pdfio_encryption_t encryption, const char *owner_password, const char *user_password) _PDFIO_PUBLIC;
extern void pdfioFileSetSubject(pdfio_file_t *pdf, const char *value) _PDFIO_PUBLIC; extern void pdfioFileSetSubject(pdfio_file_t *pdf, const char *value) _PDFIO_PUBLIC;
extern void pdfioFileSetTitle(pdfio_file_t *pdf, const char *value) _PDFIO_PUBLIC; extern void pdfioFileSetTitle(pdfio_file_t *pdf, const char *value) _PDFIO_PUBLIC;
@ -243,14 +241,14 @@ extern bool pdfioStreamClose(pdfio_stream_t *st) _PDFIO_PUBLIC;
extern bool pdfioStreamConsume(pdfio_stream_t *st, size_t bytes) _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 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 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; extern bool pdfioStreamPrintf(pdfio_stream_t *st, const char *format, ...) _PDFIO_PUBLIC _PDFIO_FORMAT(2,3);
extern bool pdfioStreamPutChar(pdfio_stream_t *st, int ch) _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 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 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 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 *pdfioStringCreate(pdfio_file_t *pdf, const char *s) _PDFIO_PUBLIC;
extern char *pdfioStringCreatef(pdfio_file_t *pdf, const char *format, ...) _PDFIO_PUBLIC; extern char *pdfioStringCreatef(pdfio_file_t *pdf, const char *format, ...) _PDFIO_FORMAT(2,3) _PDFIO_PUBLIC;
# ifdef __cplusplus # ifdef __cplusplus

View File

@ -7,7 +7,7 @@ Name: pdfio
Description: PDF read/write library Description: PDF read/write library
Version: @PDFIO_VERSION@ Version: @PDFIO_VERSION@
URL: https://www.msweet.org/pdfio URL: https://www.msweet.org/pdfio
Cflags: @PKGCONFIG_CFLAGS@ Requires: @PKGCONFIG_REQUIRES@
Libs: @PKGCONFIG_LIBS@ Libs: @PKGCONFIG_LIBS@
Libs.private: @PKGCONFIG_LIBS_PRIVATE@ Libs.private: @PKGCONFIG_LIBS_PRIVATE@
Requires: @PKGCONFIG_REQUIRES@ Cflags: @PKGCONFIG_CFLAGS@

View File

@ -115,7 +115,7 @@
<ClCompile> <ClCompile>
<WarningLevel>Level3</WarningLevel> <WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>HAVE_LIBPNG;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
</ClCompile> </ClCompile>
<Link> <Link>
@ -130,7 +130,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>HAVE_LIBPNG;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
</ClCompile> </ClCompile>
<Link> <Link>
@ -172,8 +172,6 @@
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
<Import Project="packages\libpng_native.redist.1.6.30\build\native\libpng_native.redist.targets" Condition="Exists('packages\libpng_native.redist.1.6.30\build\native\libpng_native.redist.targets')" />
<Import Project="packages\libpng_native.1.6.30\build\native\libpng_native.targets" Condition="Exists('packages\libpng_native.1.6.30\build\native\libpng_native.targets')" />
<Import Project="packages\zlib_native.redist.1.2.11\build\native\zlib_native.redist.targets" Condition="Exists('packages\zlib_native.redist.1.2.11\build\native\zlib_native.redist.targets')" /> <Import Project="packages\zlib_native.redist.1.2.11\build\native\zlib_native.redist.targets" Condition="Exists('packages\zlib_native.redist.1.2.11\build\native\zlib_native.redist.targets')" />
<Import Project="packages\zlib_native.1.2.11\build\native\zlib_native.targets" Condition="Exists('packages\zlib_native.1.2.11\build\native\zlib_native.targets')" /> <Import Project="packages\zlib_native.1.2.11\build\native\zlib_native.targets" Condition="Exists('packages\zlib_native.1.2.11\build\native\zlib_native.targets')" />
</ImportGroup> </ImportGroup>

View File

@ -1,20 +1,73 @@
LIBRARY pdfio1 LIBRARY pdfio1
VERSION 1.5 VERSION 1.4
EXPORTS EXPORTS
_pdfioArrayDebug
_pdfioArrayDecrypt
_pdfioArrayDelete
_pdfioArrayGetValue
_pdfioArrayRead
_pdfioArrayWrite
_pdfioCryptoAESDecrypt _pdfioCryptoAESDecrypt
_pdfioCryptoAESEncrypt _pdfioCryptoAESEncrypt
_pdfioCryptoAESInit _pdfioCryptoAESInit
_pdfioCryptoLock
_pdfioCryptoMD5Append _pdfioCryptoMD5Append
_pdfioCryptoMD5Finish _pdfioCryptoMD5Finish
_pdfioCryptoMD5Init _pdfioCryptoMD5Init
_pdfioCryptoMakeRandom
_pdfioCryptoMakeReader
_pdfioCryptoMakeWriter
_pdfioCryptoRC4Crypt _pdfioCryptoRC4Crypt
_pdfioCryptoRC4Init _pdfioCryptoRC4Init
_pdfioCryptoSHA256Append _pdfioCryptoSHA256Append
_pdfioCryptoSHA256Finish _pdfioCryptoSHA256Finish
_pdfioCryptoSHA256Init _pdfioCryptoSHA256Init
_pdfioCryptoUnlock
_pdfioDictDebug
_pdfioDictDecrypt
_pdfioDictDelete
_pdfioDictGetValue
_pdfioDictRead
_pdfioDictSetValue
_pdfioDictWrite
_pdfioFileAddMappedObj
_pdfioFileAddPage
_pdfioFileConsume
_pdfioFileCreateObj
_pdfioFileDefaultError
_pdfioFileError
_pdfioFileFindMappedObj
_pdfioFileFlush
_pdfioFileGetChar
_pdfioFileGets
_pdfioFilePeek
_pdfioFilePrintf
_pdfioFilePuts
_pdfioFileRead
_pdfioFileSeek
_pdfioFileTell
_pdfioFileWrite
_pdfioObjDelete
_pdfioObjGetExtension
_pdfioObjLoad
_pdfioObjSetExtension
_pdfioStreamCreate
_pdfioStreamOpen
_pdfioStringIsAllocated
_pdfioTokenClear
_pdfioTokenFlush
_pdfioTokenGet
_pdfioTokenInit _pdfioTokenInit
_pdfioTokenPush
_pdfioTokenRead
_pdfioValueCopy
_pdfioValueDebug _pdfioValueDebug
_pdfioValueDecrypt
_pdfioValueDelete
_pdfioValueRead _pdfioValueRead
_pdfioValueWrite
_pdfio_strtod
_pdfio_vsnprintf
pdfioArrayAppendArray pdfioArrayAppendArray
pdfioArrayAppendBinary pdfioArrayAppendBinary
pdfioArrayAppendBoolean pdfioArrayAppendBoolean
@ -134,7 +187,6 @@ pdfioFileCreate
pdfioFileCreateArrayObj pdfioFileCreateArrayObj
pdfioFileCreateFontObjFromBase pdfioFileCreateFontObjFromBase
pdfioFileCreateFontObjFromFile pdfioFileCreateFontObjFromFile
pdfioFileCreateICCObjFromData
pdfioFileCreateICCObjFromFile pdfioFileCreateICCObjFromFile
pdfioFileCreateImageObjFromData pdfioFileCreateImageObjFromData
pdfioFileCreateImageObjFromFile pdfioFileCreateImageObjFromFile
@ -152,7 +204,6 @@ pdfioFileGetCreationDate
pdfioFileGetCreator pdfioFileGetCreator
pdfioFileGetID pdfioFileGetID
pdfioFileGetKeywords pdfioFileGetKeywords
pdfioFileGetModificationDate
pdfioFileGetName pdfioFileGetName
pdfioFileGetNumObjs pdfioFileGetNumObjs
pdfioFileGetNumPages pdfioFileGetNumPages
@ -168,7 +219,6 @@ pdfioFileSetAuthor
pdfioFileSetCreationDate pdfioFileSetCreationDate
pdfioFileSetCreator pdfioFileSetCreator
pdfioFileSetKeywords pdfioFileSetKeywords
pdfioFileSetModificationDate
pdfioFileSetPermissions pdfioFileSetPermissions
pdfioFileSetSubject pdfioFileSetSubject
pdfioFileSetTitle pdfioFileSetTitle

View File

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

View File

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

View File

@ -7,8 +7,6 @@
:: Copy dependent DLLs to the named build directory :: Copy dependent DLLs to the named build directory
echo Copying DLLs echo Copying DLLs
copy packages\libpng_native.redist.1.6.30\build\native\bin\x64\Debug\*.dll %1
copy packages\libpng_native.redist.1.6.30\build\native\bin\x64\Release\*.dll %1
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\Debug\*.dll %1
copy packages\zlib_native.redist.1.2.11\build\native\bin\x64\Release\*.dll %1 copy packages\zlib_native.redist.1.2.11\build\native\bin\x64\Release\*.dll %1

View File

@ -1,9 +0,0 @@
PngSuite
--------
Permission to use, copy, modify and distribute these images for any
purpose and without fee is hereby granted.
(c) Willem van Schaik, 1996, 2011

Binary file not shown.

Before

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 B

Some files were not shown because too many files have changed in this diff Show More