mirror of
https://github.com/qpdf/qpdf.git
synced 2024-09-27 12:39:06 +00:00
Clean up the Design and Library Notes chapter of the manual
This commit is contained in:
parent
a6c4b293b1
commit
910a373a79
@ -8,50 +8,53 @@ Design and Library Notes
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This section was written prior to the implementation of the qpdf package
|
||||
and was subsequently modified to reflect the implementation. In some
|
||||
cases, for purposes of explanation, it may differ slightly from the
|
||||
actual implementation. As always, the source code and test suite are
|
||||
authoritative. Even if there are some errors, this document should serve
|
||||
as a road map to understanding how this code works.
|
||||
This section was written prior to the implementation of the qpdf
|
||||
library and was subsequently modified to reflect the implementation.
|
||||
In some cases, for purposes of explanation, it may differ slightly
|
||||
from the actual implementation. As always, the source code and test
|
||||
suite are authoritative. Even if there are some errors, this document
|
||||
should serve as a road map to understanding how this code works.
|
||||
|
||||
In general, one should adhere strictly to a specification when writing
|
||||
but be liberal in reading. This way, the product of our software will be
|
||||
accepted by the widest range of other programs, and we will accept the
|
||||
widest range of input files. This library attempts to conform to that
|
||||
philosophy whenever possible but also aims to provide strict checking
|
||||
for people who want to validate PDF files. If you don't want to see
|
||||
warnings and are trying to write something that is tolerant, you can
|
||||
call ``setSuppressWarnings(true)``. If you want to fail on the first
|
||||
error, you can call ``setAttemptRecovery(false)``. The default behavior
|
||||
is to generating warnings for recoverable problems. Note that recovery
|
||||
will not always produce the desired results even if it is able to get
|
||||
through the file. Unlike most other PDF files that produce generic
|
||||
warnings such as "This file is damaged,", qpdf generally issues a
|
||||
detailed error message that would be most useful to a PDF developer.
|
||||
but be liberal in reading. This way, the product of our software will
|
||||
be accepted by the widest range of other programs, and we will accept
|
||||
the widest range of input files. This library attempts to conform to
|
||||
that philosophy whenever possible but also aims to provide strict
|
||||
checking for people who want to validate PDF files. If you don't want
|
||||
to see warnings and are trying to write something that is tolerant,
|
||||
you can call ``setSuppressWarnings(true)``. If you want to fail on the
|
||||
first error, you can call ``setAttemptRecovery(false)``. The default
|
||||
behavior is to generating warnings for recoverable problems. Note that
|
||||
recovery will not always produce the desired results even if it is
|
||||
able to get through the file. Unlike most other PDF files that produce
|
||||
generic warnings such as "This file is damaged," qpdf generally issues
|
||||
a detailed error message that would be most useful to a PDF developer.
|
||||
This is by design as there seems to be a shortage of PDF validation
|
||||
tools out there. This was, in fact, one of the major motivations behind
|
||||
the initial creation of qpdf.
|
||||
tools out there. This was, in fact, one of the major motivations
|
||||
behind the initial creation of qpdf. That said, qpdf is not a strict
|
||||
PDF checker. There are many ways in which a PDF file can be out of
|
||||
conformance to the spec that qpdf doesn't notice or report.
|
||||
|
||||
.. _design-goals:
|
||||
|
||||
Design Goals
|
||||
------------
|
||||
|
||||
The QPDF package includes support for reading and rewriting PDF files.
|
||||
The qpdf library includes support for reading and rewriting PDF files.
|
||||
It aims to hide from the user details involving object locations,
|
||||
modified (appended) PDF files, the directness/indirectness of objects,
|
||||
and stream filters including encryption. It does not aim to hide
|
||||
knowledge of the object hierarchy or content stream contents. Put
|
||||
another way, a user of the qpdf library is expected to have knowledge
|
||||
about how PDF files work, but is not expected to have to keep track of
|
||||
bookkeeping details such as file positions.
|
||||
modified (appended) PDF files, use of object streams, and stream
|
||||
filters including encryption. It does not aim to hide knowledge of the
|
||||
object hierarchy or content stream contents. Put another way, a user
|
||||
of the qpdf library is expected to have knowledge about how PDF files
|
||||
work, but is not expected to have to keep track of bookkeeping details
|
||||
such as file positions.
|
||||
|
||||
A user of the library never has to care whether an object is direct or
|
||||
indirect, though it is possible to determine whether an object is direct
|
||||
or not if this information is needed. All access to objects deals with
|
||||
this transparently. All memory management details are also handled by
|
||||
the library.
|
||||
When accessing objects, a user of the library never has to care
|
||||
whether an object is direct or indirect as all access to objects deals
|
||||
with this transparently. All memory management details are also
|
||||
handled by the library. When modifying objects, it is possible to
|
||||
determine whether an object is indirect and to make copies of the
|
||||
object if needed.
|
||||
|
||||
Memory is managed mostly with ``std::shared_ptr`` object to minimize
|
||||
explicit memory handling. This library also makes use of a technique
|
||||
@ -85,29 +88,32 @@ objects to indirect objects and vice versa.
|
||||
Instances of ``QPDFObjectHandle`` can be directly created and modified
|
||||
using static factory methods in the ``QPDFObjectHandle`` class. There
|
||||
are factory methods for each type of object as well as a convenience
|
||||
method ``QPDFObjectHandle::parse`` that creates an object from a string
|
||||
representation of the object. Existing instances of ``QPDFObjectHandle``
|
||||
can also be modified in several ways. See comments in
|
||||
:file:`QPDFObjectHandle.hh` for details.
|
||||
method ``QPDFObjectHandle::parse`` that creates an object from a
|
||||
string representation of the object. The ``_qpdf`` user-defined string
|
||||
literal is also available, making it possible to create instances of
|
||||
``QPDFObjectHandle`` with ``"(pdf-syntax)"_qpdf``. Existing instances
|
||||
of ``QPDFObjectHandle`` can also be modified in several ways. See
|
||||
comments in :file:`QPDFObjectHandle.hh` for details.
|
||||
|
||||
An instance of ``QPDF`` is constructed by using the class's default
|
||||
constructor. If desired, the ``QPDF`` object may be configured with
|
||||
various methods that change its default behavior. Then the
|
||||
``QPDF::processFile()`` method is passed the name of a PDF file, which
|
||||
permanently associates the file with that QPDF object. A password may
|
||||
also be given for access to password-protected files. QPDF does not
|
||||
enforce encryption parameters and will treat user and owner passwords
|
||||
equivalently. Either password may be used to access an encrypted file.
|
||||
``QPDF`` will allow recovery of a user password given an owner password.
|
||||
The input PDF file must be seekable. (Output files written by
|
||||
``QPDFWriter`` need not be seekable, even when creating linearized
|
||||
files.) During construction, ``QPDF`` validates the PDF file's header,
|
||||
and then reads the cross reference tables and trailer dictionaries. The
|
||||
``QPDF`` class keeps only the first trailer dictionary though it does
|
||||
read all of them so it can check the ``/Prev`` key. ``QPDF`` class users
|
||||
may request the root object and the trailer dictionary specifically. The
|
||||
cross reference table is kept private. Objects may then be requested by
|
||||
number or by walking the object tree.
|
||||
constructor or with ``QPDF::create()``. If desired, the ``QPDF``
|
||||
object may be configured with various methods that change its default
|
||||
behavior. Then the ``QPDF::processFile`` method is passed the name of
|
||||
a PDF file, which permanently associates the file with that ``QPDF``
|
||||
object. A password may also be given for access to password-protected
|
||||
files. ``QPDF`` does not enforce encryption parameters and will treat
|
||||
user and owner passwords equivalently. Either password may be used to
|
||||
access an encrypted file. ``QPDF`` will allow recovery of a user
|
||||
password given an owner password. The input PDF file must be seekable.
|
||||
Output files written by ``QPDFWriter`` need not be seekable, even when
|
||||
creating linearized files. During construction, ``QPDF`` validates the
|
||||
PDF file's header, and then reads the cross reference tables and
|
||||
trailer dictionaries. The ``QPDF`` class keeps only the first trailer
|
||||
dictionary though it does read all of them so it can check the
|
||||
``/Prev`` key. ``QPDF`` class users may request the root object and
|
||||
the trailer dictionary specifically. The cross reference table is kept
|
||||
private. Objects may then be requested by number or by walking the
|
||||
object tree.
|
||||
|
||||
When a PDF file has a cross-reference stream instead of a
|
||||
cross-reference table and trailer, requesting the document's trailer
|
||||
@ -240,13 +246,14 @@ the ``QPDFObjectHandle`` type to hold onto objects and to abstract
|
||||
away in most cases whether the object is direct or indirect.
|
||||
|
||||
Internally, ``QPDFObjectHandle`` holds onto a shared pointer to the
|
||||
underlying object value. When a direct object is created, the
|
||||
``QPDFObjectHandle`` that holds it is not associated with a ``QPDF``
|
||||
object. When an indirect object reference is created, it starts off in
|
||||
an *unresolved* state and must be associated with a ``QPDF`` object,
|
||||
which is considered its *owner*. To access the actual value of the
|
||||
object, the object must be *resolved*. This happens automatically when
|
||||
the the object is accessed in any way.
|
||||
underlying object value. When a direct object is created
|
||||
programmatically by client code (rather than being read from the
|
||||
file), the ``QPDFObjectHandle`` that holds it is not associated with a
|
||||
``QPDF`` object. When an indirect object reference is created, it
|
||||
starts off in an *unresolved* state and must be associated with a
|
||||
``QPDF`` object, which is considered its *owner*. To access the actual
|
||||
value of the object, the object must be *resolved*. This happens
|
||||
automatically when the the object is accessed in any way.
|
||||
|
||||
To resolve an object, qpdf checks its object cache. If not found in
|
||||
the cache, it attempts to read the object from the input source
|
||||
@ -286,18 +293,20 @@ file.
|
||||
it is looking before the last ``%%EOF``. After getting to ``trailer``
|
||||
keyword, it invokes the parser.
|
||||
|
||||
- The parser sees ``<<``, so it calls itself recursively in
|
||||
dictionary creation mode.
|
||||
- The parser sees ``<<``, so it changes state and starts accumulating
|
||||
the keys and values of the dictionary.
|
||||
|
||||
- In dictionary creation mode, the parser keeps accumulating objects
|
||||
until it encounters ``>>``. Each object that is read is pushed onto
|
||||
a stack. If ``R`` is read, the last two objects on the stack are
|
||||
inspected. If they are integers, they are popped off the stack and
|
||||
their values are used to construct an indirect object handle which
|
||||
is then pushed onto the stack. When ``>>`` is finally read, the
|
||||
stack is converted into a ``QPDF_Dictionary`` (not directly
|
||||
accessible through the API) which is placed in a
|
||||
``QPDFObjectHandle`` and returned.
|
||||
their values are used to obtain an indirect object handle from the
|
||||
``QPDF`` class. The ``QPDF`` class consults its cache, and if
|
||||
necessary, inserts a new unresolved object, and returns an object
|
||||
handle pointing to the cache entry, which is then pushed onto the
|
||||
stack. When ``>>`` is finally read, the stack is converted into a
|
||||
``QPDF_Dictionary`` (not directly accessible through the API) which
|
||||
is placed in a ``QPDFObjectHandle`` and returned.
|
||||
|
||||
- The resulting dictionary is saved as the trailer dictionary.
|
||||
|
||||
@ -309,23 +318,21 @@ file.
|
||||
- If there is an encryption dictionary, the document's encryption
|
||||
parameters are initialized.
|
||||
|
||||
- The client requests root object. The ``QPDF`` class gets the value of
|
||||
root key from trailer dictionary and returns it. It is an unresolved
|
||||
indirect ``QPDFObjectHandle``.
|
||||
- The client requests the root object by getting the value of the
|
||||
``/Root`` key from trailer dictionary and returns it. It is an
|
||||
unresolved indirect ``QPDFObjectHandle``.
|
||||
|
||||
- The client requests the ``/Pages`` key from root
|
||||
``QPDFObjectHandle``. The ``QPDFObjectHandle`` notices that it is
|
||||
indirect so it asks ``QPDF`` to resolve it. ``QPDF`` looks in the
|
||||
object cache for an object with the root dictionary's object ID and
|
||||
generation number. Upon not seeing it, it checks the cross reference
|
||||
table, gets the offset, and reads the object present at that offset.
|
||||
It stores the result in the object cache. The cache entry's value is
|
||||
replaced by the actual value, which causes any previously unresolved
|
||||
``QPDFObjectHandle`` objects that that pointed there to now have a
|
||||
shared copy of the actual object. Modifications through any such
|
||||
``QPDFObjectHandle`` will be reflected in all of them. As the client
|
||||
continues to request objects, the same process is followed for each
|
||||
new requested object.
|
||||
``QPDFObjectHandle``. The ``QPDFObjectHandle`` notices that it is an
|
||||
unresolved indirect object, so it asks ``QPDF`` to resolve it.
|
||||
``QPDF`` checks the cross reference table, gets the offset, and
|
||||
reads the object present at that offset. The object cache entry's
|
||||
``unresolved`` value is replaced by the actual value, which causes
|
||||
any previously unresolved ``QPDFObjectHandle`` objects that pointed
|
||||
there to now have a shared copy of the actual object. Modifications
|
||||
through any such ``QPDFObjectHandle`` will be reflected in all of
|
||||
them. As the client continues to request objects, the same process
|
||||
is followed for each new requested object.
|
||||
|
||||
.. _object_internals:
|
||||
|
||||
@ -339,11 +346,12 @@ Object Internals
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``QPDF`` object has an object cache which contains a shared
|
||||
pointer to each object that was read from the file. Changes can be
|
||||
made to any of those objects through ``QPDFObjectHandle`` methods. Any
|
||||
such changes are visible to all ``QPDFObjectHandle`` instances that
|
||||
point to the same object. When a ``QPDF`` object is written by
|
||||
``QPDFWriter`` or serialized to JSON, any changes are reflected.
|
||||
pointer to each object that was read from the file or added as an
|
||||
indirect object. Changes can be made to any of those objects through
|
||||
``QPDFObjectHandle`` methods. Any such changes are visible to all
|
||||
``QPDFObjectHandle`` instances that point to the same object. When a
|
||||
``QPDF`` object is written by ``QPDFWriter`` or serialized to JSON,
|
||||
any changes are reflected.
|
||||
|
||||
Objects in qpdf 11 and Newer
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -356,30 +364,32 @@ reference to that object has a copy of that shared pointer. Each
|
||||
is an implementation for each of the basic object types (array,
|
||||
dictionary, null, boolean, string, number, etc.) as well as a few
|
||||
special ones including ``uninitialized``, ``unresolved``,
|
||||
``reserved``, and ``destroyed``. When an object is first referenced,
|
||||
``reserved``, and ``destroyed``. When an object is first created,
|
||||
its underlying ``QPDFValue`` has type ``unresolved``. When the object
|
||||
is first resolved, the ``QPDFObject`` in the cache has its internal
|
||||
is first accessed, the ``QPDFObject`` in the cache has its internal
|
||||
``QPDFValue`` replaced with the object as read from the file. Since it
|
||||
is the ``QPDFObject`` object that is shared by all referencing
|
||||
``QPDFObjectHandle`` objects as well as by the owning ``QPDF`` object,
|
||||
this ensures that any future changes to the object, including
|
||||
replacing the object with a completely different one, will be
|
||||
replacing the object with a completely different one by calling
|
||||
``QPDF::replaceObject`` or ``QPDF::swapObjects``, will be
|
||||
reflected across all ``QPDFObjectHandle`` objects that reference it.
|
||||
|
||||
A ``QPDFValue`` that originated from a PDF input source maintains a
|
||||
pointer to the ``QPDF`` object that read it (its *owner*). When that
|
||||
``QPDF`` object is destroyed, it disconnects all reachable from it by
|
||||
clearing their owner. For indirect objects (all objects in the object
|
||||
cache), it also replaces the object's value with an object of type
|
||||
``destroyed``. This means that, if there are still any referencing
|
||||
``QPDFObjectHandle`` objects floating around, requesting their owning
|
||||
``QPDF`` will return a null pointer rather than a pointer to a
|
||||
``QPDF`` object that is either invalid or points to something else,
|
||||
and any attempt to access an indirect object that is associated with a
|
||||
destroyed ``QPDF`` object will throw an exception. This operation also
|
||||
has the effect of breaking any circular references (which are common
|
||||
and, in some cases, required by the PDF specification), thus
|
||||
preventing memory leaks when ``QPDF`` objects are destroyed.
|
||||
``QPDF`` object is destroyed, it disconnects all objects reachable
|
||||
from it by clearing their owner. For indirect objects (all objects in
|
||||
the object cache), it also replaces the object's value with an object
|
||||
of type ``destroyed``. This means that, if there are still any
|
||||
referencing ``QPDFObjectHandle`` objects floating around, requesting
|
||||
their owning ``QPDF`` will return a null pointer rather than a pointer
|
||||
to a ``QPDF`` object that is either invalid or points to something
|
||||
else, and any attempt to access an indirect object that is associated
|
||||
with a destroyed ``QPDF`` object will throw an exception. This
|
||||
operation also has the effect of breaking any circular references
|
||||
(which are common and, in some cases, required by the PDF
|
||||
specification), thus preventing memory leaks when ``QPDF`` objects are
|
||||
destroyed.
|
||||
|
||||
Objects prior to qpdf 11
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -478,22 +488,6 @@ and 64-bit platforms, and the test suite is very thorough, so it is
|
||||
hard to make any of the potential errors here without being caught in
|
||||
build or test.
|
||||
|
||||
Non-const ``unsigned char*`` is used in the ``Pipeline`` interface. The
|
||||
pipeline interface has a ``write`` call that uses ``unsigned char*``
|
||||
without a ``const`` qualifier. The main reason for this is
|
||||
to support pipelines that make calls to third-party libraries, such as
|
||||
zlib, that don't include ``const`` in their interfaces. Unfortunately,
|
||||
there are many places in the code where it is desirable to have
|
||||
``const char*`` with pipelines. None of the pipeline implementations
|
||||
in qpdf
|
||||
currently modify the data passed to write, and doing so would be counter
|
||||
to the intent of ``Pipeline``, but there is nothing in the code to
|
||||
prevent this from being done. There are places in the code where
|
||||
``const_cast`` is used to remove the const-ness of pointers going into
|
||||
``Pipeline``\ s. This could theoretically be unsafe, but there is
|
||||
adequate testing to assert that it is safe and will remain safe in
|
||||
qpdf's code.
|
||||
|
||||
.. _encryption:
|
||||
|
||||
Encryption
|
||||
@ -516,14 +510,14 @@ given an encryption key. This is used by ``QPDFWriter`` when it rewrites
|
||||
encrypted files.
|
||||
|
||||
When copying encrypted files, unless otherwise directed, qpdf will
|
||||
preserve any encryption in force in the original file. qpdf can do this
|
||||
with either the user or the owner password. There is no difference in
|
||||
capability based on which password is used. When 40 or 128 bit
|
||||
encryption keys are used, the user password can be recovered with the
|
||||
owner password. With 256 keys, the user and owner passwords are used
|
||||
independently to encrypt the actual encryption key, so while either can
|
||||
be used, the owner password can no longer be used to recover the user
|
||||
password.
|
||||
preserve any encryption in effect in the original file. qpdf can do
|
||||
this with either the user or the owner password. There is no
|
||||
difference in capability based on which password is used. When 40 or
|
||||
128 bit encryption keys are used, the user password can be recovered
|
||||
with the owner password. With 256 keys, the user and owner passwords
|
||||
are used independently to encrypt the actual encryption key, so while
|
||||
either can be used, the owner password can no longer be used to
|
||||
recover the user password.
|
||||
|
||||
Starting with version 4.0.0, qpdf can read files that are not encrypted
|
||||
but that contain encrypted attachments, but it cannot write such files.
|
||||
@ -538,33 +532,37 @@ format. The only exception to this is that clear-text metadata will be
|
||||
preserved as clear-text if it is that way in the original file.
|
||||
|
||||
One point of confusion some people have about encrypted PDF files is
|
||||
that encryption is not the same as password protection. Password
|
||||
protected files are always encrypted, but it is also possible to create
|
||||
encrypted files that do not have passwords. Internally, such files use
|
||||
the empty string as a password, and most readers try the empty string
|
||||
first to see if it works and prompt for a password only if the empty
|
||||
string doesn't work. Normally such files have an empty user password and
|
||||
a non-empty owner password. In that way, if the file is opened by an
|
||||
ordinary reader without specification of password, the restrictions
|
||||
specified in the encryption dictionary can be enforced. Most users
|
||||
wouldn't even realize such a file was encrypted. Since qpdf always
|
||||
ignores the restrictions (except for the purpose of reporting what they
|
||||
are), qpdf doesn't care which password you use. QPDF will allow you to
|
||||
create PDF files with non-empty user passwords and empty owner
|
||||
passwords. Some readers will require a password when you open these
|
||||
files, and others will open the files without a password and not enforce
|
||||
restrictions. Having a non-empty user password and an empty owner
|
||||
password doesn't really make sense because it would mean that opening
|
||||
the file with the user password would be more restrictive than not
|
||||
supplying a password at all. QPDF also allows you to create PDF files
|
||||
with the same password as both the user and owner password. Some readers
|
||||
will not ever allow such files to be accessed without restrictions
|
||||
because they never try the password as the owner password if it works as
|
||||
the user password. Nonetheless, one of the powerful aspects of qpdf is
|
||||
that it allows you to finely specify the way encrypted files are
|
||||
created, even if the results are not useful to some readers. One use
|
||||
case for this would be for testing a PDF reader to ensure that it
|
||||
handles odd configurations of input files.
|
||||
that encryption is not the same as password protection.
|
||||
Password-protected files are always encrypted, but it is also possible
|
||||
to create encrypted files that do not have passwords. Internally, such
|
||||
files use the empty string as a password, and most readers try the
|
||||
empty string first to see if it works and prompt for a password only
|
||||
if the empty string doesn't work. Normally such files have an empty
|
||||
user password and a non-empty owner password. In that way, if the file
|
||||
is opened by an ordinary reader without specification of password, the
|
||||
restrictions specified in the encryption dictionary can be enforced.
|
||||
Most users wouldn't even realize such a file was encrypted. Since qpdf
|
||||
always ignores the restrictions (except for the purpose of reporting
|
||||
what they are), qpdf doesn't care which password you use. QPDF will
|
||||
allow you to create PDF files with non-empty user passwords and empty
|
||||
owner passwords. Some readers will require a password when you open
|
||||
these files, and others will open the files without a password and not
|
||||
enforce restrictions. Having a non-empty user password and an empty
|
||||
owner password doesn't really make sense because it would mean that
|
||||
opening the file with the user password would be more restrictive than
|
||||
not supplying a password at all. QPDF also allows you to create PDF
|
||||
files with the same password as both the user and owner password. Some
|
||||
readers will not ever allow such files to be accessed without
|
||||
restrictions because they never try the password as the owner password
|
||||
if it works as the user password. Nonetheless, one of the powerful
|
||||
aspects of qpdf is that it allows you to finely specify the way
|
||||
encrypted files are created, even if the results are not useful to
|
||||
some readers. One use case for this would be for testing a PDF reader
|
||||
to ensure that it handles odd configurations of input files. If you
|
||||
attempt to create an encrypted file that is not secure, qpdf will warn
|
||||
you and require you to explicitly state your intention to create an
|
||||
insecure file. So while qpdf can create insecure files, it won't let
|
||||
you do it by mistake.
|
||||
|
||||
.. _random-numbers:
|
||||
|
||||
@ -630,23 +628,21 @@ Copying Objects From Other PDF Files
|
||||
|
||||
Version 3.0 of qpdf introduced the ability to copy objects into a
|
||||
``QPDF`` object from a different ``QPDF`` object, which we refer to as
|
||||
*foreign objects*. This allows arbitrary
|
||||
merging of PDF files. The "from" ``QPDF`` object must remain valid after
|
||||
the copy as discussed in the note below. The
|
||||
:command:`qpdf` command-line tool provides limited
|
||||
support for basic page selection, including merging in pages from other
|
||||
files, but the library's API makes it possible to implement arbitrarily
|
||||
complex merging operations. The main method for copying foreign objects
|
||||
is ``QPDF::copyForeignObject``. This takes an indirect object from
|
||||
*foreign objects*. This allows arbitrary merging of PDF files. The
|
||||
:command:`qpdf` command-line tool provides limited support for basic
|
||||
page selection, including merging in pages from other files, but the
|
||||
library's API makes it possible to implement arbitrarily complex
|
||||
merging operations. The main method for copying foreign objects is
|
||||
``QPDF::copyForeignObject``. This takes an indirect object from
|
||||
another ``QPDF`` and copies it recursively into this object while
|
||||
preserving all object structure, including circular references. This
|
||||
means you can add a direct object that you create from scratch to a
|
||||
``QPDF`` object with ``QPDF::makeIndirectObject``, and you can add an
|
||||
indirect object from another file with ``QPDF::copyForeignObject``. The
|
||||
fact that ``QPDF::makeIndirectObject`` does not automatically detect a
|
||||
foreign object and copy it is an explicit design decision. Copying a
|
||||
foreign object seems like a sufficiently significant thing to do that it
|
||||
should be done explicitly.
|
||||
indirect object from another file with ``QPDF::copyForeignObject``.
|
||||
The fact that ``QPDF::makeIndirectObject`` does not automatically
|
||||
detect a foreign object and copy it is an explicit design decision.
|
||||
Copying a foreign object seems like a sufficiently significant thing
|
||||
to do that it should be done explicitly.
|
||||
|
||||
The other way to copy foreign objects is by passing a page from one
|
||||
``QPDF`` to another by calling ``QPDF::addPage``. In contrast to
|
||||
@ -654,26 +650,30 @@ The other way to copy foreign objects is by passing a page from one
|
||||
between indirect objects in the current file, foreign objects, and
|
||||
direct objects.
|
||||
|
||||
Please note: when you copy objects from one ``QPDF`` to another, the
|
||||
source ``QPDF`` object must remain valid until you have finished with
|
||||
the destination object. This is because the original object is still
|
||||
used to retrieve any referenced stream data from the copied object.
|
||||
When you copy objects from one ``QPDF`` to another, the input source
|
||||
of the original file remain valid until you have finished with the
|
||||
destination object. This is because the input source is still used
|
||||
to retrieve any referenced stream data from the copied object. If
|
||||
needed, there are methods to force the data to be copied. See comments
|
||||
near the declaration of ``copyForeignObject`` in
|
||||
:file:`include/qpdf/QPDF.hh` for details.
|
||||
|
||||
.. _rewriting:
|
||||
|
||||
Writing PDF Files
|
||||
-----------------
|
||||
|
||||
The qpdf library supports file writing of ``QPDF`` objects to PDF files
|
||||
through the ``QPDFWriter`` class. The ``QPDFWriter`` class has two
|
||||
writing modes: one for non-linearized files, and one for linearized
|
||||
files. See :ref:`linearization` for a description of
|
||||
The qpdf library supports file writing of ``QPDF`` objects to PDF
|
||||
files through the ``QPDFWriter`` class. The ``QPDFWriter`` class has
|
||||
two writing modes: one for non-linearized files, and one for
|
||||
linearized files. See :ref:`linearization` for a description of
|
||||
linearization is implemented. This section describes how we write
|
||||
non-linearized files including the creation of QDF files (see :ref:`qdf`.
|
||||
non-linearized files including the creation of QDF files (see
|
||||
:ref:`qdf`).
|
||||
|
||||
This outline was written prior to implementation and is not exactly
|
||||
accurate, but it provides a correct "notional" idea of how writing
|
||||
works. Look at the code in ``QPDFWriter`` for exact details.
|
||||
accurate, but it portrays the essence of how writing works. Look at
|
||||
the code in ``QPDFWriter`` for exact details.
|
||||
|
||||
- Initialize state:
|
||||
|
||||
@ -685,7 +685,7 @@ works. Look at the code in ``QPDFWriter`` for exact details.
|
||||
|
||||
- xref table: new id -> offset = empty
|
||||
|
||||
- Create a QPDF object from a file.
|
||||
- Create a ``QPDF`` object from a file.
|
||||
|
||||
- Write header for new PDF file.
|
||||
|
||||
@ -750,7 +750,7 @@ Filtered Streams
|
||||
----------------
|
||||
|
||||
Support for streams is implemented through the ``Pipeline`` interface
|
||||
which was designed for this package.
|
||||
which was designed for this library.
|
||||
|
||||
When reading streams, create a series of ``Pipeline`` objects. The
|
||||
``Pipeline`` abstract base requires implementation ``write()`` and
|
||||
@ -802,32 +802,20 @@ file might be, the presence of type warnings can save lots of developer
|
||||
time. They have also proven useful in exposing issues in qpdf itself
|
||||
that would have otherwise gone undetected.
|
||||
|
||||
*Can there be a type-safe ``QPDFObjectHandle``?* It would be great if
|
||||
``QPDFObjectHandle`` could be more strongly typed so that you'd have to
|
||||
have check that something was of a particular type before calling
|
||||
type-specific accessor methods. However, implementing this at this stage
|
||||
of the library's history would be quite difficult, and it would make a
|
||||
the common pattern of drilling into an object no longer work. While it
|
||||
would be possible to have a parallel interface, it would create a lot of
|
||||
extra code. If qpdf were written in a language like rust, an interface
|
||||
like this would make a lot of sense, but, for a variety of reasons, the
|
||||
qpdf API is consistent with other APIs of its time, relying on exception
|
||||
handling to catch errors. The underlying PDF objects are inherently not
|
||||
type-safe. Forcing stronger type safety in ``QPDFObjectHandle`` would
|
||||
ultimately cause a lot more code to have to be written and would like
|
||||
make software that uses qpdf more brittle, and even so, checks would
|
||||
have to occur at runtime.
|
||||
|
||||
*Why do type errors sometimes raise exceptions?* The way warnings work
|
||||
in qpdf requires a ``QPDF`` object to be associated with an object
|
||||
handle for a warning to be issued. It would be nice if this could be
|
||||
fixed, but it would require major changes to the API. Rather than
|
||||
throwing away these conditions, we convert them to exceptions. It's not
|
||||
that bad though. Since any object handle that was read from a file has
|
||||
an associated ``QPDF`` object, it would only be type errors on objects
|
||||
that were created explicitly that would cause exceptions, and in that
|
||||
case, type errors are much more likely to be the result of a coding
|
||||
error than invalid input.
|
||||
*Can there be a type-safe* ``QPDFObjectHandle``? At the time of the
|
||||
release of qpdf 11, there is active work being done toward the goal of
|
||||
creating a way to work with PDF objects that is more type-safe and
|
||||
closer in feel to the current C++ standard library. It is hoped that
|
||||
this work will make it easier to write bindings to qpdf in modern
|
||||
languages like `Rust <https://www.rust-lang.org/>`__. If this happens,
|
||||
it will likely be by providing an alternative to ``QPDFObjectHandle``
|
||||
that provides a separate path to the underlying object. Details are
|
||||
still being worked out. Fundamentally, PDF objects are not strongly
|
||||
typed. They are similar to ``JSON`` objects or to objects in dynamic
|
||||
languages like `Python <https://python.org/>`__: there are certain
|
||||
things you can only do to objects of a given type, but you can replace
|
||||
an object of one type with an object of another. Because of this,
|
||||
there will always be some checks that will happen at runtime.
|
||||
|
||||
*Why does the behavior of a type exception differ between the C and C++
|
||||
API?* There is no way to throw and catch exceptions in C short of
|
||||
|
Loading…
Reference in New Issue
Block a user