72 Commits

Author SHA1 Message Date
Michael R Sweet
8a81732f3b Add 1.6.2 release date. 2026-02-15 10:12:53 -05:00
Michael R Sweet
96840e97c5 Fix pdfioPageGetBoolean implementation. 2026-02-14 15:53:05 -05:00
Michael R Sweet
d6e4570c2e Update DLL exports. 2026-02-14 15:49:31 -05:00
Michael R Sweet
73e3805aea Fix Windows builds pt 4 (Issue #18) 2026-02-14 15:40:09 -05:00
Michael R Sweet
d4c3e5ac13 Fix Windows builds pt 3 (Issue #18) 2026-02-14 15:34:06 -05:00
Michael R Sweet
7d43cabbe0 Fix Windows builds pt 2 (Issue #18) 2026-02-14 15:31:27 -05:00
Michael R Sweet
baa9fca941 Fix Windows builds (Issue #18) 2026-02-14 15:29:14 -05:00
Michael R Sweet
d9444880c5 Add support for Unicode filenames on Windows (Issue #18) 2026-02-14 15:25:53 -05:00
Michael R Sweet
15f197d030 Fix conversions of Unicode characters above plane 0 (Issue #159) 2026-02-14 09:51:49 -05:00
Michael R Sweet
d03e5ee5d9 Update API version number references for consistency, mention GIF and WebP since v1.7. 2026-02-04 16:28:11 -05:00
Michael R Sweet
2837cafd41 When a duplicate object is seen in an xref stream, only replace the object if it has a higher generation number (Issue #155) 2026-01-29 13:01:23 -05:00
Michael R Sweet
c7bf1695fd Tweak docos. 2026-01-29 12:31:50 -05:00
Michael R Sweet
14d844e436 Update documentation. 2026-01-29 12:30:10 -05:00
Michael R Sweet
ee42352228 Move testpdfio output to testfiles subdirectory. 2026-01-29 11:20:08 -05:00
Michael R Sweet
700f7a011b Clean up some compiler warnings.
Add missing file to Xcode project.
2026-01-27 21:04:18 -05:00
Michael R Sweet
3eb9c4a13f Fix pdfioPageGetDate prototype. 2026-01-27 19:47:19 -05:00
Michael R Sweet
c7103e9558 Update debug printfs. 2026-01-27 19:26:30 -05:00
Michael R Sweet
e9ba25c0da Add NULL check for new page contents code. 2026-01-27 19:23:27 -05:00
Michael R Sweet
a6160e7f6f Merge pull request #152 from jeevansridharan/docs-add-ubuntu-deps
docs: add Ubuntu/Debian dependency installation example
2026-01-26 11:15:05 -05:00
Jeevan Sridharan
390e8cef8f docs: clarify and reorder Ubuntu/Debian dependency instructions 2026-01-26 21:37:38 +05:30
Jeevan Sridharan
78c6852413 docs: add Ubuntu/Debian dependency installation example 2026-01-22 22:15:02 +05:30
Michael R Sweet
f4055f0d7a Implement object streams (Issue #101) 2026-01-19 10:22:43 -05:00
Michael R Sweet
a37455c009 Add _pdfioStringPrintf function and support for using a string buffer to collect an object's value (Issue #101) 2026-01-19 10:22:23 -05:00
Michael R Sweet
e61e08b5d2 Minor refactoring to more easily add in object stream support (Issue #101) 2026-01-18 19:29:17 -05:00
Michael R Sweet
a818dee123 Don't generate a mask image if unnecessary. 2026-01-18 18:18:25 -05:00
Michael R Sweet
ddb57bb754 Implement WebP image support (Issue #144) 2026-01-18 16:49:26 -05:00
Michael R Sweet
61d7e0c68d Update documentation to mention GIF files (Issue #145) 2026-01-18 11:43:56 -05:00
Michael R Sweet
c2f2cd6c37 Fix handling of partial image blocks (Issue #145)
Add a partial image block version of the animation test.

Rename test images to make it clear what GIF features are being used.

Finalize test page content/layout.
2026-01-18 11:26:50 -05:00
Michael R Sweet
b3aaf2e70f Initial GIF support (Issue #145) 2026-01-18 10:31:50 -05:00
Michael R Sweet
4e9ec397f1 Implement pdfioPageGetXxx functions (Issue #150) 2026-01-16 20:54:02 -05:00
Michael R Sweet
3c60d4a886 Fix 0 key length regression (Issue #149) 2026-01-16 20:19:34 -05:00
Michael R Sweet
aac04a2a96 Fix repaired xref stream offsets and support indirect Contents arrays for pages. 2026-01-16 16:54:37 -05:00
Michael R Sweet
65098b5509 Fix implementation of LZWDecode filter to account for the EarlyChange parameter
(somewhat buried, very frustrating...)

Add some debugging and update the test suite to find page metadata in any parent
page object.
2026-01-16 15:57:43 -05:00
Michael R Sweet
e6e0b84dfc Remove dead code detected by Coverity. 2026-01-16 11:47:08 -05:00
Michael R Sweet
3e6c38a436 Update Windows DLL exports. 2026-01-16 11:36:34 -05:00
Michael R Sweet
6daf9e5e64 Fix clang warnings. 2026-01-16 11:32:39 -05:00
Michael R Sweet
1044cc71a4 Fix VC++ project. 2026-01-16 11:32:37 -05:00
Michael R Sweet
387a30f6c5 Update TTF. 2026-01-16 11:32:18 -05:00
Michael R Sweet
09520d250f Add support for LZWDecode filter, needs more testing (Issue #11) 2026-01-16 09:53:51 -05:00
Michael R Sweet
bdcd963352 Implement ASCII85Decode filter (Issue #11) 2026-01-14 09:31:41 -05:00
Michael R Sweet
4565c52ff1 Merge pull request #147 from zYg-sys/master
examples: fix builds on windows
2026-01-14 08:08:28 -05:00
Yuguo Zhang
9cb19db1c5 examples: fix builds on windows 2026-01-14 12:35:26 +08:00
Michael R Sweet
5618c432cc Add an xref table offset array to better detect xref table loops (Issue #148) 2026-01-13 18:40:44 -05:00
Michael R Sweet
4143808398 Clarify security policy. 2026-01-13 13:44:36 -05:00
Michael R Sweet
c92546ed94 More test suite tweaking. 2026-01-13 10:34:38 -05:00
Michael R Sweet
07c6005fad Add some guards to make sure you don't accidentally overwrite existing PDF files with testpdfio... 2026-01-11 14:33:19 -05:00
Michael R Sweet
6fd1b781dc Update test script. 2026-01-11 13:46:04 -05:00
Michael R Sweet
6e8bd06937 Refactor PDF encryption handler to work with more files. 2026-01-11 13:36:56 -05:00
Michael R Sweet
0feace3eb5 Fix AES-256 key initialization. 2026-01-10 14:44:47 -05:00
Michael R Sweet
846b0c9c7f Add missing range checks to pdfioArrayCopy and pdfioDictCopy. 2026-01-08 15:05:12 -05:00
Michael R Sweet
fda0963220 Update Xcode project settings. 2026-01-08 12:39:08 -05:00
Yuguo Zhang
31fb66917e examples: fix builds on windows 2026-01-08 12:16:33 +08:00
Michael R Sweet
089288946e Increase PDFIO_MAX_STRING to 128k (Issue #146) 2026-01-06 11:21:58 -05:00
Michael R Sweet
68dda34448 Fix an error propagation bug in _pdfioValueCopy (Issue #146) 2026-01-06 11:18:52 -05:00
Michael R Sweet
ae97788728 Fix Coverity status badge link. 2026-01-01 10:06:41 -05:00
Michael R Sweet
6e049d4ed1 Update GitHub CI. 2026-01-01 09:59:16 -05:00
Michael R Sweet
d70a72fdda Update prerequisites. 2026-01-01 09:57:43 -05:00
Michael R Sweet
8ba48ba4ae Fix GitHub CI badge URL (again). 2026-01-01 09:54:19 -05:00
Michael R Sweet
031ad03a38 Fix GitHub CI badge URL. 2026-01-01 09:48:42 -05:00
Michael R Sweet
9b1047b2e3 Sync up with latest TTF changes for Windows. 2026-01-01 09:43:14 -05:00
Michael R Sweet
62dabe3580 Fix builds on Windows. 2025-12-31 11:58:24 -05:00
Michael R Sweet
16e2d6c91a Fix clang issues. 2025-12-31 11:44:28 -05:00
Michael R Sweet
d1cbeec9e3 Visual Studio needs additional include dirs. 2025-12-31 11:38:12 -05:00
Michael R Sweet
29c2d131da Update Xcode and Visual Studio projects. 2025-12-31 11:33:29 -05:00
Michael R Sweet
115119a6f8 Update TTF to v1.1.0. 2025-12-31 11:29:08 -05:00
Michael R Sweet
616f053835 No more testttf in the local directory. 2025-12-26 12:31:15 -05:00
Michael R Sweet
d1536eee0c Changelog. 2025-12-26 12:14:56 -05:00
Michael R Sweet
06d3e85627 Update GitHub CI rules. 2025-12-21 22:14:20 -05:00
Michael R Sweet
850b0fa0a0 Update makesrcdist to correctly embed a copy of the TTF library. 2025-12-21 20:37:50 -05:00
Michael R Sweet
7ac8669057 Change to using external TTF library, when available, otherwise local copy.
Fix pkgconfig file.
2025-12-21 20:28:26 -05:00
Michael R Sweet
f6f3191a8d Bump version. 2025-12-21 19:18:33 -05:00
Michael R Sweet
c2b25a1fa0 Support Encrypt dictionaries as well as indirect references (Issue #139) 2025-12-21 19:04:36 -05:00
65 changed files with 4649 additions and 3822 deletions

View File

@@ -13,7 +13,9 @@ jobs:
steps:
- name: Checkout PDFio sources
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
- name: Update Build Environment
run: sudo apt-get update --fix-missing -y
- name: Install Prerequisites
@@ -37,7 +39,9 @@ jobs:
steps:
- name: Checkout PDFio sources
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
- name: Configure PDFio
run: ./configure --enable-debug --enable-sanitizer --enable-maintainer
- name: Build PDFio
@@ -53,7 +57,9 @@ jobs:
steps:
- name: Checkout PDFio sources
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Nuget Restore

View File

@@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout PDFio sources
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive

View File

@@ -8,7 +8,9 @@ jobs:
environment: Coverity
steps:
- name: Checkout PDFio sources
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
- name: Update Build Environment
run: sudo apt-get update --fix-missing -y
- name: Install Prerequisites

2
.gitignore vendored
View File

@@ -25,5 +25,5 @@
/pdfio-*.zip*
/testpdfio
/testpdfio-*.pdf
/testttf
/testfiles/testpdfio-*.pdf
/x64

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "ttf"]
path = ttf
url = https://github.com/michaelrsweet/ttf.git

View File

@@ -2,14 +2,48 @@ Changes in PDFio
================
v1.6.1 - YYYY-MM-DD
v1.7.0 - YYYY-MM-DD
-------------------
- Now use TTF 1.1 or later for font support.
- Added support for basic compound stream filters for ASCII85Decode support
(Issue #11)
- Added support for LZWDecode filters (Issue #11)
- Added support for Unicode filenames on Windows (Issue #18)
- Added support for writing object streams (Issue #101)
- Added support for GIF files (Issue #145)
- Added support for WebP files (Issue #144)
- Added `pdfioPageGetXxx` functions to get values from the page dictionaries
(Issue #150)
- Fixed a buffer overflow in the (still not enabled) AES-256 code.
v1.6.2 - 2026-02-15
-------------------
- Increased the maximum length of a single string to 128k (Issue #146)
- Added missing range checks to `pdfioArrayCopy` and `pdfioDictCopy`.
- Refactored PDF encryption code to fix unlocking with certain files.
- Improved xref table loop detection (Issue #148)
- Changed how duplicate objects are handled in PDF files (Issue #155)
- Fixed xref reconstruction for objects lacking a `Type` value.
- Fixed `pdfioPageOpenStream` for indirect `Contents` arrays.
- Fixed an error propagation bug when reading too-long values (Issue #146)
- Fixed a bug when converting Unicode characters above plane 0 (issue #159)
- Fixed a Clang warning.
v1.6.1 - 2025-12-26
-------------------
- Added missing input checking to `pdfioFileCreateFontObjFromBase` function.
- Updated support for UTF-16 strings (Issue #141)
- Updated Xcode project to use installed PNG library.
- Fixed decryption of PDF files using an Encrypt dictionary instead of an
indirect reference (Issue #139)
- Fixed character range checking in a TTF support function.
- Fixed some clang warnings.
- Fixed the generated pkg-config file.

View File

@@ -110,10 +110,11 @@ same guidelines as allowed by the language.
### Source Files
All source files names must be 16 characters or less in length to ensure
compatibility with older UNIX filesystems. Source files containing functions
have an extension of ".c" for C source files. All "include" files have an
extension of ".h". Tabs are set to 8 characters or columns.
All source files names must be lowercase and should be 16 characters or less in
length to ensure compatibility with older UNIX filesystems. Source files
containing functions have an extension of ".c" for C source files. All
"include" files have an extension of ".h". Tabs are set every 8 characters,
columns, or spaces, with an indentation of 2 spaces in code.
The top of each source file contains a header giving the purpose or nature of
the source file and the copyright and licensing notice:
@@ -141,8 +142,8 @@ private API header file will include the corresponding public API header file.
All source code utilizes block comments within functions to describe the
operations being performed by a group of statements; avoid putting a comment
per line unless absolutely necessary, and then consider refactoring the code
so that it is not necessary. C source files use the C99 comment format
("// comment"):
so that it is not necessary. C source files typically use the C99 comment
format ("// comment"):
// Clear the state array before we begin...
for (i = 0; i < (sizeof(array) / sizeof(sizeof(array[0])); i ++)
@@ -159,6 +160,17 @@ so that it is not necessary. C source files use the C99 comment format
sleep(1);
} while (i == (sizeof(array) / sizeof(array[0])));
When passing literal argument values to functions, the argument name should be
supplied as an inline comment:
// This function call lacks any inline comments so it is hard to
// know what the numbers mean!
do_something(filename, 1, 42);
// This uses inline comments to specify what the numbers mean and
// makes the code easier to read and maintain:
do_something(filename, /*initial_value*/1, /*count*/42);
### Indentation
@@ -256,16 +268,16 @@ Return/output values are indicated using an "O" prefix, input values are
indicated using the "I" prefix, and values that are both input and output use
the "IO" prefix for the corresponding in-line comment.
The [`codedoc` documentation generator][1] also understands the following
The [`codedoc` documentation generator][CODEDOC] also understands the following
special text in the function description comment:
@deprecated@ - Marks the function as deprecated (not recommended
for new development and scheduled for removal)
@since version@ - Marks the function as new in the specified version.
@since VERSION@ - Marks the function as new in the specified version.
@private@ - Marks the function as private (same as starting the
function name with an underscore)
[1]: https://www.msweet.org/codedoc
[CODEDOC]: https://www.msweet.org/codedoc
### Variables
@@ -373,11 +385,13 @@ The following variables are defined in the makefile:
- `AR`; the static library archiver command,
- `ARFLAGS`; options for the static library archiver,
- `BUILDROOT`: the destination root directory when installing - also picks up
the value of the `DESTDIR`, `DSTROOT`, and/or `RPM_BUILD_ROOT` environment
variables,
- `CC`; the C compiler command,
- `CFLAGS`; options for the C compiler,
- `CODESIGN_IDENTITY`: the code signing identity,
- `CPPFLAGS`; options for the C preprocessor,
- `DESTDIR`/`DSTROOT`: the destination root directory when installing.
- `DSO`; the shared library building command,
- `DSOFLAGS`; options for the shared library building command,
- `LDFLAGS`; options for the linker,
@@ -395,5 +409,6 @@ The following standard targets are defined in the makefile:
- `all`; creates the static library and unit test program.
- `clean`; removes all target programs libraries, documentation files, and
object files,
- `doc`; creates the library documentation files using [`codedoc`][CODEDOC],
- `install`; installs all distribution files in their corresponding locations.
- `test`; runs the unit test program, building it as needed.

View File

@@ -2,17 +2,12 @@ PDFio Examples
==============
The "examples" subdirectory contains example code showing how to do different
things with PDFio.
things with PDFio:
code128.c
---------
This example shows how to embed and use a barcode font.
md2pdf.c
--------
This example shows how to generate pages with multiple fonts, embedded images,
and headers and footers.
- `code128.c`: Shows how to embed and use a barcode font.
- `image2pdf.c`: Shows how to embed and use an image file.
- `md2pdf.c`: Shows how to generate pages with multiple fonts, embedded images,
and headers and footers.
- `pdf2text.c`: Shows how to extract plain text from a PDF.
- `pdfioinfo.c`: Shows how to extract metadata from a PDF.
- `pdfiomerge.c`: Shows how to merge two or more PDFs.

135
INSTALL.md Normal file
View File

@@ -0,0 +1,135 @@
Building, Testing, and Installing PDFio
=======================================
This file describes how to compile, test, and install the PDFio library from
source code. For more information on PDFio see the file called `README.md`.
Getting the Code
----------------
> Note: Do not use the ZIP file available via the Github "Code" button on the
> main project page, as that archive is missing the TTF submodule and will not
> compile.
The source code is available in release tarballs or via the Github repository.
For a release tarball, run the following commands:
tar xvzf pdfio-VERSION.tar.gz
cd pdfio-VERSION
Similarly, the release ZIP file can be extracted with the following commands:
unzip pdfio-VERSION.zip
cd pdfio-VERSION
From the Github sources, clone the repository with the `--recurse-submodules`
option *or* use the `git submodule` command:
git clone --recurse-submodules git@github.com:michaelrsweet/pdfio.git
cd pdfio
git clone git@github.com:michaelrsweet/pdfio.git
cd pdfio
git submodule update --init --recursive
To update an already-cloned repository:
git pull
git submodule update --init --recursive
Requirements
------------
PDFio requires the following to build the software:
- A C99 compiler such as Clang, GCC, or MS Visual C
- A POSIX-compliant `make` program
- A POSIX-compliant `sh` program
- libpng (<https://www.libpng.org/>) 1.6 or later for full PNG image support
(optional)
- libwebp (<https://developers.google.com/speed/webp>) 1.0 or later for WebP
image support (optional)
- ZLIB (<https://www.zlib.net/>) 1.1 or later for compression support
IDE files for Xcode (macOS/iOS) and Visual Studio (Windows) are also provided.
On a stock Ubuntu install, the following command will install the various
prerequisites:
sudo apt-get install build-essential libpng-dev libwebp-dev zlib1g-dev
Building and Installing on Linux/Unix
-------------------------------------
PDFio uses a configure script on Unix systems to generate a makefile:
./configure
If you want a shared library, run:
./configure --enable-shared
The default installation location is "/usr/local". Pass the `--prefix` option
to make to install it to another location:
./configure --prefix=/some/other/directory
Other configure options can be discovered by using the `--help` option:
./configure --help
Once configured, run the following command to build the library:
make all
Finally, run the following command to install the library, documentation, and
examples on the local system:
sudo make install
Use the `BUILDROOT` variable to install to an alternate root directory:
sudo make BUILDROOT=/some/other/root/directory install
> Note: PDFio also supports the GNU `DSTROOT`, Apple/Darwin `DESTDIR`, and
> Red Hat `RPM_BUILD_ROOT` variables to specify an alternate root directory.
### Running the Unit Tests
PDFio includes a unit test program that thoroughly tests the library. Type the
following to run the unit tests:
make test
Detailed test results are saved to the "test.log" file.
Building on Windows with Visual Studio
--------------------------------------
The Visual Studio solution (`pdfio.sln`) is provided for Windows developers and
generates the PDFIO1 DLL. You can also use NuGet to install the `pdfio_native`
package.
You can build and run the `testpdfio` unit tests target from within Visual
Studio to verify the functioning of the library.
Building on macOS with Xcode
----------------------------
The Xcode project (`pdfio.xcodeproj`) is provided for macOS developer which
generates a static library that will be installed under "/usr/local". Run the
following command to build and install PDFio on macOS:
sudo xcodebuild install
Alternately you can add the Xcode project to a workspace and reference the PDFio
library target as a dependency in your own project.
You can build and run the `testpdfio` unit tests target from within Xcode to
verify the functioning of the library.

View File

@@ -1,7 +1,7 @@
#
# Makefile for PDFio.
#
# Copyright © 2021-2025 by Michael R Sweet.
# Copyright © 2021-2026 by Michael R Sweet.
#
# Licensed under Apache License v2.0. See the file "LICENSE" for more
# information.
@@ -69,6 +69,8 @@ top_srcdir = @top_srcdir@
BUILDROOT = $(DSTROOT)$(RPM_BUILD_ROOT)$(DESTDIR)
TTFDIR = @TTFDIR@
# Build commands...
.SUFFIXES: .c .h .o
@@ -89,6 +91,7 @@ PUBOBJS = \
pdfio-crypto.o \
pdfio-dict.o \
pdfio-file.o \
pdfio-lzw.o \
pdfio-md5.o \
pdfio-object.o \
pdfio-page.o \
@@ -99,17 +102,14 @@ PUBOBJS = \
pdfio-token.o \
pdfio-value.o
LIBOBJS = \
$(PUBOBJS) \
ttf.o
$(PUBOBJS)
OBJS = \
$(LIBOBJS) \
testpdfio.o \
testttf.o
testpdfio.o
TARGETS = \
$(LIBPDFIO) \
$(LIBPDFIO_STATIC) \
testpdfio \
testttf
testpdfio
DOCFILES = \
doc/pdfio.html \
doc/pdfio-512.png \
@@ -135,16 +135,30 @@ EXAMPLES = \
# Make everything
all: $(TARGETS)
all:
if test "x$(TTFDIR)" != x; then \
echo Making all in $(TTFDIR)...; \
(cd $(TTFDIR); $(MAKE) $(MFLAGS) all) || exit 1; \
fi
$(MAKE) $(MFLAGS) $(TARGETS)
# Clean everything
clean:
if test "x$(TTFDIR)" != x; then \
echo Cleaning in $(TTFDIR)...; \
(cd $(TTFDIR); $(MAKE) $(MFLAGS) clean) || exit 1; \
fi
echo Cleaning build files...
rm -f $(TARGETS) $(OBJS)
# Install everything
install: $(TARGETS)
if test "x$(TTFDIR)" != x; then \
echo Installing in $(TTFDIR)...; \
(cd $(TTFDIR); $(MAKE) $(MFLAGS) install) || exit 1; \
fi
echo Installing header files to $(BUILDROOT)$(includedir)...
$(INSTALL) -d -m 755 $(BUILDROOT)$(includedir)
for file in $(PUBHEADERS); do \
@@ -186,9 +200,8 @@ install: $(TARGETS)
# Test everything
test: testpdfio testttf
./testttf 2>test.log
./testpdfio 2>>test.log
test: testpdfio
./testpdfio 2>test.log
LANG=fr_FR.UTF-8 ./testpdfio 2>>test.log
@@ -222,6 +235,7 @@ pdfio1.def: $(LIBOBJS) Makefile
echo "LIBRARY pdfio1" >$@
echo "VERSION $(PDFIO_VERSION_MAJOR).$(PDFIO_VERSION_MINOR)" >>$@
echo "EXPORTS" >>$@
echo "_pdfio_win32_open" >>$@
nm $(LIBOBJS) 2>/dev/null | grep "T _" | awk '{print $$3}' | \
grep -v '^_ttf' | sed -e '1,$$s/^_//' | sort >>$@
@@ -232,24 +246,16 @@ testpdfio: testpdfio.o libpdfio.a
$(CC) $(LDFLAGS) -o $@ testpdfio.o libpdfio.a $(LIBS)
# TTF test program
testttf: ttf.o testttf.o
echo Linking $@...
$(CC) $(LDFLAGS) -o testttf ttf.o testttf.o $(LIBS)
# Dependencies
$(OBJS): pdfio.h pdfio-private.h Makefile
pdfio-content.o: pdfio-content.h ttf.h
testpdfio.o: test.h
testttf.o: ttf.h
ttf.o: ttf.h
pdfio-content.o: pdfio-content.h
testpdfio.o: test-internal.h
# Make documentation using Codedoc <https://www.msweet.org/codedoc>
DOCFLAGS = \
--author "Michael R Sweet" \
--copyright "Copyright (c) 2021-2025 by Michael R Sweet" \
--copyright "Copyright (c) 2021-2026 by Michael R Sweet" \
--docversion $(PDFIO_VERSION)
.PHONY: doc

2
NOTICE
View File

@@ -1,6 +1,6 @@
PDFio - PDF Read/Write Library
Copyright © 2021-2025 by Michael R Sweet.
Copyright © 2021-2026 by Michael R Sweet.
(Optional) Exceptions to the Apache 2.0 License:
================================================

View File

@@ -1,10 +1,10 @@
pdfio - PDF Read/Write Library
PDFio - PDF Read/Write Library
==============================
![Version](https://img.shields.io/github/v/release/michaelrsweet/pdfio?include_prereleases)
![Apache 2.0](https://img.shields.io/github/license/michaelrsweet/pdfio)
[![Build Status](https://img.shields.io/github/workflow/status/michaelrsweet/pdfio/Build)](https://github.com/michaelrsweet/pdfio/actions/workflows/build.yml)
[![Coverity Scan Status](https://img.shields.io/coverity/scan/22385.svg)](https://scan.coverity.com/projects/michaelrsweet-pdfio)
[![Build Status](https://img.shields.io/github/actions/workflow/status/michaelrsweet/pdfio/build.yml)](https://github.com/michaelrsweet/pdfio/actions/workflows/build.yml)
[![Coverity Scan Status](https://img.shields.io/coverity/scan/23194.svg)](https://scan.coverity.com/projects/michaelrsweet-pdfio)
PDFio is a simple C library for reading and writing PDF files. The primary
goals of PDFio are:
@@ -20,77 +20,29 @@ goals of PDFio are:
PDFio is *not* concerned with rendering or viewing a PDF file, although a PDF
RIP or viewer could be written using it.
Requirements
------------
PDFio requires the following to build the software:
- A C99 compiler such as Clang, GCC, or MS Visual C
- A POSIX-compliant `make` program
- ZLIB (<https://www.zlib.net>) 1.1 or higher
IDE files for Xcode (macOS/iOS) and Visual Studio (Windows) are also provided.
Copyright © 2021-2026 by Michael R Sweet. PDFio is licensed under the Apache
License Version 2.0 with an (optional) exception to allow linking against GNU
GPL2-only software. See the files `LICENSE` and `NOTICE` for more information.
Documentation
-------------
Reading the Documentation
-------------------------
See the man page (`pdfio.3`) and full HTML documentation (`pdfio.html`) for
information on using PDFio.
Initial documentation to get you started is provided in the root directory of
the PDFio sources:
- `CHANGES.md`: A list of changes for each release of PDFio.
- `CODE_OF_CONDUCT.md`: Code of conduct for the project.
- `CONTRIBUTING.md`: Guidelines for contributing to the project.
- `INSTALL.md`: Instructions for building, testing, and installing PDFio.
- `LICENSE`: The PDFio license agreement (Apache 2.0).
- `NOTICE`: Copyright notices and exceptions to the PDFio license agreement.
- `README.md`: This file.
- `SECURITY.md`: How (and when) to report security issues.
Installing PDFio
----------------
You will find the PDFio documentation in HTML, EPUB, and man formats in the
`doc` directory.
PDFio uses a configure script on Unix systems to generate a makefile:
Examples can be found in the `examples` directory.
./configure
If you want a shared library, run:
./configure --enable-shared
The default installation location is "/usr/local". Pass the `--prefix` option
to make to install it to another location:
./configure --prefix=/some/other/directory
Once configured, run the following to make the library:
make all
To test it, run:
make test
To install it, run:
sudo make install
Visual Studio Project
---------------------
The Visual Studio solution ("pdfio.sln") is provided for Windows developers and
generates the PDFIO1 DLL. You can also use NuGet to install the `pdfio_native`
package.
Xcode Project
-------------
There is also an Xcode project ("pdfio.xcodeproj") you can use on macOS which
generates a static library that will be installed under "/usr/local" with:
sudo xcodebuild install
Legal Stuff
-----------
PDFio is Copyright © 2021-2025 by Michael R Sweet.
This software 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.
*Please read the documentation before asking questions.*

View File

@@ -5,12 +5,40 @@ This file describes how security issues are reported and handled, and what the
expectations are for security issues reported to this project.
What is a Security Bug?
-----------------------
Not every bug is a security bug.
Certain bugs that might be considered security bugs in a program, such as bugs
that lead to a Denial of Service, are *not* considered security bugs simply
because this project *does not provide a service*. Some might argue that, "my
server uses this library and the bug in this library causes a denial of service
for my server", however it is the responsibility of the *server* to protect
against DoS attacks, not a subordinate library, because only the server knows
what is an appropriate use of memory, CPU, time, and other resources.
Similarly, bugs caused by incorrect API usage such as passing `NULL` pointers
where such pointers are not allowed, passing the wrong kinds of pointers or
objects to an API, or using a private API are not security bugs because they
are not caused by an attacker but by the developer.
Finally, bugs that only exist in unreleased (non-production) or inactive code
are not security bugs because they do not affect ordinary users. See the
[Supported Versions](#supported-versions) section below for more information
about what versions of the project are covered by this security policy.
If the bug you've found falls into one of these three categories, please report
the bug as an the ordinary project issue at
<https://github.com/michaelrsweet/pdfio/issues>.
Reporting a Security Bug
------------------------
For the purposes of this project, a security bug is a software defect that
allows a *local or remote user* to gain unauthorized access or privileges on the
host computer or to cause the software to crash. Such defects should be
host computer or to causes the software to crash. Such defects should be
reported to the project security advisory page at
<https://github.com/michaelrsweet/pdfio/security/advisories>.
@@ -18,11 +46,6 @@ Alternately, security bugs can be reported to "security AT msweet.org" using the
PGP public key below. Expect a response within 5 business days. Any proposed
embargo date should be at least 30 days and no more than 90 days in the future.
> *Note:* If you've found a software defect that allows a *program* to gain
> unauthorized access or privileges on the host computer or causes the program
> to crash, that defect should be reported as an ordinary project issue at
> <https://github.com/michaelrsweet/pdfio/issues>.
Responsible Disclosure
----------------------
@@ -68,6 +91,9 @@ example:
1.0b2
1.0rc1
Pre-release code in a Git branch ("master", "v1.6.x", etc.) is similarly *not*
production release code.
PGP Public Key
--------------

268
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for pdfio 1.6.0.
# Generated by GNU Autoconf 2.71 for pdfio 1.7.0.
#
# Report bugs to <https://github.com/michaelrsweet/pdfio/issues>.
#
@@ -610,8 +610,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='pdfio'
PACKAGE_TARNAME='pdfio'
PACKAGE_VERSION='1.6.0'
PACKAGE_STRING='pdfio 1.6.0'
PACKAGE_VERSION='1.7.0'
PACKAGE_STRING='pdfio 1.7.0'
PACKAGE_BUGREPORT='https://github.com/michaelrsweet/pdfio/issues'
PACKAGE_URL='https://www.msweet.org/pdfio'
@@ -647,13 +647,16 @@ ac_includes_default="\
#endif"
ac_header_c_list=
enable_option_checking=no
ac_subst_vars='LTLIBOBJS
LIBOBJS
WARNINGS
CSFLAGS
LIBPDFIO_STATIC
LIBPDFIO
PKGCONFIG_LIBPNG
TTFDIR
subdirs
PKGCONFIG_REQUIRES_PRIVATE
PKGCONFIG_REQUIRES
PKGCONFIG_LIBS_PRIVATE
PKGCONFIG_LIBS
@@ -731,6 +734,7 @@ ac_subst_files=''
ac_user_opts='
enable_option_checking
enable_libpng
enable_libwebp
enable_static
enable_shared
enable_debug
@@ -747,7 +751,7 @@ CFLAGS
LDFLAGS
LIBS
CPPFLAGS'
ac_subdirs_all='ttf'
# Initialize some variables set by options.
ac_init_help=
@@ -1295,7 +1299,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures pdfio 1.6.0 to adapt to many kinds of systems.
\`configure' configures pdfio 1.7.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1361,7 +1365,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of pdfio 1.6.0:";;
short | recursive ) echo "Configuration of pdfio 1.7.0:";;
esac
cat <<\_ACEOF
@@ -1369,7 +1373,9 @@ Optional Features:
--disable-option-checking ignore unrecognized --enable/--with options
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
--enable-libpng use libpng for pdfioFileCreateImageObjFromFile,
--disable-libpng use libpng for pdfioFileCreateImageObjFromFile,
default=auto
--disable-libwebp use libwebp for pdfioFileCreateImageObjFromFile,
default=auto
--disable-static do not install static library
--enable-shared install shared library
@@ -1460,7 +1466,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
pdfio configure 1.6.0
pdfio configure 1.7.0
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@@ -1678,7 +1684,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by pdfio $as_me 1.6.0, which was
It was created by pdfio $as_me 1.7.0, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@@ -2434,9 +2440,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
PDFIO_VERSION="1.6.0"
PDFIO_VERSION_MAJOR="`echo 1.6.0 | awk -F. '{print $1}'`"
PDFIO_VERSION_MINOR="`echo 1.6.0 | awk -F. '{printf("%d\n",$2);}'`"
PDFIO_VERSION="1.7.0"
PDFIO_VERSION_MAJOR="`echo 1.7.0 | awk -F. '{print $1}'`"
PDFIO_VERSION_MINOR="`echo 1.7.0 | awk -F. '{printf("%d\n",$2);}'`"
@@ -4138,13 +4144,44 @@ fi
PKGCONFIG_CFLAGS="-I\${includedir}"
PKGCONFIG_LIBS="-L\${libdir} -lpdfio"
PKGCONFIG_LIBS_PRIVATE="-lm"
PKGCONFIG_REQUIRES="zlib"
PKGCONFIG_REQUIRES=""
PKGCONFIG_REQUIRES_PRIVATE="ttf"
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ttf library" >&5
printf %s "checking for ttf library... " >&6; }
if $PKGCONFIG --exists ttf
then :
# Use installed TTF library...
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
printf "%s\n" "yes" >&6; }
CPPFLAGS="$CPPFLAGS $($PKGCONFIG --cflags ttf)"
TTFDIR=""
LIBS="$($PKGCONFIG --libs ttf) $LIBS"
else $as_nop
# Use embedded TTF library...
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no, using embedded version" >&5
printf "%s\n" "no, using embedded version" >&6; }
CPPFLAGS="$CPPFLAGS -Ittf"
TTFDIR="ttf"
LIBS="-Lttf \`PKG_CONFIG_PATH=ttf $PKGCONFIG --libs ttf\` $LIBS"
subdirs="$subdirs ttf"
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for zlib via pkg-config" >&5
printf %s "checking for zlib via pkg-config... " >&6; }
if $PKGCONFIG --exists zlib
@@ -4154,6 +4191,7 @@ then :
printf "%s\n" "yes" >&6; }
CPPFLAGS="$($PKGCONFIG --cflags zlib) $CPPFLAGS"
LIBS="$($PKGCONFIG --libs zlib) $LIBS"
PKGCONFIG_REQUIRES_PRIVATE="$PKGCONFIG_REQUIRES_PRIVATE, zlib"
else $as_nop
@@ -4216,11 +4254,11 @@ then :
fi
PKGCONFIG_REQUIRES=""
PKGCONFIG_LIBS_PRIVATE="-lz $PKGCONFIG_LIBS_PRIVATE"
fi
# Check whether --enable-libpng was given.
if test ${enable_libpng+y}
then :
@@ -4228,9 +4266,6 @@ then :
fi
PKGCONFIG_LIBPNG=""
if test "x$PKGCONFIG" != x -a x$enable_libpng != xno
then :
@@ -4246,8 +4281,7 @@ 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"
PKGCONFIG_REQUIRES_PRIVATE="libpng >= 1.6, $PKGCONFIG_REQUIRES_PRIVATE"
else $as_nop
@@ -4270,6 +4304,51 @@ then :
fi
# Check whether --enable-libwebp was given.
if test ${enable_libwebp+y}
then :
enableval=$enable_libwebp;
fi
if test "x$PKGCONFIG" != x -a x$enable_libwebp != xno
then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libwebp" >&5
printf %s "checking for libwebp... " >&6; }
if $PKGCONFIG --exists libwebp
then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
printf "%s\n" "yes" >&6; };
printf "%s\n" "#define HAVE_LIBWEBP 1" >>confdefs.h
CPPFLAGS="$($PKGCONFIG --cflags libwebp) -DHAVE_LIBWEBP=1 $CPPFLAGS"
LIBS="$($PKGCONFIG --libs libwebp) -lz $LIBS"
PKGCONFIG_REQUIRES_PRIVATE="libwebp, $PKGCONFIG_REQUIRES_PRIVATE"
else $as_nop
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
printf "%s\n" "no" >&6; };
if test x$enable_libwebp = xyes
then :
as_fn_error $? "libwebp-dev required for --enable-libwebp." "$LINENO" 5
fi
fi
elif test x$enable_libwebp = xyes
then :
as_fn_error $? "libwebp-dev required for --enable-libwebp." "$LINENO" 5
fi
# Check whether --enable-static was given.
if test ${enable_static+y}
then :
@@ -4314,6 +4393,8 @@ else $as_nop
LIBPDFIO_STATIC=""
PKGCONFIG_LIBS="$PKGCONFIG_LIBS $PKGCONFIG_LIBS_PRIVATE"
PKGCONFIG_LIBS_PRIVATE=""
PKGCONFIG_REQUIRES="$PKGCONFIG_REQUIRES_PRIVATE"
PKGCONFIG_REQUIRES_PRIVATE=""
fi
@@ -5106,7 +5187,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by pdfio $as_me 1.6.0, which was
This file was extended by pdfio $as_me 1.7.0, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -5162,7 +5243,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
pdfio config.status 1.6.0
pdfio config.status 1.7.0
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"
@@ -5718,6 +5799,149 @@ if test "$no_create" != yes; then
# would make configure fail if this is the last instruction.
$ac_cs_success || as_fn_exit 1
fi
#
# CONFIG_SUBDIRS section.
#
if test "$no_recursion" != yes; then
# Remove --cache-file, --srcdir, and --disable-option-checking arguments
# so they do not pile up.
ac_sub_configure_args=
ac_prev=
eval "set x $ac_configure_args"
shift
for ac_arg
do
if test -n "$ac_prev"; then
ac_prev=
continue
fi
case $ac_arg in
-cache-file | --cache-file | --cache-fil | --cache-fi \
| --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
ac_prev=cache_file ;;
-cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
| --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* \
| --c=*)
;;
--config-cache | -C)
;;
-srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
ac_prev=srcdir ;;
-srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
;;
-prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
ac_prev=prefix ;;
-prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
;;
--disable-option-checking)
;;
*)
case $ac_arg in
*\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
esac
as_fn_append ac_sub_configure_args " '$ac_arg'" ;;
esac
done
# Always prepend --prefix to ensure using the same prefix
# in subdir configurations.
ac_arg="--prefix=$prefix"
case $ac_arg in
*\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
esac
ac_sub_configure_args="'$ac_arg' $ac_sub_configure_args"
# Pass --silent
if test "$silent" = yes; then
ac_sub_configure_args="--silent $ac_sub_configure_args"
fi
# Always prepend --disable-option-checking to silence warnings, since
# different subdirs can have different --enable and --with options.
ac_sub_configure_args="--disable-option-checking $ac_sub_configure_args"
ac_popdir=`pwd`
for ac_dir in : $subdirs; do test "x$ac_dir" = x: && continue
# Do not complain, so a configure script can configure whichever
# parts of a large source tree are present.
test -d "$srcdir/$ac_dir" || continue
ac_msg="=== configuring in $ac_dir (`pwd`/$ac_dir)"
printf "%s\n" "$as_me:${as_lineno-$LINENO}: $ac_msg" >&5
printf "%s\n" "$ac_msg" >&6
as_dir="$ac_dir"; as_fn_mkdir_p
ac_builddir=.
case "$ac_dir" in
.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
*)
ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'`
# A ".." for each directory in $ac_dir_suffix.
ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
case $ac_top_builddir_sub in
"") ac_top_builddir_sub=. ac_top_build_prefix= ;;
*) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
esac ;;
esac
ac_abs_top_builddir=$ac_pwd
ac_abs_builddir=$ac_pwd$ac_dir_suffix
# for backward compatibility:
ac_top_builddir=$ac_top_build_prefix
case $srcdir in
.) # We are building in place.
ac_srcdir=.
ac_top_srcdir=$ac_top_builddir_sub
ac_abs_top_srcdir=$ac_pwd ;;
[\\/]* | ?:[\\/]* ) # Absolute name.
ac_srcdir=$srcdir$ac_dir_suffix;
ac_top_srcdir=$srcdir
ac_abs_top_srcdir=$srcdir ;;
*) # Relative name.
ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
ac_top_srcdir=$ac_top_build_prefix$srcdir
ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
esac
ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
cd "$ac_dir"
# Check for configure.gnu first; this name is used for a wrapper for
# Metaconfig's "Configure" on case-insensitive file systems.
if test -f "$ac_srcdir/configure.gnu"; then
ac_sub_configure=$ac_srcdir/configure.gnu
elif test -f "$ac_srcdir/configure"; then
ac_sub_configure=$ac_srcdir/configure
else
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: no configuration information is in $ac_dir" >&5
printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2;}
ac_sub_configure=
fi
# The recursion is here.
if test -n "$ac_sub_configure"; then
# Make the cache file name correct relative to the subdirectory.
case $cache_file in
[\\/]* | ?:[\\/]* ) ac_sub_cache_file=$cache_file ;;
*) # Relative name.
ac_sub_cache_file=$ac_top_build_prefix$cache_file ;;
esac
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: running $SHELL $ac_sub_configure $ac_sub_configure_args --cache-file=$ac_sub_cache_file --srcdir=$ac_srcdir" >&5
printf "%s\n" "$as_me: running $SHELL $ac_sub_configure $ac_sub_configure_args --cache-file=$ac_sub_cache_file --srcdir=$ac_srcdir" >&6;}
# The eval makes quoting arguments work.
eval "\$SHELL \"\$ac_sub_configure\" $ac_sub_configure_args \
--cache-file=\"\$ac_sub_cache_file\" --srcdir=\"\$ac_srcdir\"" ||
as_fn_error $? "$ac_sub_configure failed for $ac_dir" "$LINENO" 5
fi
cd "$ac_popdir"
done
fi
if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}

View File

@@ -1,7 +1,7 @@
dnl
dnl Configuration script for PDFio
dnl
dnl Copyright © 2023-2025 by Michael R Sweet
dnl Copyright © 2023-2026 by Michael R Sweet
dnl
dnl Licensed under Apache License v2.0. See the file "LICENSE" for more
dnl information.
@@ -21,7 +21,7 @@ AC_PREREQ([2.70])
dnl Package name and version...
AC_INIT([pdfio], [1.6.0], [https://github.com/michaelrsweet/pdfio/issues], [pdfio], [https://www.msweet.org/pdfio])
AC_INIT([pdfio], [1.7.0], [https://github.com/michaelrsweet/pdfio/issues], [pdfio], [https://www.msweet.org/pdfio])
PDFIO_VERSION="AC_PACKAGE_VERSION"
PDFIO_VERSION_MAJOR="`echo AC_PACKAGE_VERSION | awk -F. '{print $1}'`"
@@ -119,11 +119,32 @@ AC_PATH_TOOL([PKGCONFIG], [pkg-config])
PKGCONFIG_CFLAGS="-I\${includedir}"
PKGCONFIG_LIBS="-L\${libdir} -lpdfio"
PKGCONFIG_LIBS_PRIVATE="-lm"
PKGCONFIG_REQUIRES="zlib"
PKGCONFIG_REQUIRES=""
PKGCONFIG_REQUIRES_PRIVATE="ttf"
AC_SUBST([PKGCONFIG_CFLAGS])
AC_SUBST([PKGCONFIG_LIBS])
AC_SUBST([PKGCONFIG_LIBS_PRIVATE])
AC_SUBST([PKGCONFIG_REQUIRES])
AC_SUBST([PKGCONFIG_REQUIRES_PRIVATE])
dnl TTF library for font support...
AC_MSG_CHECKING([for ttf library])
AS_IF([$PKGCONFIG --exists ttf], [
# Use installed TTF library...
AC_MSG_RESULT([yes])
CPPFLAGS="$CPPFLAGS $($PKGCONFIG --cflags ttf)"
TTFDIR=""
LIBS="$($PKGCONFIG --libs ttf) $LIBS"
], [
# Use embedded TTF library...
AC_MSG_RESULT([no, using embedded version])
CPPFLAGS="$CPPFLAGS -Ittf"
TTFDIR="ttf"
LIBS="-Lttf \`PKG_CONFIG_PATH=ttf $PKGCONFIG --libs ttf\` $LIBS"
AC_CONFIG_SUBDIRS([ttf])
])
AC_SUBST([TTFDIR])
dnl ZLIB
@@ -132,6 +153,7 @@ AS_IF([$PKGCONFIG --exists zlib], [
AC_MSG_RESULT([yes])
CPPFLAGS="$($PKGCONFIG --cflags zlib) $CPPFLAGS"
LIBS="$($PKGCONFIG --libs zlib) $LIBS"
PKGCONFIG_REQUIRES_PRIVATE="$PKGCONFIG_REQUIRES_PRIVATE, zlib"
],[
AC_MSG_RESULT([no])
AC_CHECK_HEADER([zlib.h])
@@ -141,15 +163,12 @@ AS_IF([$PKGCONFIG --exists zlib], [
AC_MSG_ERROR([Sorry, this software requires zlib 1.1 or higher.])
])
PKGCONFIG_REQUIRES=""
PKGCONFIG_LIBS_PRIVATE="-lz $PKGCONFIG_LIBS_PRIVATE"
])
dnl libpng...
AC_ARG_ENABLE([libpng], AS_HELP_STRING([--enable-libpng], [use libpng for pdfioFileCreateImageObjFromFile, default=auto]))
PKGCONFIG_LIBPNG=""
AC_SUBST([PKGCONFIG_LIBPNG])
dnl libpng...
AC_ARG_ENABLE([libpng], AS_HELP_STRING([--disable-libpng], [use libpng for pdfioFileCreateImageObjFromFile, default=auto]))
AS_IF([test "x$PKGCONFIG" != x -a x$enable_libpng != xno], [
AC_MSG_CHECKING([for libpng-1.6.x])
@@ -158,8 +177,7 @@ AS_IF([test "x$PKGCONFIG" != x -a x$enable_libpng != xno], [
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"
PKGCONFIG_REQUIRES_PRIVATE="libpng >= 1.6, $PKGCONFIG_REQUIRES_PRIVATE"
], [
AC_MSG_RESULT([no]);
AS_IF([test x$enable_libpng = xyes], [
@@ -171,6 +189,28 @@ AS_IF([test "x$PKGCONFIG" != x -a x$enable_libpng != xno], [
])
dnl libwebp...
AC_ARG_ENABLE([libwebp], AS_HELP_STRING([--disable-libwebp], [use libwebp for pdfioFileCreateImageObjFromFile, default=auto]))
AS_IF([test "x$PKGCONFIG" != x -a x$enable_libwebp != xno], [
AC_MSG_CHECKING([for libwebp])
AS_IF([$PKGCONFIG --exists libwebp], [
AC_MSG_RESULT([yes]);
AC_DEFINE([HAVE_LIBWEBP], 1, [Have WebP library?])
CPPFLAGS="$($PKGCONFIG --cflags libwebp) -DHAVE_LIBWEBP=1 $CPPFLAGS"
LIBS="$($PKGCONFIG --libs libwebp) -lz $LIBS"
PKGCONFIG_REQUIRES_PRIVATE="libwebp, $PKGCONFIG_REQUIRES_PRIVATE"
], [
AC_MSG_RESULT([no]);
AS_IF([test x$enable_libwebp = xyes], [
AC_MSG_ERROR([libwebp-dev required for --enable-libwebp.])
])
])
], [test x$enable_libwebp = xyes], [
AC_MSG_ERROR([libwebp-dev required for --enable-libwebp.])
])
dnl Library target...
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]))
@@ -192,6 +232,8 @@ AS_IF([test x$enable_shared = xyes], [
LIBPDFIO_STATIC=""
PKGCONFIG_LIBS="$PKGCONFIG_LIBS $PKGCONFIG_LIBS_PRIVATE"
PKGCONFIG_LIBS_PRIVATE=""
PKGCONFIG_REQUIRES="$PKGCONFIG_REQUIRES_PRIVATE"
PKGCONFIG_REQUIRES_PRIVATE=""
])
AC_SUBST([LIBPDFIO])

View File

@@ -1,4 +1,4 @@
.TH pdfio 3 "pdf read/write library" "2025-10-05" "pdf read/write library"
.TH pdfio 3 "pdf read/write library" "2026-02-04" "pdf read/write library"
.SH NAME
pdfio \- pdf read/write library
.SH Introduction
@@ -34,7 +34,7 @@ PDFio is
.I not
concerned with rendering or viewing a PDF file, although a PDF RIP or viewer could be written using it.
.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.
Copyright \[co] 2021\-2026 by Michael R Sweet. PDFio is licensed under the Apache License Version 2.0 with an (optional) exception to allow linking against GNU GPL2\-only software. See the files LICENSE and NOTICE for more information.
.SS Requirements
.PP
PDFio requires the following to build the software:
@@ -52,14 +52,26 @@ A POSIX\-compliant sh program
.IP \(bu 5
.PP
ZLIB (https://www.zlib.net/) 1.0 or higher
libpng (https://www.libpng.org/) 1.6 or later for full PNG image support (optional)
.IP \(bu 5
.PP
PDFio will also use libpng 1.6 or higher (https://www.libpng.org/) to provide enhanced PNG image support.
libwebp (https://developers.google.com/speed/webp) 1.0 or later for WebP image support (optional)
.IP \(bu 5
.PP
ZLIB (https://www.zlib.net/) 1.1 or later for compression support
.PP
IDE files for Xcode (macOS/iOS) and Visual Studio (Windows) are also provided.
.SS Installing PDFio
.PP
On a stock Ubuntu install, the following command will install the various prerequisites:
.nf
sudo apt\-get install build\-essential libpng\-dev libwebp\-dev zlib1g\-dev
.fi
.SS Building and Installing PDFio on Linux/Unix
.PP
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:
.nf
@@ -99,16 +111,22 @@ Other configure options can be found using the \-\-help option:
\./configure \-\-help
.fi
.SS Visual Studio Project
.SS Building on Windows with Visual Studio
.PP
The Visual Studio solution ("pdfio.sln") is provided for Windows developers and generates both a static library and DLL.
.SS Xcode Project
The Visual Studio solution ("pdfio.sln") is provided for Windows developers and generates the PDFIO1 DLL. You can also use NuGet to install the pdfio_native package.
.PP
There is also an Xcode project ("pdfio.xcodeproj") you can use on macOS which generates a static library that will be installed under "/usr/local" with:
You can build and run the testpdfio unit tests target from within Visual Studio to verify the functioning of the library.
.SS Building on macOS with Xcode
.PP
The Xcode project (pdfio.xcodeproj) is provided for macOS developer which generates a static library that will be installed under "/usr/local". Run the following command to build and install PDFio on macOS:
.nf
sudo xcodebuild install
.fi
.PP
Alternately you can add the Xcode project to a workspace and reference the PDFio library target as a dependency in your own project.
.PP
You can build and run the testpdfio unit tests target from within Xcode to verify the functioning of the library.
.SS Detecting PDFio
.PP
PDFio can be detected using the pkg\-config command, for example:
@@ -361,41 +379,28 @@ Each PDF file contains one or more pages. The pdfioFileGetNumPages function retu
}
.fi
.PP
Each page is represented by a "page tree" object (what pdfioFileGetPage returns) that specifies information about the page and one or more "content" objects that contain the images, fonts, text, and graphics that appear on the page. Use the pdfioPageGetNumStreams and pdfioPageOpenStream functions to access the content streams for each page, and pdfioObjGetDict to get the associated page object dictionary. For example, if you want to display the media and crop boxes for a given page:
Each page is represented by a "page tree" object (what pdfioFileGetPage returns) that specifies information about the page and one or more "content" objects that contain the images, fonts, text, and graphics that appear on the page. Use the pdfioPageGetNumStreams and pdfioPageOpenStream functions to access the content streams for each page, pdfioObjGetDict to get the associated page object dictionary, and pdfioPageGetArray, pdfioPageGetBinary, pdfioPageGetBoolean, pdfioPageGetDate, pdfioPageGetDict, pdfioPageGetName, pdfioPageGetObj, pdfioPageGetRect, and pdfioPageGetString to get a value from the page object dictionary or its parents. For example, if you want to display the media and crop boxes for a given page:
.nf
pdfio_file_t *pdf; // PDF file
size_t i; // Looping var
size_t count; // Number of pages
pdfio_obj_t *page; // Current page
pdfio_dict_t *dict; // Current page dictionary
pdfio_array_t *media_box; // MediaBox array
double media_values[4]; // MediaBox values
pdfio_array_t *crop_box; // CropBox array
double crop_values[4]; // CropBox values
pdfio_rect_t media_box; // MediaBox values
pdfio_rect_t crop_box; // CropBox values
// Iterate the pages in the PDF file
for (i = 0, count = pdfioFileGetNumPages(pdf); i < count; i ++)
{
page = pdfioFileGetPage(pdf, i);
dict = pdfioObjGetDict(page);
media_box = pdfioDictGetArray(dict, "MediaBox");
media_values[0] = pdfioArrayGetNumber(media_box, 0);
media_values[1] = pdfioArrayGetNumber(media_box, 1);
media_values[2] = pdfioArrayGetNumber(media_box, 2);
media_values[3] = pdfioArrayGetNumber(media_box, 3);
crop_box = pdfioDictGetArray(dict, "CropBox");
crop_values[0] = pdfioArrayGetNumber(crop_box, 0);
crop_values[1] = pdfioArrayGetNumber(crop_box, 1);
crop_values[2] = pdfioArrayGetNumber(crop_box, 2);
crop_values[3] = pdfioArrayGetNumber(crop_box, 3);
pdfioPageGetRect(page, "MediaBox", &media_box);
pdfioPageGetRect(page, "CropBox", &crop_box);
printf("Page %u: MediaBox=[%g %g %g %g], CropBox=[%g %g %g %g]\\n",
(unsigned)(i + 1),
media_values[0], media_values[1], media_values[2], media_values[3],
crop_values[0], crop_values[1], crop_values[2], crop_values[3]);
media_box.x1, media_box.y1, media_box.x2, media_box.y2,
crop_box.x1, crop_box.y1, crop_box.x2, crop_box.y2);
}
.fi
.PP
@@ -784,16 +789,13 @@ will create an object for a 1024x1024 RGBA image in memory, using the default co
.PP
The "interpolate" argument specifies whether the colors in the image should be smoothed/interpolated when scaling. This is most useful for photographs but should be false for screenshot and barcode images.
.PP
If you have a JPEG or PNG file, use the pdfioFileCreateImageObjFromFile function to copy the image into a PDF image object, for example:
If you have a GIF, JPEG, PNG, or WebP file, use the pdfioFileCreateImageObjFromFile function to copy the image into a PDF image object, for example:
.nf
pdfio_file_t *pdf = pdfioFileCreate(...);
pdfio_obj_t *img =
pdfioFileCreateImageObjFromFile(pdf, "myphoto.jpg", /*interpolate*/true);
.fi
.PP
Note: Currently pdfioFileCreateImageObjFromFile does not support 12 bit JPEG files or PNG files with an alpha channel.
.PP
Page Dictionary Functions
.PP
@@ -1176,16 +1178,10 @@ The pdfioinfo.c example program opens a PDF file and prints the title, author, c
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);
page = pdfioFileGetPage(pdf, cur);
if (pdfioDictGetRect(page_dict, "MediaBox", &cur_box))
break;
}
cur_box.x1 = cur_box.x2 = cur_box.y1 = cur_box.y2 = 0.0;
pdfioPageGetRect(page, "MediaBox", &cur_box);
// If this MediaBox is different from the previous one, show the range of
// pages that have that size...
@@ -1458,7 +1454,7 @@ Then we loop through the differences array, keeping track of the current index w
.fi
.SS Create a PDF File With Text and an Image
.PP
The image2pdf.c example code creates a PDF file containing a JPEG or PNG image file and optional caption on a single page. The create_pdf_image_file 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:
The image2pdf.c example code creates a PDF file containing a GIF, JPEG, or PNG image file and optional caption on a single page. The create_pdf_image_file 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:
.nf
#include <pdfio.h>
@@ -2555,7 +2551,7 @@ ASCIIHexDecode filter (reading only)
.TP 5
PDFIO_FILTER_CCITTFAX
.br
CCITTFaxDecode filter
CCITTFaxDecode filter (reading only)
.TP 5
PDFIO_FILTER_CRYPT
.br
@@ -2571,7 +2567,7 @@ FlateDecode filter
.TP 5
PDFIO_FILTER_JBIG2
.br
JBIG2Decode filter
JBIG2Decode filter (reading only)
.TP 5
PDFIO_FILTER_JPX
.br
@@ -4209,15 +4205,21 @@ pdfio_obj_t * pdfioFileCreateImageObjFromFile (
);
.fi
.PP
This function creates an image object in a PDF file from a JPEG or PNG file.
The "filename" parameter specifies the name of the JPEG or PNG file, while
the "interpolate" parameter specifies whether to interpolate when scaling the
image on the page.
This function creates an image object in a PDF file from a GIF, JPEG, PNG, or
WebP file. The "filename" parameter specifies the name of the GIF, JPEG,
PNG, or WebP file, while the "interpolate" parameter specifies whether to
interpolate when scaling the image on the page.
.PP
.IP 5
Note: PNG files containing transparency cannot be used when producing
.IP 5
PDF/A files.
PDF/A files. Files containing animation yield the final frame of the
.IP 5
animation. GIF and (optional) WebP support first appeared in PDFio v1.7.
.IP 5
WebP content is added as a Flate-compressed image with alpha mask as
.IP 5
needed and is usually significantly larger than the original image.
.SS pdfioFileCreateNameObj
Create a new object in a PDF file containing a name.
.PP
@@ -4924,6 +4926,91 @@ bool pdfioPageDictAddImage (
pdfio_obj_t *obj
);
.fi
.SS pdfioPageGetArray
Get an array value from the page dictionary.
.PP
.nf
pdfio_array_t * pdfioPageGetArray (
pdfio_obj_t *page,
const char *key
);
.fi
.PP
This function looks up an array value in the page dictionary, either in the
specified object or one of its parents.
.SS pdfioPageGetBinary
Get a binary value from the page dictionary.
.PP
.nf
unsigned char * pdfioPageGetBinary (
pdfio_obj_t *page,
const char *key,
size_t *length
);
.fi
.PP
This function looks up a binary value in the page dictionary, either in the
specified object or one of its parents.
.SS pdfioPageGetBoolean
Get a boolean value from the page dictionary.
.PP
.nf
bool pdfioPageGetBoolean (
pdfio_obj_t *page,
const char *key
);
.fi
.PP
This function looks up a boolean value in the page dictionary, either in the
specified object or one of its parents.
.SS pdfioPageGetDate
Get a date value from the page dictionary.
.PP
.nf
time_t pdfioPageGetDate (
pdfio_obj_t *page,
const char *key
);
.fi
.PP
This function looks up a date value in the page dictionary, either in the
specified object or one of its parents.
.SS pdfioPageGetDict
Get a dictionary value from the page dictionary.
.PP
.nf
pdfio_dict_t * pdfioPageGetDict (
pdfio_obj_t *page,
const char *key
);
.fi
.PP
This function looks up a dictionary value in the page dictionary, either in
the specified object or one of its parents.
.SS pdfioPageGetName
Get a name value from the page dictionary.
.PP
.nf
const char * pdfioPageGetName (
pdfio_obj_t *page,
const char *key
);
.fi
.PP
This function looks up a name value in the page dictionary, either in the
specified object or one of its parents.
.SS pdfioPageGetNumStreams
Get the number of content streams for a page object.
.PP
@@ -4932,6 +5019,63 @@ size_t pdfioPageGetNumStreams (
pdfio_obj_t *page
);
.fi
.SS pdfioPageGetNumber
Get a number value from the page dictionary.
.PP
.nf
double pdfioPageGetNumber (
pdfio_obj_t *page,
const char *key
);
.fi
.PP
This function looks up a number value in the page dictionary, either in the
specified object or one of its parents.
.SS pdfioPageGetObj
Get an indirect object value from the page dictionary.
.PP
.nf
pdfio_obj_t * pdfioPageGetObj (
pdfio_obj_t *page,
const char *key
);
.fi
.PP
This function looks up an indirect object value in the page dictionary,
either in the specified object or one of its parents.
.SS pdfioPageGetRect
Get a rectangle value from the page dictionary.
.PP
.nf
pdfio_rect_t * pdfioPageGetRect (
pdfio_obj_t *page,
const char *key,
pdfio_rect_t *rect
);
.fi
.PP
This function looks up a rectangle value in the page dictionary, either in
the specified object or one of its parents.
.SS pdfioPageGetString
Get a string value from the page dictionary.
.PP
.nf
const char * pdfioPageGetString (
pdfio_obj_t *page,
const char *key
);
.fi
.PP
This function looks up a string value in the page dictionary, either in the
specified object or one of its parents.
.SS pdfioPageOpenStream
Open a content stream for a page.
.PP
@@ -5209,4 +5353,4 @@ typedef enum pdfio_valtype_e pdfio_valtype_t;
Michael R Sweet
.SH COPYRIGHT
.PP
Copyright (c) 2021-2025 by Michael R Sweet
Copyright (c) 2021-2026 by Michael R Sweet

View File

@@ -1,13 +1,13 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>PDFio Programming Manual v1.6.0</title>
<title>PDFio Programming Manual v1.7.0</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="generator" content="codedoc v3.8">
<meta name="author" content="Michael R Sweet">
<meta name="language" content="en-US">
<meta name="copyright" content="Copyright © 2021-2025 by Michael R Sweet">
<meta name="version" content="1.6.0">
<meta name="copyright" content="Copyright © 2021-2026 by Michael R Sweet">
<meta name="version" content="1.7.0">
<style type="text/css"><!--
body {
background: white;
@@ -92,13 +92,16 @@ blockquote :first-child {
margin-bottom: 0;
}
p code, li code, p.code, pre, ul.code li {
background: rgba(127,127,127,0.25);
border: thin dotted gray;
font-family: monospace;
hyphens: manual;
-webkit-hyphens: manual;
}
p code, li code {
padding: 0 5px;
}
p.code, pre, ul.code li {
background: rgba(127,127,127,0.25);
border: thin dotted gray;
padding: 10px;
page-break-inside: avoid;
}
@@ -217,6 +220,21 @@ span.string {
a:link:hover, a:visited:hover, a:active {
color: #f06;
}
span.comment {
color: #7c7;
}
span.directive {
color: red;
}
span.number {
color: #c64;
}
span.reserved {
color: #77f;
}
span.string {
color: #f7f;
}
}
/* Show contents on left side in web browser */
@media screen and (min-width: 800px) {
@@ -251,18 +269,18 @@ span.string {
<body>
<div class="header">
<p><img class="title" src="pdfio-512.png"></p>
<h1 class="title">PDFio Programming Manual v1.6.0</h1>
<h1 class="title">PDFio Programming Manual v1.7.0</h1>
<p>Michael R Sweet</p>
<p>Copyright © 2021-2025 by Michael R Sweet</p>
<p>Copyright © 2021-2026 by Michael R Sweet</p>
</div>
<div class="contents">
<h2 class="title">Contents</h2>
<ul class="contents">
<li><a href="#introduction">Introduction</a><ul class="subcontents">
<li><a href="#requirements">Requirements</a></li>
<li><a href="#installing-pdfio">Installing PDFio</a></li>
<li><a href="#visual-studio-project">Visual Studio Project</a></li>
<li><a href="#xcode-project">Xcode Project</a></li>
<li><a href="#building-and-installing-pdfio-on-linuxunix">Building and Installing PDFio on Linux/Unix</a></li>
<li><a href="#building-on-windows-with-visual-studio">Building on Windows with Visual Studio</a></li>
<li><a href="#building-on-macos-with-xcode">Building on macOS with Xcode</a></li>
<li><a href="#detecting-pdfio">Detecting PDFio</a></li>
<li><a href="#header-files">Header Files</a></li>
<li><a href="#understanding-pdf-files">Understanding PDF Files</a></li>
@@ -464,7 +482,17 @@ span.string {
<li><a href="#pdfioPageDictAddColorSpace">pdfioPageDictAddColorSpace</a></li>
<li><a href="#pdfioPageDictAddFont">pdfioPageDictAddFont</a></li>
<li><a href="#pdfioPageDictAddImage">pdfioPageDictAddImage</a></li>
<li><a href="#pdfioPageGetArray">pdfioPageGetArray</a></li>
<li><a href="#pdfioPageGetBinary">pdfioPageGetBinary</a></li>
<li><a href="#pdfioPageGetBoolean">pdfioPageGetBoolean</a></li>
<li><a href="#pdfioPageGetDate">pdfioPageGetDate</a></li>
<li><a href="#pdfioPageGetDict">pdfioPageGetDict</a></li>
<li><a href="#pdfioPageGetName">pdfioPageGetName</a></li>
<li><a href="#pdfioPageGetNumStreams">pdfioPageGetNumStreams</a></li>
<li><a href="#pdfioPageGetNumber">pdfioPageGetNumber</a></li>
<li><a href="#pdfioPageGetObj">pdfioPageGetObj</a></li>
<li><a href="#pdfioPageGetRect">pdfioPageGetRect</a></li>
<li><a href="#pdfioPageGetString">pdfioPageGetString</a></li>
<li><a href="#pdfioPageOpenStream">pdfioPageOpenStream</a></li>
<li><a href="#pdfioStreamClose">pdfioStreamClose</a></li>
<li><a href="#pdfioStreamConsume">pdfioStreamConsume</a></li>
@@ -532,7 +560,7 @@ span.string {
</li>
</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 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>Copyright © 2021-2026 by Michael R Sweet. PDFio is licensed under the Apache License Version 2.0 with an (optional) exception to allow linking against GNU GPL2-only software. See the files <code>LICENSE</code> and <code>NOTICE</code> for more information.</p>
<h3 class="title" id="requirements">Requirements</h3>
<p>PDFio requires the following to build the software:</p>
<ul>
@@ -542,12 +570,18 @@ span.string {
</li>
<li><p>A POSIX-compliant <code>sh</code> program</p>
</li>
<li><p>ZLIB (<a href="https://www.zlib.net/">https://www.zlib.net/</a>) 1.0 or higher</p>
<li><p>libpng (<a href="https://www.libpng.org/">https://www.libpng.org/</a>) 1.6 or later for full PNG image support (optional)</p>
</li>
<li><p>libwebp (<a href="https://developers.google.com/speed/webp">https://developers.google.com/speed/webp</a>) 1.0 or later for WebP image support (optional)</p>
</li>
<li><p>ZLIB (<a href="https://www.zlib.net/">https://www.zlib.net/</a>) 1.1 or later for compression support</p>
</li>
</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>
<h3 class="title" id="installing-pdfio">Installing PDFio</h3>
<p>On a stock Ubuntu install, the following command will install the various prerequisites:</p>
<pre><code>sudo apt-get install build-essential libpng-dev libwebp-dev zlib1g-dev
</code></pre>
<h3 class="title" id="building-and-installing-pdfio-on-linuxunix">Building and Installing PDFio on Linux/Unix</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>
<pre><code>./configure
make
@@ -569,12 +603,15 @@ sudo make install
<p>Other configure options can be found using the <code>--help</code> option:</p>
<pre><code>./configure --help
</code></pre>
<h3 class="title" id="visual-studio-project">Visual Studio Project</h3>
<p>The Visual Studio solution (&quot;pdfio.sln&quot;) is provided for Windows developers and generates both a static library and DLL.</p>
<h3 class="title" id="xcode-project">Xcode Project</h3>
<p>There is also an Xcode project (&quot;pdfio.xcodeproj&quot;) you can use on macOS which generates a static library that will be installed under &quot;/usr/local&quot; with:</p>
<h3 class="title" id="building-on-windows-with-visual-studio">Building on Windows with Visual Studio</h3>
<p>The Visual Studio solution (&quot;pdfio.sln&quot;) is provided for Windows developers and generates the PDFIO1 DLL. You can also use NuGet to install the <code>pdfio_native</code> package.</p>
<p>You can build and run the <code>testpdfio</code> unit tests target from within Visual Studio to verify the functioning of the library.</p>
<h3 class="title" id="building-on-macos-with-xcode">Building on macOS with Xcode</h3>
<p>The Xcode project (<code>pdfio.xcodeproj</code>) is provided for macOS developer which generates a static library that will be installed under &quot;/usr/local&quot;. Run the following command to build and install PDFio on macOS:</p>
<pre><code>sudo xcodebuild install
</code></pre>
<p>Alternately you can add the Xcode project to a workspace and reference the PDFio library target as a dependency in your own project.</p>
<p>You can build and run the <code>testpdfio</code> unit tests target from within Xcode to verify the functioning of the library.</p>
<h3 class="title" id="detecting-pdfio">Detecting PDFio</h3>
<p>PDFio can be detected using the <code>pkg-config</code> command, for example:</p>
<pre><code>if pkg-config --exists pdfio; then
@@ -764,39 +801,26 @@ pdfio_obj_t *page; <span class="comment">// Current page</span>
<span class="comment">// do something with page</span>
}
</code></pre>
<p>Each page is represented by a &quot;page tree&quot; object (what <a href="#pdfioFileGetPage"><code>pdfioFileGetPage</code></a> returns) that specifies information about the page and one or more &quot;content&quot; objects that contain the images, fonts, text, and graphics that appear on the page. Use the <a href="#pdfioPageGetNumStreams"><code>pdfioPageGetNumStreams</code></a> and <a href="#pdfioPageOpenStream"><code>pdfioPageOpenStream</code></a> functions to access the content streams for each page, and <a href="#pdfioObjGetDict"><code>pdfioObjGetDict</code></a> to get the associated page object dictionary. For example, if you want to display the media and crop boxes for a given page:</p>
<p>Each page is represented by a &quot;page tree&quot; object (what <a href="#pdfioFileGetPage"><code>pdfioFileGetPage</code></a> returns) that specifies information about the page and one or more &quot;content&quot; objects that contain the images, fonts, text, and graphics that appear on the page. Use the <a href="#pdfioPageGetNumStreams"><code>pdfioPageGetNumStreams</code></a> and <a href="#pdfioPageOpenStream"><code>pdfioPageOpenStream</code></a> functions to access the content streams for each page, <a href="#pdfioObjGetDict"><code>pdfioObjGetDict</code></a> to get the associated page object dictionary, and <a href="#pdfioPageGetArray"><code>pdfioPageGetArray</code></a>, <a href="#pdfioPageGetBinary"><code>pdfioPageGetBinary</code></a>, <a href="#pdfioPageGetBoolean"><code>pdfioPageGetBoolean</code></a>, <a href="#pdfioPageGetDate"><code>pdfioPageGetDate</code></a>, <a href="#pdfioPageGetDict"><code>pdfioPageGetDict</code></a>, <a href="#pdfioPageGetName"><code>pdfioPageGetName</code></a>, <a href="#pdfioPageGetObj"><code>pdfioPageGetObj</code></a>, <a href="#pdfioPageGetRect"><code>pdfioPageGetRect</code></a>, and <a href="#pdfioPageGetString"><code>pdfioPageGetString</code></a> to get a value from the page object dictionary or its parents. For example, if you want to display the media and crop boxes for a given page:</p>
<pre><code class="language-c">pdfio_file_t *pdf; <span class="comment">// PDF file</span>
size_t i; <span class="comment">// Looping var</span>
size_t count; <span class="comment">// Number of pages</span>
pdfio_obj_t *page; <span class="comment">// Current page</span>
pdfio_dict_t *dict; <span class="comment">// Current page dictionary</span>
pdfio_array_t *media_box; <span class="comment">// MediaBox array</span>
<span class="reserved">double</span> media_values[<span class="number">4</span>]; <span class="comment">// MediaBox values</span>
pdfio_array_t *crop_box; <span class="comment">// CropBox array</span>
<span class="reserved">double</span> crop_values[<span class="number">4</span>]; <span class="comment">// CropBox values</span>
pdfio_rect_t media_box; <span class="comment">// MediaBox values</span>
pdfio_rect_t crop_box; <span class="comment">// CropBox values</span>
<span class="comment">// Iterate the pages in the PDF file</span>
<span class="reserved">for</span> (i = <span class="number">0</span>, count = pdfioFileGetNumPages(pdf); i &lt; count; i ++)
{
page = pdfioFileGetPage(pdf, i);
dict = pdfioObjGetDict(page);
media_box = pdfioDictGetArray(dict, <span class="string">&quot;MediaBox&quot;</span>);
media_values[<span class="number">0</span>] = pdfioArrayGetNumber(media_box, <span class="number">0</span>);
media_values[<span class="number">1</span>] = pdfioArrayGetNumber(media_box, <span class="number">1</span>);
media_values[<span class="number">2</span>] = pdfioArrayGetNumber(media_box, <span class="number">2</span>);
media_values[<span class="number">3</span>] = pdfioArrayGetNumber(media_box, <span class="number">3</span>);
crop_box = pdfioDictGetArray(dict, <span class="string">&quot;CropBox&quot;</span>);
crop_values[<span class="number">0</span>] = pdfioArrayGetNumber(crop_box, <span class="number">0</span>);
crop_values[<span class="number">1</span>] = pdfioArrayGetNumber(crop_box, <span class="number">1</span>);
crop_values[<span class="number">2</span>] = pdfioArrayGetNumber(crop_box, <span class="number">2</span>);
crop_values[<span class="number">3</span>] = pdfioArrayGetNumber(crop_box, <span class="number">3</span>);
pdfioPageGetRect(page, <span class="string">&quot;MediaBox&quot;</span>, &amp;media_box);
pdfioPageGetRect(page, <span class="string">&quot;CropBox&quot;</span>, &amp;crop_box);
printf(<span class="string">&quot;Page %u: MediaBox=[%g %g %g %g], CropBox=[%g %g %g %g]\n&quot;</span>,
(<span class="reserved">unsigned</span>)(i + <span class="number">1</span>),
media_values[<span class="number">0</span>], media_values[<span class="number">1</span>], media_values[<span class="number">2</span>], media_values[<span class="number">3</span>],
crop_values[<span class="number">0</span>], crop_values[<span class="number">1</span>], crop_values[<span class="number">2</span>], crop_values[<span class="number">3</span>]);
media_box.x1, media_box.y1, media_box.x2, media_box.y2,
crop_box.x1, crop_box.y1, crop_box.x2, crop_box.y2);
}
</code></pre>
<p>Page object dictionaries have several (mostly optional) key/value pairs, including:</p>
@@ -1031,14 +1055,11 @@ pdfio_obj_t *img =
<span class="comment">/*alpha*/</span><span class="reserved">true</span>, <span class="comment">/*interpolate*/</span><span class="reserved">false</span>);
</code></pre>
<p>The &quot;interpolate&quot; argument specifies whether the colors in the image should be smoothed/interpolated when scaling. This is most useful for photographs but should be <code>false</code> for screenshot and barcode images.</p>
<p>If you have a JPEG or PNG file, use the <a href="#pdfioFileCreateImageObjFromFile"><code>pdfioFileCreateImageObjFromFile</code></a> function to copy the image into a PDF image object, for example:</p>
<p>If you have a GIF, JPEG, PNG, or WebP file, use the <a href="#pdfioFileCreateImageObjFromFile"><code>pdfioFileCreateImageObjFromFile</code></a> function to copy the image into a PDF image object, for example:</p>
<pre><code class="language-c">pdfio_file_t *pdf = pdfioFileCreate(...);
pdfio_obj_t *img =
pdfioFileCreateImageObjFromFile(pdf, <span class="string">&quot;myphoto.jpg&quot;</span>, <span class="comment">/*interpolate*/</span><span class="reserved">true</span>);
</code></pre>
<blockquote>
<p>Note: Currently <code>pdfioFileCreateImageObjFromFile</code> does not support 12 bit JPEG files or PNG files with an alpha channel.</p>
</blockquote>
<h4 id="page-dictionary-functions">Page Dictionary Functions</h4>
<p>PDF pages each have an associated dictionary to specify the images, fonts, and color spaces used by the page. PDFio provides functions to add these resources to the dictionary:</p>
<ul>
@@ -1294,16 +1315,10 @@ main(<span class="reserved">int</span> argc, <span clas
<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);
page = pdfioFileGetPage(pdf, cur);
<span class="reserved">if</span> (pdfioDictGetRect(page_dict, <span class="string">&quot;MediaBox&quot;</span>, &amp;cur_box))
<span class="reserved">break</span>;
}
cur_box.x1 = cur_box.x2 = cur_box.y1 = cur_box.y2 = <span class="number">0.0</span>;
pdfioPageGetRect(page, <span class="string">&quot;MediaBox&quot;</span>, &amp;cur_box);
<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>
@@ -1542,7 +1557,7 @@ load_encoding(
}
</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>
<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 GIF, 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>
<pre><code class="language-c"><span class="directive">#include &lt;pdfio.h&gt;</span>
<span class="directive">#include &lt;pdfio-content.h&gt;</span>
<span class="directive">#include &lt;string.h&gt;</span>
@@ -2820,7 +2835,7 @@ size_t pdfioArrayGetSize(<a href="#pdfio_array_t">pdfio_array_t</a> *a);</p>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description"><code>true</code> on success, <code>false</code> otherwise</p>
<h3 class="function"><span class="info">&#160;PDFio 1.6&#160;</span><a id="pdfioContentBeginMarked">pdfioContentBeginMarked</a></h3>
<h3 class="function"><span class="info">&#160;PDFio v1.6&#160;</span><a id="pdfioContentBeginMarked">pdfioContentBeginMarked</a></h3>
<p class="description">Start marked content with an optional dictionary.</p>
<p class="code">
<span class="reserved">bool</span> pdfioContentBeginMarked(<a href="#pdfio_stream_t">pdfio_stream_t</a> *st, <span class="reserved">const</span> <span class="reserved">char</span> *tag, <a href="#pdfio_dict_t">pdfio_dict_t</a> *dict);</p>
@@ -2886,7 +2901,7 @@ the document catalog. The caller is responsible for setting the
<h4 class="discussion">Discussion</h4>
<p class="discussion">The object name must be part of the page dictionary resources, typically
using the <a href="#pdfioPageDictAddImage"><code>pdfioPageDictAddImage</code></a> function.</p>
<h3 class="function"><span class="info">&#160;PDFio 1.6&#160;</span><a id="pdfioContentEndMarked">pdfioContentEndMarked</a></h3>
<h3 class="function"><span class="info">&#160;PDFio v1.6&#160;</span><a id="pdfioContentEndMarked">pdfioContentEndMarked</a></h3>
<p class="description">End marked content.</p>
<p class="code">
<span class="reserved">bool</span> pdfioContentEndMarked(<a href="#pdfio_stream_t">pdfio_stream_t</a> *st);</p>
@@ -4145,7 +4160,7 @@ have been iterated.
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description"><code>true</code> on success, <code>false</code> on failure</p>
<h3 class="function"><span class="info">&#160;PDFio 1.6&#160;</span><a id="pdfioFileAddOutputIntent">pdfioFileAddOutputIntent</a></h3>
<h3 class="function"><span class="info">&#160;PDFio v1.6&#160;</span><a id="pdfioFileAddOutputIntent">pdfioFileAddOutputIntent</a></h3>
<p class="description">Add an OutputIntent to a file.</p>
<p class="code">
<span class="reserved">void</span> pdfioFileAddOutputIntent(<a href="#pdfio_file_t">pdfio_file_t</a> *pdf, <span class="reserved">const</span> <span class="reserved">char</span> *subtype, <span class="reserved">const</span> <span class="reserved">char</span> *condition, <span class="reserved">const</span> <span class="reserved">char</span> *cond_id, <span class="reserved">const</span> <span class="reserved">char</span> *reg_name, <span class="reserved">const</span> <span class="reserved">char</span> *info, <a href="#pdfio_obj_t">pdfio_obj_t</a> *profile);</p>
@@ -4474,14 +4489,17 @@ files do not support alpha-based transparency.</blockquote>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Object</p>
<h4 class="discussion">Discussion</h4>
<p class="discussion">This function creates an image object in a PDF file from a JPEG or PNG file.
The &quot;filename&quot; parameter specifies the name of the JPEG or PNG file, while
the &quot;interpolate&quot; parameter specifies whether to interpolate when scaling the
image on the page.<br>
<p class="discussion">This function creates an image object in a PDF file from a GIF, JPEG, PNG, or
WebP file. The &quot;filename&quot; parameter specifies the name of the GIF, JPEG,
PNG, or WebP file, while the &quot;interpolate&quot; parameter specifies whether to
interpolate when scaling the image on the page.<br>
<br>
</p><blockquote>
Note: PNG files containing transparency cannot be used when producing
PDF/A files.</blockquote>
PDF/A files. Files containing animation yield the final frame of the
animation. GIF and (optional) WebP support first appeared in PDFio v1.7.
WebP content is added as a Flate-compressed image with alpha mask as
needed and is usually significantly larger than the original image.</blockquote>
<h3 class="function"><span class="info">&#160;PDFio v1.4&#160;</span><a id="pdfioFileCreateNameObj">pdfioFileCreateNameObj</a></h3>
<p class="description">Create a new object in a PDF file containing a name.</p>
<p class="code">
@@ -4744,7 +4762,7 @@ list of objects while this function takes the object number.</p>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Author or <code>NULL</code> for none</p>
<h3 class="function"><span class="info">&#160;PDFio 1.3&#160;</span><a id="pdfioFileGetCatalog">pdfioFileGetCatalog</a></h3>
<h3 class="function"><span class="info">&#160;PDFio v1.3&#160;</span><a id="pdfioFileGetCatalog">pdfioFileGetCatalog</a></h3>
<p class="description">Get the document catalog dictionary.</p>
<p class="code">
<a href="#pdfio_dict_t">pdfio_dict_t</a> *pdfioFileGetCatalog(<a href="#pdfio_file_t">pdfio_file_t</a> *pdf);</p>
@@ -4799,7 +4817,7 @@ time_t pdfioFileGetCreationDate(<a href="#pdfio_file_t">pdfio_file_t</a> *pdf);<
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Keywords string or <code>NULL</code> for none</p>
<h3 class="function"><span class="info">&#160;PDFio 1.6&#160;</span><a id="pdfioFileGetLanguage">pdfioFileGetLanguage</a></h3>
<h3 class="function"><span class="info">&#160;PDFio v1.6&#160;</span><a id="pdfioFileGetLanguage">pdfioFileGetLanguage</a></h3>
<p class="description">Get the language metadata for a PDF file.</p>
<p class="code">
<span class="reserved">const</span> <span class="reserved">char</span> *pdfioFileGetLanguage(<a href="#pdfio_file_t">pdfio_file_t</a> *pdf);</p>
@@ -5044,7 +5062,7 @@ ignore the return value of the error callback.</blockquote>
<tr><th>value</th>
<td class="description">Value</td></tr>
</tbody></table>
<h3 class="function"><span class="info">&#160;PDFio 1.6&#160;</span><a id="pdfioFileSetLanguage">pdfioFileSetLanguage</a></h3>
<h3 class="function"><span class="info">&#160;PDFio v1.6&#160;</span><a id="pdfioFileSetLanguage">pdfioFileSetLanguage</a></h3>
<p class="description">Set the language metadata for a PDF file.</p>
<p class="code">
<span class="reserved">void</span> pdfioFileSetLanguage(<a href="#pdfio_file_t">pdfio_file_t</a> *pdf, <span class="reserved">const</span> <span class="reserved">char</span> *value);</p>
@@ -5409,6 +5427,116 @@ array that was created using the
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description"><code>true</code> on success, <code>false</code> on failure</p>
<h3 class="function"><span class="info">&#160;PDFio v1.7&#160;</span><a id="pdfioPageGetArray">pdfioPageGetArray</a></h3>
<p class="description">Get an array value from the page dictionary.</p>
<p class="code">
<a href="#pdfio_array_t">pdfio_array_t</a> *pdfioPageGetArray(<a href="#pdfio_obj_t">pdfio_obj_t</a> *page, <span class="reserved">const</span> <span class="reserved">char</span> *key);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>page</th>
<td class="description">Page object</td></tr>
<tr><th>key</th>
<td class="description">Dictionary key</td></tr>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Array or <code>NULL</code> if none</p>
<h4 class="discussion">Discussion</h4>
<p class="discussion">This function looks up an array value in the page dictionary, either in the
specified object or one of its parents.
</p>
<h3 class="function"><span class="info">&#160;PDFio v1.7&#160;</span><a id="pdfioPageGetBinary">pdfioPageGetBinary</a></h3>
<p class="description">Get a binary value from the page dictionary.</p>
<p class="code">
<span class="reserved">unsigned</span> <span class="reserved">char</span> *pdfioPageGetBinary(<a href="#pdfio_obj_t">pdfio_obj_t</a> *page, <span class="reserved">const</span> <span class="reserved">char</span> *key, size_t *length);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>page</th>
<td class="description">Page object</td></tr>
<tr><th>key</th>
<td class="description">Dictionary key</td></tr>
<tr><th>length</th>
<td class="description">Length of value</td></tr>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Pointer to binary data or <code>NULL</code> if none</p>
<h4 class="discussion">Discussion</h4>
<p class="discussion">This function looks up a binary value in the page dictionary, either in the
specified object or one of its parents.
</p>
<h3 class="function"><span class="info">&#160;PDFio v1.7&#160;</span><a id="pdfioPageGetBoolean">pdfioPageGetBoolean</a></h3>
<p class="description">Get a boolean value from the page dictionary.</p>
<p class="code">
<span class="reserved">bool</span> pdfioPageGetBoolean(<a href="#pdfio_obj_t">pdfio_obj_t</a> *page, <span class="reserved">const</span> <span class="reserved">char</span> *key);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>page</th>
<td class="description">Page object</td></tr>
<tr><th>key</th>
<td class="description">Dictionary key</td></tr>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Boolean value or <code>false</code> if none</p>
<h4 class="discussion">Discussion</h4>
<p class="discussion">This function looks up a boolean value in the page dictionary, either in the
specified object or one of its parents.
</p>
<h3 class="function"><span class="info">&#160;PDFio v1.7&#160;</span><a id="pdfioPageGetDate">pdfioPageGetDate</a></h3>
<p class="description">Get a date value from the page dictionary.</p>
<p class="code">
time_t pdfioPageGetDate(<a href="#pdfio_obj_t">pdfio_obj_t</a> *page, <span class="reserved">const</span> <span class="reserved">char</span> *key);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>page</th>
<td class="description">Page object</td></tr>
<tr><th>key</th>
<td class="description">Dictionary key</td></tr>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Date/time or <code>0</code> if none</p>
<h4 class="discussion">Discussion</h4>
<p class="discussion">This function looks up a date value in the page dictionary, either in the
specified object or one of its parents.
</p>
<h3 class="function"><span class="info">&#160;PDFio v1.7&#160;</span><a id="pdfioPageGetDict">pdfioPageGetDict</a></h3>
<p class="description">Get a dictionary value from the page dictionary.</p>
<p class="code">
<a href="#pdfio_dict_t">pdfio_dict_t</a> *pdfioPageGetDict(<a href="#pdfio_obj_t">pdfio_obj_t</a> *page, <span class="reserved">const</span> <span class="reserved">char</span> *key);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>page</th>
<td class="description">Page object</td></tr>
<tr><th>key</th>
<td class="description">Dictionary key</td></tr>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Dictionary or <code>NULL</code> if none</p>
<h4 class="discussion">Discussion</h4>
<p class="discussion">This function looks up a dictionary value in the page dictionary, either in
the specified object or one of its parents.
</p>
<h3 class="function"><span class="info">&#160;PDFio v1.7&#160;</span><a id="pdfioPageGetName">pdfioPageGetName</a></h3>
<p class="description">Get a name value from the page dictionary.</p>
<p class="code">
<span class="reserved">const</span> <span class="reserved">char</span> *pdfioPageGetName(<a href="#pdfio_obj_t">pdfio_obj_t</a> *page, <span class="reserved">const</span> <span class="reserved">char</span> *key);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>page</th>
<td class="description">Page object</td></tr>
<tr><th>key</th>
<td class="description">Dictionary key</td></tr>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Name string or <code>NULL</code> if none</p>
<h4 class="discussion">Discussion</h4>
<p class="discussion">This function looks up a name value in the page dictionary, either in the
specified object or one of its parents.
</p>
<h3 class="function"><a id="pdfioPageGetNumStreams">pdfioPageGetNumStreams</a></h3>
<p class="description">Get the number of content streams for a page object.</p>
<p class="code">
@@ -5420,6 +5548,80 @@ size_t pdfioPageGetNumStreams(<a href="#pdfio_obj_t">pdfio_obj_t</a> *page);</p>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Number of streams</p>
<h3 class="function"><span class="info">&#160;PDFio v1.7&#160;</span><a id="pdfioPageGetNumber">pdfioPageGetNumber</a></h3>
<p class="description">Get a number value from the page dictionary.</p>
<p class="code">
<span class="reserved">double</span> pdfioPageGetNumber(<a href="#pdfio_obj_t">pdfio_obj_t</a> *page, <span class="reserved">const</span> <span class="reserved">char</span> *key);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>page</th>
<td class="description">Page object</td></tr>
<tr><th>key</th>
<td class="description">Dictionary key</td></tr>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Number value or <code>0.0</code> if none</p>
<h4 class="discussion">Discussion</h4>
<p class="discussion">This function looks up a number value in the page dictionary, either in the
specified object or one of its parents.
</p>
<h3 class="function"><span class="info">&#160;PDFio v1.7&#160;</span><a id="pdfioPageGetObj">pdfioPageGetObj</a></h3>
<p class="description">Get an indirect object value from the page dictionary.</p>
<p class="code">
<a href="#pdfio_obj_t">pdfio_obj_t</a> *pdfioPageGetObj(<a href="#pdfio_obj_t">pdfio_obj_t</a> *page, <span class="reserved">const</span> <span class="reserved">char</span> *key);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>page</th>
<td class="description">Page object</td></tr>
<tr><th>key</th>
<td class="description">Dictionary key</td></tr>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Object or <code>NULL</code> if none</p>
<h4 class="discussion">Discussion</h4>
<p class="discussion">This function looks up an indirect object value in the page dictionary,
either in the specified object or one of its parents.
</p>
<h3 class="function"><span class="info">&#160;PDFio v1.7&#160;</span><a id="pdfioPageGetRect">pdfioPageGetRect</a></h3>
<p class="description">Get a rectangle value from the page dictionary.</p>
<p class="code">
<a href="#pdfio_rect_t">pdfio_rect_t</a> *pdfioPageGetRect(<a href="#pdfio_obj_t">pdfio_obj_t</a> *page, <span class="reserved">const</span> <span class="reserved">char</span> *key, <a href="#pdfio_rect_t">pdfio_rect_t</a> *rect);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>page</th>
<td class="description">Page object</td></tr>
<tr><th>key</th>
<td class="description">Dictionary key</td></tr>
<tr><th>rect</th>
<td class="description">Rectangle</td></tr>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">Rectangle or <code>NULL</code> if none</p>
<h4 class="discussion">Discussion</h4>
<p class="discussion">This function looks up a rectangle value in the page dictionary, either in
the specified object or one of its parents.
</p>
<h3 class="function"><span class="info">&#160;PDFio v1.7&#160;</span><a id="pdfioPageGetString">pdfioPageGetString</a></h3>
<p class="description">Get a string value from the page dictionary.</p>
<p class="code">
<span class="reserved">const</span> <span class="reserved">char</span> *pdfioPageGetString(<a href="#pdfio_obj_t">pdfio_obj_t</a> *page, <span class="reserved">const</span> <span class="reserved">char</span> *key);</p>
<h4 class="parameters">Parameters</h4>
<table class="list"><tbody>
<tr><th>page</th>
<td class="description">Page object</td></tr>
<tr><th>key</th>
<td class="description">Dictionary key</td></tr>
</tbody></table>
<h4 class="returnvalue">Return Value</h4>
<p class="description">String value or <code>NULL</code> if none</p>
<h4 class="discussion">Discussion</h4>
<p class="discussion">This function looks up a string value in the page dictionary, either in the
specified object or one of its parents.
</p>
<h3 class="function"><a id="pdfioPageOpenStream">pdfioPageOpenStream</a></h3>
<p class="description">Open a content stream for a page.</p>
<p class="code">
@@ -5759,11 +5961,11 @@ typedef enum <a href="#pdfio_valtype_e">pdfio_valtype_e</a> pdfio_valtype_t;
<table class="list"><tbody>
<tr><th>PDFIO_FILTER_ASCII85 </th><td class="description">ASCII85Decode filter (reading only)</td></tr>
<tr><th>PDFIO_FILTER_ASCIIHEX </th><td class="description">ASCIIHexDecode filter (reading only)</td></tr>
<tr><th>PDFIO_FILTER_CCITTFAX </th><td class="description">CCITTFaxDecode filter</td></tr>
<tr><th>PDFIO_FILTER_CCITTFAX </th><td class="description">CCITTFaxDecode filter (reading only)</td></tr>
<tr><th>PDFIO_FILTER_CRYPT </th><td class="description">Encryption filter</td></tr>
<tr><th>PDFIO_FILTER_DCT </th><td class="description">DCTDecode (JPEG) filter</td></tr>
<tr><th>PDFIO_FILTER_FLATE </th><td class="description">FlateDecode filter</td></tr>
<tr><th>PDFIO_FILTER_JBIG2 </th><td class="description">JBIG2Decode filter</td></tr>
<tr><th>PDFIO_FILTER_JBIG2 </th><td class="description">JBIG2Decode filter (reading only)</td></tr>
<tr><th>PDFIO_FILTER_JPX </th><td class="description">JPXDecode filter (reading only)</td></tr>
<tr><th>PDFIO_FILTER_LZW </th><td class="description">LZWDecode filter (reading only)</td></tr>
<tr><th>PDFIO_FILTER_NONE </th><td class="description">No filter</td></tr>

View File

@@ -15,9 +15,9 @@ goals of PDFio are:
PDFio is *not* concerned with rendering or viewing a PDF file, although a PDF
RIP or viewer could be written using it.
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 "LICENSE" and "NOTICE" for more information.
Copyright © 2021-2026 by Michael R Sweet. PDFio is licensed under the Apache
License Version 2.0 with an (optional) exception to allow linking against GNU
GPL2-only software. See the files `LICENSE` and `NOTICE` for more information.
Requirements
@@ -28,16 +28,22 @@ PDFio requires the following to build the software:
- A C99 compiler such as Clang, GCC, or MS Visual C
- A POSIX-compliant `make` program
- A POSIX-compliant `sh` program
- 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.
- libpng (<https://www.libpng.org/>) 1.6 or later for full PNG image support
(optional)
- libwebp (<https://developers.google.com/speed/webp>) 1.0 or later for WebP
image support (optional)
- ZLIB (<https://www.zlib.net/>) 1.1 or later for compression support
IDE files for Xcode (macOS/iOS) and Visual Studio (Windows) are also provided.
On a stock Ubuntu install, the following command will install the various
prerequisites:
Installing PDFio
----------------
sudo apt-get install build-essential libpng-dev libwebp-dev zlib1g-dev
Building and Installing PDFio on Linux/Unix
-------------------------------------------
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:
@@ -69,21 +75,32 @@ Other configure options can be found using the `--help` option:
./configure --help
Visual Studio Project
---------------------
Building on Windows with Visual Studio
--------------------------------------
The Visual Studio solution ("pdfio.sln") is provided for Windows developers and
generates both a static library and DLL.
generates the PDFIO1 DLL. You can also use NuGet to install the `pdfio_native`
package.
You can build and run the `testpdfio` unit tests target from within Visual
Studio to verify the functioning of the library.
Xcode Project
-------------
Building on macOS with Xcode
----------------------------
There is also an Xcode project ("pdfio.xcodeproj") you can use on macOS which
generates a static library that will be installed under "/usr/local" with:
The Xcode project (`pdfio.xcodeproj`) is provided for macOS developer which
generates a static library that will be installed under "/usr/local". Run the
following command to build and install PDFio on macOS:
sudo xcodebuild install
Alternately you can add the Xcode project to a workspace and reference the PDFio
library target as a dependency in your own project.
You can build and run the `testpdfio` unit tests target from within Xcode to
verify the functioning of the library.
Detecting PDFio
---------------
@@ -387,43 +404,35 @@ Each page is represented by a "page tree" object (what [`pdfioFileGetPage`](@@)
returns) that specifies information about the page and one or more "content"
objects that contain the images, fonts, text, and graphics that appear on the
page. Use the [`pdfioPageGetNumStreams`](@@) and [`pdfioPageOpenStream`](@@)
functions to access the content streams for each page, and
[`pdfioObjGetDict`](@@) to get the associated page object dictionary. For
example, if you want to display the media and crop boxes for a given page:
functions to access the content streams for each page, [`pdfioObjGetDict`](@@)
to get the associated page object dictionary, and [`pdfioPageGetArray`](@@),
[`pdfioPageGetBinary`](@@), [`pdfioPageGetBoolean`](@@),
[`pdfioPageGetDate`](@@), [`pdfioPageGetDict`](@@), [`pdfioPageGetName`](@@),
[`pdfioPageGetObj`](@@), [`pdfioPageGetRect`](@@), and
[`pdfioPageGetString`](@@) to get a value from the page object dictionary or its
parents. For example, if you want to display the media and crop boxes for a
given page:
```c
pdfio_file_t *pdf; // PDF file
size_t i; // Looping var
size_t count; // Number of pages
pdfio_obj_t *page; // Current page
pdfio_dict_t *dict; // Current page dictionary
pdfio_array_t *media_box; // MediaBox array
double media_values[4]; // MediaBox values
pdfio_array_t *crop_box; // CropBox array
double crop_values[4]; // CropBox values
pdfio_rect_t media_box; // MediaBox values
pdfio_rect_t crop_box; // CropBox values
// Iterate the pages in the PDF file
for (i = 0, count = pdfioFileGetNumPages(pdf); i < count; i ++)
{
page = pdfioFileGetPage(pdf, i);
dict = pdfioObjGetDict(page);
media_box = pdfioDictGetArray(dict, "MediaBox");
media_values[0] = pdfioArrayGetNumber(media_box, 0);
media_values[1] = pdfioArrayGetNumber(media_box, 1);
media_values[2] = pdfioArrayGetNumber(media_box, 2);
media_values[3] = pdfioArrayGetNumber(media_box, 3);
crop_box = pdfioDictGetArray(dict, "CropBox");
crop_values[0] = pdfioArrayGetNumber(crop_box, 0);
crop_values[1] = pdfioArrayGetNumber(crop_box, 1);
crop_values[2] = pdfioArrayGetNumber(crop_box, 2);
crop_values[3] = pdfioArrayGetNumber(crop_box, 3);
pdfioPageGetRect(page, "MediaBox", &media_box);
pdfioPageGetRect(page, "CropBox", &crop_box);
printf("Page %u: MediaBox=[%g %g %g %g], CropBox=[%g %g %g %g]\n",
(unsigned)(i + 1),
media_values[0], media_values[1], media_values[2], media_values[3],
crop_values[0], crop_values[1], crop_values[2], crop_values[3]);
media_box.x1, media_box.y1, media_box.x2, media_box.y2,
crop_box.x1, crop_box.y1, crop_box.x2, crop_box.y2);
}
```
@@ -760,8 +769,9 @@ The "interpolate" argument specifies whether the colors in the image should be
smoothed/interpolated when scaling. This is most useful for photographs but
should be `false` for screenshot and barcode images.
If you have a JPEG or PNG file, use the [`pdfioFileCreateImageObjFromFile`](@@)
function to copy the image into a PDF image object, for example:
If you have a GIF, JPEG, PNG, or WebP file, use the
[`pdfioFileCreateImageObjFromFile`](@@) function to copy the image into a PDF
image object, for example:
```c
pdfio_file_t *pdf = pdfioFileCreate(...);
@@ -769,9 +779,6 @@ pdfio_obj_t *img =
pdfioFileCreateImageObjFromFile(pdf, "myphoto.jpg", /*interpolate*/true);
```
> Note: Currently `pdfioFileCreateImageObjFromFile` does not support 12 bit JPEG
> files or PNG files with an alpha channel.
### Page Dictionary Functions
@@ -1029,16 +1036,10 @@ main(int argc, // I - Number of command-line arguments
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);
page = pdfioFileGetPage(pdf, cur);
if (pdfioDictGetRect(page_dict, "MediaBox", &cur_box))
break;
}
cur_box.x1 = cur_box.x2 = cur_box.y1 = cur_box.y2 = 0.0;
pdfioPageGetRect(page, "MediaBox", &cur_box);
// If this MediaBox is different from the previous one, show the range of
// pages that have that size...
@@ -1342,7 +1343,7 @@ Unicode glyph for the current index:
Create a PDF File With Text and an Image
----------------------------------------
The `image2pdf.c` example code creates a PDF file containing a JPEG or PNG
The `image2pdf.c` example code creates a PDF file containing a GIF, JPEG, or PNG
image file and optional caption on a single page. The `create_pdf_image_file`
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

View File

@@ -13,6 +13,10 @@
#include <pdfio.h>
#include <pdfio-content.h>
#ifdef _WIN32
# include <io.h>
# include <fcntl.h>
#endif // _WIN32
//

View File

@@ -25,6 +25,7 @@
#include <math.h>
#ifdef _WIN32
# include <io.h>
# include <fcntl.h>
#else
# include <unistd.h>
#endif // _WIN32

View File

@@ -1,7 +1,7 @@
//
// PDF metadata example for PDFio.
//
// Copyright © 2023-2025 by Michael R Sweet.
// Copyright © 2023-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -113,16 +113,10 @@ main(int argc, // I - Number of command-line arguments
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);
page = pdfioFileGetPage(pdf, cur);
if (pdfioDictGetRect(page_dict, "MediaBox", &cur_box))
break;
}
cur_box.x1 = cur_box.x2 = cur_box.y1 = cur_box.y2 = 0.0;
pdfioPageGetRect(page, "MediaBox", &cur_box);
// If this MediaBox is different from the previous one, show the range of
// pages that have that size...

View File

@@ -7,6 +7,10 @@
# ./makesrcdist [--snapshot] VERSION
#
# Save the current directory...
basedir="$(pwd)"
# Support "--snapshot" option...
if test "$1" == "--snapshot"; then
shift
@@ -15,18 +19,21 @@ else
snapshot=0
fi
# Get version...
# Get the release version...
if test $# != 1; then
echo "Usage: ./makesrcdist [--snapshot] VERSION"
exit 1
fi
status=0
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...
status=0
if test $(grep AC_INIT configure.ac | awk '{print $2}') != "[$version],"; then
echo "Still need to update AC_INIT version in 'configure.ac'."
status=1
@@ -78,18 +85,32 @@ if test $status = 1; then
exit 1
fi
# Tag release...
if test $snapshot = 0; then
echo Creating tag for release...
echo "Creating tag v$version for release..."
git tag -m "Tag $version" v$version
git push origin v$version
fi
# Make source archives...
echo Creating pdfio-$version.tar.gz...
git archive --format tar --prefix=pdfio-$version/ HEAD | gzip -v9 >pdfio-$version.tar.gz
gpg --detach-sign pdfio-$version.tar.gz
echo Creating pdfio-$version.zip...
git archive --format zip --prefix=pdfio-$version/ HEAD >pdfio-$version.zip
gpg --detach-sign pdfio-$version.zip
# Make and sign source archives...
echo "Exporting $version..."
rm -rf $TMPDIR/pdfio-$version
mkdir $TMPDIR/pdfio-$version
git archive --format tar HEAD | (cd $TMPDIR/pdfio-$version; tar xf -)
(cd ttf; git archive --prefix=ttf/ HEAD) | (cd $TMPDIR/pdfio-$version; tar xf -)
cd $TMPDIR
echo "Creating pdfio-$version.tar.gz..."
tar cf - pdfio-$version | gzip -v9 >"$basedir/pdfio-$version.tar.gz"
gpg --detach-sign "$basedir/pdfio-$version.tar.gz"
echo "Creating pdfio-$version.zip..."
zip -r "$basedir/pdfio-$version.zip" pdfio-$version
gpg --detach-sign "$basedir/pdfio-$version.zip"
# Clean up...
echo "Removing temporary files..."
rm -rf pdfio-$version

View File

@@ -1,7 +1,7 @@
//
// AES functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -117,7 +117,7 @@ _pdfioCryptoAESInit(
memcpy(ctx->round_key, key, keylen);
// All other round keys are found from the previous round keys.
for (rkptr0 = ctx->round_key, rkptr = rkptr0 + keylen, rkend = rkptr + 16 * ctx->round_size, i = nwords; rkptr < rkend; i ++)
for (rkptr0 = ctx->round_key, rkptr = rkptr0 + keylen, rkend = rkptr0 + 16 * ctx->round_size + 16, i = nwords; rkptr < rkend; i ++)
{
if ((i % nwords) == 0)
{

View File

@@ -1,7 +1,7 @@
//
// PDF array functions for PDFio.
//
// Copyright © 2021-2024 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -264,6 +264,10 @@ pdfioArrayCopy(pdfio_file_t *pdf, // I - PDF file
PDFIO_DEBUG("pdfioArrayCopy(pdf=%p, a=%p(%p))\n", (void *)pdf, (void *)a, a ? (void *)a->pdf : NULL);
// Range check input...
if (!pdf || !a)
return (NULL);
// Create the new array...
if ((na = pdfioArrayCreate(pdf)) == NULL)
return (NULL);
@@ -666,27 +670,29 @@ pdfioArrayRemove(pdfio_array_t *a, // I - Array
//
bool // O - `true` on success, `false` otherwise
_pdfioArrayWrite(pdfio_array_t *a, // I - Array
pdfio_obj_t *obj) // I - Object, if any
_pdfioArrayWrite(
_pdfio_printf_t cb, // I - Printf callback function
void *cbdata, // I - Printf callback data
pdfio_obj_t *obj, // I - Object, if any
pdfio_array_t *a) // I - Array
{
pdfio_file_t *pdf = a->pdf; // PDF file
size_t i; // Looping var
_pdfio_value_t *v; // Current value
// Arrays are surrounded by square brackets ([ ... ])
if (!_pdfioFilePuts(pdf, "["))
if (!(cb)(cbdata, "["))
return (false);
// Write each value...
for (i = a->num_values, v = a->values; i > 0; i --, v ++)
{
if (!_pdfioValueWrite(pdf, obj, v, NULL))
if (!_pdfioValueWrite(cb, cbdata, obj, v, NULL))
return (false);
}
// Closing bracket...
return (_pdfioFilePuts(pdf, "]"));
return ((cb)(cbdata, "]"));
}

View File

@@ -1,7 +1,7 @@
//
// Content helper functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -12,9 +12,13 @@
#include "pdfio-base-font-widths.h"
#include "pdfio-cgats001-compat.h"
#include "ttf.h"
#include <sys/stat.h>
#ifdef HAVE_LIBPNG
# include <png.h>
#endif // HAVE_LIBPNG
#ifdef HAVE_LIBWEBP
# include <webp/decode.h>
#endif // HAVE_LIBWEBP
#include <math.h>
#ifndef M_PI
# define M_PI 3.14159265358979323846264338327950288
@@ -25,6 +29,10 @@
// Local constants...
//
#define _PDFIO_GIF_COLORBITS 0x07 // Color bits
#define _PDFIO_GIF_INTERLACE 0x40 // Interlace flag
#define _PDFIO_GIF_COLORMAP 0x80 // Colormap present flag
#define _PDFIO_JPEG_SOF0 0xc0 // Start of frame (0)
#define _PDFIO_JPEG_SOF1 0xc1 // Start of frame (1)
#define _PDFIO_JPEG_SOF2 0xc2 // Start of frame (2)
@@ -75,6 +83,8 @@
// Local types...
//
typedef uint8_t _pdfio_gif_cmap_t[256][3];
typedef pdfio_obj_t *(*_pdfio_image_func_t)(pdfio_dict_t *dict, int fd);
@@ -82,19 +92,30 @@ typedef pdfio_obj_t *(*_pdfio_image_func_t)(pdfio_dict_t *dict, int fd);
// Local functions...
//
static pdfio_obj_t *copy_gif(pdfio_dict_t *dict, int fd);
static pdfio_obj_t *copy_jpeg(pdfio_dict_t *dict, int fd);
static pdfio_obj_t *copy_png(pdfio_dict_t *dict, int fd);
#ifdef HAVE_LIBWEBP
static pdfio_obj_t *copy_webp(pdfio_dict_t *dict, int fd);
#endif // HAVE_LIBWEBP
static bool create_cp1252(pdfio_file_t *pdf);
static pdfio_obj_t *create_font(pdfio_obj_t *file_obj, ttf_t *font, bool unicode);
static pdfio_obj_t *create_image(pdfio_dict_t *dict, const unsigned char *data, size_t width, size_t height, size_t depth, size_t num_colors, bool alpha);
static ssize_t gif_get_block(pdfio_file_t *pdf, int fd, uint8_t *buffer, size_t bufsize);
static bool gif_read_image(pdfio_file_t *pdf, int fd, uint8_t *data, size_t width, size_t height, int *ncolors, _pdfio_gif_cmap_t cmap);
#ifdef HAVE_LIBPNG
static void png_error_func(png_structp pp, png_const_charp message);
static void png_read_func(png_structp png_ptr, png_bytep data, size_t length);
#endif // HAVE_LIBPNG
static void ttf_error_cb(pdfio_file_t *pdf, const char *message);
#ifndef HAVE_LIBPNG
static unsigned update_png_crc(unsigned crc, const unsigned char *buffer, size_t length);
#endif // !HAVE_LIBPNG
static bool write_array(pdfio_stream_t *st, pdfio_array_t *a);
static bool write_dict(pdfio_stream_t *st, pdfio_dict_t *dict);
static bool write_string(pdfio_stream_t *st, bool unicode, const char *s, bool *newline);
@@ -476,7 +497,7 @@ pdfioArrayCreateColorFromStandard(
// the document catalog. The caller is responsible for setting the
// "StructTreeRoot" dictionary when creating marked content.
//
// @since PDFio 1.6@
// @since PDFio v1.6@
//
bool // O - `true` on success, `false` on failure
@@ -559,7 +580,7 @@ pdfioContentDrawImage(
// This function ends an area of marked content that was started using the
// @link pdfioContentBeginMarked@ function.
//
// @since PDFio 1.6@
// @since PDFio v1.6@
//
bool // O - `true` on success, `false` on failure
@@ -1598,7 +1619,7 @@ pdfioContentTextShowJustified(
// condition. If `NULL`, the PDF consumer will attempt to look up the correct
// profile using the "cond_id" value.
//
// @since PDFio 1.6@
// @since PDFio v1.6@
//
void
@@ -1864,7 +1885,7 @@ pdfioFileCreateFontObjFromFile(
return (NULL);
}
if ((fd = open(filename, O_RDONLY | O_BINARY)) < 0)
if ((fd = open(filename, O_RDONLY | O_BINARY, 0)) < 0)
{
_pdfioFileError(pdf, "Unable to open font file '%s': %s", filename, strerror(errno));
return (NULL);
@@ -2000,7 +2021,7 @@ pdfioFileCreateICCObjFromFile(
return (NULL);
}
if ((fd = open(filename, O_RDONLY | O_BINARY)) < 0)
if ((fd = open(filename, O_RDONLY | O_BINARY, 0)) < 0)
{
_pdfioFileError(pdf, "Unable to open ICC profile '%s': %s", filename, strerror(errno));
return (NULL);
@@ -2127,13 +2148,16 @@ pdfioFileCreateImageObjFromData(
//
// 'pdfioFileCreateImageObjFromFile()' - Add an image object to a PDF file from a file.
//
// This function creates an image object in a PDF file from a JPEG or PNG file.
// The "filename" parameter specifies the name of the JPEG or PNG file, while
// the "interpolate" parameter specifies whether to interpolate when scaling the
// image on the page.
// This function creates an image object in a PDF file from a GIF, JPEG, PNG, or
// WebP file. The "filename" parameter specifies the name of the GIF, JPEG,
// PNG, or WebP file, while the "interpolate" parameter specifies whether to
// interpolate when scaling the image on the page.
//
// > Note: PNG files containing transparency cannot be used when producing
// > PDF/A files.
// > PDF/A files. Files containing animation yield the final frame of the
// > animation. GIF and (optional) WebP support first appeared in PDFio v1.7.
// > WebP content is added as a Flate-compressed image with alpha mask as
// > needed and is usually significantly larger than the original image.
//
pdfio_obj_t * // O - Object
@@ -2156,7 +2180,7 @@ pdfioFileCreateImageObjFromFile(
return (NULL);
// Try opening the file...
if ((fd = open(filename, O_RDONLY | O_BINARY)) < 0)
if ((fd = open(filename, O_RDONLY | O_BINARY, 0)) < 0)
{
_pdfioFileError(pdf, "Unable to open image file '%s': %s", filename, strerror(errno));
return (NULL);
@@ -2174,16 +2198,28 @@ pdfioFileCreateImageObjFromFile(
PDFIO_DEBUG("pdfioFileCreateImageObjFromFile: buffer=<%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X>\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18], buffer[19], buffer[20], buffer[21], buffer[22], buffer[23], buffer[24], buffer[25], buffer[26], buffer[27], buffer[28], buffer[29], buffer[30], buffer[31]);
if (!memcmp(buffer, "\211PNG\015\012\032\012\000\000\000\015IHDR", 16))
if (!memcmp(buffer, "GIF87a", 6) || !memcmp(buffer, "GIF89a", 6))
{
// GIF image...
copy_func = copy_gif;
}
else if (!memcmp(buffer, "\211PNG\015\012\032\012\000\000\000\015IHDR", 16))
{
// PNG image...
copy_func = copy_png;
}
else if (!memcmp(buffer, "\377\330\377", 3))
{
// JPEG image...
// JPEG image...
copy_func = copy_jpeg;
}
#ifdef HAVE_LIBWEBP
else if (!memcmp(buffer, "RIFF", 4) && !memcmp(buffer + 8, "WEBP", 4))
{
// WebP image
copy_func = copy_webp;
}
#endif // HAVE_LIBWEBP
else
{
// Something else that isn't supported...
@@ -2456,6 +2492,169 @@ pdfioPageDictAddImage(
}
/*
* 'copy_gif()' - Copy a GIF image file.
*/
static pdfio_obj_t * // O - Image object or `NULL` on error
copy_gif(pdfio_dict_t *dict, // I - Image dictionary
int fd) // I - File to read from
{
pdfio_obj_t *image_obj = NULL;
// Image object
uint8_t header[13]; // Image header
_pdfio_gif_cmap_t cmap; // Colormap
int ncolors, // Number of colors/bits per pixel
transparent; // Transparent color index
size_t width, // Width of image
height; // Height of image
uint8_t *data = NULL; // Image data
size_t datasize; // Size of image data
uint8_t desc, // Descriptor header
extdesc; // Extension descriptor
uint8_t block[256]; // Extension block
ssize_t blocklen; // Length of block
pdfio_dict_t *decode; // Decode parameters
// Read the header; we already know it is a GIF file...
if (read(fd, header, sizeof(header)) < (ssize_t)sizeof(header))
{
_pdfioFileError(dict->pdf, "Unable to read GIF header: %s", strerror(errno));
return (NULL);
}
PDFIO_DEBUG("copy_gif: header=<%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X>\n", header[0], header[1], header[2], header[3], header[4], header[5], header[6], header[7], header[8], header[9], header[10], header[11], header[12]);
width = (size_t)((header[7] << 8) | header[6]);
height = (size_t)((header[9] << 8) | header[8]);
ncolors = 2 << (header[10] & _PDFIO_GIF_COLORBITS);
PDFIO_DEBUG("copy_gif: width=%u, height=%u, ncolors=%d\n", (unsigned)width, (unsigned)height, ncolors);
if (width < 1 || width > 16384 || height < 1 || height > 16384)
{
_pdfioFileError(dict->pdf, "Unsupported image dimensions %ux%ux%d.", (unsigned)width, (unsigned)height, ncolors);
return (NULL);
}
if (header[10] & _PDFIO_GIF_COLORMAP)
{
// Read colormap after the file header...
PDFIO_DEBUG("copy_gif: Have colormap.\n");
if (read(fd, cmap, (size_t)(ncolors * 3)) < (ssize_t)(ncolors * 3))
{
_pdfioFileError(dict->pdf, "Unable to read GIF colormap: %s", strerror(errno));
return (NULL);
}
}
else
{
// Make a grayscale colormap for the number of colors...
PDFIO_DEBUG("copy_gif: Using default grayscale colormap.\n");
for (int i = 0; i < ncolors; i ++)
cmap[i][0] = cmap[i][1] = cmap[i][2] = (uint8_t)(255 * i / (ncolors - 1));
}
#if DEBUG > 1
for (int i = 0; i < ncolors; i ++)
PDFIO_DEBUG("copy_gif: cmap[%d]={%4d,%4d,%4d}\n", i, cmap[i][0], cmap[i][1], cmap[i][2]);
#endif // DEBUG > 1
transparent = -1;
// Allocate memory for the image and clear it to the background color...
datasize = width * height;
if ((data = malloc(datasize)) == NULL)
{
_pdfioFileError(dict->pdf, "Unable to allocate memory for GIF image: %s", strerror(errno));
return (NULL);
}
memset(data, header[11], datasize);
// Read the image...
while (read(fd, &desc, 1) > 0)
{
PDFIO_DEBUG("copy_gif: desc=%02X('%c')\n", desc, desc);
switch (desc)
{
case ';' : // End of image
pdfioDictSetNumber(dict, "Width", width);
pdfioDictSetNumber(dict, "Height", height);
pdfioDictSetNumber(dict, "BitsPerComponent", 8);
pdfioDictSetArray(dict, "ColorSpace", pdfioArrayCreateColorFromPalette(dict->pdf, (size_t)ncolors, cmap[0]));
if (transparent >= 0)
{
pdfio_array_t *mask = pdfioArrayCreate(dict->pdf);
pdfioArrayAppendNumber(mask, transparent);
pdfioArrayAppendNumber(mask, transparent);
pdfioDictSetArray(dict, "Mask", mask);
}
if ((decode = pdfioDictCreate(dict->pdf)) == NULL)
{
_pdfioFileError(dict->pdf, "Unable to create decode parameters for WebP image.");
goto done;
}
pdfioDictSetNumber(decode, "BitsPerComponent", 8);
pdfioDictSetNumber(decode, "Colors", 1);
pdfioDictSetNumber(decode, "Columns", width);
pdfioDictSetNumber(decode, "Predictor", _PDFIO_PREDICTOR_PNG_AUTO);
pdfioDictSetDict(dict, "DecodeParms", decode);
pdfioDictSetName(dict, "Filter", "FlateDecode");
image_obj = create_image(dict, data, width, height, 8, 1, /*alpha*/false);
goto done;
case '!' : // Extension record
if (read(fd, &extdesc, 1) < 1)
{
_pdfioFileError(dict->pdf, "Unable to read extension descriptor.");
goto done;
}
if (extdesc == 0xf9) // Graphic Control Extension
{
if (gif_get_block(dict->pdf, fd, block, sizeof(block)) < 4)
goto done;
if (block[0] & 1) // Get transparent color index
{
transparent = block[3];
PDFIO_DEBUG("copy_gif: transparent=%d\n", transparent);
}
}
do
{
}
while ((blocklen = gif_get_block(dict->pdf, fd, block, sizeof(block))) > 0);
if (blocklen < 0)
goto done;
break;
case ',' : // Image data
if (!gif_read_image(dict->pdf, fd, data, width, height, &ncolors, cmap))
goto done;
break;
}
}
done:
free(data);
return (image_obj);
}
//
// 'copy_jpeg()' - Copy a JPEG image.
//
@@ -3284,6 +3483,86 @@ copy_png(pdfio_dict_t *dict, // I - Dictionary
}
#ifdef HAVE_LIBWEBP
//
// 'copy_webp()' - Copy a WebP image.
//
static pdfio_obj_t * // O - Image object
copy_webp(pdfio_dict_t *dict, // I - Image dictionary
int fd) // I - Image file
{
struct stat finfo; // Image file information
uint8_t *fdata = NULL; // Image file data
int width, // Width of image
height; // Height of image
uint8_t *pixels = NULL; // Image pixel (RGBA) data
pdfio_obj_t *obj = NULL; // Image object
pdfio_dict_t *decode; // Stream decode parameters
if (fstat(fd, &finfo))
{
_pdfioFileError(dict->pdf, "Unable to get WebP file information: %s", strerror(errno));
return (NULL);
}
if (finfo.st_size > (1024 * 1024))
{
_pdfioFileError(dict->pdf, "WebP image is too large.");
goto done;
}
if ((fdata = malloc((size_t)finfo.st_size)) == NULL)
{
_pdfioFileError(dict->pdf, "Unable to allocate memory for WebP file: %s", strerror(errno));
goto done;
}
if (read(fd, fdata, (size_t)finfo.st_size) < (ssize_t)finfo.st_size)
{
_pdfioFileError(dict->pdf, "Unable to read WebP file.");
goto done;
}
if ((pixels = WebPDecodeRGBA(fdata, (size_t)finfo.st_size, &width, &height)) == NULL)
{
_pdfioFileError(dict->pdf, "Invalid WebP file.");
goto done;
}
pdfioDictSetNumber(dict, "Width", width);
pdfioDictSetNumber(dict, "Height", height);
pdfioDictSetNumber(dict, "BitsPerComponent", 8);
pdfioDictSetArray(dict, "ColorSpace", pdfioArrayCreateColorFromStandard(dict->pdf, /*num_colors*/3, PDFIO_CS_SRGB));
pdfioDictSetName(dict, "Filter", "FlateDecode");
if ((decode = pdfioDictCreate(dict->pdf)) == NULL)
{
_pdfioFileError(dict->pdf, "Unable to create decode parameters for WebP image.");
goto done;
}
pdfioDictSetNumber(decode, "BitsPerComponent", 8);
pdfioDictSetNumber(decode, "Colors", 3);
pdfioDictSetNumber(decode, "Columns", width);
pdfioDictSetNumber(decode, "Predictor", _PDFIO_PREDICTOR_PNG_AUTO);
pdfioDictSetDict(dict, "DecodeParms", decode);
obj = create_image(dict, pixels, width, height, /*depth*/8, /*num_colors*/3, /*alpha*/true);
done:
free(fdata);
if (pixels)
WebPFree(pixels);
return (obj);
}
#endif // HAVE_LIBWEBP
//
// 'create_cp1252()' - Create the CP1252 font encoding object.
//
@@ -3699,66 +3978,80 @@ create_image(
// Generate a mask image, as needed...
if (alpha)
{
// Create the image mask dictionary...
if ((mask_dict = pdfioDictCopy(pdf, dict)) == NULL)
// See if the image mask is actually needed...
size_t remaining = width * height; // Remaining pixels
for (dataptr = data + bpp; remaining > 0; remaining --, dataptr += bpp + bpc)
{
free(line);
return (NULL);
if (*dataptr < 255)
break;
if (bpc == 2 && dataptr[1] < 255)
break;
}
// Transparency masks are always grayscale...
pdfioDictSetName(mask_dict, "ColorSpace", "DeviceGray");
// Set the automatic PNG predictor to optimize compression...
if ((decode = pdfioDictCreate(pdf)) == NULL)
if (remaining > 0)
{
free(line);
return (NULL);
}
pdfioDictSetNumber(decode, "BitsPerComponent", (double)depth);
pdfioDictSetNumber(decode, "Colors", 1);
pdfioDictSetNumber(decode, "Columns", (double)width);
pdfioDictSetNumber(decode, "Predictor", _PDFIO_PREDICTOR_PNG_AUTO);
pdfioDictSetDict(mask_dict, "DecodeParms", decode);
// Create the mask object and write the mask image...
if ((mask_obj = pdfioFileCreateObj(pdf, mask_dict)) == NULL)
{
free(line);
return (NULL);
}
if ((st = pdfioObjCreateStream(mask_obj, PDFIO_FILTER_FLATE)) == NULL)
{
free(line);
pdfioObjClose(mask_obj);
return (NULL);
}
for (y = height, dataptr = data + bpp; y > 0; y --)
{
if (bpc == 1)
// Create the image mask dictionary...
if ((mask_dict = pdfioDictCopy(pdf, dict)) == NULL)
{
for (x = width, lineptr = line; x > 0; x --, dataptr += bpp)
*lineptr++ = *dataptr++;
free(line);
return (NULL);
}
else
// Transparency masks are always grayscale...
pdfioDictSetName(mask_dict, "ColorSpace", "DeviceGray");
// Set the automatic PNG predictor to optimize compression...
if ((decode = pdfioDictCreate(pdf)) == NULL)
{
for (x = width, lineptr = line; x > 0; x --, dataptr += bpp)
free(line);
return (NULL);
}
pdfioDictSetNumber(decode, "BitsPerComponent", (double)depth);
pdfioDictSetNumber(decode, "Colors", 1);
pdfioDictSetNumber(decode, "Columns", (double)width);
pdfioDictSetNumber(decode, "Predictor", _PDFIO_PREDICTOR_PNG_AUTO);
pdfioDictSetDict(mask_dict, "DecodeParms", decode);
// Create the mask object and write the mask image...
if ((mask_obj = pdfioFileCreateObj(pdf, mask_dict)) == NULL)
{
free(line);
return (NULL);
}
if ((st = pdfioObjCreateStream(mask_obj, PDFIO_FILTER_FLATE)) == NULL)
{
free(line);
pdfioObjClose(mask_obj);
return (NULL);
}
for (y = height, dataptr = data + bpp; y > 0; y --)
{
if (bpc == 1)
{
*lineptr++ = *dataptr++;
*lineptr++ = *dataptr++;
for (x = width, lineptr = line; x > 0; x --, dataptr += bpp)
*lineptr++ = *dataptr++;
}
else
{
for (x = width, lineptr = line; x > 0; x --, dataptr += bpp)
{
*lineptr++ = *dataptr++;
*lineptr++ = *dataptr++;
}
}
pdfioStreamWrite(st, line, width * bpc);
}
pdfioStreamWrite(st, line, width * bpc);
pdfioStreamClose(st);
// Use the transparency mask...
pdfioDictSetObj(dict, "SMask", mask_obj);
}
pdfioStreamClose(st);
// Use the transparency mask...
pdfioDictSetObj(dict, "SMask", mask_obj);
}
// Set the automatic PNG predictor to optimize compression...
@@ -3819,6 +4112,225 @@ create_image(
}
//
// 'gif_get_block()' - Read a GIF data block...
//
static ssize_t // O - Number characters read
gif_get_block(pdfio_file_t *pdf, // I - PDF file
int fd, // I - File to read from
uint8_t *buffer, // I - Input buffer (at least 256 bytes)
size_t bufsize) // I - Size of buffer
{
uint8_t count; // Number of bytes to read
// Make sure the buffer is sufficiently large...
if (bufsize < 256)
{
_pdfioFileError(pdf, "GIF block buffer too small.");
return (-1);
}
// Read the count byte followed by the data from the file...
if (read(fd, &count, 1) < 1)
{
_pdfioFileError(pdf, "Unable to read GIF block length.");
return (-1);
}
PDFIO_DEBUG("gif_get_block: count=%u\n", count);
if (count > 0 && read(fd, buffer, count) < count)
{
_pdfioFileError(pdf, "Unable to read GIF block data.");
return (-1);
}
return (count);
}
//
// 'gif_read_image()' - Read a GIF image stream...
//
static bool // I - `true` on success, `false` on failure
gif_read_image(
pdfio_file_t *pdf, // I - PDF file
int fd, // I - Input file
uint8_t *data, // I - Indexed image data
size_t width, // I - Width of image
size_t height, // I - Height of image
int *ncolors, // IO - Number of colors
_pdfio_gif_cmap_t cmap) // I - Colormap
{
bool ret = false; // Return value
uint8_t code_size; // Code size
_pdfio_lzw_t *lzw; // LZW decompressor state
uint8_t block[256]; // Data block from GIF file
ssize_t blocklen; // Length of data block
uint8_t pixels[1024], // Temporary pixel buffer
*pixptr = pixels, // Pointer into pixel buffer
*pixend = pixels; // End of pixel buffer
uint8_t *dataptr; // Current pixel in image
uint8_t iheader[9]; // Image header
size_t ileft, // Left position
itop, // Top position
iwidth, // Width
iheight; // Height
bool interlace; // Interlaced image?
size_t xpos = 0, // Current X position
ypos = 0, // Current Y position
pass = 0, // Current pass
remaining; // Remaining pixels
bool saw_endblock = false; // Have we seen the 0-length block?
static size_t xpasses[4] = { 8, 8, 4, 2 },
ypasses[5] = { 0, 4, 2, 1, 0 };
if (read(fd, iheader, sizeof(iheader)) < (ssize_t)sizeof(iheader))
{
_pdfioFileError(pdf, "Unable to read GIF image header.");
return (false);
}
PDFIO_DEBUG("gif_read_image: iheader=<%02X%02X%02X%02X%02X%02X%02X%02X%02X>\n", iheader[0], iheader[1], iheader[2], iheader[3], iheader[4], iheader[5], iheader[6], iheader[7], iheader[8]);
ileft = (size_t)(iheader[0] | (iheader[1] << 8));
itop = (size_t)(iheader[2] | (iheader[3] << 8));
iwidth = (size_t)(iheader[4] | (iheader[5] << 8));
iheight = (size_t)(iheader[6] | (iheader[7] << 8));
interlace = (iheader[8] & _PDFIO_GIF_INTERLACE) != 0;
PDFIO_DEBUG("gif_read_image: ileft=%u, itop=%u, iwidth=%u, iheight=%u, interlace=%s\n", (unsigned)ileft, (unsigned)itop, (unsigned)iwidth, (unsigned)iheight, interlace ? "true" : "false");
if ((ileft + iwidth) > width || (itop + iheight) > height)
{
_pdfioFileError(pdf, "Invalid GIF image %ux%u offset %ux%u.", (unsigned)iwidth, (unsigned)iheight, (unsigned)ileft, (unsigned)itop);
return (false);
}
remaining = iwidth * iheight;
iwidth += ileft;
iheight += itop;
if (iheader[8] & _PDFIO_GIF_COLORMAP)
{
// Local image colormap...
int ncolors2 = 2 << (iheader[8] & _PDFIO_GIF_COLORBITS);
PDFIO_DEBUG("gif_read_image: Colormap with %d colors.\n", ncolors2);
if (read(fd, cmap, (size_t)(3 * ncolors2)) < (ssize_t)(3 * ncolors2))
{
_pdfioFileError(pdf, "Unable to read GIF image colormap.");
return (false);
}
if (ncolors2 > *ncolors)
*ncolors = ncolors2;
}
xpos = ileft;
ypos = itop;
pass = 0;
code_size = 0;
if (read(fd, &code_size, 1) < 1 || code_size < 2 || code_size > 12)
{
_pdfioFileError(pdf, "Invalid code size %u in GIF file.", code_size);
return (false);
}
PDFIO_DEBUG("gif_read_image: code_size=%u\n", code_size);
if ((lzw = _pdfioLZWCreate(code_size, /*early*/0, /*reversed*/true)) == NULL)
{
_pdfioFileError(pdf, "Unable to create GIF decompressor for code size %u.", code_size);
return (false);
}
// TODO: Support 1-bit and 4-bit per output data; current code is 8-bit only
dataptr = data + ypos * width + xpos;
while (remaining > 0)
{
// Fill the pixel buffer as needed...
while (pixptr >= pixend)
{
// Fill input buffer as needed...
if (lzw->avail_in == 0 && !saw_endblock)
{
blocklen = gif_get_block(pdf, fd, block, sizeof(block));
saw_endblock = blocklen <= 0;
if (blocklen < 0)
goto done;
lzw->avail_in = (size_t)blocklen;
lzw->next_in = block;
}
lzw->avail_out = sizeof(pixels);
lzw->next_out = pixels;
if (!_pdfioLZWInflate(lzw))
{
_pdfioFileError(pdf, "Unable to decompress GIF image: %s", lzw->error);
goto done;
}
pixptr = pixels;
pixend = pixels + sizeof(pixels) - lzw->avail_out;
}
// Add this pixel and advance to the next one...
*dataptr++ = *pixptr++;
remaining --;
xpos ++;
if (xpos >= iwidth)
{
// New line...
xpos = ileft;
if (interlace)
{
// Skip lines when the image is interlaced...
ypos += xpasses[pass];
if (ypos >= iheight)
{
pass ++;
ypos = ypasses[pass] + itop;
}
}
else
{
// No interlacing, move to next line...
ypos ++;
}
dataptr = data + ypos * width + xpos;
}
}
ret = true;
done:
// Read any remaining blocks...
while (!saw_endblock)
saw_endblock = gif_get_block(pdf, fd, block, sizeof(block)) <= 0;
// Free the LZW decoder state and return...
_pdfioLZWDelete(lzw);
return (ret);
}
#ifdef HAVE_LIBPNG
//
// 'png_error_func()' - PNG error message function.

View File

@@ -1,7 +1,7 @@
//
// Cryptographic support functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -96,12 +96,14 @@ static uint8_t pdf_passpad[32] = // Padding for passwords
// Local functions...
//
static void decrypt_user_key(pdfio_encryption_t encryption, const uint8_t *file_key, uint8_t user_key[32]);
static void encrypt_user_key(pdfio_encryption_t encryption, const uint8_t *file_key, uint8_t user_key[32]);
static void make_file_key(pdfio_encryption_t encryption, pdfio_permission_t permissions, const unsigned char *file_id, size_t file_idlen, const uint8_t *user_pad, const uint8_t *owner_key, bool encrypt_metadata, uint8_t file_key[16]);
static void make_owner_key(pdfio_encryption_t encryption, const uint8_t *owner_pad, const uint8_t *user_pad, uint8_t owner_key[32]);
static void decrypt_ou_key(pdfio_encryption_t encryption, const uint8_t *file_key, size_t file_keylen, uint8_t ou_key[32]);
static void encrypt_ou_key(pdfio_encryption_t encryption, const uint8_t *file_key, size_t file_keylen, uint8_t ou_key[32]);
static void make_file_key(pdfio_encryption_t encryption, size_t file_keylen, pdfio_permission_t permissions, const unsigned char *file_id, size_t file_idlen, const uint8_t *user_pad, const uint8_t *owner_key, bool encrypt_metadata, uint8_t file_key[16]);
static void make_owner_key(pdfio_encryption_t encryption, size_t file_keylen, const uint8_t *owner_pad, const uint8_t *user_pad, uint8_t owner_key[32]);
static void make_password_key(pdfio_encryption_t encryption, size_t file_keylen, const uint8_t *pad, uint8_t *key);
static void make_user_key(const unsigned char *file_id, size_t file_idlen, uint8_t user_key[32]);
static void pad_password(const char *password, uint8_t pad[32]);
static bool test_password(pdfio_encryption_t encryption, size_t file_keylen, pdfio_permission_t permisions,const unsigned char *file_id, size_t file_idlen, const uint8_t *user_pad, const uint8_t *user_key, const uint8_t *owner_key, bool encrypt_metadata, uint8_t file_key[16]);
//
@@ -139,6 +141,7 @@ _pdfioCryptoLock(
case PDFIO_ENCRYPTION_AES_128 :
// Create the 128-bit encryption keys...
pad_password(user_password, user_pad);
pdf->file_keylen = 16;
if (!owner_password && user_password && *user_password)
{
@@ -152,18 +155,17 @@ _pdfioCryptoLock(
}
// Compute the owner key...
make_owner_key(encryption, owner_pad, user_pad, pdf->owner_key);
make_owner_key(encryption, pdf->file_keylen, owner_pad, user_pad, pdf->owner_key);
pdf->owner_keylen = 32;
// Generate the encryption key
file_id = pdfioArrayGetBinary(pdf->id_array, 0, &file_idlen);
make_file_key(encryption, permissions, file_id, file_idlen, user_pad, pdf->owner_key, pdf->encrypt_metadata, pdf->file_key);
pdf->file_keylen = 16;
make_file_key(encryption, pdf->file_keylen, permissions, file_id, file_idlen, user_pad, pdf->owner_key, pdf->encrypt_metadata, pdf->file_key);
// Generate the user key...
make_user_key(file_id, file_idlen, pdf->user_key);
encrypt_user_key(encryption, pdf->file_key, pdf->user_key);
encrypt_ou_key(encryption, pdf->file_key, pdf->file_keylen, pdf->user_key);
pdf->user_keylen = 32;
// Save everything in the dictionary...
@@ -214,8 +216,9 @@ _pdfioCryptoLock(
pdfioObjClose(pdf->encrypt_obj);
pdf->encryption = encryption;
pdf->permissions = permissions;
pdf->encrypt_dict = dict;
pdf->encryption = encryption;
pdf->permissions = permissions;
return (true);
}
@@ -570,7 +573,6 @@ _pdfioCryptoUnlock(
{
int tries; // Number of tries
const char *password = NULL; // Password to try
pdfio_dict_t *encrypt_dict; // Encrypt objection dictionary
int version, // Version value
revision, // Revision value
length; // Key length value
@@ -590,20 +592,14 @@ _pdfioCryptoUnlock(
_pdfio_value_t *value; // Encrypt dictionary value, if any
// See if we support the type of encryption specified by the Encrypt object
// See if we support the type of encryption specified by the Encrypt
// dictionary...
if ((encrypt_dict = pdfioObjGetDict(pdf->encrypt_obj)) == NULL)
{
_pdfioFileError(pdf, "Unable to get encryption dictionary.");
return (false);
}
handler = pdfioDictGetName(pdf->encrypt_dict, "Filter");
version = (int)pdfioDictGetNumber(pdf->encrypt_dict, "V");
revision = (int)pdfioDictGetNumber(pdf->encrypt_dict, "R");
length = (int)pdfioDictGetNumber(pdf->encrypt_dict, "Length");
handler = pdfioDictGetName(encrypt_dict, "Filter");
version = (int)pdfioDictGetNumber(encrypt_dict, "V");
revision = (int)pdfioDictGetNumber(encrypt_dict, "R");
length = (int)pdfioDictGetNumber(encrypt_dict, "Length");
if ((value = _pdfioDictGetValue(encrypt_dict, "EncryptMetadata")) != NULL && value->type == PDFIO_VALTYPE_BOOLEAN)
if ((value = _pdfioDictGetValue(pdf->encrypt_dict, "EncryptMetadata")) != NULL && value->type == PDFIO_VALTYPE_BOOLEAN)
pdf->encrypt_metadata = value->value.boolean;
else
pdf->encrypt_metadata = true;
@@ -622,9 +618,9 @@ _pdfioCryptoUnlock(
pdfio_dict_t *filter; // Crypt Filter
const char *cfm; // Crypt filter method
stream_filter = pdfioDictGetName(encrypt_dict, "StmF");
string_filter = pdfioDictGetName(encrypt_dict, "StrF");
cf_dict = pdfioDictGetDict(encrypt_dict, "CF");
stream_filter = pdfioDictGetName(pdf->encrypt_dict, "StmF");
string_filter = pdfioDictGetName(pdf->encrypt_dict, "StrF");
cf_dict = pdfioDictGetDict(pdf->encrypt_dict, "CF");
if (!cf_dict)
{
@@ -697,11 +693,16 @@ _pdfioCryptoUnlock(
_pdfioFileError(pdf, "Unsupported encryption V%d R%d.", version, revision);
return (false);
}
else if (length < 40 || length > 128)
{
_pdfioFileError(pdf, "Unsupported key length %d.", length);
return (false);
}
// Grab the remaining values we need to unlock the PDF...
pdf->file_keylen = (size_t)(length / 8);
p = pdfioDictGetNumber(encrypt_dict, "P");
p = pdfioDictGetNumber(pdf->encrypt_dict, "P");
PDFIO_DEBUG("_pdfioCryptoUnlock: P=%.0f\n", p);
if (p < 0x7fffffff) // Handle integers > 2^31-1
pdf->permissions = (pdfio_permission_t)p;
@@ -709,8 +710,8 @@ _pdfioCryptoUnlock(
pdf->permissions = (pdfio_permission_t)(p - 4294967296.0);
PDFIO_DEBUG("_pdfioCryptoUnlock: permissions=%d\n", pdf->permissions);
owner_key = pdfioDictGetBinary(encrypt_dict, "O", &owner_keylen);
user_key = pdfioDictGetBinary(encrypt_dict, "U", &user_keylen);
owner_key = pdfioDictGetBinary(pdf->encrypt_dict, "O", &owner_keylen);
user_key = pdfioDictGetBinary(pdf->encrypt_dict, "U", &user_keylen);
if (!owner_key)
{
@@ -739,7 +740,7 @@ _pdfioCryptoUnlock(
return (false);
}
PDFIO_DEBUG("_pdfioCryptoUnlock: user_key[%d]=%02X%02X%02X%02X...%02X%02X%02X%02X\n", (int)user_keylen, user_key[0], user_key[1], user_key[2], user_key[3], user_key[28], user_key[29], user_key[30], user_key[31]);
PDFIO_DEBUG("_pdfioCryptoUnlock: user_key[%d]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", (int)user_keylen, user_key[0], user_key[1], user_key[2], user_key[3], user_key[28], user_key[29], user_key[30], user_key[31]);
memcpy(pdf->user_key, user_key, user_keylen);
pdf->user_keylen = user_keylen;
@@ -750,6 +751,9 @@ _pdfioCryptoUnlock(
return (false);
}
PDFIO_DEBUG("_pdfioCryptoUnlock: P=%d\n", pdf->permissions);
PDFIO_DEBUG("_pdfioCryptoUnlock: file_id(%d)=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", (int)file_idlen, file_id[0], file_id[1], file_id[2], file_id[3], file_id[12], file_id[13], file_id[14], file_id[15]);
// Generate a base hash from known values...
_pdfioCryptoMD5Init(&md5);
_pdfioCryptoMD5Append(&md5, pdf_passpad, 32);
@@ -761,56 +765,34 @@ _pdfioCryptoUnlock(
{
if (pdf->encryption <= PDFIO_ENCRYPTION_AES_128)
{
// RC4 and AES-128 encryption...
uint8_t pad[32], // Padded password
file_key[16], // File key
user_pad[32], // Padded user password
own_user_key[32], // Calculated user key
pdf_user_key[32]; // Decrypted user key
padkey[16], // Password key
file_key[16]; // File key
// Pad the supplied password, if any...
pad_password(password, pad);
// Generate keys to see if things match...
PDFIO_DEBUG("_pdfioCryptoUnlock: Trying %02X%02X%02X%02X...%02X%02X%02X%02X\n", pad[0], pad[1], pad[2], pad[3], pad[28], pad[29], pad[30], pad[31]);
PDFIO_DEBUG("_pdfioCryptoUnlock: P=%d\n", pdf->permissions);
PDFIO_DEBUG("_pdfioCryptoUnlock: Fid(%d)=%02X%02X%02X%02X...%02X%02X%02X%02X\n", (int)file_idlen, file_id[0], file_id[1], file_id[2], file_id[3], file_id[12], file_id[13], file_id[14], file_id[15]);
// Test the user password...
PDFIO_DEBUG("_pdfioCryptoUnlock: Trying <%02X%02X%02X%02X...%02X%02X%02X%02X>\n", pad[0], pad[1], pad[2], pad[3], pad[28], pad[29], pad[30], pad[31]);
make_owner_key(pdf->encryption, pad, pdf->owner_key, user_pad);
PDFIO_DEBUG("_pdfioCryptoUnlock: Upad=%02X%02X%02X%02X...%02X%02X%02X%02X\n", user_pad[0], user_pad[1], user_pad[2], user_pad[3], user_pad[28], user_pad[29], user_pad[30], user_pad[31]);
make_file_key(pdf->encryption, pdf->permissions, file_id, file_idlen, user_pad, pdf->owner_key, pdf->encrypt_metadata, file_key);
PDFIO_DEBUG("_pdfioCryptoUnlock: Fown=%02X%02X%02X%02X...%02X%02X%02X%02X\n", file_key[0], file_key[1], file_key[2], file_key[3], file_key[12], file_key[13], file_key[14], file_key[15]);
make_user_key(file_id, file_idlen, own_user_key);
PDFIO_DEBUG("_pdfioCryptoUnlock: U=%02X%02X%02X%02X...%02X%02X%02X%02X\n", pdf->user_key[0], pdf->user_key[1], pdf->user_key[2], pdf->user_key[3], pdf->user_key[28], pdf->user_key[29], pdf->user_key[30], pdf->user_key[31]);
PDFIO_DEBUG("_pdfioCryptoUnlock: Uown=%02X%02X%02X%02X...%02X%02X%02X%02X\n", own_user_key[0], own_user_key[1], own_user_key[2], own_user_key[3], own_user_key[28], own_user_key[29], own_user_key[30], own_user_key[31]);
if (!memcmp(own_user_key, pdf->user_key, sizeof(own_user_key)))
if (test_password(pdf->encryption, pdf->file_keylen, pdf->permissions, file_id, file_idlen, pad, pdf->user_key, pdf->owner_key, pdf->encrypt_metadata, file_key))
{
// Matches!
memcpy(pdf->file_key, file_key, sizeof(pdf->file_key));
memcpy(pdf->file_key, file_key, pdf->file_keylen);
memcpy(pdf->password, pad, sizeof(pdf->password));
return (true);
}
// Not the owner password, try the user password...
make_file_key(pdf->encryption, pdf->permissions, file_id, file_idlen, pad, pdf->owner_key, pdf->encrypt_metadata, file_key);
PDFIO_DEBUG("_pdfioCryptoUnlock: Fuse=%02X%02X%02X%02X...%02X%02X%02X%02X\n", file_key[0], file_key[1], file_key[2], file_key[3], file_key[12], file_key[13], file_key[14], file_key[15]);
// Test the owner password...
make_password_key(pdf->encryption, pdf->file_keylen, pad, padkey);
make_user_key(file_id, file_idlen, own_user_key);
memcpy(pad, pdf->owner_key, sizeof(pad));
decrypt_ou_key(pdf->encryption, padkey, pdf->file_keylen, pad);
memcpy(pdf_user_key, pdf->user_key, sizeof(pdf_user_key));
decrypt_user_key(pdf->encryption, file_key, pdf_user_key);
PDFIO_DEBUG("_pdfioCryptoUnlock: Uuse=%02X%02X%02X%02X...%02X%02X%02X%02X\n", user_key[0], user_key[1], user_key[2], user_key[3], user_key[28], user_key[29], user_key[30], user_key[31]);
PDFIO_DEBUG("_pdfioCryptoUnlock: Updf=%02X%02X%02X%02X...%02X%02X%02X%02X\n", pdf_user_key[0], pdf_user_key[1], pdf_user_key[2], pdf_user_key[3], pdf_user_key[28], pdf_user_key[29], pdf_user_key[30], pdf_user_key[31]);
if (!memcmp(pad, pdf_user_key, 32) || !memcmp(own_user_key, pdf_user_key, 16))
if (test_password(pdf->encryption, pdf->file_keylen, pdf->permissions, file_id, file_idlen, pad, pdf->user_key, pdf->owner_key, pdf->encrypt_metadata, file_key))
{
// Matches!
memcpy(pdf->file_key, file_key, sizeof(pdf->file_key));
memcpy(pdf->file_key, file_key, pdf->file_keylen);
memcpy(pdf->password, pad, sizeof(pdf->password));
return (true);
@@ -838,14 +820,15 @@ _pdfioCryptoUnlock(
//
// 'decrypt_user_key()' - Decrypt the user key.
// 'decrypt_ou_key()' - Decrypt the user key.
//
static void
decrypt_user_key(
decrypt_ou_key(
pdfio_encryption_t encryption, // I - Type of encryption
const uint8_t *file_key, // I - File encryption key
uint8_t user_key[32]) // IO - User key
size_t file_keylen, // I - Length of file encryption key
uint8_t ou_key[32]) // IO - Owner/User key
{
size_t i, j; // Looping vars
_pdfio_rc4_t rc4; // RC4 encryption context
@@ -855,38 +838,38 @@ decrypt_user_key(
{
// Encrypt the result once...
_pdfioCryptoRC4Init(&rc4, file_key, 5);
_pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32);
_pdfioCryptoRC4Crypt(&rc4, ou_key, ou_key, 32);
}
else
{
// Encrypt the result 20 times...
uint8_t key[16]; // Current encryption key
for (i = 19; i > 0; i --)
for (i = 20; i > 0;)
{
// XOR each byte in the key with the loop counter...
for (j = 0; j < 16; j ++)
i --;
for (j = 0; j < file_keylen; j ++)
key[j] = (uint8_t)(file_key[j] ^ i);
_pdfioCryptoRC4Init(&rc4, key, 16);
_pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32);
_pdfioCryptoRC4Init(&rc4, key, file_keylen);
_pdfioCryptoRC4Crypt(&rc4, ou_key, ou_key, 32);
}
_pdfioCryptoRC4Init(&rc4, file_key, 16);
_pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32);
}
}
//
// 'encrypt_user_key()' - Encrypt the user key.
// 'encrypt_ou_key()' - Encrypt the owner/user key.
//
static void
encrypt_user_key(
encrypt_ou_key(
pdfio_encryption_t encryption, // I - Type of encryption
const uint8_t *file_key, // I - File encryption key
uint8_t user_key[32]) // IO - User key
size_t file_keylen, // I - Length of file encryption key
uint8_t ou_key[32]) // IO - Owner/User key
{
size_t i, j; // Looping vars
_pdfio_rc4_t rc4; // RC4 encryption context
@@ -896,7 +879,7 @@ encrypt_user_key(
{
// Encrypt the result once...
_pdfioCryptoRC4Init(&rc4, file_key, 5);
_pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32);
_pdfioCryptoRC4Crypt(&rc4, ou_key, ou_key, 32);
}
else
{
@@ -906,11 +889,11 @@ encrypt_user_key(
for (i = 0; i < 20; i ++)
{
// XOR each byte in the key with the loop counter...
for (j = 0; j < 16; j ++)
for (j = 0; j < file_keylen; j ++)
key[j] = (uint8_t)(file_key[j] ^ i);
_pdfioCryptoRC4Init(&rc4, key, 16);
_pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32);
_pdfioCryptoRC4Init(&rc4, key, file_keylen);
_pdfioCryptoRC4Crypt(&rc4, ou_key, ou_key, 32);
}
}
}
@@ -923,6 +906,7 @@ encrypt_user_key(
static void
make_file_key(
pdfio_encryption_t encryption, // I - Type of encryption
size_t file_keylen, // I - Encryption key length
pdfio_permission_t permissions, // I - File permissions
const unsigned char *file_id, // I - File ID value
size_t file_idlen, // I - Length of file ID
@@ -968,14 +952,14 @@ make_file_key(
for (i = 0; i < 50; i ++)
{
_pdfioCryptoMD5Init(&md5);
_pdfioCryptoMD5Append(&md5, digest, 16);
_pdfioCryptoMD5Append(&md5, digest, file_keylen);
_pdfioCryptoMD5Finish(&md5, digest);
}
}
PDFIO_DEBUG("make_file_key: file_key[16]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", digest[0], digest[1], digest[2], digest[3], digest[12], digest[13], digest[14], digest[15]);
memcpy(file_key, digest, 16);
memcpy(file_key, digest, file_keylen);
}
@@ -986,57 +970,57 @@ make_file_key(
static void
make_owner_key(
pdfio_encryption_t encryption, // I - Type of encryption
size_t file_keylen, // I - Length of encryption key
const uint8_t *owner_pad, // I - Padded owner password
const uint8_t *user_pad, // I - Padded user password
uint8_t owner_key[32]) // O - Owner key value
{
size_t i, j; // Looping vars
_pdfio_md5_t md5; // MD5 context
uint8_t digest[16]; // 128-bit MD5 digest
_pdfio_rc4_t rc4; // RC4 encryption context
uint8_t key[16]; // Base encryption key
// Hash the owner password...
make_password_key(encryption, file_keylen, owner_pad, key);
// Copy and encrypt the padded user password...
memcpy(owner_key, user_pad, 32);
decrypt_ou_key(encryption, key, file_keylen, owner_key);
}
//
// 'make_password_key()' - Make a password key.
//
static void
make_password_key(
pdfio_encryption_t encryption, // I - Type of encryption
size_t file_keylen, // I - Length of encryption key
const uint8_t *pad, // I - Padded password
uint8_t *key) // O - Key data
{
size_t i; // Looping var
_pdfio_md5_t md5; // MD5 context
uint8_t digest[16]; // 128-bit MD5 digest
// Hash the padded password...
_pdfioCryptoMD5Init(&md5);
_pdfioCryptoMD5Append(&md5, owner_pad, 32);
_pdfioCryptoMD5Append(&md5, pad, 32);
_pdfioCryptoMD5Finish(&md5, digest);
if (encryption != PDFIO_ENCRYPTION_RC4_40)
{
// Hash the hash another 50 times...
for (i = 0; i < 50; i ++)
{
_pdfioCryptoMD5Init(&md5);
_pdfioCryptoMD5Append(&md5, digest, 16);
_pdfioCryptoMD5Append(&md5, digest, file_keylen);
_pdfioCryptoMD5Finish(&md5, digest);
}
}
// Copy and encrypt the padded user password...
memcpy(owner_key, user_pad, 32);
if (encryption == PDFIO_ENCRYPTION_RC4_40)
{
// Encrypt once...
_pdfioCryptoRC4Init(&rc4, digest, 5);
_pdfioCryptoRC4Crypt(&rc4, owner_key, owner_key, 32);
}
else
{
// Encrypt 20 times...
uint8_t encrypt_key[16]; // RC4 encryption key
for (i = 20; i > 0;)
{
// XOR each byte in the digest with the loop counter to make a key...
i --;
for (j = 0; j < sizeof(encrypt_key); j ++)
encrypt_key[j] = (uint8_t)(digest[j] ^ i);
_pdfioCryptoRC4Init(&rc4, encrypt_key, sizeof(encrypt_key));
_pdfioCryptoRC4Crypt(&rc4, owner_key, owner_key, 32);
}
}
// Copy the key portion of the hashed password to the key...
memcpy(key, digest, file_keylen);
}
@@ -1091,3 +1075,52 @@ pad_password(const char *password, // I - Password string or `NULL`
if (len < 32)
memcpy(pad + len, pdf_passpad, 32 - len);
}
//
// 'test_password()' - Test a user password...
//
static bool // O - `true` if password is correct, `false` otherwise
test_password(
pdfio_encryption_t encryption, // I - Type of encryption
size_t file_keylen, // I - Length of encryption key
pdfio_permission_t permissions, // I - File permissions
const unsigned char *file_id, // I - File ID value
size_t file_idlen, // I - Length of file ID
const uint8_t *user_pad, // I - Padded user password
const uint8_t *user_key, // I - User key
const uint8_t *owner_key, // I - Owner key
bool encrypt_metadata,
// I - Encrypt metadata?
uint8_t file_key[16]) // O - Encryption key
{
uint8_t pdf_user_key[32]; // Decrypted user key
// Make the file key...
make_file_key(encryption, file_keylen, permissions, file_id, file_idlen, user_pad, owner_key, encrypt_metadata, file_key);
// Decrypt the user key using the file key...
memcpy(pdf_user_key, user_key, sizeof(pdf_user_key));
decrypt_ou_key(encryption, file_key, file_keylen, pdf_user_key);
PDFIO_DEBUG("test_password: pdf_user_key[32]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", pdf_user_key[0], pdf_user_key[1], pdf_user_key[2], pdf_user_key[3], pdf_user_key[28], pdf_user_key[29], pdf_user_key[30], pdf_user_key[31]);
if (encryption == PDFIO_ENCRYPTION_RC4_40 && !memcmp(pdf_user_key, pdf_passpad, 32))
{
// 40-bit encryption just compares the eecrypted user key matches...
return (true);
}
else
{
// 128-bit encryption needs to match the calculated user key...
uint8_t own_user_key[32]; // Calculated user key
// Calculate what the user key should be...
make_user_key(file_id, file_idlen, own_user_key);
PDFIO_DEBUG("test_password: own_user_key[32]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", own_user_key[0], own_user_key[1], own_user_key[2], own_user_key[3], own_user_key[28], own_user_key[29], own_user_key[30], own_user_key[31]);
// Return whether they match...
return (!memcmp(pdf_user_key, own_user_key, 16));
}
}

View File

@@ -1,7 +1,7 @@
//
// PDF dictionary functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -79,6 +79,10 @@ pdfioDictCopy(pdfio_file_t *pdf, // I - PDF file
PDFIO_DEBUG("pdfioDictCopy(pdf=%p, dict=%p(%p))\n", (void *)pdf, (void *)dict, dict ? (void *)dict->pdf : NULL);
// Range check input...
if (!pdf || !dict)
return (NULL);
// Create the new dictionary...
if ((ndict = pdfioDictCreate(pdf)) == NULL)
return (NULL);
@@ -92,6 +96,8 @@ pdfioDictCopy(pdfio_file_t *pdf, // I - PDF file
// Copy and add each of the source dictionary's key/value pairs...
for (i = dict->num_pairs, p = dict->pairs; i > 0; i --, p ++)
{
PDFIO_DEBUG("pdfioDictCopy: key=\"%s\", value.type=%d\n", p->key, p->value.type);
if (!strcmp(p->key, "Length") && p->value.type == PDFIO_VALTYPE_INDIRECT && dict->pdf != pdf)
{
// Don't use indirect stream lengths for copied objects...
@@ -102,15 +108,28 @@ pdfioDictCopy(pdfio_file_t *pdf, // I - PDF file
if (lenobj)
{
if (lenobj->value.type == PDFIO_VALTYPE_NONE)
_pdfioObjLoad(lenobj);
{
if (!_pdfioObjLoad(lenobj))
{
PDFIO_DEBUG("pdfioDictCopy: Unable to copy value of \"%s\", returning NULL.\n", p->key);
return (NULL);
}
}
v.value.number = lenobj->value.value.number;
}
else
{
v.value.number = 0.0;
}
PDFIO_DEBUG("pdfioDictCopy: Length is %.0f.\n", v.value.number);
}
else if (!_pdfioValueCopy(pdf, &v, dict->pdf, &p->value))
{
PDFIO_DEBUG("pdfioDictCopy: Unable to copy value of \"%s\", returning NULL.\n", p->key);
return (NULL); // Let pdfioFileClose do the cleanup...
}
if (_pdfioStringIsAllocated(dict->pdf, p->key))
key = pdfioStringCreate(pdf, p->key);
@@ -650,8 +669,7 @@ _pdfioDictRead(pdfio_file_t *pdf, // I - PDF file
}
else if (_pdfioDictGetValue(dict, key + 1))
{
// Issue 118: Discard duplicate key/value pairs, in the future this will
// be a warning message...
// Issue 118: Discard duplicate key/value pairs...
_pdfioValueDelete(&value);
if (_pdfioFileError(pdf, "WARNING: Discarding value for duplicate dictionary key '%s'.", key + 1))
continue;
@@ -1055,11 +1073,13 @@ _pdfioDictSetValue(
//
bool // O - `true` on success, `false` on failure
_pdfioDictWrite(pdfio_dict_t *dict, // I - Dictionary
pdfio_obj_t *obj, // I - Object, if any
off_t *length) // I - Offset to length value
_pdfioDictWrite(
_pdfio_printf_t cb, // I - Printf callback function
void *cbdata, // I - Printf callback data
pdfio_obj_t *obj, // I - Object, if any
pdfio_dict_t *dict, // I - Dictionary
off_t *length) // I - Offset to length value
{
pdfio_file_t *pdf = dict->pdf; // PDF file
size_t i; // Looping var
_pdfio_pair_t *pair; // Current key/value pair
@@ -1068,28 +1088,30 @@ _pdfioDictWrite(pdfio_dict_t *dict, // I - Dictionary
*length = 0;
// Dictionaries are bounded by "<<" and ">>"...
if (!_pdfioFilePuts(pdf, "<<"))
if (!(cb)(cbdata, "<<"))
return (false);
// Write all of the key/value pairs...
for (i = dict->num_pairs, pair = dict->pairs; i > 0; i --, pair ++)
{
if (!_pdfioFilePrintf(pdf, "%N", pair->key))
if (!(cb)(cbdata, "%N", pair->key))
return (false);
if (length && !strcmp(pair->key, "Length") && pair->value.type == PDFIO_VALTYPE_NUMBER && pair->value.value.number <= 0.0)
{
// Writing an object dictionary with an undefined length
*length = _pdfioFileTell(pdf) + 1;
if (!_pdfioFilePuts(pdf, " 9999999999"))
*length = _pdfioFileTell(dict->pdf) + 1;
if (!(cb)(cbdata, " 9999999999"))
return (false);
}
else if (!_pdfioValueWrite(pdf, obj, &pair->value, NULL))
else if (!_pdfioValueWrite(cb, cbdata, obj, &pair->value, NULL))
{
return (false);
}
}
// Close it up...
return (_pdfioFilePuts(pdf, ">>"));
return ((cb)(cbdata, ">>"));
}

View File

@@ -1,7 +1,7 @@
//
// PDF file functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -29,6 +29,7 @@ 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 repair_xref(pdfio_file_t *pdf, pdfio_password_cb_t password_cb, void *password_data);
static bool write_metadata(pdfio_file_t *pdf);
static bool write_objstm(pdfio_file_t *pdf);
static bool write_pages(pdfio_file_t *pdf);
static bool write_trailer(pdfio_file_t *pdf);
@@ -131,7 +132,7 @@ pdfioFileClose(pdfio_file_t *pdf) // I - PDF file
pdfioFileAddOutputIntent(pdf, /*subtype*/"GTS_PDFA1", /*condition*/"CMYK", /*cond_id*/"CGATS001", /*reg_name*/NULL, /*info*/"CMYK Printing", /*profile*/NULL);
// Close and write out the last bits...
if (write_metadata(pdf) && pdfioObjClose(pdf->info_obj) && write_pages(pdf) && pdfioObjClose(pdf->root_obj) && write_trailer(pdf))
if (write_metadata(pdf) && pdfioObjClose(pdf->info_obj) && write_objstm(pdf) && write_pages(pdf) && pdfioObjClose(pdf->root_obj) && write_trailer(pdf))
ret = _pdfioFileFlush(pdf);
}
@@ -793,7 +794,7 @@ pdfioFileFindObj(
if (number == pdf->objs[current]->number)
{
// Fast match...
PDFIO_DEBUG("pdfioFileFindObj: Returning %lu (%p)\n", (unsigned long)current, (void *)pdf->objs[current]);
PDFIO_DEBUG("pdfioFileFindObj(%lu): Returning %lu (%p)\n", (unsigned long)number, (unsigned long)current, (void *)pdf->objs[current]);
return (pdf->objs[current]);
}
else if (number < pdf->objs[current]->number)
@@ -821,17 +822,17 @@ pdfioFileFindObj(
if (number == pdf->objs[left]->number)
{
PDFIO_DEBUG("pdfioFileFindObj: Returning %lu (%p)\n", (unsigned long)left, (void *)pdf->objs[left]);
PDFIO_DEBUG("pdfioFileFindObj(%lu): Returning %lu (%p)\n", (unsigned long)number, (unsigned long)left, (void *)pdf->objs[left]);
return (pdf->objs[left]);
}
else if (number == pdf->objs[right]->number)
{
PDFIO_DEBUG("pdfioFileFindObj: Returning %lu (%p)\n", (unsigned long)right, (void *)pdf->objs[right]);
PDFIO_DEBUG("pdfioFileFindObj(%lu): Returning %lu (%p)\n", (unsigned long)number, (unsigned long)right, (void *)pdf->objs[right]);
return (pdf->objs[right]);
}
else
{
PDFIO_DEBUG("pdfioFileFindObj: Returning NULL\n");
PDFIO_DEBUG("pdfioFileFindObj(%lu): Returning NULL\n", (unsigned long)number);
return (NULL);
}
}
@@ -851,7 +852,7 @@ pdfioFileGetAuthor(pdfio_file_t *pdf) // I - PDF file
//
// 'pdfioFileGetCatalog()' - Get the document catalog dictionary.
//
// @since PDFio 1.3@
// @since PDFio v1.3@
//
pdfio_dict_t * // O - Catalog dictionary
@@ -914,7 +915,7 @@ pdfioFileGetKeywords(pdfio_file_t *pdf) // I - PDF file
// "lang-REGION". For example, the string "en-CA" specifies Canadian English
// and the string "fr-CA" specifies Canadian French.
//
// @since PDFio 1.6@
// @since PDFio v1.6@
//
const char * // O - Language or `NULL` for none
@@ -1165,7 +1166,7 @@ pdfioFileOpen(
pdf->permissions = PDFIO_PERMISSION_ALL;
// Open the file...
if ((pdf->fd = open(filename, O_RDONLY | O_BINARY)) < 0)
if ((pdf->fd = open(filename, O_RDONLY | O_BINARY, 0)) < 0)
{
_pdfioFileError(pdf, "Unable to open file - %s", strerror(errno));
free(pdf->filename);
@@ -1317,7 +1318,7 @@ pdfioFileSetKeywords(
// "lang-REGION". For example, the string "en-CA" specifies Canadian English
// and the string "fr-CA" specifies Canadian French.
//
// @since PDFio 1.6@
// @since PDFio v1.6@
//
void
@@ -1413,8 +1414,88 @@ pdfioFileSetTitle(pdfio_file_t *pdf, // I - PDF file
}
#if _WIN32
//
// '_pdfioObjAdd()' - Add an object to a file.
// '_pdfio_win32_open()' - Open or create a file.
//
// This function handles mapping of UTF-8 filenames to UTF-16 on Windows.
//
int // O - File descriptor or -1 on error
_pdfio_win32_open(const char *filename, // I - UTF-8 filename
int oflag, // I - Open flags
int mode) // I - File permissions
{
wchar_t utf16name[1024], // UTF-16 filename
*utf16ptr; // Pointer into UTF-16 filename
int unich; // Unicode character
// Convert the UTF-8 string to UTF-16...
utf16ptr = utf16name;
while (*filename && utf16ptr < (utf16name + sizeof(utf16name) / sizeof(utf16name[0]) - 2))
{
if ((unich = *filename++) & 0x80)
{
if ((unich & 0xe0) == 0xc0 && (*filename & 0xc0) == 0x80)
{
// 2-byte UTF-8
unich = ((unich & 0x1f) << 6) | (*filename & 0x3f);
filename ++;
}
else if ((unich & 0xf0) == 0xe0 && (*filename & 0xc0) == 0x80 && (filename[1] & 0xc0) == 0x80)
{
// 3-byte UTF-8
unich = ((unich & 0x0f) << 12) | ((*filename & 0x3f) << 6) | (filename[1] & 0x3f);
filename += 2;
}
else if ((unich & 0xf8) == 0xf0 && (*filename & 0xc0) == 0x80 && (filename[1] & 0xc0) == 0x80 && (filename[2] & 0xc0) == 0x80)
{
// 4-byte UTF-8
unich = ((unich & 0x07) << 18) | ((*filename & 0x3f) << 12) | ((filename[1] & 0x3f) << 6) | (filename[2] & 0x3f);
filename += 3;
}
else
{
// Invalid UTF-8 char...
errno = EINVAL;
return (-1);
}
}
// Copy the unicode character...
if (unich > 0xffff)
{
// Two-word sequence...
*utf16ptr++ = 0xd800 | ((unich >> 10) & 0x03ff);
*utf16ptr++ = 0xdc00 | (unich & 0x03ff);
}
else
{
// One-word...
*utf16ptr++ = unich;
}
}
*utf16ptr = '\0';
if (*filename)
{
// Filename too long...
errno = EINVAL;
return (-1);
}
else
{
// Pass on to _wopen...
return (_wopen(utf16name, oflag, mode));
}
}
#endif // WIN32
//
// 'add_obj()' - Add an object to a file.
//
static pdfio_obj_t * // O - Object
@@ -1714,6 +1795,15 @@ create_common(
if ((pdf->root_obj = pdfioFileCreateObj(pdf, dict)) == NULL)
goto error;
// For PDF 1.5 and later, create a (compressed) object stream for value objects...
if (strcmp(file_version, "1.5") >= 0 && !pdf->output_cb)
{
if ((pdf->objstm_dict = pdfioDictCreate(pdf)) == NULL)
goto error;
pdfioDictSetName(pdf->objstm_dict, "Type", "ObjStm");
}
// Create random file ID values...
_pdfioCryptoMakeRandom(id_value, sizeof(id_value));
@@ -2006,6 +2096,9 @@ load_xref(
_pdfio_token_t tb; // Token buffer/stack
off_t line_offset; // Offset to start of line
pdfio_obj_t *pages_obj; // Pages object
size_t num_xrefs = 1; // Number of xref offsets
off_t xrefs[100] = { xref_offset };
// xref offsets
while (!done)
@@ -2223,20 +2316,26 @@ load_xref(
// Create a placeholder for the object in memory...
if ((current = pdfioFileFindObj(pdf, (size_t)number)) != NULL)
{
PDFIO_DEBUG("load_xref: existing object, prev offset=%u\n", (unsigned)current->offset);
PDFIO_DEBUG("load_xref: existing object, prev offset=%u, generation=%u, new generation=%u\n", (unsigned)current->offset, (unsigned)current->generation, (unsigned)generation);
if (w[0] == 0 || buffer[0] == 1)
if (generation > current->generation)
{
// Location of object...
current->offset = (off_t)offset;
}
else if (number != offset)
{
// Object is part of a stream, offset is the object number...
current->offset = 0;
}
// Newer version of an existing object - update the references...
current->generation = generation;
PDFIO_DEBUG("load_xref: new offset=%u\n", (unsigned)current->offset);
if (w[0] == 0 || buffer[0] == 1)
{
// Location of object...
current->offset = (off_t)offset;
}
else if (number != offset)
{
// Object is part of a stream, offset is the object number...
current->offset = 0;
}
PDFIO_DEBUG("load_xref: new offset=%u\n", (unsigned)current->offset);
}
}
if (w[0] > 0 && buffer[0] == 2)
@@ -2283,12 +2382,18 @@ load_xref(
{
// Save the trailer dictionary and grab the root (catalog) and info
// objects...
pdfio_obj_t *encrypt_obj; // Encryption object
pdf->trailer_dict = trailer.value.dict;
pdf->encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt");
pdf->id_array = pdfioDictGetArray(pdf->trailer_dict, "ID");
if ((encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt")) != NULL)
pdf->encrypt_dict = pdfioObjGetDict(encrypt_obj);
else
pdf->encrypt_dict = pdfioDictGetDict(pdf->trailer_dict, "Encrypt");
// If the trailer contains an Encrypt key, try unlocking the file...
if (pdf->encrypt_obj && !_pdfioCryptoUnlock(pdf, password_cb, password_data))
if (pdf->encrypt_dict && !_pdfioCryptoUnlock(pdf, password_cb, password_data))
return (false);
}
@@ -2434,12 +2539,18 @@ load_xref(
{
// Save the trailer dictionary and grab the root (catalog) and info
// objects...
pdfio_obj_t *encrypt_obj; // Encryption object
pdf->trailer_dict = trailer.value.dict;
pdf->encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt");
pdf->id_array = pdfioDictGetArray(pdf->trailer_dict, "ID");
if ((encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt")) != NULL)
pdf->encrypt_dict = pdfioObjGetDict(encrypt_obj);
else
pdf->encrypt_dict = pdfioDictGetDict(pdf->trailer_dict, "Encrypt");
// If the trailer contains an Encrypt key, try unlocking the file...
if (pdf->encrypt_obj && !_pdfioCryptoUnlock(pdf, password_cb, password_data))
if (pdf->encrypt_dict && !_pdfioCryptoUnlock(pdf, password_cb, password_data))
return (false);
}
}
@@ -2459,13 +2570,31 @@ load_xref(
{
done = true;
}
else if (new_offset == xref_offset)
else
{
_pdfioFileError(pdf, "Recursive xref table.");
return (false);
}
// See if we've seen this xref table before...
size_t i; // Looping var
xref_offset = new_offset;
for (i = 0; i < num_xrefs; i ++)
{
if (new_offset == xrefs[i])
{
// Yes, error out...
_pdfioFileError(pdf, "Recursive xref table.");
return (false);
}
}
// No, save it...
if (i >= (sizeof(xrefs) / sizeof(xrefs[0])))
{
// Too many xref tables...
_pdfioFileError(pdf, "Too many xref tables.");
return (false);
}
xrefs[num_xrefs ++] = xref_offset = new_offset;
}
}
// Once we have all of the xref tables loaded, get the important objects and
@@ -2529,7 +2658,7 @@ repair_xref(
pdf->root_obj = NULL;
pdf->info_obj = NULL;
pdf->pages_obj = NULL;
pdf->encrypt_obj = NULL;
pdf->encrypt_dict = NULL;
// Read from the beginning of the file, looking for objects...
if ((line_offset = _pdfioFileSeek(pdf, 0, SEEK_SET)) < 0)
@@ -2588,25 +2717,32 @@ repair_xref(
_pdfioTokenFlush(&tb);
if (type && !strcmp(line, "stream"))
if (!strcmp(line, "stream"))
{
// Possible object or XRef stream...
obj->stream_offset = _pdfioFileTell(pdf);
if (!strcmp(type, "ObjStm") && num_sobjs < (sizeof(sobjs) / sizeof(sobjs[0])))
if (type && !strcmp(type, "ObjStm") && num_sobjs < (sizeof(sobjs) / sizeof(sobjs[0])))
{
PDFIO_DEBUG("repair_xref: Object stream...\n");
sobjs[num_sobjs] = obj;
num_sobjs ++;
}
if (!strcmp(type, "XRef") && !pdf->trailer_dict)
if (type && !strcmp(type, "XRef") && !pdf->trailer_dict)
{
// Save the trailer dictionary...
pdfio_obj_t *encrypt_obj;
// Encryption object
PDFIO_DEBUG("repair_xref: XRef stream...\n");
pdf->trailer_dict = pdfioObjGetDict(obj);
pdf->encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt");
pdf->id_array = pdfioDictGetArray(pdf->trailer_dict, "ID");
if ((encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt")) != NULL)
pdf->encrypt_dict = pdfioObjGetDict(encrypt_obj);
else
pdf->encrypt_dict = pdfioDictGetDict(pdf->trailer_dict, "Encrypt");
}
}
else if (type && !strcmp(line, "endobj"))
@@ -2660,11 +2796,17 @@ repair_xref(
{
// Save the trailer dictionary and grab the root (catalog) and info
// objects...
pdfio_obj_t *encrypt_obj; // Encryption object
PDFIO_DEBUG("repair_xref: Using this trailer dictionary.\n");
pdf->trailer_dict = trailer.value.dict;
pdf->encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt");
pdf->id_array = pdfioDictGetArray(pdf->trailer_dict, "ID");
if ((encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt")) != NULL)
pdf->encrypt_dict = pdfioObjGetDict(encrypt_obj);
else
pdf->encrypt_dict = pdfioDictGetDict(pdf->trailer_dict, "Encrypt");
}
}
@@ -2678,7 +2820,7 @@ repair_xref(
pdf->trailer_dict = backup_trailer;
// If the trailer contains an Encrypt key, try unlocking the file...
if (pdf->encrypt_obj && !_pdfioCryptoUnlock(pdf, password_cb, password_data))
if (pdf->encrypt_dict && !_pdfioCryptoUnlock(pdf, password_cb, password_data))
return (false);
// Load any stream objects...
@@ -2828,6 +2970,86 @@ write_metadata(pdfio_file_t *pdf) // I - PDF file
}
//
// 'write_objstm()' - Write an object stream.
//
static bool // O - `true` on success, `false` on failure
write_objstm(pdfio_file_t *pdf) // I - PDF file
{
bool ret = false; // Return value
size_t i; // Looping var
pdfio_obj_t *obj; // Current object
pdfio_stream_t *st = NULL; // Output stream
_pdfio_strbuf_t *bptr = NULL; // String buffer
size_t length; // Length of stream objects
// If we aren't creating object streams, return now...
if (!pdf->objstm_dict || pdf->num_objstm == 0)
return (true);
// Create a string buffer to hold the list of objects in the stream...
if (!_pdfioStringAllocBuffer(pdf, &bptr))
return (false);
// Loop through the array of objects and
for (i = 0, length = 0; i < pdf->num_objs; i ++)
{
// Skip non-stream objects...
obj = pdf->objs[i];
if (!obj->objstm_data)
continue;
// Add this object to the initial list of objects and offsets for the stream...
_pdfioStringPrintf(bptr, "%lu %lu\n", (unsigned long)obj->number, (unsigned long)length);
length += obj->objstm_datalen;
}
// Now create the object stream with the collected data...
pdfioDictSetNumber(pdf->objstm_dict, "First", bptr->bufptr - bptr->buffer);
pdfioDictSetNumber(pdf->objstm_dict, "N", pdf->num_objstm);
pdfioDictSetName(pdf->objstm_dict, "Filter", "FlateDecode");
if ((pdf->objstm_obj = pdfioFileCreateObj(pdf, pdf->objstm_dict)) == NULL)
goto done;
if ((st = pdfioObjCreateStream(pdf->objstm_obj, PDFIO_FILTER_FLATE)) == NULL)
{
pdfioObjClose(pdf->objstm_obj);
goto done;
}
if (!pdfioStreamWrite(st, bptr->buffer, (size_t)(bptr->bufptr - bptr->buffer)))
goto done;
for (i = 0; i < pdf->num_objs; i ++)
{
// Skip non-stream objects...
obj = pdf->objs[i];
if (!obj->objstm_data)
continue;
// Write the stream data...
if (!pdfioStreamWrite(st, obj->objstm_data, obj->objstm_datalen))
goto done;
}
ret = true;
done:
pdfioStreamClose(st);
_pdfioStringFreeBuffer(pdf, bptr->buffer);
return (ret);
}
//
// 'write_pages()' - Write the PDF pages objects.
//
@@ -2877,32 +3099,38 @@ write_trailer(pdfio_file_t *pdf) // I - PDF file
pdfio_array_t *w_array; // W array
pdfio_obj_t *xref_obj; // Object
pdfio_stream_t *xref_st; // Stream
int offsize; // Size of object offsets
unsigned char buffer[10]; // Buffer entry
int offsize, // Size of object offsets
idxsize; // Size of object stream indexes
unsigned char buffer[20]; // 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)
// Figure out how many bytes are needed for the object offsets
if (xref_offset < 0xff && pdf->num_objs < 0xff)
offsize = 1;
else if (xref_offset < 0xffff)
else if (xref_offset < 0xffff && pdf->num_objs < 0xffff)
offsize = 2;
else if (xref_offset < 0xffffff)
else if (xref_offset < 0xffffff && pdf->num_objs < 0xffffff)
offsize = 3;
else if (xref_offset < 0xffffffff)
else if (xref_offset < 0xffffffff && pdf->num_objs < 0xffffffff)
offsize = 4;
else if (xref_offset < 0xffffffffff)
else if (xref_offset < 0xffffffffff && pdf->num_objs < 0xffffffffff)
offsize = 5;
else if (xref_offset < 0xffffffffffff)
else if (xref_offset < 0xffffffffffff && pdf->num_objs < 0xffffffffffff)
offsize = 6;
else if (xref_offset < 0xffffffffffffff)
else if (xref_offset < 0xffffffffffffff && pdf->num_objs < 0xffffffffffffff)
offsize = 7;
else
offsize = 8;
if (pdf->num_objstm <= 0x100)
idxsize = 1;
else // pdfioObjClose limits number of object stream values to 64k
idxsize = 2;
// Create the object...
if ((w_array = pdfioArrayCreate(pdf)) == NULL)
{
@@ -2913,7 +3141,7 @@ write_trailer(pdfio_file_t *pdf) // I - PDF file
pdfioArrayAppendNumber(w_array, 1);
pdfioArrayAppendNumber(w_array, offsize);
pdfioArrayAppendNumber(w_array, 1);
pdfioArrayAppendNumber(w_array, idxsize);
if ((xref_dict = pdfioDictCreate(pdf)) == NULL)
{
@@ -2954,74 +3182,166 @@ write_trailer(pdfio_file_t *pdf) // I - PDF file
pdfioStreamWrite(xref_st, buffer, (size_t)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)
if (obj->objstm_data)
{
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;
#ifdef _WIN32
default :
#endif // _WIN32
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;
// Stream object
size_t number = pdf->objstm_obj->number;
// Object stream object number
buffer[0] = 2;
switch (offsize)
{
case 1 :
buffer[1] = number & 255;
break;
case 2 :
buffer[1] = (number >> 8) & 255;
buffer[2] = number & 255;
break;
case 3 :
buffer[1] = (number >> 16) & 255;
buffer[2] = (number >> 8) & 255;
buffer[3] = number & 255;
break;
case 4 :
buffer[1] = (number >> 24) & 255;
buffer[2] = (number >> 16) & 255;
buffer[3] = (number >> 8) & 255;
buffer[4] = number & 255;
break;
case 5 :
#ifndef _WIN32 // Windows off_t is 32-bits?!?
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;
#endif // !_WIN32
buffer[1] = (number >> 32) & 255;
#endif // _WIN32
buffer[2] = (number >> 24) & 255;
buffer[3] = (number >> 16) & 255;
buffer[4] = (number >> 8) & 255;
buffer[5] = number & 255;
break;
case 6 :
#ifndef _WIN32 // Windows off_t is 32-bits?!?
buffer[1] = (number >> 40) & 255;
buffer[2] = (number >> 32) & 255;
#endif // _WIN32
buffer[3] = (number >> 24) & 255;
buffer[4] = (number >> 16) & 255;
buffer[5] = (number >> 8) & 255;
buffer[6] = number & 255;
break;
case 7 :
#ifndef _WIN32 // Windows off_t is 32-bits?!?
buffer[1] = (number >> 48) & 255;
buffer[2] = (number >> 40) & 255;
buffer[3] = (number >> 32) & 255;
#endif // _WIN32
buffer[4] = (number >> 24) & 255;
buffer[5] = (number >> 16) & 255;
buffer[6] = (number >> 8) & 255;
buffer[7] = number & 255;
break;
default :
#ifndef _WIN32 // Windows off_t is 32-bits?!?
buffer[1] = (number >> 56) & 255;
buffer[2] = (number >> 48) & 255;
buffer[3] = (number >> 40) & 255;
buffer[4] = (number >> 32) & 255;
#endif // _WIN32
buffer[5] = (number >> 24) & 255;
buffer[6] = (number >> 16) & 255;
buffer[7] = (number >> 8) & 255;
buffer[8] = number & 255;
break;
}
switch (idxsize)
{
case 1 :
buffer[1 + offsize] = obj->objstm_number & 255;
break;
case 2 :
default :
buffer[1 + offsize] = (obj->objstm_number >> 8) & 255;
buffer[2 + offsize] = obj->objstm_number & 255;
break;
}
}
else
{
// Regular object
buffer[0] = 1;
memset(buffer + 1 + offsize, 0, idxsize);
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 :
#ifndef _WIN32 // Windows off_t is 32-bits?!?
buffer[1] = (obj->offset >> 32) & 255;
#endif // _WIN32
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 :
#ifndef _WIN32 // Windows off_t is 32-bits?!?
buffer[1] = (obj->offset >> 40) & 255;
buffer[2] = (obj->offset >> 32) & 255;
#endif // _WIN32
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 :
#ifndef _WIN32 // Windows off_t is 32-bits?!?
buffer[1] = (obj->offset >> 48) & 255;
buffer[2] = (obj->offset >> 40) & 255;
buffer[3] = (obj->offset >> 32) & 255;
#endif // _WIN32
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 :
#ifndef _WIN32 // Windows off_t is 32-bits?!?
buffer[1] = (obj->offset >> 56) & 255;
buffer[2] = (obj->offset >> 48) & 255;
buffer[3] = (obj->offset >> 40) & 255;
buffer[4] = (obj->offset >> 32) & 255;
#endif // _WIN32
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, (size_t)offsize + 2))
if (!pdfioStreamWrite(xref_st, buffer, (size_t)(1 + offsize + idxsize)))
{
_pdfioFileError(pdf, "Unable to write cross-reference table.");
ret = false;
@@ -3078,7 +3398,7 @@ write_trailer(pdfio_file_t *pdf) // I - PDF file
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))
if (!_pdfioDictWrite((_pdfio_printf_t)_pdfioFilePrintf, pdf, /*obj*/NULL, pdf->trailer_dict, /*length*/NULL))
{
_pdfioFileError(pdf, "Unable to write trailer.");
ret = false;

338
pdfio-lzw.c Normal file
View File

@@ -0,0 +1,338 @@
//
// LZW decoding functions for PDFio.
//
// This code is used to support (legacy) PDF object streams using the LZWDecode
// filter as well as when embedding (legacy) GIF images. None of this is public
// API and we only support reading (decoding) since FlateDecode is superior in
// every way.
//
// Copyright © 2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
//
#include "pdfio-private.h"
//
// Local functions...
//
static void lzw_clear(_pdfio_lzw_t *lzw);
static int lzw_get_code(_pdfio_lzw_t *lzw);
//
// '_pdfioLZWCreate()' - Create a LZW decompressor.
//
_pdfio_lzw_t * // O - LZW state
_pdfioLZWCreate(int code_size, // I - Data code size in bits (typically 8 for PDF, 2-8 for GIF)
int early, // I - Number of early codes
bool reversed) // I - Reversed (GIF) LZW bit encoding?
{
_pdfio_lzw_t *lzw; // LZW state
if ((lzw = (_pdfio_lzw_t *)calloc(1, sizeof(_pdfio_lzw_t))) != NULL)
{
lzw->def_code_size = (uint8_t)(code_size + 1);
lzw->clear_code = (uint16_t)(1 << code_size);
lzw->eod_code = lzw->clear_code + 1;
lzw->early = (uint8_t)early;
lzw->reversed = reversed;
lzw_clear(lzw);
}
return (lzw);
}
//
// '_pdfioLZWDelete()' - Delete a LZW decompressor.
//
void
_pdfioLZWDelete(_pdfio_lzw_t *lzw) // I - LZW state
{
free(lzw);
}
//
// '_pdfioLZWInflate()' - Decompress pending input data.
//
bool // O - `true` on success, `false` on error
_pdfioLZWInflate(_pdfio_lzw_t *lzw) // I - LZW state
{
int cur_code, // Current code
in_code; // Input code
// Stop if we already saw the "end of data" code...
if (lzw->saw_eod)
{
PDFIO_DEBUG("_pdfioLZWInflate: EOD, returning false.\n");
lzw->error = "End of data.";
return (false);
}
// Copy pending compressed data to the output buffer...
while (lzw->stptr > lzw->stack && lzw->avail_out > 0)
{
*(lzw->next_out++) = (uint8_t)*(--lzw->stptr);
lzw->avail_out --;
PDFIO_DEBUG("_pdfioLZWInflate: Unrolled value %d, stptr=%p(%ld), avail_out=%u\n", *(lzw->stptr), (void *)lzw->stptr, lzw->stptr - lzw->stack, (unsigned)lzw->avail_out);
}
// Loop as long as we have room in the output buffer and data in the input
// buffer...
while (lzw->avail_out > 0)
{
if ((in_code = lzw_get_code(lzw)) < 0)
{
// Out of data, stop now...
PDFIO_DEBUG("_pdfioLZWInflate: Out of data.\n");
break;
}
else if (in_code == lzw->clear_code)
{
// Clear the compression tables and reset...
lzw_clear(lzw);
PDFIO_DEBUG("_pdfioLZWInflate: Clear.\n");
continue;
}
else if (in_code == lzw->eod_code)
{
// End of data...
lzw->saw_eod = true;
PDFIO_DEBUG("_pdfioLZWInflate: EOD.\n");
break;
}
// If we get this far we have something to write to the output buffer and/or
// stack...
if (lzw->first_code == 0xffff)
{
// First code...
lzw->first_code = lzw->old_code = (uint16_t)in_code;
*(lzw->next_out++) = (uint8_t)in_code;
lzw->avail_out --;
PDFIO_DEBUG("_pdfioLZWInflate: first_code=%d.\n", in_code);
continue;
}
PDFIO_DEBUG("_pdfioLZWInflate: in_code=%d, old_code=%d.\n", in_code, lzw->old_code);
cur_code = in_code;
if (cur_code >= lzw->next_code)
{
PDFIO_DEBUG("_pdfioLZWInflate: New cur_code=%d, next_code=%d\n", cur_code, lzw->next_code);
*(lzw->stptr++) = lzw->first_code;
cur_code = lzw->old_code;
}
while (cur_code >= lzw->clear_code)
{
PDFIO_DEBUG("_pdfioLZWInflate: cur_code=%d (%d,%d)\n", cur_code, lzw->table[cur_code].prefix_code, lzw->table[cur_code].suffix);
// Protect against overflow/loops...
if (lzw->stptr >= (lzw->stack + sizeof(lzw->stack) / sizeof(lzw->stack[0])))
{
PDFIO_DEBUG("_pdfioLZWInflate: Stack overflow, returning false.\n");
lzw->error = "Output overflow.";
return (false);
}
// Add this character to the output stack and move to the next character
// in the sequence...
*(lzw->stptr++) = lzw->table[cur_code].suffix;
if (cur_code == lzw->table[cur_code].prefix_code)
{
PDFIO_DEBUG("_pdfioLZWInflate: Table loop on code %d, returning false.\n", cur_code);
lzw->error = "Table loop detected.";
return (false);
}
cur_code = lzw->table[cur_code].prefix_code;
}
if (lzw->stptr >= (lzw->stack + sizeof(lzw->stack) / sizeof(lzw->stack[0])))
{
PDFIO_DEBUG("_pdfioLZWInflate: Stack overflow, returning false.\n");
lzw->error = "Output overflow.";
return (false);
}
*(lzw->stptr++) = lzw->first_code = lzw->table[cur_code].suffix;
if ((cur_code = lzw->next_code) < 4096)
{
PDFIO_DEBUG("_pdfioLZWInflate: Adding code %d (%d,%d), next_size_code=%d\n", cur_code, lzw->old_code, lzw->first_code, lzw->next_size_code);
lzw->table[cur_code].prefix_code = lzw->old_code;
lzw->table[cur_code].suffix = lzw->first_code;
lzw->next_code ++;
if (lzw->next_code >= lzw->next_size_code && lzw->cur_code_size < 12)
{
lzw->cur_code_size ++;
lzw->next_size_code = (uint16_t)((1 << lzw->cur_code_size) - lzw->early);
PDFIO_DEBUG("_pdfioLZWInflate: Increased code size to %u, next_size_code=%u\n", lzw->cur_code_size, lzw->next_size_code);
}
}
lzw->old_code = (uint16_t)in_code;
while (lzw->stptr > lzw->stack && lzw->avail_out > 0)
{
*(lzw->next_out++) = (uint8_t)*(--lzw->stptr);
lzw->avail_out --;
PDFIO_DEBUG("_pdfioLZWInflate: Unrolled value %d, stptr=%p(%ld), avail_out=%u\n", *(lzw->stptr), (void *)lzw->stptr, lzw->stptr - lzw->stack, (unsigned)lzw->avail_out);
}
}
PDFIO_DEBUG("_pdfioLZWInflate: Returning true, avail_in=%u, avail_out=%u.\n", (unsigned)lzw->avail_in, (unsigned)lzw->avail_out);
return (true);
}
//
// 'lzw_clear()' - Clear the compression table.
//
static void
lzw_clear(_pdfio_lzw_t *lzw) // I - LZW state
{
uint16_t i; // Looping var
lzw->cur_code_size = lzw->def_code_size;
lzw->next_code = lzw->clear_code + 2;
lzw->next_size_code = (uint16_t)((1 << lzw->cur_code_size) - lzw->early);
lzw->first_code = 0xffff;
lzw->old_code = 0xffff;
memset(lzw->table, 0, sizeof(lzw->table));
for (i = 0; i < lzw->clear_code; i ++)
lzw->table[i].suffix = i;
lzw->stptr = lzw->stack;
}
//
// 'lzw_get_code()' - Get a code from the input buffer.
//
static int // O - Code or -1 if there is not enough data available
lzw_get_code(_pdfio_lzw_t *lzw) // I - LZW state
{
uint16_t code, // Code
in_bit; // Bit offset in buffer
uint8_t bits, // Bits in current byte
boff, // Bit offset in current byte
byte, // Current byte
remaining; // Remaining bits for code
static uint8_t mask[8] = // Value mask
{
0xff, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f
};
static uint8_t rbits[8] = // Right-to-left bit masks
{
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
};
// Fill input bytes as needed...
if ((lzw->in_bit + lzw->cur_code_size) > lzw->in_bits)
{
uint16_t in_used = lzw->in_bits / 8,
// Number of input bytes
in_offset = lzw->in_bit / 8,
// Offset to current input
in_add; // Number of bytes to "read"
if (lzw->avail_in == 0)
{
// No more data
PDFIO_DEBUG("lzw_get_code: No data, returning -1.\n");
return (-1);
}
if (in_offset > 0)
{
// Make room in the input buffer
memmove(lzw->in_bytes, lzw->in_bytes + in_offset, in_used - in_offset);
in_used -= in_offset;
lzw->in_bit &= 7;
}
if ((in_add = sizeof(lzw->in_bytes) - in_used) > lzw->avail_in)
in_add = (uint16_t)lzw->avail_in;
memcpy(lzw->in_bytes + in_used, lzw->next_in, in_add);
lzw->next_in += in_add;
lzw->avail_in -= in_add;
lzw->in_bits = 8 * (in_used + in_add);
if ((lzw->in_bit + lzw->cur_code_size) > lzw->in_bits)
{
// Not enough data
PDFIO_DEBUG("lzw_get_code: Not enough data, returning -1.\n");
return (-1);
}
}
PDFIO_DEBUG("lzw_get_code: in_bit=%u, in_bits=%u, in_bytes=<...%02X%02X%02X...>, cur_code_size=%u\n", lzw->in_bit, lzw->in_bits, lzw->in_bytes[lzw->in_bit / 8], lzw->in_bytes[lzw->in_bit / 8 + 1], lzw->in_bytes[lzw->in_bit / 8 + 2], lzw->cur_code_size);
// Now extract the code from the buffer...
if (lzw->reversed)
{
// Insane GIF-style right-to-left bit encoding...
for (code = 0, in_bit = lzw->in_bit + lzw->cur_code_size - 1, remaining = lzw->cur_code_size; remaining > 0; in_bit --, remaining --)
{
code = (uint16_t)((code << 1) | ((lzw->in_bytes[in_bit / 8] & rbits[in_bit & 7]) != 0));
}
}
else
{
// TIFF/PDF-style left-to-right bit encoding...
for (code = 0, in_bit = lzw->in_bit, remaining = lzw->cur_code_size; remaining > 0; in_bit += bits, remaining -= bits)
{
// See how many bits we can extract from the current byte...
boff = (in_bit & 7);
byte = lzw->in_bytes[in_bit / 8];
bits = 8 - boff;
if (bits > remaining)
bits = remaining;
// Get those bits
if (bits == 8) // Full byte from buffer
code = (uint16_t)((code << 8) | byte);
else // Partial byte from buffer
code = (uint16_t)((code << bits) | ((byte >> (8 - bits - boff)) & mask[bits]));
}
}
// Update the position in the input buffer and return the code...
lzw->in_bit += lzw->cur_code_size;
#ifdef DEBUG
if (code >= 0x20 && code < 0x7f)
PDFIO_DEBUG("lzw_get_code: Returning %u('%c').\n", code, code);
else
PDFIO_DEBUG("lzw_get_code: Returning %u.\n", code);
#endif // DEBUG
return ((int)code);
}

View File

@@ -1,7 +1,7 @@
//
// PDF object functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -32,8 +32,43 @@ pdfioObjClose(pdfio_obj_t *obj) // I - Object
}
// Write what remains for the object...
if (!obj->offset)
if (!obj->offset && !obj->objstm_data)
{
// Are we using object streams and is this object eligible?
if (obj->pdf->objstm_dict && !obj->pdf->objstm_obj && obj != obj->pdf->encrypt_obj && obj->pdf->num_objstm < 0x10000 && (obj->value.type == PDFIO_VALTYPE_ARRAY || obj->value.type == PDFIO_VALTYPE_DICT))
{
// Add this to the object stream...
_pdfio_strbuf_t *bptr; // String buffer
if (!_pdfioStringAllocBuffer(obj->pdf, &bptr))
goto regular_obj;
if (!_pdfioValueWrite((_pdfio_printf_t)_pdfioStringPrintf, bptr, /*obj*/NULL, &(obj->value), /*length*/NULL))
{
_pdfioStringFreeBuffer(obj->pdf, bptr->buffer);
goto regular_obj;
}
_pdfioStringPrintf(bptr, "\n");
if ((obj->objstm_data = pdfioStringCreate(obj->pdf, bptr->buffer)) == NULL)
{
_pdfioStringFreeBuffer(obj->pdf, bptr->buffer);
goto regular_obj;
}
// If we got this far then we have the value string and can assign an
// object number, which is all that is required for now...
obj->objstm_datalen = (size_t)(bptr->bufptr - bptr->buffer);
obj->objstm_number = obj->pdf->num_objstm;
obj->pdf->num_objstm ++;
_pdfioStringFreeBuffer(obj->pdf, bptr->buffer);
return (true);
}
regular_obj:
// Write the object value
if (!_pdfioObjWriteHeader(obj))
return (false);
@@ -69,7 +104,7 @@ pdfioObjCopy(pdfio_file_t *pdf, // I - PDF file
ssize_t bytes; // Bytes read
PDFIO_DEBUG("pdfioObjCopy(pdf=%p, srcobj=%p(%p))\n", (void *)pdf, (void *)srcobj, srcobj ? (void *)srcobj->pdf : NULL);
PDFIO_DEBUG("pdfioObjCopy(pdf=%p, srcobj=%p(%u,%p))\n", (void *)pdf, (void *)srcobj, srcobj ? (unsigned)srcobj->number : 0, srcobj ? (void *)srcobj->pdf : NULL);
// Range check input
if (!pdf || !srcobj)
@@ -77,7 +112,10 @@ pdfioObjCopy(pdfio_file_t *pdf, // I - PDF file
// Load the object value if needed...
if (srcobj->value.type == PDFIO_VALTYPE_NONE)
_pdfioObjLoad(srcobj);
{
if (!_pdfioObjLoad(srcobj))
return (NULL);
}
// See if we have already mapped this object...
if ((dstobj = _pdfioFileFindMappedObj(pdf, srcobj->pdf, srcobj->number)) != NULL)
@@ -544,6 +582,8 @@ pdfioObjOpenStream(pdfio_obj_t *obj, // I - Object
pdfio_stream_t *st; // Stream
PDFIO_DEBUG("pdfioObjOpenStream(obj=%p(%lu), decode=%s)\n", (void *)obj, obj ? (unsigned long)obj->number : 0, decode ? "true" : "false");
// Range check input...
if (!obj)
return (NULL);
@@ -563,7 +603,10 @@ pdfioObjOpenStream(pdfio_obj_t *obj, // I - Object
// No stream if there is no dict or offset to a stream...
if (obj->value.type != PDFIO_VALTYPE_DICT || !obj->stream_offset)
{
PDFIO_DEBUG("pdfioObjOpenStream: value.type=%d, stream_offset=%ld\n", obj->value.type, (long)obj->stream_offset);
return (NULL);
}
// Open the stream...
if ((st = _pdfioStreamOpen(obj, decode)) != NULL)
@@ -600,7 +643,7 @@ _pdfioObjWriteHeader(pdfio_obj_t *obj) // I - Object
if (!_pdfioFilePrintf(obj->pdf, "%lu %u obj\n", (unsigned long)obj->number, obj->generation))
return (false);
if (!_pdfioValueWrite(obj->pdf, obj, &obj->value, &obj->length_offset))
if (!_pdfioValueWrite((_pdfio_printf_t)_pdfioFilePrintf, obj->pdf, obj, &obj->value, &obj->length_offset))
return (false);
return (_pdfioFilePuts(obj->pdf, "\n"));

View File

@@ -1,7 +1,7 @@
//
// PDF page functions for PDFio.
//
// Copyright © 2021-2022 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -15,6 +15,7 @@
//
static _pdfio_value_t *get_contents(pdfio_obj_t *page);
static _pdfio_value_t *get_page_value(pdfio_obj_t *page, const char *key);
//
@@ -52,6 +53,158 @@ pdfioPageCopy(pdfio_file_t *pdf, // I - PDF file
}
//
// 'pdfioPageGetArray()' - Get an array value from the page dictionary.
//
// This function looks up an array value in the page dictionary, either in the
// specified object or one of its parents.
//
// @since PDFio v1.7@
//
pdfio_array_t * // O - Array or `NULL` if none
pdfioPageGetArray(pdfio_obj_t *page, // I - Page object
const char *key) // I - Dictionary key
{
_pdfio_value_t *v = get_page_value(page, key);
// Dictionary value
if (v && v->type == PDFIO_VALTYPE_ARRAY)
return (v->value.array);
else
return (NULL);
}
//
// 'pdfioPageGetBinary()' - Get a binary value from the page dictionary.
//
// This function looks up a binary value in the page dictionary, either in the
// specified object or one of its parents.
//
// @since PDFio v1.7@
//
unsigned char * // O - Pointer to binary data or `NULL` if none
pdfioPageGetBinary(pdfio_obj_t *page, // I - Page object
const char *key, // I - Dictionary key
size_t *length) // O - Length of value
{
_pdfio_value_t *v = get_page_value(page, key);
// Dictionary value
if (v && v->type == PDFIO_VALTYPE_BINARY)
{
if (length)
*length = v->value.binary.datalen;
return (v->value.binary.data);
}
else
{
return (NULL);
}
}
//
// 'pdfioPageGetBoolean()' - Get a boolean value from the page dictionary.
//
// This function looks up a boolean value in the page dictionary, either in the
// specified object or one of its parents.
//
// @since PDFio v1.7@
//
bool // O - Boolean value or `false` if none
pdfioPageGetBoolean(pdfio_obj_t *page, // I - Page object
const char *key) // I - Dictionary key
{
_pdfio_value_t *v = get_page_value(page, key);
// Dictionary value
if (v && v->type == PDFIO_VALTYPE_BOOLEAN)
return (v->value.boolean);
else
return (false);
}
//
// 'pdfioPageGetDate()' - Get a date value from the page dictionary.
//
// This function looks up a date value in the page dictionary, either in the
// specified object or one of its parents.
//
// @since PDFio v1.7@
//
time_t // O - Date/time or `0` if none
pdfioPageGetDate(pdfio_obj_t *page, // I - Page object
const char *key) // I - Dictionary key
{
_pdfio_value_t *v = get_page_value(page, key);
// Dictionary value
if (v && v->type == PDFIO_VALTYPE_DATE)
return (v->value.date);
else
return (0);
}
//
// 'pdfioPageGetDict()' - Get a dictionary value from the page dictionary.
//
// This function looks up a dictionary value in the page dictionary, either in
// the specified object or one of its parents.
//
// @since PDFio v1.7@
//
pdfio_dict_t * // O - Dictionary or `NULL` if none
pdfioPageGetDict(pdfio_obj_t *page, // I - Page object
const char *key) // I - Dictionary key
{
_pdfio_value_t *v = get_page_value(page, key);
// Dictionary value
if (v && v->type == PDFIO_VALTYPE_DICT)
return (v->value.dict);
else
return (NULL);
}
//
// 'pdfioPageGetName()' - Get a name value from the page dictionary.
//
// This function looks up a name value in the page dictionary, either in the
// specified object or one of its parents.
//
// @since PDFio v1.7@
//
const char * // O - Name string or `NULL` if none
pdfioPageGetName(pdfio_obj_t *page, // I - Page object
const char *key) // I - Dictionary key
{
_pdfio_value_t *v = get_page_value(page, key);
// Dictionary value
if (v && v->type == PDFIO_VALTYPE_NAME)
return (v->value.name);
else
return (NULL);
}
//
// 'pdfioPageGetNumStreams()' - Get the number of content streams for a page object.
//
@@ -73,6 +226,112 @@ pdfioPageGetNumStreams(
}
//
// 'pdfioPageGetNumber()' - Get a number value from the page dictionary.
//
// This function looks up a number value in the page dictionary, either in the
// specified object or one of its parents.
//
// @since PDFio v1.7@
//
double // O - Number value or `0.0` if none
pdfioPageGetNumber(pdfio_obj_t *page, // I - Page object
const char *key) // I - Dictionary key
{
_pdfio_value_t *v = get_page_value(page, key);
// Dictionary value
if (v && v->type == PDFIO_VALTYPE_NUMBER)
return (v->value.number);
else
return (0.0);
}
//
// 'pdfioPageGetObj()' - Get an indirect object value from the page dictionary.
//
// This function looks up an indirect object value in the page dictionary,
// either in the specified object or one of its parents.
//
// @since PDFio v1.7@
//
pdfio_obj_t * // O - Object or `NULL` if none
pdfioPageGetObj(pdfio_obj_t *page, // I - Page object
const char *key) // I - Dictionary key
{
_pdfio_value_t *v = get_page_value(page, key);
// Dictionary value
if (v && v->type == PDFIO_VALTYPE_INDIRECT)
return (pdfioFileFindObj(page->pdf, v->value.indirect.number));
else
return (NULL);
}
//
// 'pdfioPageGetRect()' - Get a rectangle value from the page dictionary.
//
// This function looks up a rectangle value in the page dictionary, either in
// the specified object or one of its parents.
//
// @since PDFio v1.7@
//
pdfio_rect_t * // O - Rectangle or `NULL` if none
pdfioPageGetRect(pdfio_obj_t *page, // I - Page object
const char *key, // I - Dictionary key
pdfio_rect_t *rect) // O - Rectangle
{
_pdfio_value_t *v = get_page_value(page, key);
// Dictionary value
if (v && v->type == PDFIO_VALTYPE_ARRAY && pdfioArrayGetSize(v->value.array) == 4)
{
rect->x1 = pdfioArrayGetNumber(v->value.array, 0);
rect->y1 = pdfioArrayGetNumber(v->value.array, 1);
rect->x2 = pdfioArrayGetNumber(v->value.array, 2);
rect->y2 = pdfioArrayGetNumber(v->value.array, 3);
return (rect);
}
else
{
return (NULL);
}
}
//
// 'pdfioPageGetString()' - Get a string value from the page dictionary.
//
// This function looks up a string value in the page dictionary, either in the
// specified object or one of its parents.
//
// @since PDFio v1.7@
//
const char * // O - String value or `NULL` if none
pdfioPageGetString(pdfio_obj_t *page, // I - Page object
const char *key) // I - Dictionary key
{
_pdfio_value_t *v = get_page_value(page, key);
// Dictionary value
if (v && v->type == PDFIO_VALTYPE_STRING)
return (v->value.string);
else
return (NULL);
}
//
// 'pdfioPageOpenStream()' - Open a content stream for a page.
//
@@ -87,14 +346,28 @@ pdfioPageOpenStream(
// Contents value
PDFIO_DEBUG("pdfioPageOpenStream(page=%p(%lu), n=%lu, decode=%s)\n", (void *)page, page ? (unsigned long)page->number : 0, (unsigned long)n, decode ? "true" : "false");
if (!contents)
{
PDFIO_DEBUG("pdfioPageOpenStream: No contents.\n");
return (NULL);
}
else if (contents->type == PDFIO_VALTYPE_ARRAY && n < pdfioArrayGetSize(contents->value.array))
{
PDFIO_DEBUG("pdfioPageOpenStream: Contents is array, opening numbered content stream.\n");
return (pdfioObjOpenStream(pdfioArrayGetObj(contents->value.array, n), decode));
}
else if (n)
{
PDFIO_DEBUG("pdfioPageOpenStream: Numbered stream does not exist.\n");
return (NULL);
}
else
{
PDFIO_DEBUG("pdfioPageOpenStream: Opening single content stream %d.\n", (int)contents->value.indirect.number);
return (pdfioObjOpenStream(pdfioFileFindObj(page->pdf, contents->value.indirect.number), decode));
}
}
@@ -105,6 +378,10 @@ pdfioPageOpenStream(
static _pdfio_value_t * // O - Value or NULL on error
get_contents(pdfio_obj_t *page) // I - Page object
{
_pdfio_value_t *contents; // Contents value
pdfio_obj_t *obj; // Contents object
// Range check input...
if (!page)
return (NULL);
@@ -119,5 +396,57 @@ get_contents(pdfio_obj_t *page) // I - Page object
if (page->value.type != PDFIO_VALTYPE_DICT)
return (NULL);
return (_pdfioDictGetValue(page->value.value.dict, "Contents"));
if ((contents = _pdfioDictGetValue(page->value.value.dict, "Contents")) == NULL)
return (NULL);
if (contents->type == PDFIO_VALTYPE_INDIRECT)
{
// See if the indirect object is a stream or an array of indirect object
// references...
if ((obj = pdfioFileFindObj(page->pdf, contents->value.indirect.number)) != NULL)
{
if (obj->value.type == PDFIO_VALTYPE_NONE)
{
if (!_pdfioObjLoad(obj))
return (NULL);
}
if (obj->value.type == PDFIO_VALTYPE_ARRAY)
contents = &(obj->value);
}
}
return (contents);
}
//
// 'get_page_value()' - Get a page dictionary value, including parents.
//
static _pdfio_value_t * // O - Dictionary value or `NULL` if none
get_page_value(pdfio_obj_t *page, // I - Page object
const char *key) // I - Dictionary key
{
_pdfio_value_t *v = NULL; // Dictionary value
while (page != NULL)
{
// Load the page object as needed...
if (page->value.type == PDFIO_VALTYPE_NONE && !_pdfioObjLoad(page))
break;
// If there isn't a dictionary for the object, stop...
if (page->value.type != PDFIO_VALTYPE_DICT)
break;
// Lookup the key...
if ((v = _pdfioDictGetValue(page->value.value.dict, key)) != NULL)
break;
page = pdfioDictGetObj(page->value.value.dict, "Parent");
}
return (v);
}

View File

@@ -1,7 +1,7 @@
//
// Private header file for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -30,7 +30,7 @@
# define fileno _fileno
# define lseek(f,o,w) (off_t)_lseek((f),(long)(o),(w))
# define mkdir(d,p) _mkdir(d)
# define open _open
# define open _pdfio_win32_open
# define read(f,b,s) _read((f),(b),(unsigned)(s))
# define rmdir _rmdir
# define snprintf _snprintf
@@ -94,10 +94,12 @@
//
# define PDFIO_MAX_DEPTH 32 // Maximum nesting depth for values
# define PDFIO_MAX_STRING 65536 // Maximum length of string
# define PDFIO_MAX_STRING 131072 // Maximum length of string
typedef void (*_pdfio_extfree_t)(void *);
typedef void (*_pdfio_extfree_t)(void *p);
// Extension data free function
typedef bool (*_pdfio_printf_t)(void *data, const char *format, ...);
// "printf" function
typedef enum _pdfio_mode_e // Read/write mode
{
@@ -176,7 +178,7 @@ typedef struct _pdfio_value_s // Value structure
typedef struct _pdfio_aes_s // AES encryption state
{
size_t round_size; // Size of round key
uint8_t round_key[240], // Round key
uint8_t round_key[256], // Round key
iv[16]; // Initialization vector
} _pdfio_aes_t;
@@ -211,6 +213,38 @@ typedef union _pdfio_crypto_ctx_u // Cryptographic contexts
} _pdfio_crypto_ctx_t;
typedef size_t (*_pdfio_crypto_cb_t)(_pdfio_crypto_ctx_t *ctx, uint8_t *outbuffer, const uint8_t *inbuffer, size_t len);
typedef struct _pdfio_lzws_s // LZW string table
{
uint16_t prefix_code, // Prefix code
suffix; // Suffix (character)
} _pdfio_lzws_t;
typedef struct _pdfio_lzw_s // LZW state
{
uint8_t *next_in; // Next input byte
size_t avail_in; // Available input bytes
uint8_t in_bytes[256]; // Current input bytes
uint16_t in_bit, // Current input bit
in_bits; // Total input bits
uint8_t *next_out; // Next output byte
size_t avail_out; // Available output bytes
uint8_t cur_code_size, // Current code size
def_code_size, // Initial/default code size
early; // Early code change offset
bool reversed; // Reversed bit encoding?
uint16_t clear_code, // Clear code
eod_code, // End code
next_code, // Next code to be used
next_size_code, // Code where we need to increase the code size
first_code, // First code in sequence
old_code, // Previous code in sequence
stack[8192], // Output stack
*stptr; // Current stack pointer
_pdfio_lzws_t table[4096]; // String table
bool saw_eod; // Saw end-of-data code?
const char *error; // Error, if any
} _pdfio_lzw_t;
struct _pdfio_array_s
{
pdfio_file_t *pdf; // PDF file
@@ -243,9 +277,11 @@ typedef struct _pdfio_objmap_s // PDF object map
typedef struct _pdfio_strbuf_s // PDF string buffer
{
struct _pdfio_strbuf_s *next; // Next string buffer
pdfio_file_t *pdf; // PDF file
bool bufused; // Is this string buffer being used?
char buffer[PDFIO_MAX_STRING + 32];
char buffer[PDFIO_MAX_STRING + 32],
// String buffer
*bufptr; // Pointer into buffer
} _pdfio_strbuf_t;
struct _pdfio_file_s // PDF file structure
@@ -283,10 +319,14 @@ struct _pdfio_file_s // PDF file structure
pdfio_obj_t *root_obj; // Root object/dictionary
pdfio_obj_t *info_obj; // Information object
pdfio_obj_t *pages_obj; // Root pages object
pdfio_obj_t *encrypt_obj; // De/Encryption object/dictionary
pdfio_obj_t *encrypt_obj; // Encryption object (not used for reading)
pdfio_dict_t *encrypt_dict; // De/Encryption dictionary
pdfio_obj_t *cgats001_obj, // CGATS001 ICC profile object
*cp1252_obj, // CP1252 font encoding object
*unicode_obj; // Unicode font encoding object
*unicode_obj, // Unicode font encoding object
*objstm_obj; // Object stream object
pdfio_dict_t *objstm_dict; // Object stream dictionary
size_t num_objstm; // Number of objects in object stream
pdfio_array_t *id_array; // ID array
bool encrypt_metadata; // Encrypt metadata?
pdfio_dict_t *markinfo; // MarkInfo dictionary, if any
@@ -326,6 +366,9 @@ struct _pdfio_obj_s // Object
size_t stream_length; // Length of stream, if any
_pdfio_value_t value; // Dictionary/number/etc. value
pdfio_stream_t *stream; // Open stream, if any
size_t objstm_number; // Object stream number
char *objstm_data; // Object stream data, if any
size_t objstm_datalen; // Object stream data length
void *data; // Extension data, if any
_pdfio_extfree_t datafree; // Free callback for extension data
};
@@ -340,12 +383,20 @@ struct _pdfio_stream_s // Stream
char buffer[8192], // Read/write buffer
*bufptr, // Current position in buffer
*bufend; // End of buffer
size_t a85size; // Size of ASCII85Decode buffer
char *a85buffer, // ASCII85Decode buffer, if any
*a85bufptr, // Pointer into ASCII85Decode buffer
*a85bufend, // End of data in ASCII85Decode buffer
a85decode[4], // Current block of decoded characters
*a85decptr, // Pointer into decoded characters
*a85decend; // Last decoded character
z_stream flate; // Flate filter state
_pdfio_lzw_t *lzw; // LZW filter state
_pdfio_predictor_t predictor; // Predictor function, if any
size_t pbpixel, // Size of a pixel in bytes
pbsize, // Predictor buffer size, if any
cbsize; // Compressed data buffer size
unsigned char *cbuffer, // Compressed data buffer
uint8_t *cbuffer, // Compressed data buffer
*prbuffer, // Raw buffer (previous line), as needed
*psbuffer; // PNG filter buffer, as needed
_pdfio_crypto_cb_t crypto_cb; // Encryption/descryption callback, if any
@@ -361,13 +412,14 @@ extern size_t _pdfio_strlcpy(char *dst, const char *src, size_t dstsize) _PDFIO
extern double _pdfio_strtod(pdfio_file_t *pdf, const char *s) _PDFIO_INTERNAL;
extern void _pdfio_utf16cpy(char *dst, const unsigned char *src, size_t srclen, size_t dstsize) _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 int _pdfio_win32_open(const char *filename, int oflag, int mode) _PDFIO_INTERNAL;
extern bool _pdfioArrayDecrypt(pdfio_file_t *pdf, pdfio_obj_t *obj, pdfio_array_t *a, size_t depth) _PDFIO_INTERNAL;
extern void _pdfioArrayDebug(pdfio_array_t *a, FILE *fp) _PDFIO_INTERNAL;
extern void _pdfioArrayDelete(pdfio_array_t *a) _PDFIO_INTERNAL;
extern _pdfio_value_t *_pdfioArrayGetValue(pdfio_array_t *a, size_t n) _PDFIO_INTERNAL;
extern pdfio_array_t *_pdfioArrayRead(pdfio_file_t *pdf, pdfio_obj_t *obj, _pdfio_token_t *ts, size_t depth) _PDFIO_INTERNAL;
extern bool _pdfioArrayWrite(pdfio_array_t *a, pdfio_obj_t *obj) _PDFIO_INTERNAL;
extern bool _pdfioArrayWrite(_pdfio_printf_t cb, void *cbdata, pdfio_obj_t *obj, pdfio_array_t *a) _PDFIO_INTERNAL;
extern void _pdfioCryptoAESInit(_pdfio_aes_t *ctx, const uint8_t *key, size_t keylen, const uint8_t *iv) _PDFIO_INTERNAL;
extern size_t _pdfioCryptoAESDecrypt(_pdfio_aes_t *ctx, uint8_t *outbuffer, const uint8_t *inbuffer, size_t len) _PDFIO_INTERNAL;
@@ -392,7 +444,7 @@ extern void _pdfioDictDelete(pdfio_dict_t *dict) _PDFIO_INTERNAL;
extern _pdfio_value_t *_pdfioDictGetValue(pdfio_dict_t *dict, const char *key) _PDFIO_INTERNAL;
extern pdfio_dict_t *_pdfioDictRead(pdfio_file_t *pdf, pdfio_obj_t *obj, _pdfio_token_t *ts, size_t depth) _PDFIO_INTERNAL;
extern bool _pdfioDictSetValue(pdfio_dict_t *dict, const char *key, _pdfio_value_t *value) _PDFIO_INTERNAL;
extern bool _pdfioDictWrite(pdfio_dict_t *dict, pdfio_obj_t *obj, off_t *length) _PDFIO_INTERNAL;
extern bool _pdfioDictWrite(_pdfio_printf_t cb, void *cbdata, pdfio_obj_t *obj, pdfio_dict_t *dict, off_t *length) _PDFIO_INTERNAL;
extern bool _pdfioFileAddMappedObj(pdfio_file_t *pdf, pdfio_obj_t *dst_obj, pdfio_obj_t *src_obj) _PDFIO_INTERNAL;
extern bool _pdfioFileAddPage(pdfio_file_t *pdf, pdfio_obj_t *obj) _PDFIO_INTERNAL;
@@ -412,6 +464,10 @@ extern off_t _pdfioFileSeek(pdfio_file_t *pdf, off_t offset, int whence) _PDFIO
extern off_t _pdfioFileTell(pdfio_file_t *pdf) _PDFIO_INTERNAL;
extern bool _pdfioFileWrite(pdfio_file_t *pdf, const void *buffer, size_t bytes) _PDFIO_INTERNAL;
extern _pdfio_lzw_t *_pdfioLZWCreate(int def_code_size, int early, bool reversed) _PDFIO_INTERNAL;
extern void _pdfioLZWDelete(_pdfio_lzw_t *lzw) _PDFIO_INTERNAL;
extern bool _pdfioLZWInflate(_pdfio_lzw_t *lzw) _PDFIO_INTERNAL;
extern void _pdfioObjDelete(pdfio_obj_t *obj) _PDFIO_INTERNAL;
extern void *_pdfioObjGetExtension(pdfio_obj_t *obj) _PDFIO_INTERNAL;
extern bool _pdfioObjLoad(pdfio_obj_t *obj) _PDFIO_INTERNAL;
@@ -421,9 +477,10 @@ extern bool _pdfioObjWriteHeader(pdfio_obj_t *obj) _PDFIO_INTERNAL;
extern pdfio_stream_t *_pdfioStreamCreate(pdfio_obj_t *obj, pdfio_obj_t *length_obj, size_t cbsize, pdfio_filter_t compression) _PDFIO_INTERNAL;
extern pdfio_stream_t *_pdfioStreamOpen(pdfio_obj_t *obj, bool decode) _PDFIO_INTERNAL;
extern char *_pdfioStringAllocBuffer(pdfio_file_t *pdf);
extern char *_pdfioStringAllocBuffer(pdfio_file_t *pdf, _pdfio_strbuf_t **bptr);
extern void _pdfioStringFreeBuffer(pdfio_file_t *pdf, char *buffer);
extern bool _pdfioStringIsAllocated(pdfio_file_t *pdf, const char *s) _PDFIO_INTERNAL;
extern bool _pdfioStringPrintf(_pdfio_strbuf_t *bptr, const char *format, ...) _PDFIO_INTERNAL;
extern void _pdfioTokenClear(_pdfio_token_t *tb) _PDFIO_INTERNAL;
extern void _pdfioTokenFlush(_pdfio_token_t *tb) _PDFIO_INTERNAL;
@@ -437,7 +494,7 @@ extern bool _pdfioValueDecrypt(pdfio_file_t *pdf, pdfio_obj_t *obj, _pdfio_valu
extern void _pdfioValueDebug(_pdfio_value_t *v, FILE *fp) _PDFIO_INTERNAL;
extern void _pdfioValueDelete(_pdfio_value_t *v) _PDFIO_INTERNAL;
extern _pdfio_value_t *_pdfioValueRead(pdfio_file_t *pdf, pdfio_obj_t *obj, _pdfio_token_t *ts, _pdfio_value_t *v, size_t depth) _PDFIO_INTERNAL;
extern bool _pdfioValueWrite(pdfio_file_t *pdf, pdfio_obj_t *obj, _pdfio_value_t *v, off_t *length) _PDFIO_INTERNAL;
extern bool _pdfioValueWrite(_pdfio_printf_t cb, void *cbdata, pdfio_obj_t *obj, _pdfio_value_t *v, off_t *length) _PDFIO_INTERNAL;
#endif // !PDFIO_PRIVATE_H

View File

@@ -1,7 +1,7 @@
//
// PDF stream functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -14,6 +14,8 @@
// Local functions...
//
static ssize_t stream_get_bytes(pdfio_stream_t *st, void *buffer, size_t bytes);
static ssize_t stream_inflate(pdfio_stream_t *st, uint8_t *buffer, size_t bytes, bool exactly);
static unsigned char stream_paeth(unsigned char a, unsigned char b, unsigned char c);
static ssize_t stream_read(pdfio_stream_t *st, char *buffer, size_t bytes);
static bool stream_write(pdfio_stream_t *st, const void *buffer, size_t bytes);
@@ -39,6 +41,8 @@ pdfioStreamClose(pdfio_stream_t *st) // I - Stream
{
if (st->filter == PDFIO_FILTER_FLATE)
inflateEnd(&(st->flate));
else if (st->filter == PDFIO_FILTER_LZW)
_pdfioLZWDelete(st->lzw);
}
else
{
@@ -172,6 +176,7 @@ pdfioStreamClose(pdfio_stream_t *st) // I - Stream
st->pdf->current_obj = NULL;
free(st->a85buffer);
free(st->cbuffer);
free(st->prbuffer);
free(st->psbuffer);
@@ -479,10 +484,34 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
pdfio_array_t *fa = pdfioDictGetArray(dict, "Filter");
// Filter array
if (!filter && fa && pdfioArrayGetSize(fa) == 1)
if (!filter && fa)
{
// Support single-valued arrays...
filter = pdfioArrayGetName(fa, 0);
const char *filter0 = pdfioArrayGetName(fa, 0);
// First filter
if (pdfioArrayGetSize(fa) == 1)
{
// Support single-valued arrays...
filter = filter0;
}
else if (pdfioArrayGetSize(fa) == 2 && filter0 && !strcmp(filter0, "ASCII85Decode"))
{
// Support ASCII85Decode + something else
st->a85size = 5200; // Enough for 4k of decoded data
// Allocate the ASCII85Decode buffer...
if ((st->a85buffer = malloc(st->a85size)) == NULL)
{
_pdfioFileError(st->pdf, "Unable to allocate ASCII85Decode buffer.");
goto error;
}
st->a85bufptr = st->a85buffer;
st->a85bufend = st->a85buffer;
// Get the second filter...
filter = pdfioArrayGetName(fa, 1);
}
}
if (!filter)
@@ -490,7 +519,6 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
// No single filter name, do we have a compound filter?
if (fa)
{
// TODO: Implement compound filters...
_pdfioFileError(st->pdf, "Unsupported compound stream filter.");
goto error;
}
@@ -498,9 +526,9 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
// No filter, read as-is...
st->filter = PDFIO_FILTER_NONE;
}
else if (!strcmp(filter, "FlateDecode"))
else if (!strcmp(filter, "FlateDecode") || !strcmp(filter, "LZWDecode"))
{
// Flate compression
// Flate or LZW compression
pdfio_dict_t *params = pdfioDictGetDict(dict, "DecodeParms");
// Decoding parameters
int bpc = (int)pdfioDictGetNumber(params, "BitsPerComponent");
@@ -511,12 +539,11 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
// Number of columns
int predictor = (int)pdfioDictGetNumber(params, "Predictor");
// Predictory value, if any
int status; // ZLIB status
ssize_t rbytes; // Bytes read
PDFIO_DEBUG("_pdfioStreamOpen: FlateDecode - BitsPerComponent=%d, Colors=%d, Columns=%d, Predictor=%d\n", bpc, colors, columns, predictor);
PDFIO_DEBUG("_pdfioStreamOpen: %s - BitsPerComponent=%d, Colors=%d, Columns=%d, Predictor=%d\n", filter, bpc, colors, columns, predictor);
st->filter = PDFIO_FILTER_FLATE;
st->filter = !strcmp(filter, "FlateDecode") ? PDFIO_FILTER_FLATE : PDFIO_FILTER_LZW;
if (bpc == 0)
{
@@ -583,42 +610,57 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
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);
_pdfioFileError(st->pdf, "Unable to allocate %lu bytes for FlateDecode decompression buffer.", (unsigned long)st->cbsize);
goto error;
}
PDFIO_DEBUG("_pdfioStreamOpen: pos=%ld\n", (long)_pdfioFileTell(st->pdf));
if (st->cbsize > st->remaining)
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining);
else
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->cbsize);
if (rbytes <= 0)
if ((rbytes = stream_get_bytes(st, st->cbuffer, st->cbsize)) <= 0)
{
_pdfioFileError(st->pdf, "Unable to read bytes for stream.");
goto error;
}
if (st->crypto_cb)
rbytes = (ssize_t)(st->crypto_cb)(&st->crypto_ctx, st->cbuffer, st->cbuffer, (size_t)rbytes);
st->flate.next_in = (Bytef *)st->cbuffer;
st->flate.avail_in = (uInt)rbytes;
PDFIO_DEBUG("_pdfioStreamOpen: avail_in=%u, cbuffer=<%02X%02X%02X%02X%02X%02X%02X%02X...>\n", st->flate.avail_in, st->cbuffer[0], st->cbuffer[1], st->cbuffer[2], st->cbuffer[3], st->cbuffer[4], st->cbuffer[5], st->cbuffer[6], st->cbuffer[7]);
if ((status = inflateInit(&(st->flate))) != Z_OK)
if (st->filter == PDFIO_FILTER_FLATE)
{
_pdfioFileError(st->pdf, "Unable to start Flate filter: %s", zstrerror(status));
goto error;
}
// Flate decompression...
int status; // ZLIB status
st->remaining -= st->flate.avail_in;
}
else if (!strcmp(filter, "LZWDecode"))
{
// LZW compression
st->filter = PDFIO_FILTER_LZW;
st->flate.next_in = (Bytef *)st->cbuffer;
st->flate.avail_in = (uInt)rbytes;
PDFIO_DEBUG("_pdfioStreamOpen: avail_in=%u, cbuffer=<%02X%02X%02X%02X%02X%02X%02X%02X...>\n", st->flate.avail_in, st->cbuffer[0], st->cbuffer[1], st->cbuffer[2], st->cbuffer[3], st->cbuffer[4], st->cbuffer[5], st->cbuffer[6], st->cbuffer[7]);
if ((status = inflateInit(&(st->flate))) != Z_OK)
{
_pdfioFileError(st->pdf, "Unable to start FlateDecode filter: %s", zstrerror(status));
goto error;
}
}
else
{
// LZW decompression...
int early = 1;
if (pdfioDictGetType(params, "EarlyChange") == PDFIO_VALTYPE_NUMBER)
{
early = (int)pdfioDictGetNumber(params, "EarlyChange");
if (early < 0 || early > 100)
{
_pdfioFileError(st->pdf, "Bad EarlyChange value %d for LZWDecode filter.", early);
goto error;
}
}
if ((st->lzw = _pdfioLZWCreate(/*code_size*/8, early, /*reversed*/false)) == NULL)
{
_pdfioFileError(st->pdf, "Unable to initialize LZWDecode filter: %s", strerror(errno));
goto error;
}
st->lzw->next_in = st->cbuffer;
st->lzw->avail_in = (size_t)rbytes;
}
}
else
{
@@ -638,12 +680,13 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
// If we get here something went wrong...
error:
free(st->a85buffer);
free(st->cbuffer);
free(st->prbuffer);
free(st->psbuffer);
free(st);
return (NULL);
return (NULL);
}
@@ -1011,6 +1054,271 @@ pdfioStreamWrite(
}
//
// 'stream_get_bytes()' - Read and decrypt raw or ASCII85Decode-encoded data.
//
static ssize_t // O - Bytes read or `-1` on error
stream_get_bytes(
pdfio_stream_t *st, // I - Stream
void *buffer, // I - Buffer
size_t bytes) // I - Maximum number of bytes to read
{
ssize_t rbytes; // Bytes read
if (st->a85buffer)
{
// Decode through the ASCII85Decode buffer...
char *bufptr = (char *)buffer; // Pointer into read buffer
rbytes = 0;
// Read as much as we can...
while (bytes > 0 && (st->a85bufptr < st->a85bufend || st->remaining > 0))
{
unsigned count, // Number of ASCII85 chars
a85val; // ASCII85 "chunk" value
char *a85bufptr; // Local copy of buffer pointer
size_t declen; // Decoded length
// First use up any remaining decoded chars...
if (st->a85decptr)
{
declen = (size_t)(st->a85decend - st->a85decptr);
if (bytes >= declen)
{
memcpy(bufptr, st->a85decptr, declen);
bufptr += declen;
rbytes += (ssize_t)declen;
st->a85decptr = NULL;
bytes -= declen;
if (bytes == 0)
break;
}
else
{
memcpy(bufptr, st->a85decptr, bytes);
rbytes += (ssize_t)bytes;
st->a85decptr += bytes;
break;
}
}
if ((st->a85bufend - st->a85bufptr) < 5 && st->remaining > 0)
{
// Fill the ASCII85Decode buffer...
ssize_t a85bytes = st->a85bufend - st->a85bufptr;
// Bytes in the ASCII85Decode buffer
size_t a85remaining = st->a85size - (size_t)a85bytes;
// Remaining capacity in the buffer
// First move any remaining bytes to the front of the buffer...
if (a85bytes > 0)
memmove(st->a85buffer, st->a85bufptr, (size_t)a85bytes);
st->a85bufptr = st->a85buffer;
st->a85bufend = st->a85buffer + a85bytes;
// Then read more data from the file...
if (a85remaining > st->remaining)
a85bytes = _pdfioFileRead(st->pdf, st->a85bufend, st->remaining);
else
a85bytes = _pdfioFileRead(st->pdf, st->a85bufend, a85remaining);
if (a85bytes > 0)
{
st->remaining -= (size_t)a85bytes;
if (st->crypto_cb)
(st->crypto_cb)(&st->crypto_ctx, (uint8_t *)st->a85bufend, (uint8_t *)st->a85bufend, (size_t)a85bytes);
st->a85bufend += a85bytes;
}
else
{
_pdfioFileError(st->pdf, "Read error in stream.");
return (-1);
}
}
// Grab the next chunk...
for (a85bufptr = st->a85bufptr, a85val = 0, count = 0; a85bufptr < st->a85bufend && count < 5; a85bufptr ++)
{
char a85ch = *a85bufptr; // Current character
if (a85ch >= '!' && a85ch <= 'u')
{
// Valid ASCII85Decode character...
a85val = a85val * 85 + (unsigned)a85ch - '!';
count ++;
}
else if (a85ch == 'z' && count == 0)
{
// 'z' == 0's
a85val = 0;
count = 5;
a85bufptr++;
}
else if (a85ch == '~')
{
break;
}
else if (!isspace(a85ch & 255))
{
// Invalid ASCII85Decode character...
_pdfioFileError(st->pdf, "Invalid ASCII85Decode character '%c' in stream.", a85ch);
return (-1);
}
}
st->a85bufptr = a85bufptr;
if (*a85bufptr == '~')
break;
if (count < 2)
{
// Need at least 2 characters to decode a single byte...
_pdfioFileError(st->pdf, "Invalid ASCII85Decode sequence in stream.");
return (-1);
}
declen = count - 1;
// Add rounds to properly align the decoded value...
while (count < 5)
{
a85val = a85val * 85 + 84;
count ++;
}
// Copy the bytes to the decode buffer...
st->a85decode[0] = (char)(a85val >> 24);
st->a85decode[1] = (char)((a85val >> 16) & 255);
st->a85decode[2] = (char)((a85val >> 8) & 255);
st->a85decode[3] = (char)(a85val & 255);
st->a85decptr = st->a85decode;
st->a85decend = st->a85decode + declen;
}
PDFIO_DEBUG("stream_get_bytes: Returning %ld ASCII85 bytes for stream.\n", (long)rbytes);
return (rbytes);
}
else
{
// Limit reads to the length of the stream...
if (bytes > st->remaining)
rbytes = _pdfioFileRead(st->pdf, buffer, st->remaining);
else
rbytes = _pdfioFileRead(st->pdf, buffer, bytes);
if (rbytes > 0)
{
st->remaining -= (size_t)rbytes;
if (st->crypto_cb)
(st->crypto_cb)(&st->crypto_ctx, (uint8_t *)buffer, (uint8_t *)buffer, (size_t)rbytes);
}
PDFIO_DEBUG("stream_get_bytes: Returning %ld raw bytes for stream.\n", (long)rbytes);
return (rbytes);
}
}
//
// 'stream_inflate()' - Decompress bytes from a stream (Flate or LZW) into the specified buffer.
//
static ssize_t
stream_inflate(pdfio_stream_t *st, // I - Stream
uint8_t *buffer, // I - Output buffer
size_t bytes, // I - Number of bytes
bool exactly) // I - Require exactly the number of bytes
{
ssize_t rbytes; // Bytes read
// Setup decompression to the output buffer...
if (st->filter == PDFIO_FILTER_FLATE)
{
st->flate.next_out = (Bytef *)buffer;
st->flate.avail_out = (uInt)bytes;
}
else
{
st->lzw->next_out = buffer;
st->lzw->avail_out = bytes;
}
// Loop to get the bytes...
do
{
if (st->filter == PDFIO_FILTER_FLATE)
{
// Flate decompress
int status; // Status of decompression
PDFIO_DEBUG("stream_inflate: avail_in=%u, avail_out=%u\n", st->flate.avail_in, st->flate.avail_out);
if (st->flate.avail_in == 0)
{
// Read more from the file...
if ((rbytes = stream_get_bytes(st, st->cbuffer, st->cbsize)) <= 0)
return (-1); // End of file...
st->flate.next_in = (Bytef *)st->cbuffer;
st->flate.avail_in = (uInt)rbytes;
}
if ((status = inflate(&(st->flate), Z_NO_FLUSH)) < Z_OK)
{
PDFIO_DEBUG("stream_inflate: inflate() returned %d\n", status);
_pdfioFileError(st->pdf, "Unable to decompress stream data for object %ld: %s", (long)st->obj->number, zstrerror(status));
return (-1);
}
bytes = (size_t)st->flate.avail_out;
}
else
{
// LZW decompress
if (st->lzw->avail_in == 0)
{
// Read more from the file...
if ((rbytes = stream_get_bytes(st, st->cbuffer, st->cbsize)) <= 0)
return (-1); // End of file...
st->lzw->next_in = st->cbuffer;
st->lzw->avail_in = (size_t)rbytes;
}
if (!_pdfioLZWInflate(st->lzw) && !st->lzw->saw_eod)
{
_pdfioFileError(st->pdf, "Unable to decompress stream data for object %ld: %s", (long)st->obj->number, st->lzw->error);
return (-1);
}
bytes = st->lzw->avail_out;
}
}
while (bytes > 0 && exactly);
if (st->filter == PDFIO_FILTER_FLATE)
return (st->flate.next_out - (Bytef *)buffer);
else
return (st->lzw->next_out - (uint8_t *)buffer);
}
//
// 'stream_paeth()' - PaethPredictor function for PNG decompression filter.
//
@@ -1038,67 +1346,20 @@ stream_read(pdfio_stream_t *st, // I - Stream
char *buffer, // I - Buffer
size_t bytes) // I - Number of bytes to read
{
ssize_t rbytes; // Bytes read
uInt avail_in, avail_out; // Previous flate values
if (st->filter == PDFIO_FILTER_NONE)
{
// No filtering, but limit reads to the length of the stream...
if (bytes > st->remaining)
rbytes = _pdfioFileRead(st->pdf, buffer, st->remaining);
else
rbytes = _pdfioFileRead(st->pdf, buffer, bytes);
if (rbytes > 0)
{
st->remaining -= (size_t)rbytes;
if (st->crypto_cb)
(st->crypto_cb)(&st->crypto_ctx, (uint8_t *)buffer, (uint8_t *)buffer, (size_t)rbytes);
}
return (rbytes);
// No filtering...
return (stream_get_bytes(st, buffer, bytes));
}
else if (st->filter == PDFIO_FILTER_FLATE)
else if (st->filter == PDFIO_FILTER_FLATE || st->filter == PDFIO_FILTER_LZW)
{
// Deflate compression...
int status; // Status of decompression
// Flate or LZW compression...
if (st->predictor == _PDFIO_PREDICTOR_NONE)
{
// Decompress into the buffer...
PDFIO_DEBUG("stream_read: No predictor.\n");
if (st->flate.avail_in == 0)
{
// Read more from the file...
if (st->cbsize > st->remaining)
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining);
else
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->cbsize);
if (rbytes <= 0)
return (-1); // End of file...
if (st->crypto_cb)
rbytes = (ssize_t)(st->crypto_cb)(&st->crypto_ctx, st->cbuffer, st->cbuffer, (size_t)rbytes);
st->remaining -= (size_t)rbytes;
st->flate.next_in = (Bytef *)st->cbuffer;
st->flate.avail_in = (uInt)rbytes;
}
st->flate.next_out = (Bytef *)buffer;
st->flate.avail_out = (uInt)bytes;
if ((status = inflate(&(st->flate), Z_NO_FLUSH)) < Z_OK)
{
_pdfioFileError(st->pdf, "Unable to decompress stream data for object %ld: %s", (long)st->obj->number, zstrerror(status));
return (-1);
}
return (st->flate.next_out - (Bytef *)buffer);
return (stream_inflate(st, (uint8_t *)buffer, bytes, /*exactly*/false));
}
else if (st->predictor == _PDFIO_PREDICTOR_TIFF2)
{
@@ -1106,9 +1367,9 @@ stream_read(pdfio_stream_t *st, // I - Stream
// Size of pixel in bytes
remaining = st->pbsize;
// Remaining bytes
unsigned char *bufptr = (unsigned char *)buffer,
uint8_t *bufptr = (uint8_t *)buffer,
// Pointer into buffer
*bufsecond = (unsigned char *)buffer + pbpixel,
*bufsecond = (uint8_t *)buffer + pbpixel,
// Pointer to second pixel in buffer
*sptr = st->psbuffer;
// Current (raw) line
@@ -1121,43 +1382,7 @@ stream_read(pdfio_stream_t *st, // I - Stream
return (-1);
}
st->flate.next_out = (Bytef *)sptr;
st->flate.avail_out = (uInt)st->pbsize;
while (st->flate.avail_out > 0)
{
if (st->flate.avail_in == 0)
{
// Read more from the file...
if (st->cbsize > st->remaining)
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining);
else
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->cbsize);
if (rbytes <= 0)
return (-1); // End of file...
if (st->crypto_cb)
rbytes = (ssize_t)(st->crypto_cb)(&st->crypto_ctx, st->cbuffer, st->cbuffer, (size_t)rbytes);
st->remaining -= (size_t)rbytes;
st->flate.next_in = (Bytef *)st->cbuffer;
st->flate.avail_in = (uInt)rbytes;
}
avail_in = st->flate.avail_in;
avail_out = st->flate.avail_out;
if ((status = inflate(&(st->flate), Z_NO_FLUSH)) < Z_OK)
{
_pdfioFileError(st->pdf, "Unable to decompress stream data for object %ld: %s", (long)st->obj->number, zstrerror(status));
return (-1);
}
else if (status == Z_STREAM_END || (avail_in == st->flate.avail_in && avail_out == st->flate.avail_out))
break;
}
if (st->flate.avail_out > 0)
if (stream_inflate(st, sptr, st->pbsize, /*exactly*/true) < 0)
return (-1); // Early end of stream
for (; bufptr < bufsecond; remaining --, sptr ++)
@@ -1174,9 +1399,9 @@ stream_read(pdfio_stream_t *st, // I - Stream
// Size of pixel in bytes
remaining = st->pbsize - 1;
// Remaining bytes
unsigned char *bufptr = (unsigned char *)buffer,
uint8_t *bufptr = (uint8_t *)buffer,
// Pointer into buffer
*bufsecond = (unsigned char *)buffer + pbpixel,
*bufsecond = (uint8_t *)buffer + pbpixel,
// Pointer to second pixel in buffer
*sptr = st->psbuffer + 1,
// Current (raw) line
@@ -1191,46 +1416,10 @@ stream_read(pdfio_stream_t *st, // I - Stream
return (-1);
}
st->flate.next_out = (Bytef *)sptr - 1;
st->flate.avail_out = (uInt)st->pbsize;
while (st->flate.avail_out > 0)
{
if (st->flate.avail_in == 0)
{
// Read more from the file...
if (st->cbsize > st->remaining)
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining);
else
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->cbsize);
if (rbytes <= 0)
return (-1); // End of file...
if (st->crypto_cb)
rbytes = (ssize_t)(st->crypto_cb)(&st->crypto_ctx, st->cbuffer, st->cbuffer, (size_t)rbytes);
st->remaining -= (size_t)rbytes;
st->flate.next_in = (Bytef *)st->cbuffer;
st->flate.avail_in = (uInt)rbytes;
}
avail_in = st->flate.avail_in;
avail_out = st->flate.avail_out;
if ((status = inflate(&(st->flate), Z_NO_FLUSH)) < Z_OK)
{
_pdfioFileError(st->pdf, "Unable to decompress stream data for object %ld: %s", (long)st->obj->number, zstrerror(status));
return (-1);
}
else if (status == Z_STREAM_END || (avail_in == st->flate.avail_in && avail_out == st->flate.avail_out))
break;
}
if (st->flate.avail_out > 0)
if (stream_inflate(st, sptr - 1, st->pbsize, /*exactly*/true) < 0)
{
// Early end of stream
PDFIO_DEBUG("stream_read: Early EOF (remaining=%u, avail_in=%d, avail_out=%d, data_type=%d, next_in=<%02X%02X%02X%02X...>).\n", (unsigned)st->remaining, st->flate.avail_in, st->flate.avail_out, st->flate.data_type, st->flate.next_in[0], st->flate.next_in[1], st->flate.next_in[2], st->flate.next_in[3]);
PDFIO_DEBUG("stream_read: Early EOF (remaining=%u).\n", (unsigned)st->remaining);
return (-1);
}
@@ -1333,8 +1522,6 @@ stream_write(pdfio_stream_t *st, // I - Stream
outbytes = cbytes;
}
// fprintf(stderr, "stream_write: bytes=%u, outbytes=%u\n", (unsigned)bytes, (unsigned)outbytes);
if (!_pdfioFileWrite(st->pdf, st->cbuffer, outbytes))
return (false);

View File

@@ -1,7 +1,7 @@
//
// PDF string functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -229,7 +229,7 @@ _pdfio_utf16cpy(
else
{
// 4-byte UTF-8
*dstptr++ = (char)(0xe0 | (ch >> 18));
*dstptr++ = (char)(0xf0 | (ch >> 18));
*dstptr++ = (char)(0x80 | ((ch >> 12) & 0x3f));
*dstptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
*dstptr++ = (char)(0x80 | (ch & 0x3f));
@@ -674,7 +674,8 @@ _pdfio_vsnprintf(pdfio_file_t *pdf, // I - PDF file
char * // O - Buffer or `NULL` on error
_pdfioStringAllocBuffer(
pdfio_file_t *pdf) // I - PDF file
pdfio_file_t *pdf, // I - PDF file
_pdfio_strbuf_t **bptr) // O - String buffer pointer
{
_pdfio_strbuf_t *current; // Current string buffer
@@ -683,21 +684,34 @@ _pdfioStringAllocBuffer(
for (current = pdf->strbuffers; current; current = current->next)
{
if (!current->bufused)
{
current->bufused = true;
return (current->buffer);
}
goto done;
}
// Didn't find one, allocate a new one...
if ((current = calloc(1, sizeof(_pdfio_strbuf_t))) == NULL)
{
if (bptr)
*bptr = NULL;
return (NULL);
}
// Add to the linked list of string buffers...
current->next = pdf->strbuffers;
current->pdf = pdf;
current->next = pdf->strbuffers;
pdf->strbuffers = current;
// Claim and return the free string buffer...
done:
current->bufused = true;
pdf->strbuffers = current;
if (bptr)
{
*bptr = current;
current->buffer[0] = '\0';
current->bufptr = current->buffer;
}
return (current->buffer);
}
@@ -857,6 +871,36 @@ _pdfioStringIsAllocated(
}
//
// '_pdfioStringPrintf()' - Append a formatted string to a string buffer.
//
bool // O - `true` on success, `false` on failure
_pdfioStringPrintf(
_pdfio_strbuf_t *bptr, // I - String buffer
const char *format, // I - Format string
...) // I - Additional arguments as needed
{
va_list ap; // Pointer to additional arguments
size_t remaining; // Remaining bytes
ssize_t bytes; // Formatted bytes
// Format the string in the buffer...
va_start(ap, format);
remaining = sizeof(bptr->buffer) - (size_t)(bptr->bufptr - bptr->buffer);
bytes = _pdfio_vsnprintf(bptr->pdf, bptr->bufptr, remaining, format, ap);
va_end(ap);
// Advance the current position in the buffer and return.
bptr->bufptr += strlen(bptr->bufptr);
return (bytes < (ssize_t)remaining && bytes >= 0);
}
//
// 'find_string()' - Find an element in the array.
//

View File

@@ -397,7 +397,7 @@ _pdfioTokenRead(_pdfio_token_t *tb, // I - Token buffer/stack
{
// UTF-16 string, convert to UTF-8...
PDFIO_DEBUG("_pdfioTokenRead: Converting string to UTF-8.\n", stderr);
_pdfio_utf16cpy(buffer + 1, (unsigned char *)buffer + 1, bufptr - buffer - 1, bufsize - 1);
_pdfio_utf16cpy(buffer + 1, (unsigned char *)buffer + 1, (size_t)(bufptr - buffer - 1), bufsize - 1);
PDFIO_DEBUG("_pdfioTokenRead: Read '%s'.\n", buffer);
return (true);

View File

@@ -1,7 +1,7 @@
//
// PDF value functions for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -76,7 +76,8 @@ _pdfioValueCopy(pdfio_file_t *pdfdst, // I - Destination PDF file
return (NULL);
case PDFIO_VALTYPE_ARRAY :
vdst->value.array = pdfioArrayCopy(pdfdst, vsrc->value.array);
if ((vdst->value.array = pdfioArrayCopy(pdfdst, vsrc->value.array)) == NULL)
return (NULL);
break;
case PDFIO_VALTYPE_BINARY :
@@ -97,12 +98,14 @@ _pdfioValueCopy(pdfio_file_t *pdfdst, // I - Destination PDF file
return (vdst);
case PDFIO_VALTYPE_DICT :
vdst->value.dict = pdfioDictCopy(pdfdst, vsrc->value.dict);
if ((vdst->value.dict = pdfioDictCopy(pdfdst, vsrc->value.dict)) == NULL)
return (NULL);
break;
case PDFIO_VALTYPE_NAME :
case PDFIO_VALTYPE_STRING :
vdst->value.name = pdfioStringCreate(pdfdst, vsrc->value.name);
if ((vdst->value.name = pdfioStringCreate(pdfdst, vsrc->value.name)) == NULL)
return (NULL);
break;
}
@@ -157,7 +160,7 @@ _pdfioValueDecrypt(pdfio_file_t *pdf, // I - PDF file
_pdfioFileError(pdf, "Unable to read encrypted binary string - too long.");
return (false);
}
else if ((temp = (uint8_t *)_pdfioStringAllocBuffer(pdf)) == NULL)
else if ((temp = (uint8_t *)_pdfioStringAllocBuffer(pdf, /*bptr*/NULL)) == NULL)
{
_pdfioFileError(pdf, "Unable to read encrypted binary string - out of memory.");
return (false);
@@ -188,7 +191,7 @@ _pdfioValueDecrypt(pdfio_file_t *pdf, // I - PDF file
_pdfioFileError(pdf, "Unable to read encrypted string - too long.");
return (false);
}
else if ((temp = (uint8_t *)_pdfioStringAllocBuffer(pdf)) == NULL)
else if ((temp = (uint8_t *)_pdfioStringAllocBuffer(pdf, /*bptr*/NULL)) == NULL)
{
_pdfioFileError(pdf, "Unable to read encrypted binary string - out of memory.");
return (false);
@@ -338,7 +341,7 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file
size_t depth) // I - Depth of value
{
_pdfio_value_t *ret = NULL; // Return value
char *token = _pdfioStringAllocBuffer(pdf);
char *token = _pdfioStringAllocBuffer(pdf, /*bptr*/NULL);
// Token buffer
time_t timeval; // Date/time value
#ifdef DEBUG
@@ -600,10 +603,12 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file
//
bool // O - `true` on success, `false` on failure
_pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
pdfio_obj_t *obj, // I - Object, if any
_pdfio_value_t *v, // I - Value
off_t *length)// O - Offset to /Length value, if any
_pdfioValueWrite(
_pdfio_printf_t cb, // I - Printf callback function
void *cbdata, // I - Printf callback data
pdfio_obj_t *obj, // I - Object, if any
_pdfio_value_t *v, // I - Value
off_t *length) // O - Offset to /Length value, if any
{
switch (v->type)
{
@@ -611,7 +616,7 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
return (false);
case PDFIO_VALTYPE_ARRAY :
return (_pdfioArrayWrite(v->value.array, obj));
return (_pdfioArrayWrite(cb, cbdata, obj, v->value.array));
case PDFIO_VALTYPE_BINARY :
{
@@ -621,26 +626,26 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
bool ret = false; // Return value
if (obj && pdf->encryption)
if (obj && obj->pdf->encryption)
{
// Write encrypted string...
_pdfio_crypto_ctx_t ctx; // Encryption context
_pdfio_crypto_cb_t cb; // Encryption callback
_pdfio_crypto_cb_t ccb; // Encryption callback
size_t ivlen; // Number of initialization vector bytes
if (v->value.binary.datalen > PDFIO_MAX_STRING)
{
_pdfioFileError(pdf, "Unable to write encrypted binary string - too long.");
_pdfioFileError(obj->pdf, "Unable to write encrypted binary string - too long.");
return (false);
}
else if ((temp = (uint8_t *)_pdfioStringAllocBuffer(pdf)) == NULL)
else if ((temp = (uint8_t *)_pdfioStringAllocBuffer(obj->pdf, /*bptr*/NULL)) == NULL)
{
_pdfioFileError(pdf, "Unable to write encrypted binary string - out of memory.");
_pdfioFileError(obj->pdf, "Unable to write encrypted binary string - out of memory.");
return (false);
}
cb = _pdfioCryptoMakeWriter(pdf, obj, &ctx, temp, &ivlen);
databytes = (cb)(&ctx, temp + ivlen, v->value.binary.data, v->value.binary.datalen) + ivlen;
ccb = _pdfioCryptoMakeWriter(obj->pdf, obj, &ctx, temp, &ivlen);
databytes = (ccb)(&ctx, temp + ivlen, v->value.binary.data, v->value.binary.datalen) + ivlen;
dataptr = temp;
}
else
@@ -649,33 +654,33 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
databytes = v->value.binary.datalen;
}
if (!_pdfioFilePuts(pdf, "<"))
if (!(cb)(cbdata, "<"))
goto bindone;
for (; databytes > 1; databytes -= 2, dataptr += 2)
{
if (!_pdfioFilePrintf(pdf, "%02X%02X", dataptr[0], dataptr[1]))
if (!(cb)(cbdata, "%02X%02X", dataptr[0], dataptr[1]))
goto bindone;
}
if (databytes > 0 && !_pdfioFilePrintf(pdf, "%02X", dataptr[0]))
if (databytes > 0 && !(cb)(cbdata, "%02X", dataptr[0]))
goto bindone;
ret = _pdfioFilePuts(pdf, ">");
ret = (cb)(cbdata, ">");
bindone:
if (temp)
_pdfioStringFreeBuffer(pdf, (char *)temp);
_pdfioStringFreeBuffer(obj->pdf, (char *)temp);
return (ret);
}
case PDFIO_VALTYPE_BOOLEAN :
if (v->value.boolean)
return (_pdfioFilePuts(pdf, " true"));
return ((cb)(cbdata, " true"));
else
return (_pdfioFilePuts(pdf, " false"));
return ((cb)(cbdata, " false"));
case PDFIO_VALTYPE_DATE :
{
@@ -690,64 +695,64 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
snprintf(datestr, sizeof(datestr), "D:%04d%02d%02d%02d%02d%02dZ", date.tm_year + 1900, date.tm_mon + 1, date.tm_mday, date.tm_hour, date.tm_min, date.tm_sec);
if (obj && pdf->encryption)
if (obj && obj->pdf->encryption)
{
// Write encrypted string...
uint8_t temp[64], // Encrypted bytes
*tempptr; // Pointer into encrypted bytes
_pdfio_crypto_ctx_t ctx; // Encryption context
_pdfio_crypto_cb_t cb; // Encryption callback
_pdfio_crypto_cb_t ccb; // Encryption callback
size_t len = strlen(datestr),
// Length of value
ivlen, // Number of initialization vector bytes
tempbytes; // Number of output bytes
cb = _pdfioCryptoMakeWriter(pdf, obj, &ctx, temp, &ivlen);
tempbytes = (cb)(&ctx, temp + ivlen, (const uint8_t *)datestr, len) + ivlen;
ccb = _pdfioCryptoMakeWriter(obj->pdf, obj, &ctx, temp, &ivlen);
tempbytes = (ccb)(&ctx, temp + ivlen, (const uint8_t *)datestr, len) + ivlen;
if (!_pdfioFilePuts(pdf, "<"))
if (!(cb)(cbdata, "<"))
return (false);
for (tempptr = temp; tempbytes > 1; tempbytes -= 2, tempptr += 2)
{
if (!_pdfioFilePrintf(pdf, "%02X%02X", tempptr[0], tempptr[1]))
if (!(cb)(cbdata, "%02X%02X", tempptr[0], tempptr[1]))
return (false);
}
if (tempbytes > 0)
return (_pdfioFilePrintf(pdf, "%02X>", *tempptr));
return ((cb)(cbdata, "%02X>", *tempptr));
else
return (_pdfioFilePuts(pdf, ">"));
return ((cb)(cbdata, ">"));
}
else
{
return (_pdfioFilePrintf(pdf, "%S", datestr));
return ((cb)(cbdata, "%S", datestr));
}
}
case PDFIO_VALTYPE_DICT :
return (_pdfioDictWrite(v->value.dict, obj, length));
return (_pdfioDictWrite(cb, cbdata, obj, v->value.dict, length));
case PDFIO_VALTYPE_INDIRECT :
return (_pdfioFilePrintf(pdf, " %lu %u R", (unsigned long)v->value.indirect.number, v->value.indirect.generation));
return ((cb)(cbdata, " %lu %u R", (unsigned long)v->value.indirect.number, v->value.indirect.generation));
case PDFIO_VALTYPE_NAME :
return (_pdfioFilePrintf(pdf, "%N", v->value.name));
return ((cb)(cbdata, "%N", v->value.name));
case PDFIO_VALTYPE_NULL :
return (_pdfioFilePuts(pdf, " null"));
return ((cb)(cbdata, " null"));
case PDFIO_VALTYPE_NUMBER :
return (_pdfioFilePrintf(pdf, " %.6f", v->value.number));
return ((cb)(cbdata, " %.6f", v->value.number));
case PDFIO_VALTYPE_STRING :
if (obj && pdf->encryption)
if (obj && obj->pdf->encryption)
{
// Write encrypted string...
uint8_t *temp = NULL, // Encrypted bytes
*tempptr; // Pointer into encrypted bytes
_pdfio_crypto_ctx_t ctx; // Encryption context
_pdfio_crypto_cb_t cb; // Encryption callback
_pdfio_crypto_cb_t ccb; // Encryption callback
size_t len = strlen(v->value.string),
// Length of value
ivlen, // Number of initialization vector bytes
@@ -756,42 +761,42 @@ _pdfioValueWrite(pdfio_file_t *pdf, // I - PDF file
if (len > PDFIO_MAX_STRING)
{
_pdfioFileError(pdf, "Unable to write encrypted string - too long.");
_pdfioFileError(obj->pdf, "Unable to write encrypted string - too long.");
return (false);
}
else if ((temp = (uint8_t *)_pdfioStringAllocBuffer(pdf)) == NULL)
else if ((temp = (uint8_t *)_pdfioStringAllocBuffer(obj->pdf, /*bptr*/NULL)) == NULL)
{
_pdfioFileError(pdf, "Unable to write encrypted string - out of memory.");
_pdfioFileError(obj->pdf, "Unable to write encrypted string - out of memory.");
return (false);
}
cb = _pdfioCryptoMakeWriter(pdf, obj, &ctx, temp, &ivlen);
tempbytes = (cb)(&ctx, temp + ivlen, (const uint8_t *)v->value.string, len) + ivlen;
ccb = _pdfioCryptoMakeWriter(obj->pdf, obj, &ctx, temp, &ivlen);
tempbytes = (ccb)(&ctx, temp + ivlen, (const uint8_t *)v->value.string, len) + ivlen;
if (!_pdfioFilePuts(pdf, "<"))
if (!(cb)(cbdata, "<"))
goto strdone;
for (tempptr = temp; tempbytes > 1; tempbytes -= 2, tempptr += 2)
{
if (!_pdfioFilePrintf(pdf, "%02X%02X", tempptr[0], tempptr[1]))
if (!(cb)(cbdata, "%02X%02X", tempptr[0], tempptr[1]))
goto strdone;
}
if (tempbytes > 0 && !_pdfioFilePrintf(pdf, "%02X", *tempptr))
if (tempbytes > 0 && !(cb)(cbdata, "%02X", *tempptr))
goto strdone;
ret = _pdfioFilePuts(pdf, ">");
ret = (cb)(cbdata, ">");
strdone :
_pdfioStringFreeBuffer(pdf, (char *)temp);
_pdfioStringFreeBuffer(obj->pdf, (char *)temp);
return (ret);
}
else
{
// Write unencrypted string...
return (_pdfioFilePrintf(pdf, "%S", v->value.string));
return ((cb)(cbdata, "%S", v->value.string));
}
}

20
pdfio.h
View File

@@ -1,7 +1,7 @@
//
// Public header file for PDFio.
//
// Copyright © 2021-2025 by Michael R Sweet.
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@@ -23,9 +23,9 @@ extern "C" {
// Version numbers...
//
# define PDFIO_VERSION "1.6.0"
# define PDFIO_VERSION "1.7.0"
# define PDFIO_VERSION_MAJOR 1
# define PDFIO_VERSION_MINOR 6
# define PDFIO_VERSION_MINOR 7
//
@@ -72,11 +72,11 @@ typedef enum pdfio_filter_e // Compression/decompression filters for streams
PDFIO_FILTER_NONE, // No filter
PDFIO_FILTER_ASCIIHEX, // ASCIIHexDecode filter (reading only)
PDFIO_FILTER_ASCII85, // ASCII85Decode filter (reading only)
PDFIO_FILTER_CCITTFAX, // CCITTFaxDecode filter
PDFIO_FILTER_CCITTFAX, // CCITTFaxDecode filter (reading only)
PDFIO_FILTER_CRYPT, // Encryption filter
PDFIO_FILTER_DCT, // DCTDecode (JPEG) filter
PDFIO_FILTER_FLATE, // FlateDecode filter
PDFIO_FILTER_JBIG2, // JBIG2Decode filter
PDFIO_FILTER_JBIG2, // JBIG2Decode filter (reading only)
PDFIO_FILTER_JPX, // JPXDecode filter (reading only)
PDFIO_FILTER_LZW, // LZWDecode filter (reading only)
PDFIO_FILTER_RUNLENGTH, // RunLengthDecode filter (reading only)
@@ -238,7 +238,17 @@ extern const char *pdfioObjGetType(pdfio_obj_t *obj) _PDFIO_PUBLIC;
extern pdfio_stream_t *pdfioObjOpenStream(pdfio_obj_t *obj, bool decode) _PDFIO_PUBLIC;
extern bool pdfioPageCopy(pdfio_file_t *pdf, pdfio_obj_t *srcpage) _PDFIO_PUBLIC;
extern pdfio_array_t *pdfioPageGetArray(pdfio_obj_t *page, const char *key) _PDFIO_PUBLIC;
extern unsigned char *pdfioPageGetBinary(pdfio_obj_t *page, const char *key, size_t *length) _PDFIO_PUBLIC;
extern bool pdfioPageGetBoolean(pdfio_obj_t *page, const char *key) _PDFIO_PUBLIC;
extern time_t pdfioPageGetDate(pdfio_obj_t *page, const char *key) _PDFIO_PUBLIC;
extern pdfio_dict_t *pdfioPageGetDict(pdfio_obj_t *page, const char *key) _PDFIO_PUBLIC;
extern const char *pdfioPageGetName(pdfio_obj_t *page, const char *key) _PDFIO_PUBLIC;
extern size_t pdfioPageGetNumStreams(pdfio_obj_t *page) _PDFIO_PUBLIC;
extern double pdfioPageGetNumber(pdfio_obj_t *page, const char *key) _PDFIO_PUBLIC;
extern pdfio_obj_t *pdfioPageGetObj(pdfio_obj_t *page, const char *key) _PDFIO_PUBLIC;
extern pdfio_rect_t *pdfioPageGetRect(pdfio_obj_t *page, const char *key, pdfio_rect_t *rect) _PDFIO_PUBLIC;
extern const char *pdfioPageGetString(pdfio_obj_t *page, const char *key) _PDFIO_PUBLIC;
extern pdfio_stream_t *pdfioPageOpenStream(pdfio_obj_t *page, size_t n, bool decode) _PDFIO_PUBLIC;
extern bool pdfioStreamClose(pdfio_stream_t *st) _PDFIO_PUBLIC;

View File

@@ -11,3 +11,4 @@ Cflags: @PKGCONFIG_CFLAGS@
Libs: @PKGCONFIG_LIBS@
Libs.private: @PKGCONFIG_LIBS_PRIVATE@
Requires: @PKGCONFIG_REQUIRES@
Requires.private: @PKGCONFIG_REQUIRES_PRIVATE@

View File

@@ -87,6 +87,7 @@
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>ttf;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
@@ -101,6 +102,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>ttf;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
@@ -115,6 +117,7 @@
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>ttf;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>HAVE_LIBPNG;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
@@ -130,6 +133,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>ttf;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>HAVE_LIBPNG;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
@@ -145,7 +149,8 @@
<ClInclude Include="pdfio-content.h" />
<ClInclude Include="pdfio-private.h" />
<ClInclude Include="pdfio.h" />
<ClInclude Include="ttf.h" />
<ClInclude Include="ttf\ttf.h" />
<ClInclude Include="ttf\ttf-private.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="pdfio-aes.c" />
@@ -155,6 +160,7 @@
<ClCompile Include="pdfio-crypto.c" />
<ClCompile Include="pdfio-dict.c" />
<ClCompile Include="pdfio-file.c" />
<ClCompile Include="pdfio-lzw.c" />
<ClCompile Include="pdfio-md5.c" />
<ClCompile Include="pdfio-object.c" />
<ClCompile Include="pdfio-page.c" />
@@ -164,7 +170,8 @@
<ClCompile Include="pdfio-string.c" />
<ClCompile Include="pdfio-token.c" />
<ClCompile Include="pdfio-value.c" />
<ClCompile Include="ttf.c" />
<ClCompile Include="ttf\ttf-cache.c" />
<ClCompile Include="ttf\ttf-file.c" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -22,8 +22,11 @@
273440CD263D727800FBFD63 /* pdfio-page.c in Sources */ = {isa = PBXBuildFile; fileRef = 273440C2263D727800FBFD63 /* pdfio-page.c */; };
273440D8263D72E100FBFD63 /* testpdfio.c in Sources */ = {isa = PBXBuildFile; fileRef = 273440D7263D72E100FBFD63 /* testpdfio.c */; };
273440E4263DD7EA00FBFD63 /* pdfio-token.c in Sources */ = {isa = PBXBuildFile; fileRef = 273440E3263DD7EA00FBFD63 /* pdfio-token.c */; };
279E1035267D043B00D3A349 /* ttf.h in Headers */ = {isa = PBXBuildFile; fileRef = 279E1033267D043B00D3A349 /* ttf.h */; };
279E1036267D043B00D3A349 /* ttf.c in Sources */ = {isa = PBXBuildFile; fileRef = 279E1034267D043B00D3A349 /* ttf.c */; };
273707A52F299C3100953C95 /* pdfio-lzw.c in Sources */ = {isa = PBXBuildFile; fileRef = 273707A42F299C3100953C95 /* pdfio-lzw.c */; };
2741C9A22F05872C002D93F2 /* ttf-cache.c in Sources */ = {isa = PBXBuildFile; fileRef = 2741C99F2F05872C002D93F2 /* ttf-cache.c */; };
2741C9A32F05872C002D93F2 /* ttf-file.c in Sources */ = {isa = PBXBuildFile; fileRef = 2741C9A02F05872C002D93F2 /* ttf-file.c */; };
2741C9A42F05872C002D93F2 /* ttf-private.h in Headers */ = {isa = PBXBuildFile; fileRef = 2741C9A12F05872C002D93F2 /* ttf-private.h */; };
2741C9A52F05872C002D93F2 /* ttf.h in Headers */ = {isa = PBXBuildFile; fileRef = 2741C99E2F05872C002D93F2 /* ttf.h */; };
279E103B267D04E600D3A349 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 279E103A267D04E600D3A349 /* libz.tbd */; };
27CF90442711DFFE00E50FE4 /* pdfio-aes.c in Sources */ = {isa = PBXBuildFile; fileRef = 27CF90432711DFFE00E50FE4 /* pdfio-aes.c */; };
27ECBD8926419DAB0025312A /* libpdfio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 273440B0263D6FE200FBFD63 /* libpdfio.a */; };
@@ -82,8 +85,11 @@
273440E1263D73A300FBFD63 /* pdfio.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = pdfio.html; path = doc/pdfio.html; sourceTree = "<group>"; };
273440E2263D73A300FBFD63 /* pdfio.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = pdfio.3; path = doc/pdfio.3; sourceTree = "<group>"; };
273440E3263DD7EA00FBFD63 /* pdfio-token.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "pdfio-token.c"; sourceTree = "<group>"; };
279E1033267D043B00D3A349 /* ttf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ttf.h; sourceTree = "<group>"; };
279E1034267D043B00D3A349 /* ttf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ttf.c; sourceTree = "<group>"; };
273707A42F299C3100953C95 /* pdfio-lzw.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "pdfio-lzw.c"; sourceTree = "<group>"; };
2741C99E2F05872C002D93F2 /* ttf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ttf.h; path = ttf/ttf.h; sourceTree = "<group>"; };
2741C99F2F05872C002D93F2 /* ttf-cache.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = "ttf-cache.c"; path = "ttf/ttf-cache.c"; sourceTree = "<group>"; };
2741C9A02F05872C002D93F2 /* ttf-file.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = "ttf-file.c"; path = "ttf/ttf-file.c"; sourceTree = "<group>"; };
2741C9A12F05872C002D93F2 /* ttf-private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "ttf-private.h"; path = "ttf/ttf-private.h"; sourceTree = "<group>"; };
279E103A267D04E600D3A349 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
27CF90432711DFFE00E50FE4 /* pdfio-aes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "pdfio-aes.c"; sourceTree = "<group>"; };
27F2F05D2710BE92008ECD36 /* pdfio-md5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "pdfio-md5.c"; sourceTree = "<group>"; };
@@ -170,6 +176,7 @@
27F2F05F2710BE92008ECD36 /* pdfio-crypto.c */,
273440BE263D727800FBFD63 /* pdfio-dict.c */,
273440BD263D727800FBFD63 /* pdfio-file.c */,
273707A42F299C3100953C95 /* pdfio-lzw.c */,
27F2F05D2710BE92008ECD36 /* pdfio-md5.c */,
273440BC263D727800FBFD63 /* pdfio-object.c */,
273440C2263D727800FBFD63 /* pdfio-page.c */,
@@ -179,8 +186,10 @@
273440B9263D727800FBFD63 /* pdfio-string.c */,
273440E3263DD7EA00FBFD63 /* pdfio-token.c */,
273440C0263D727800FBFD63 /* pdfio-value.c */,
279E1034267D043B00D3A349 /* ttf.c */,
279E1033267D043B00D3A349 /* ttf.h */,
2741C99E2F05872C002D93F2 /* ttf.h */,
2741C99F2F05872C002D93F2 /* ttf-cache.c */,
2741C9A02F05872C002D93F2 /* ttf-file.c */,
2741C9A12F05872C002D93F2 /* ttf-private.h */,
);
name = Library;
sourceTree = "<group>";
@@ -209,10 +218,11 @@
buildActionMask = 2147483647;
files = (
27FCBDE42D19F9B300485EEE /* pdfio-base-font-widths.h in Headers */,
2741C9A42F05872C002D93F2 /* ttf-private.h in Headers */,
2741C9A52F05872C002D93F2 /* ttf.h in Headers */,
273440CC263D727800FBFD63 /* pdfio.h in Headers */,
271EA706265B2B1000ACDD39 /* pdfio-content.h in Headers */,
273440C3263D727800FBFD63 /* pdfio-private.h in Headers */,
279E1035267D043B00D3A349 /* ttf.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -295,14 +305,16 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
279E1036267D043B00D3A349 /* ttf.c in Sources */,
273440C9263D727800FBFD63 /* pdfio-dict.c in Sources */,
273440C8263D727800FBFD63 /* pdfio-file.c in Sources */,
273440CB263D727800FBFD63 /* pdfio-value.c in Sources */,
273707A52F299C3100953C95 /* pdfio-lzw.c in Sources */,
273440CA263D727800FBFD63 /* pdfio-stream.c in Sources */,
273440CD263D727800FBFD63 /* pdfio-page.c in Sources */,
27F2F0622710BE92008ECD36 /* pdfio-crypto.c in Sources */,
27F2F0642711243D008ECD36 /* pdfio-sha256.c in Sources */,
2741C9A22F05872C002D93F2 /* ttf-cache.c in Sources */,
2741C9A32F05872C002D93F2 /* ttf-file.c in Sources */,
273440C5263D727800FBFD63 /* pdfio-array.c in Sources */,
273440E4263DD7EA00FBFD63 /* pdfio-token.c in Sources */,
273440C7263D727800FBFD63 /* pdfio-object.c in Sources */,
@@ -360,7 +372,7 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_IMPLICIT_FALLTHROUGH = YES;
CLANG_WARN_IMPLICIT_FALLTHROUGH = NO;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
@@ -398,7 +410,7 @@
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -460,7 +472,7 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_IMPLICIT_FALLTHROUGH = YES;
CLANG_WARN_IMPLICIT_FALLTHROUGH = NO;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
@@ -497,7 +509,7 @@
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;

View File

@@ -1,6 +1,7 @@
LIBRARY pdfio1
VERSION 1.6
VERSION 1.7
EXPORTS
_pdfio_win32_open
_pdfio_strlcpy
_pdfio_strtod
_pdfio_utf16cpy
@@ -51,6 +52,9 @@ _pdfioFileRead
_pdfioFileSeek
_pdfioFileTell
_pdfioFileWrite
_pdfioLZWCreate
_pdfioLZWDelete
_pdfioLZWInflate
_pdfioObjDelete
_pdfioObjGetExtension
_pdfioObjLoad
@@ -61,6 +65,7 @@ _pdfioStreamOpen
_pdfioStringAllocBuffer
_pdfioStringFreeBuffer
_pdfioStringIsAllocated
_pdfioStringPrintf
_pdfioTokenClear
_pdfioTokenFlush
_pdfioTokenGet
@@ -255,7 +260,17 @@ pdfioPageCopy
pdfioPageDictAddColorSpace
pdfioPageDictAddFont
pdfioPageDictAddImage
pdfioPageGetArray
pdfioPageGetBinary
pdfioPageGetBoolean
pdfioPageGetDate
pdfioPageGetDict
pdfioPageGetName
pdfioPageGetNumber
pdfioPageGetNumStreams
pdfioPageGetObj
pdfioPageGetRect
pdfioPageGetString
pdfioPageOpenStream
pdfioStreamClose
pdfioStreamConsume

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
#
# Script to test PDFio against a directory of PDF files.
#
# Copyright © 2025 by Michael R Sweet.
# Copyright © 2025-2026 by Michael R Sweet.
#
# Licensed under Apache License v2.0. See the file "LICENSE" for more
# information.
@@ -17,14 +17,28 @@ if test $# = 0; then
exit 1
fi
if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then
ac_n=-n
ac_c=
else
ac_n=
ac_c='\c'
fi
for file in $(find "$@" -name \*.pdf -print); do
# Run testpdfio to test loading the file...
./testpdfio $file >$file.log 2>&1
echo $ac_n "\r$file: $ac_c"
./testpdfio --verbose $file >/dev/null 2>$file.log
if test $? = 0; then
# Passed
echo PASS
rm -f $file.log
else
# Failed, preserve log and write filename to stdout...
echo $file
# Failed, preserve log and write to stdout...
echo FAIL
cat $file.log
echo ""
fi
done

View File

@@ -96,6 +96,16 @@ static int test_progress; // Current progress
static char test_title[1024] = ""; // Current test title
// Add printf syntax checking on supported compilers...
#if defined(__has_extension) || defined(__GNUC__)
# define TEST_FORMAT(a,b) __attribute__ ((__format__(__printf__,a,b)))
static inline void testBegin(const char *title, ...) TEST_FORMAT(1,2);
static inline void testEndMessage(bool pass, const char *message, ...) TEST_FORMAT(2,3);
static inline void testError(const char *error, ...) TEST_FORMAT(1,2);
static inline void testMessage(const char *error, ...) TEST_FORMAT(1,2);
#endif // __has_extension || __GNUC__
// Start a test
static inline void
testBegin(const char *title, ...) // I - printf-style title string

BIN
testfiles/color-8bit-i.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

BIN
testfiles/color.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
testfiles/gray-4bit.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
testfiles/gray.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
testfiles/pdfio-1bit.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
testfiles/pdfio-2bit.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
testfiles/pdfio-4bit-it.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
testfiles/pdfio-4bit.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
testfiles/pdfio-8bit-a.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
testfiles/pdfio-8bit-i.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
testfiles/pdfio-8bit-it.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
testfiles/pdfio-color.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
testfiles/pdfio-gray.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
testfiles/pdfio-rgba.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

File diff suppressed because it is too large Load Diff

396
testttf.c
View File

@@ -1,396 +0,0 @@
//
// Unit test program for TTF library
//
// https://github.com/michaelrsweet/ttf
//
// Copyright © 2018-2025 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
//
// Usage:
//
// ./testttf [FILENAME]
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include "ttf.h"
//
// Local functions...
//
static void error_cb(void *data, const char *message);
static int test_font(const char *filename);
//
// 'main()' - Main entry for unit tests.
//
int // O - Exit status
main(int argc, // I - Number of command-line arguments
char *argv[]) // I - Command-line arguments
{
int i; // Looping var
int errors = 0; // Number of errors
if (argc > 1)
{
for (i = 1; i < argc; i ++)
errors += test_font(argv[i]);
}
else
{
// Test with the bundled TrueType files...
errors += test_font("testfiles/OpenSans-Bold.ttf");
errors += test_font("testfiles/OpenSans-Regular.ttf");
errors += test_font("testfiles/NotoSansJP-Regular.otf");
}
if (!errors)
puts("\nALL TESTS PASSED");
else
printf("\n%d TEST(S) FAILED\n", errors);
return (errors);
}
//
// 'error_cb()' - Error callback.
//
static void
error_cb(void *data, // I - User data (not used)
const char *message) // I - Message string
{
fprintf(stderr, "FAIL (%s)\n", message);
}
//
// 'test_font()' - Test a font file.
//
static int // O - Number of errors
test_font(const char *filename) // I - Font filename
{
int i, // Looping var
errors = 0; // Number of errors
ttf_t *font; // Font
struct stat fileinfo; // Font file information
FILE *fp = NULL; // File pointer
void *data = NULL; // Memory buffer for font file
const char *value; // Font (string) value
int intvalue; // Font (integer) value
float realvalue; // Font (real) value
char psname[1024]; // Postscript font name
ttf_rect_t bounds; // Bounds
ttf_rect_t extents; // Extents
size_t num_fonts; // Number of fonts
ttf_style_t style; // Font style
ttf_weight_t weight; // Font weight
static const char * const stretches[] =
{ // Font stretch strings
"TTF_STRETCH_NORMAL", // normal
"TTF_STRETCH_ULTRA_CONDENSED", // ultra-condensed
"TTF_STRETCH_EXTRA_CONDENSED", // extra-condensed
"TTF_STRETCH_CONDENSED", // condensed
"TTF_STRETCH_SEMI_CONDENSED", // semi-condensed
"TTF_STRETCH_SEMI_EXPANDED", // semi-expanded
"TTF_STRETCH_EXPANDED", // expanded
"TTF_STRETCH_EXTRA_EXPANDED", // extra-expanded
"TTF_STRETCH_ULTRA_EXPANDED" // ultra-expanded
};
static const char * const strings[] = // Test strings
{
"Hello, World!", // English
"مرحبا بالعالم!", // Arabic
"Bonjour le monde!", // French
"Γειά σου Κόσμε!", // Greek
"שלום עולם!", // Hebrew
"Привет мир!", // Russian
"こんにちは世界!" // Japanese
};
static const char * const styles[] = // Font style names
{
"TTF_STYLE_NORMAL",
"TTF_STYLE_ITALIC",
"TTF_STYLE_OBLIQUE"
};
printf("ttfCreate(\"%s\"): ", filename);
fflush(stdout);
if ((font = ttfCreate(filename, 0, error_cb, NULL)) != NULL)
puts("PASS");
else
errors ++;
fputs("ttfGetAscent: ", stdout);
if ((intvalue = ttfGetAscent(font)) > 0)
{
printf("PASS (%d)\n", intvalue);
}
else
{
printf("FAIL (%d)\n", intvalue);
errors ++;
}
fputs("ttfGetBounds: ", stdout);
if (ttfGetBounds(font, &bounds))
{
printf("PASS (%g %g %g %g)\n", bounds.left, bounds.bottom, bounds.right, bounds.top);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetCapHeight: ", stdout);
if ((intvalue = ttfGetCapHeight(font)) > 0)
{
printf("PASS (%d)\n", intvalue);
}
else
{
printf("FAIL (%d)\n", intvalue);
errors ++;
}
fputs("ttfGetCopyright: ", stdout);
if ((value = ttfGetCopyright(font)) != NULL)
{
printf("PASS (%s)\n", value);
}
else
{
puts("WARNING (no copyright found)");
}
for (i = 0; i < (int)(sizeof(strings) / sizeof(strings[0])); i ++)
{
printf("ttfGetExtents(\"%s\"): ", strings[i]);
if (ttfGetExtents(font, 12.0f, strings[i], &extents))
{
printf("PASS (%.1f %.1f %.1f %.1f)\n", extents.left, extents.bottom, extents.right, extents.top);
}
else
{
puts("FAIL");
errors ++;
}
}
fputs("ttfGetFamily: ", stdout);
if ((value = ttfGetFamily(font)) != NULL)
{
printf("PASS (%s)\n", value);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetItalicAngle: ", stdout);
if ((realvalue = ttfGetItalicAngle(font)) >= -180.0 && realvalue <= 180.0)
{
printf("PASS (%g)\n", realvalue);
}
else
{
printf("FAIL (%g)\n", realvalue);
errors ++;
}
fputs("ttfGetNumFonts: ", stdout);
if ((num_fonts = ttfGetNumFonts(font)) > 0)
{
printf("PASS (%u)\n", (unsigned)num_fonts);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetPostScriptName: ", stdout);
if ((value = ttfGetPostScriptName(font)) != NULL)
{
printf("PASS (%s)\n", value);
strncpy(psname, value, sizeof(psname) - 1);
psname[sizeof(psname) - 1] = '\0';
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetStretch: ", stdout);
if ((intvalue = (int)ttfGetStretch(font)) >= TTF_STRETCH_NORMAL && intvalue <= TTF_STRETCH_ULTRA_EXPANDED)
{
printf("PASS (%s)\n", stretches[intvalue]);
}
else
{
printf("FAIL (%d)\n", intvalue);
errors ++;
}
fputs("ttfGetStyle: ", stdout);
if ((style = ttfGetStyle(font)) >= TTF_STYLE_NORMAL && style <= TTF_STYLE_ITALIC)
{
printf("PASS (%s)\n", styles[style]);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetVersion: ", stdout);
if ((value = ttfGetVersion(font)) != NULL)
{
printf("PASS (%s)\n", value);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetWeight: ", stdout);
if ((weight = ttfGetWeight(font)) >= 0)
{
printf("PASS (%u)\n", (unsigned)weight);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetWidth(' '): ", stdout);
if ((intvalue = ttfGetWidth(font, ' ')) > 0)
{
printf("PASS (%d)\n", intvalue);
}
else
{
printf("FAIL (%d)\n", intvalue);
errors ++;
}
fputs("ttfGetXHeight: ", stdout);
if ((intvalue = ttfGetXHeight(font)) > 0)
{
printf("PASS (%d)\n", intvalue);
}
else
{
printf("FAIL (%d)\n", intvalue);
errors ++;
}
fputs("ttfIsFixedPitch: ", stdout);
if (ttfIsFixedPitch(font))
puts("PASS (true)");
else
puts("PASS (false)");
ttfDelete(font);
font = NULL;
// Now copy the font to memory and open it that way...
printf("fopen(\"%s\", \"rb\"): ", filename);
if ((fp = fopen(filename, "rb")) == NULL)
{
printf("FAIL (%s)\n", strerror(errno));
errors ++;
}
else
{
printf("PASS (%d)\n", fileno(fp));
printf("fstat(%d): ", fileno(fp));
if (fstat(fileno(fp), &fileinfo))
{
printf("FAIL (%s)\n", strerror(errno));
errors ++;
}
else
{
printf("PASS (%lu bytes)\n", (unsigned long)fileinfo.st_size);
fputs("malloc(): ", stdout);
if ((data = malloc((size_t)fileinfo.st_size)) == NULL)
{
printf("FAIL (%s)\n", strerror(errno));
errors ++;
}
else
{
puts("PASS");
fputs("fread(): ", stdout);
if (fread(data, (size_t)fileinfo.st_size, 1, fp) != 1)
{
printf("FAIL (%s)\n", strerror(errno));
errors ++;
}
else
{
puts("PASS");
fputs("ttfCreateData(): ", stdout);
if ((font = ttfCreateData(data, (size_t)fileinfo.st_size, /*idx*/0, error_cb, /*err_data*/NULL)) == NULL)
{
puts("FAIL");
errors ++;
}
else
{
puts("PASS");
fputs("ttfGetPostScriptName: ", stdout);
if ((value = ttfGetPostScriptName(font)) != NULL)
{
if (!strcmp(value, psname))
{
printf("PASS (%s)\n", value);
}
else
{
printf("FAIL (got \"%s\", expected \"%s\")\n", value, psname);
errors ++;
}
}
else
{
puts("FAIL");
errors ++;
}
}
}
}
}
if (fp)
fclose(fp);
free(data);
ttfDelete(font);
}
return (errors);
}

1
ttf Submodule

Submodule ttf added at f2e6f45ab3

2280
ttf.c

File diff suppressed because it is too large Load Diff

111
ttf.h
View File

@@ -1,111 +0,0 @@
//
// Header file for TTF library
//
// https://github.com/michaelrsweet/ttf
//
// Copyright © 2018-2025 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
//
#ifndef TTF_H
# define TTF_H
# include <stddef.h>
# include <stdbool.h>
# include <sys/types.h>
# ifdef __cplusplus
extern "C" {
# endif // __cplusplus
//
// Types...
//
typedef struct _ttf_s ttf_t; // Font object
typedef void (*ttf_err_cb_t)(void *data, const char *message);
// Font error callback
typedef enum ttf_stretch_e // Font stretch
{
TTF_STRETCH_NORMAL, // normal
TTF_STRETCH_ULTRA_CONDENSED, // ultra-condensed
TTF_STRETCH_EXTRA_CONDENSED, // extra-condensed
TTF_STRETCH_CONDENSED, // condensed
TTF_STRETCH_SEMI_CONDENSED, // semi-condensed
TTF_STRETCH_SEMI_EXPANDED, // semi-expanded
TTF_STRETCH_EXPANDED, // expanded
TTF_STRETCH_EXTRA_EXPANDED, // extra-expanded
TTF_STRETCH_ULTRA_EXPANDED // ultra-expanded
} ttf_stretch_t;
typedef enum ttf_style_e // Font style
{
TTF_STYLE_NORMAL, // Normal font
TTF_STYLE_ITALIC, // Italic font
TTF_STYLE_OBLIQUE // Oblique (angled) font
} ttf_style_t;
typedef enum ttf_variant_e // Font variant
{
TTF_VARIANT_NORMAL, // Normal font
TTF_VARIANT_SMALL_CAPS // Font whose lowercase letters are small capitals
} ttf_variant_t;
typedef enum ttf_weight_e // Font weight
{
TTF_WEIGHT_100 = 100, // Weight 100 (Thin)
TTF_WEIGHT_200 = 200, // Weight 200 (Extra/Ultra-Light)
TTF_WEIGHT_300 = 300, // Weight 300 (Light)
TTF_WEIGHT_400 = 400, // Weight 400 (Normal/Regular)
TTF_WEIGHT_500 = 500, // Weight 500 (Medium)
TTF_WEIGHT_600 = 600, // Weight 600 (Semi/Demi-Bold)
TTF_WEIGHT_700 = 700, // Weight 700 (Bold)
TTF_WEIGHT_800 = 800, // Weight 800 (Extra/Ultra-Bold)
TTF_WEIGHT_900 = 900 // Weight 900 (Black/Heavy)
} ttf_weight_t;
typedef struct ttf_rect_s // Bounding rectangle
{
float left; // Left offset
float top; // Top offset
float right; // Right offset
float bottom; // Bottom offset
} ttf_rect_t;
//
// Functions...
//
extern ttf_t *ttfCreate(const char *filename, size_t idx, ttf_err_cb_t err_cb, void *err_data);
extern ttf_t *ttfCreateData(const void *data, size_t data_size, size_t idx, ttf_err_cb_t err_cb, void *err_data);
extern void ttfDelete(ttf_t *font);
extern int ttfGetAscent(ttf_t *font);
extern ttf_rect_t *ttfGetBounds(ttf_t *font, ttf_rect_t *bounds);
extern const int *ttfGetCMap(ttf_t *font, size_t *num_cmap);
extern int ttfGetCapHeight(ttf_t *font);
extern const char *ttfGetCopyright(ttf_t *font);
extern int ttfGetDescent(ttf_t *font);
extern ttf_rect_t *ttfGetExtents(ttf_t *font, float size, const char *s, ttf_rect_t *extents);
extern const char *ttfGetFamily(ttf_t *font);
extern float ttfGetItalicAngle(ttf_t *font);
extern int ttfGetMaxChar(ttf_t *font);
extern int ttfGetMinChar(ttf_t *font);
extern size_t ttfGetNumFonts(ttf_t *font);
extern const char *ttfGetPostScriptName(ttf_t *font);
extern ttf_stretch_t ttfGetStretch(ttf_t *font);
extern ttf_style_t ttfGetStyle(ttf_t *font);
extern const char *ttfGetVersion(ttf_t *font);
extern int ttfGetWidth(ttf_t *font, int ch);
extern ttf_weight_t ttfGetWeight(ttf_t *font);
extern int ttfGetXHeight(ttf_t *font);
extern bool ttfIsFixedPitch(ttf_t *font);
# ifdef __cplusplus
}
# endif // __cplusplus
#endif // !TTF_H