pplib 1.x

pplib is a library for raw PDF access. It parses PDF documents and provides an interface to access document structures in C.

C-API

Types

pplib defines several C-types to represent PDF types:

  • ppint - signed integer (int64_t)
  • ppnum - real number (double)
  • ppname - PDF name
  • ppstring - PDF string
  • pparray - PDF array
  • ppdict - PDF dict
  • ppstream - PDF stream
  • ppref - PDF indirect reference
  • ppobj - a container of all above

Among ppint and ppnum, we also use ppuint - unsigned integer (machine word, alias to size_t).

Other API types:

  • ppdoc - PDF document
  • ppxref - cross-references table
  • ppcontext - … later
  • pprect - rectangle
  • ppmatrix - matrix

Integer, number, name and string are treated as simple types. Names and strings are actually C-structures, but exposed to API as typedefs to const char *. Other types (array, dict, stream, reference, object container, xref, PDF) are C-structures, and you operate it their pointers. So when you declare a simple type variable you say:

ppuint u;
ppnum n;
ppname name;
ppstring string;
...

And when you declare a compound type you operate on pointers:

ppobj *obj;
pparray *array;
ppdict *dict;
ppstream *stream;
ppref *ref;
ppdoc *pdf;

Some of those C-types are defined in library header ppapi.h (complete types). Some others are incomplete (eg. you can’t say sizeof(ppdoc) or sizeof(ppxref)). This is to avoid unnecesary dependencies in the header. [At some points it’s not clear to me what to hide and what to expose, will see.] The library itself uses pplib.h but for auxilary applications including a standalone ppapi.h header should be enough.

pplib was designed having read-only PDF access in mind. Even if some structure is completelly exposed, so that you can directly access its members, you should treat them as read-only. I don’t make them const because then all variable declarations would need to be const, which is annoying, and I’d need some trickery in the library internals to unconst. Besides, nothing is really const for C type casts.

Object

A common container for all elementary PDF object types is ppobj structure. ppobj has a type identifier (integer) and union of values:

struct ppobj {
  ppobjtp type;
  union {
    ppint integer;
    ppnum number;
    ppname name;
    ppstring string;
    pparray *array;
    ppdict *dict;
    ppstream *stream;
    ppref *ref;
    void *any;
  };
};

Object type is one of constants (enum):

PPNONE
PPNULL
PPBOOL
PPINT
PPNUM
PPNAME
PPSTRING
PPARRAY
PPDICT
PPSTREAM
PPREF

The type determines the structure member you’re allowed to access:

ppobj *obj;
...
switch (obj->type)
{
  case PPNONE: // shouldn't actually happen, indicates some failure
    break;
  case PPNULL: // valid PDF null object, no value
    break;
  case PPBOOL: // do something with obj->integer (ppint), value 0 or 1
    break;
  case PPINT:  // do something with obj->integer (ppint)
    break;
  case PPNUM:  // do something with obj->number (ppnum)
    break;
  case PPNAME: // do something with obj->name (ppname)
    break;
  case PPSTRING: // do something with obj->string (ppstring)
    break;
  case PPARRAY: // do something with obj->array (pparray *)
    break;
  case PPDICT: // do something with obj->dict (ppdict *)
    break;
  case PPSTREAM: // do something with obj->stream (ppstream *)
    break;
  case PPREF: // do something with obj->ref (ppref *)
    break;
}

More often then not you know exactly what type of object value is expected, in which case you may use one of the following macros:

// returns 1 if o->type is PPNULL
int ppobj_get_null(o)         \

// if o->type is PPBOOL, sets int v to 0 or 1 and returns 1, 0 otherwise
int ppobj_get_bool(o, v)

// if o->type is PPINT, sets ppint v and returns 1, 0 otherwise
int ppobj_get_int(o, v)

// if o->type is PPINT and >= 0, sets ppuint v and returns 1, 0 otherwise
int ppobj_get_uint(o, v)

// if o->type is PPNUM or PPINT, sets ppnum v and returns 1, 0 otherwise
int ppobj_get_num(o, v)

// if o->type is PPNAME returns the name, NULL otherwise
ppname ppobj_get_name(o)

// if o->type is PPSTRING returns the string, NULL otherwise
ppstring ppobj_get_string(o)

// if o->type is PPARRAY returns the array, NULL otherwise
pparray * ppobj_get_array(o)

// if o->type is PPDICT returns the dict, NULL otherwise
ppdict * ppobj_get_dict(o)

// if o->type is PPSTREAM returns the stream, NULL otherwise
ppstream * ppobj_get_stream(o)

// if o->type is PPREF returns the reference, NULL otherwise
ppref * ppobj_get_ref(o)

Note the coercion from integer to real number, but not reverse. In practise, whenever you expect a real number, you should also handle integer (eg. ‘1’ used instead of ‘1.0’).

It is a common case that the object is given as an indirect reference, but what you actually want is not the reference, but the object referred by it. Here is a helper for it:

// if o->type is PPREF, returns what the reference points, otherwise returns o
ppobj * ppobj_rget_obj(o)

Also every ppobj_get_* macro has ppobj_rget_* counterpart that makes a check for the expected type, but if the object is PPREF, it jumps to the target object. So for example ppobj_rget_dict(obj) will return dict if obj is of type PPDICT or if it is of type PPREF and obj->ref hosts an object of type PPDICT.

Names

PDF names are represented as ppname. I find it convenient to have ppname type pretending const char *. This allows to use ppname in all C-style string functions like printf("%s", name).

Be aware, however, that ppname is actually a C-structure. It is perfectly ok to cast ppname to const char *:

ppname name;
...
(const char *)name;

But reverse is forbidden:

const char *cstr = "cstring";
...
(ppname)cstr; // expect segmentation fault soon

For convenient use in C, names are '\0' terminated. But to get the length of name better always use ppname_size() macro. ppname object knows its size, don’t use strlen():

size_t ppname_size(ppname name); // macro, returns length of name in bytes

In current implementation names are not hashed anyhow, so name-to-name comparison is not smarter than memcmp(). Use macros:

int ppname_is(ppname name, "literal");    // to compare ppname with C-literal string
int ppname_eq(ppname name, ppname other); // to compare ppname with a different name

If you’ll use pplib to parse contents streams, you may need to distinguish names from operators (more precisely executable names). Names in PDF are preceeded by ‘/’, executable names aren’t. In both cases PDF parser will produce ppname but can be distingushed with:

int ppname_exec(ppname name); // returns non-zero if name is executable

Names are kept in their raw form, with possible PDF specific escapes (in text below we call it encoded form). Leading ‘/’ is omitted, though. One may need a decoded name, with no PDF escapes. A pair of functions provides a simple interface to switch between those two forms:

ppname ppname_decoded (ppname name); // returns decoded (unescaped) form of the name
ppname ppname_encoded (ppname name); // returns encoded (escaped) form of the name

In pretty most cases PDF names contains only letters (no special characters, no escapes), so decoded and encoded forms are identical. In that case both functions simply return the argument. It is ok to call ppname_decoded() on already decoded form and ppname_encoded() on already encoded form. Both forms are produced by PDF objects parser, so accessing ppname alter ego in whatever direction needs no extra decoding or allocation costs.

String

PDF strings have the same internal construction as names, so most of names description above applies to strings as well. ppstring is a typedef of const char *, roughly '\0' terminiated C-string. To get the size of the string:

size_t ppstring_size(ppstring string); // macro, returns the length of the string in bytes

Strings are provided in their raw form, preserving PDF specific escapes, but with no () or <> delims. To distinguish plain strings from hex strings:

int ppstring_hex(ppstring string); // macro, returns non zero if hex string

Or if you prefer:

switch (ppstring_type(string))
{
  case PPSTRING_PLAIN: // literal string, surrounded by ``(`` and ``)`` in PDF
    break;
  case PPSTRING_BASE16: // hex string, surrounded by ``<`` and ``>`` in PDF
    break;
  case PPSTRING_BASE85: // base85 string surrounded by ``<~`` and ``~>`` in PDF
    break;
}

The last is actually Postscript specific, not used in PDF, but I think it might appear in contents streams… No matter how the string is given in PDF (plain or hex), here are two functions to switch between encoded and decoded strings forms:

ppstring ppstring_decoded (ppstring string); // returns decoded string possibly with PDF escapes
ppstring ppstring_encoded (ppstring string); // returns encoded string with no PDF escapes

For hex strings, encoded form contains hex digits, while decoded form contains arbitrary bytes (the result of hex decoding). Plain strings usually contains printable ASCII characters, but they might contain any binary data. As with names, objects parser produces both forms. The raw form with PDF escapes (or raw hex form) is considered the main one. Eg. when you access obj->string you always get the encoded form. At any moment you can switch to its alter ego.

No matter if the string is plain or hex, if its first two bytes (decoded) are UTF16 BOM, the string is considered unicode. ppstring object knows it is unicode or not:

switch (ppstring_utf(string))
{
  case PPSTRING_UTF16LE: // unicode string, utf16le
    break;
  case PPSTRING_UTF16BE: // unicode string, utf16be
    break;
  default:               // no unicode
}

Or simply:

if (ppstring_utf(string) != 0) {
  // handle unicode string
}

If the string is unicode, BOM remains the part of the string – pplib parser does not strip it. Unicode or not, encoded or decoded, strings are always C-arrays of bytes and ppstring_size() always returns the size in bytes.

Array

PDF arrays are represented as pparray type, which is C-array of ppobj structures. To get the size:

size_t pparray_size(pparray *array) // macro, returns the number of array items

To get ppobj * at a given index:

ppobj * pparray_at(array, index)  // macro, returns ppobj * (no index check)
ppobj * pparray_get(array, index) // macro, returns ppobj * or NULL (with index check)
ppobj * pparray_get_obj (pparray *array, size_t index);  // function equiv to pparray_get()

Iterating over array elements:

pparray *array;
size_t index, size;
ppobj *obj;
for (size = pparray_size(array), pparray_first(array, index, obj); index < size; pparray_next(index, obj))
{
  // do something with index and obj
}

There is no magic first/next macros, just iteration over pointers. One could also use something like:

for (index = 0, size = array->size; index < size; ++index)
{
  obj = pparray_at(array, index);
  // do something with index and obj
}

When getting values from array and expecting a result of known type, use one of the following:

int pparray_get_bool (pparray *array, size_t index, int *v);    // get boolean value
int pparray_get_int (pparray *array, size_t index, ppint *v);   // get ppint value
int pparray_get_uint (pparray *array, size_t index, ppuint *v); // get ppuint value
int pparray_get_num (pparray *array, size_t index, ppnum *v);   // get ppnum value
ppname pparray_get_name (pparray *array, size_t index);         // get ppname value
ppstring pparray_get_string (pparray *array, size_t index);     // get ppstring value
pparray * pparray_get_array (pparray *array, size_t index);     // get pparray * value
ppdict * pparray_get_dict (pparray *array, size_t index);       // get ppdict * value
ppref * pparray_get_ref (pparray *array, size_t index);         // get ppref * value

As with ppobj_get_* suite, numeric types getters set the value of a given type and returns 1, if the type matches. Otherwise sets nothing and returns 0. Other getters return the value if the type matches, or NULL.

Every function from pparray_get_* suite have its pparray_rget_* counterpart that that dereferences indirect objects (as explained for ppobj_rget_* getters). Note that there is no pparray_get_stream() function, as streams in PDF are always indirect. To get the stream from array use:

ppstream * pparray_rget_stream (pparray *array, size_t index);

Dict

PDF dicts are represented as ppdict structure, which is C-array of ppobj with parallel C-array of ppname pointers. To get the size of a dict:

size_t ppdict_size(ppdict *dict) // macro, returns the number of key-val pairs

To get the value at a given index (integer):

ppobj * ppdict_at(ppdict *dict, index) // macro, no index check

To get the name (key) at a given index:

ppname ppdict_key(ppdict *dict, index) // macro, no index check

To iterate over dict key-val pairs:

ppdict *dict;
ppname *pkey;
ppobj *obj;

for (ppdict_first(dict, pkey, obj); *pkey != NULL; ppdict_next(pkey, obj))
{
  // do something with *pkey and obj
}

There is no magic in first/next macros, just iteration through keys and values lists pointers. For convenient iteration, a list of keys is terminated with NULL, so in the code above *pkey != NULL is used as the loop condition. One may also iterate via indices:

ppdict *dict;
size_t index, size;
ppname key;
ppobj *obj;
for (index = 0, size = ppdict_size(dict); index < size; ++index)
{
  key = ppdict_key(dict, index);
  obj = ppdict_at(dict, index);
  // do something with key and obj
}

To get the object associated with a given name, use one of the following:

ppobj * ppdict_get_obj (ppdict *dict, const char *name);
int ppdict_get_bool (ppdict *dict, const char *name, int *v);
int ppdict_get_int (ppdict *dict, const char *name, ppint *v);
int ppdict_get_uint (ppdict *dict, const char *name, ppuint *v);
int ppdict_get_num (ppdict *dict, const char *name, ppnum *v);
ppname ppdict_get_name (ppdict *dict, const char *name);
ppstring ppdict_get_string (ppdict *dict, const char *name);
pparray * ppdict_get_array (ppdict *dict, const char *name);
ppdict * ppdict_get_dict (ppdict *dict, const char *name);
ppref * ppdict_get_ref (ppdict *dict, const char *name);

Note that all getters accepts const char * as key, so it is ok to say:

ppdict_rget_dict(dict, "Resources");

as well as:

ppdic_rget_dict(dict, name); // ppname name

Every ppdict_get_* getter has ppdict_rget_* counterpart that dereferences indirect objects if necessary. Note that there is no ppdict_get_stream() function, but there is:

ppstream * ppdict_rget_stream (ppdict *dict, const char *name);

So far dicts comes with no names mapping, so by-name dict accessors perform a linear search through the keys list. PDF dicts are usually small, so it is fast enough. Building names lookup for every dict in PDF makes no sense I think, as pplib applications will query just several dicts I guess.. However, some apps may extensively query resources, which may refer to hundreds of objects (eg. images). So some mapping for dicts is still considered.

Stream

PDF streams are represented as ppstream objects. To get the stream dict:

ppdict * ppstream_dict(ppstream *stream) // macro

To read the stream data:

uint8_t * ppstream_first (ppstream *stream, size_t *size, int decode);
uint8_t * ppstream_next (ppstream *stream, size_t *size);
void ppstream_done (ppstream *stream);

Both first` and ``next functions return a chunk of stream data and sets the size of the chunk. decode parameter tell the reader to decompress the stream (1) or return raw (0). A call to ppstream_next() must be preceeded by ppstream_first(). Once you’re done with the stream, you have to call ppstream_done(), no matter if the stream has been read to the end or not. The stream data iterator in use:

uint8_t *data;
size_t size;
ppstream *stream;
int decode = 1; // 1 - get decompressed, 0 - get raw

for (data = ppstream_first(stream, &size, decode); data != NULL; data = ppstream_next(stream, &size))
{
  // do something with data and its size
}
ppstream_done(stream);

Every subsequent iterator call invalidates the previous reader output, so you have to utilize the returned chunk of data just after you ot that. So the following is wrong:

data1 = ppstream_first(stream, &size, 1);
data2 = ppstream_next(stream, &size);
data3 = ppstream_next(stream, &size);
some_output(data1, size);
some_output(data2, size);

The reader calls usually return the same pointer to internal buffer, just filled with a different data. pplib allocates reasonably large buffer and fills that buffer on subsequent calls to the reader.

If the source stream has no compression, using both decode == 1 and decode == 0 should give the same result. You can check if the stream is actually compressed with:

ppstream_compressed(stream) // macro, returns non zero if /Filter is present

It might be necessary to load the entire stream data at once:

uint8_t * ppstream_all (ppstream *stream, size_t *size, int decode);

If the initial buffer size is insufficient, it grows until the entire stream data is loaded. You must call ppstream_done(stream) after using returned data.

ppstream_done() doesn’t invalidate the stream object, it just closes its internal reader. The stream itself remains a valid object (eg. one can read it again if necessary), but the reader buffer is released. It is actually not freed but kept for future the reuse with that on some other stream, but you still need to mark it ready for reuse to avoid allocating a separate buffer for every stream you’re going to read.

Stream data readers will return NULL if you haven’t close the previous reader process with ppstream_done(). All below is wrong:

data1 = ppstream_all(stream, &size, 1);
data2 = ppstream_all(stream, &size, 1); // data2 == NULL
// or
data1 = ppstream_first(stream, &size, 1);
data2 = ppstream_first(stream, &size, 1); // data2 == NULL
// or
data1 = ppstream_first(stream, &size, 1);
data2 = ppstream_all(stream, &size, 1); // data2 == NULL

To avoid unnecessary dependencies, pplib does not support image filters (/DCT, /JPX, /JBIG, /CCITT). But it is ok to read the stream with decode set to 1 on such streams. pplib assumes that the image is the final/target stream form and just returns it as-is. Eg. in the case of JPEG (/DCT filtered) image both calls should give the same results:

ppstream_all(jpegstream, &jpegsize, 0); // don't decode, return what's there
ppstream_all(jpegstream, &jpegsize, 1); // decode but found image filter, effectively the same

A bit more about streams memory. As mentioned, pplib allocates buffers for stream readers. After ppstream_done(), the stream no longer owns the buffer space. But the buffer may remain allocated, to be reused with future readers. pplib keeps a pool of several buffers. This means, that when you use stream readers, pplib eats some memory (1MB or so) that is not freed, even if no streams are used. And even if you free all objects. If you suffer from this, you can optionally use a pair of functions:

void ppstream_init_buffers (void);
void ppstream_free_buffers (void);

The first initializes buffers pool, unless done so far. Currently pplib cares of it before opening every stream reader, so it is not obligatory. The second frees a pool of buffers. The intended use is to call ppstream_init_buffers() once as kind of library initializer and to call ppstream_free_buffers() once, as the library finalizer.

Filters

In version v1.00 (20190916) ppstream API has been extended with filters information. ppstream knows its filter(s) and keps it as stream->filter:

// ppstream *stream;
ppstream_filter *info = &stream->filter;

ppstream_filter is the following structure:

typedef struct {
  ppstreamtp *filters; // c-array of filter identifiers (enum integers)
  ppdict **params;     // c-array of ppdict pointers
  size_t count;        // number of filters, length of the arrays (typically 1)
} ppstream_filter;

If count > 0 then filters member is not NULL. Filters array keeps integer constants:

PPSTREAM_BASE16    /* /ASCIIHexDecode  */
PPSTREAM_BASE85    /* /ASCII85Decode   */
PPSTREAM_RUNLENGTH /* /RunLengthDecode */
PPSTREAM_FLATE     /* /FlateDecode     */
PPSTREAM_LZW       /* /LZWDecode       */
PPSTREAM_CCITT     /* /CCITTFaxDecode  */
PPSTREAM_DCT       /* /DCTDecode       */
PPSTREAM_JBIG2     /* /JBIG2Decode     */
PPSTREAM_JPX       /* /JPXDecode       */
PPSTREAM_CRYPT     /* /Crypt           */

Params array keeps corresponding filter parameters (/DecodeParms) if present. params member is not NULL if count > 0 and the stream dict has /DecodeParms entry. Even if params is there, for every N-th filter, params[N] may be NULL (corresponding to PDF null).

stream->filter keeps the source stream filter information, which may not correspond to the result of stream readers (ppstream_first(), ppstream_next(), ppstream_all()). The get the filters info relevant to the result from readers:

void ppstream_filter_info (ppstream *stream, ppstream_filter *info, int decode);

The function fills ppstream_filter structure according to the expected result from stream readers (example 3 shows how to use it to reconstruct /Filter and /DecodeParms when copying the stream to some other PDF).

To convert filter identifier (ppstreamtp) to a corresponding PDF filter name:

const char * ppstream_filter_name[];

To covert ppname to filter identifier:

int ppstream_filter_type (ppname filtername, ppstreamtp *filtertype);
// returns 1 and sets filtertype if filtername is the proper filter name

Additional information about the stream can be fetched from macros:

ppstream_compressed(stream) /* stream->flags & (PPSTREAM_FILTER|PPSTREAM_IMAGE) */
ppstream_filtered(stream)   /* stream->flags & PPSTREAM_FILTER */
ppstream_image(stream)      /* stream->flags * PPSTREAM_IMAGE */

stream->flags is a binary sum of the following:

PPSTREAM_FILTER        /* set iff the stream filters list has one of: BASE16, BASE85, RUNLENGTH, FLATE, LZW */
PPSTREAM_IMAGE         /* set iff the stream filters list has one of: CCITT, DCT, JBIG2, JPX */
PPSTREAM_ENCRYPTED     /* set iff the stream is encrypted */
PPSTREAM_ENCRYPTED_OWN /* set iff the stream has own CRYPT filter */

Note that PPSTREAM_COMPRESSED is not there any longer, use ppstream_compressed() instead. And there is some more, see ppapi.h.

Ref

Indirect objects are represented as ppref structure. To get the object that the reference refers to:

ppobj * ppref_obj(ppref *ref) // macro

ppref structure also keeps the reference number and version, a pointer to cross reference table it belongs to and others, but I guess you won’t need anything but the referenced object. pplib parser resolves references on-fly. So if there is a dict with indirect objects:

<<
  /Type /Page
  /Resources 123 0 R
  ...
>>

the parser will produce ppdict with Resources key pointing the proper ppref * value. If you need more, access ppref members:

struct ppref {
  ppobj object;           // target object
  ppuint number, version; // identifiers
  size_t offset;          // file offset (useless for you, may be zero for compressed objects)
  ppuint length;          // the length of the original object data
  ppxref *xref;           // cross reference table it belongs to
};

XRef

Cross reference table is exposed as ppxref (incomplete type, you can only oprate on its pointer). To get top document xref:

ppxref * ppdoc_xref (ppdoc *pdf);

To get previous (older) xref:

ppxref * ppxref_prev (ppxref *xref);

To find an object of a given refnumber:

ppref * ppxref_find (ppxref *xref, ppuint refnumber);

[Note: since pplib v0.98 in case of documents with incremental update, ppxref_find() returns the newest available version of a given object rather than the object in a given body.]

PDF

PDF document is represented as ppdoc structure (incomplete type, you can only operate on its pointer). To load a document from file:

ppdoc * ppdoc_load (const char *filename);

To load a document from memory data:

ppdoc * ppdoc_mem (const void *data, size_t size);

The data is assumed to be a buffer allocated with malloc - it is freed when destroying ppdoc.

Both loaders returns NULL on failure.

To free ppdoc and all objects it refers to:

void ppdoc_free (ppdoc *pdf);

So far we haven’t mention about any explicit object reclaimers. There are no dedicated free functions for other objects. You don’t allocate or free objects yourself. ppdoc object is an owner of all beings it refers to. It also means that every object described so far is alive as long as the containing ppdoc is alive.

To access main PDF dicts:

ppdict * ppdoc_trailer(ppdoc *pdf); // returns top xref trailer dict
ppdict * ppdoc_catalog(ppdoc *pdf); // returns catalog referred from the trailer
ppdict * ppdoc_info(ppdoc *pdf);    // returns info dict referred from the trailer

To get the PDF version:

const char * ppdoc_version_string (ppdoc *pdf);    // version string
int ppdoc_version_number (ppdoc *pdf, int *minor); // minor and major numbers

To get the file size of the source PDF document:

size_t ppdoc_file_size (ppdoc *pdf);

To get the number of objects in all xrefs:

ppuint ppdoc_objects (ppdoc *pdf);

To get the approx usage of memory:

size_t ppdoc_memory (ppdoc *pdf, size_t *waste);

Encryption

pplib handles encrypted (password protected) documents. If a document is encrypted, most of strings and streams are ciphered. In that form they are unreadable and rather useless, you can’t even rewrite such strings/streams as-is to a different PDF output. It is a common practise to protect documents with an empty password. Such documents remain readable in Acrobat (just opens them without prompting for a password), but some features (eg. printing) may restricted by the application.

When pplib detects encryption, it follows Acrobat approach and first tries an empty password. If it succeeds, pplib proceeeds normally, providing an access to decrypted strings and streams, as if they weren’t ciphered. If the document is protected with non-empty password, pplib gives a way to provide a password and proceed. Until you provide a password, ppdoc object returned by ppdoc_load() function has all object wntries set to null.

After loading a document you should check encryption status with:

ppcrypt_status ppdoc_crypt_status (ppdoc *pdf);

ppcrypt_status (integer) may have the following values:

PPCRYPT_NONE - no encryption, go ahead PPCRYPT_DONE - encryption present but password succeeded, go ahead PPCRYPT_PASS - encryption present, need non-empty password PPCRYPT_FAIL - invalid or unsupported encryption (eg. undocumented in pdf spec)

If a password is needed, you can provide one with:

ppcrypt_status ppdoc_crypt_pass (ppdoc *pdf, const void *userpass, size_t userpasslength,
                                             const void *ownerpass, size_t ownerpasslength);

Well, yes, there are actually two passwords in encrypted documents. Relation between them is obscure to me, but enough to know that having one of them is enough to decrypt the document. If you know the password, you probably mean userpass, in which case you should put NULL as ownerpass. The function returns PPCRYPT_DONE if the password succeeds and the previous status otherwise. Your custom loader function may look like that:

ppdoc *pdf;
pdf = ppdoc_load("file.pdf");
if (pdf == NULL)
  return NULL;
switch (ppdoc_crypt_status(pdf))
{
  case PPCRYPT_NONE:
  case PPCRYPT_DONE:
    return pdf;
  case PPCRYPT_PASS:
    if (ppdoc_crypt_pass(pdf, "dummy", 5, NULL, 0) == PPCRYPT_DONE ||
        ppdoc_crypt_pass(pdf, NULL, 0, "dummy", 5) == PPCRYPT_DONE)
      return pdf;
    printf("sorry, password needed\n");
    ppdoc_free(pdf);
    return NULL;
  case PPCRYPT_FAIL:
    printf("sorry, encryption failed\n");
    ppdoc_free(pdf);
    return NULL;
}

[If you get PPCRYPT_FAIL it might mean I failed, so treat as a bug.]

If you’d like to know what permissions are given/restricted to encrypted document:

ppint ppdoc_permissions (ppdoc *pdf);

Returned value can be queried with the following binary flags (you can verify with Acrobat File -> Properties -> Security tab):

PPDOC_ALLOW_PRINT       // printing
PPDOC_ALLOW_MODIFY      // filling form fields, signing, creating template pages
PPDOC_ALLOW_COPY        // copying, copying for accessibility
PPDOC_ALLOW_ANNOTS      // filling form fields, copying, signing
PPDOC_ALLOW_EXTRACT     // contents copying for accessibility
PPDOC_ALLOW_ASSEMBLY    // (no effect)
PPDOC_ALLOW_PRINT_HIRES // (no effect)

pplib does absolutelly nothing with permissions, it cares only to decrypt the document. As mentioned, encryption applies to strings and streams. pplib decrypt strings when parsing document objects, so the result you get is normal (not ciphered). Streams are decrypted whenever you access them. Even if you ask for a raw stream data, you’ll get a raw (compressed) stream, but decrypted. So except the check to ppdoc_crypt_status(), you shouldn’t bother about encryption.

In encrypted documents most of streams are encrypted. To check if a given stream is encrypted:

ppstream_encrypted(stream) // macro, returns non-zero if encrypted

Encryption is independent from compression, don’t confuse with ppstream_compressed()

Pages

Several helpers to deal with pages. To get the number of pages:

ppuint ppdoc_page_count (ppdoc *pdf);

To access the root pages tree node:

ppref * ppdoc_pages(ppdoc *pdf);

To get the page reference at a given index:

ppref * ppdoc_page (ppdoc *pdf, ppuint index);

index is a page number. First page has number 1. For index out of bounds ppdoc_page() returns NULL. Iterating over pages using index from 1 to ppdoc_page_count() and calling ppdoc_page() on each iteration would be suboptimal. Here is a dedicted iterator for this:

ppref *  ppdoc_first_page (ppdoc *pdf);
ppref * ppdoc_next_page (ppdoc *pdf);

The iterator in use:

ppdoc *pdf;
ppref *ref;
ppdict *dict;
int pageno;

pdf = ppdoc_load("file.pdf");
for (ref = ppdoc_first_page(pdf), pageno = 1; ref != NULL; ref = ppdoc_next_page(pdf), ++pageno)
{
  dict = ppref_obj(obj)->dict; // take for granted it is a dict
  // do something with the page dict
}

Functions related to pages return ppref * ensured to contain dict object, so you don’t need sanity type checks here.

Contents

PDF page contents can be given as a stream or array of streams. Here is a convenience iterator over page contents streams:

ppstream * ppcontents_first (ppdict *dict);
ppstream * ppcontents_next (ppdict *dict, ppstream *stream);

A complete example of contents stream parser use is given below (example 2). But before we get there, we need to introduce ppcontext object. Conceptually, ppcontext is an owner (memory handler) of objects created on demand (beyond the ppdoc). So far used only with contents stream parser, which might produce quite some data that we want to release just after used. To create a new context:

pcontext * ppcontext_new (void);

It initializes a new context and its internal memory heap, taking about 64kB on start. After that, the context is ready to produce objects (contents parsing functions below). Once objects produced from a given context are no longer needed:

void ppcontext_done (ppcontext *context);

It restores the context to its initial state, as after ppcontext_new(). It means that the context is ready to produce another bunch of beings (in the example below, all objects from the next page contents). Once the context is not needed anymore:

void ppcontext_free (ppcontext *context);

Now, contents stream parser functions take the context as an argument. Iterator form of contents stream parser that allows to process the contents operator by operator:

ppobj * ppcontents_first_op (ppcontext *context, ppstream *stream, size_t *psize, ppname *pname);
ppobj * ppcontents_next_op (ppcontext *context, ppstream *stream, size_t *psize, ppname *pname);

Returned ppobj * is a pointer to operands list. *psize is the number of operands on stack. The operator itself is stored as *pname.

To parse the entire contents stream at once with no stop at every operator:

ppobj * ppcontents_parse (ppcontext *context, ppstream *stream, size_t *psize);

Returns probably quite long list of all parsed objects (operands and operatos) in one piece. The number of objects is stored to *psize.

[Contents may contain so called inline images, that breaks a simple scheme of operands / operator syntax:

BI <keyval pairs> ID <binary image data> EI

Contents parser treats this genuine triplet as a single piece, producing two operands (dict and string) followed by EI operator name.]

Boxes

Boxes (rectangles) in PDF are roughly 4-number arrays, but with a special intent. pplib provides a basic interface for these special arrays:

typedef struct {
  ppnum lx, ly, rx, ry;
} pprect;

This type is used only by helper functions - PDF parser is not aware of the rectangle type. To convert pparray to pprect:

pprect * pparray_to_rect (pparray *array, pprect *rect); // returns rect or NULL

In example:

pprect rect;
if (pparray_to_rect(array, &rect) != NULL)
 ; // do something with rect

To get some image bounding box:

pprect * ppdict_get_rect (ppdict *dict, const char *name, pprect *rect);
// eg. ppdict_get_rect(imagedict, "BBox", &rect)

To get some page box:

pprect * ppdict_get_box (ppdict *dict, const char *name, pprect *rect);
// eg. ppdict_get_box(pagedict, "MediaBox", &rect)

The later not only checks the pagedict, but also goes through parent page nodes.

Transforms

Transformations are given as 6-number arrays, but with a special intent. pplib provides a basic interface for these special arrays:

typedef struct {
  ppnum xx, xy, yx, yy, x, y;
} ppmatrix;

This type is used only by helper functions - PDF parser is not aware of the matrix type. To convert pparray to ppmatrix:

ppmatrix * pparray_to_matrix (pparray *array, ppmatrix *matrix);

In example:

ppmatrix matrix;
if (pparray_to_matrix(array, &matrix) != NULL)
  ; // do something with matrix

To get the matrix from dict:

ppmatrix * ppdict_get_matrix (ppdict *dict, const char *name, ppmatrix *matrix);
// eg. ppdict_get_matrix(imagedict, "Matrix", &matrix)

Errors handling

pplib is not verbose, but might happen that it needs to log some error message, eg. when parsing of some PDF boject fails due to invalid offsets. By default, pplib prints the message to stdout, eg.:

invalid 123 0 R object at offset 123123

To replace the default logger, you can provide your own:

void pplog_callback (pplogger_callback logger, void *alien);

pplogger_callback is a function:

void your_callback (const char *message, void *alien);

In example, to redirect messages to stderr you may define a function:

void your_callback (const char *message, void *alien)
{
  fprintf((FILE *)alien, "\nooops: %s\n", message);
}

Then set the callback somewhere before loading documents:

pplog_callback(your_callback, stderr);

(example 2 uses that).

To set the default log messages prefix, eg. pplib:, use:

int pplog_prefix (const char *prefix)

Default is empty. The function succeeds if provided prefix is reasonably short (less then 32 bytes).