update release date to actual date

git-svn-id: svn+q:///qpdf/trunk@599 71b93d88-0707-0410-a8cf-f5a4172ac649
This commit is contained in:
Jay Berkenbilt 2008-04-29 12:55:25 +00:00
commit 9a0b88bf77
531 changed files with 79083 additions and 0 deletions

191
Artistic-2.0 Normal file
View File

@ -0,0 +1,191 @@
Artistic License 2.0
Copyright (c) 2000-2006, The Perl Foundation.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Preamble
This license establishes the terms under which a given free software
Package may be copied, modified, distributed, and/or
redistributed. The intent is that the Copyright Holder maintains some
artistic control over the development of that Package while still
keeping the Package available as open source and free software.
You are always permitted to make arrangements wholly outside of this
license directly with the Copyright Holder of a given Package. If the
terms of this license do not permit the full use that you propose to
make of the Package, you should contact the Copyright Holder and seek
a different licensing arrangement.
Definitions
"Copyright Holder" means the individual(s) or organization(s) named
in the copyright notice for the entire Package.
"Contributor" means any party that has contributed code or other
material to the Package, in accordance with the Copyright Holder's
procedures.
"You" and "your" means any person who would like to copy,
distribute, or modify the Package.
"Package" means the collection of files distributed by the
Copyright Holder, and derivatives of that collection and/or of
those files. A given Package may consist of either the Standard
Version, or a Modified Version.
"Distribute" means providing a copy of the Package or making it
accessible to anyone else, or in the case of a company or
organization, to others outside of your company or organization.
"Distributor Fee" means any fee that you charge for Distributing
this Package or providing support for this Package to another
party. It does not mean licensing fees.
"Standard Version" refers to the Package if it has not been
modified, or has been modified only in ways explicitly requested by
the Copyright Holder.
"Modified Version" means the Package, if it has been changed, and
such changes were not explicitly requested by the Copyright Holder.
"Original License" means this Artistic License as Distributed with
the Standard Version of the Package, in its current version or as
it may be modified by The Perl Foundation in the future.
"Source" form means the source code, documentation source, and
configuration files for the Package.
"Compiled" form means the compiled bytecode, object code, binary,
or any other form resulting from mechanical transformation or
translation of the Source form.
Permission for Use and Modification Without Distribution
(1) You are permitted to use the Standard Version and create and use
Modified Versions for any purpose without restriction, provided that
you do not Distribute the Modified Version.
Permissions for Redistribution of the Standard Version
(2) You may Distribute verbatim copies of the Source form of the
Standard Version of this Package in any medium without restriction,
either gratis or for a Distributor Fee, provided that you duplicate
all of the original copyright notices and associated disclaimers. At
your discretion, such verbatim copies may or may not include a
Compiled form of the Package.
(3) You may apply any bug fixes, portability changes, and other
modifications made available from the Copyright Holder. The resulting
Package will still be considered the Standard Version, and as such
will be subject to the Original License.
Distribution of Modified Versions of the Package as Source
(4) You may Distribute your Modified Version as Source (either gratis
or for a Distributor Fee, and with or without a Compiled form of the
Modified Version) provided that you clearly document how it differs
from the Standard Version, including, but not limited to, documenting
any non-standard features, executables, or modules, and provided that
you do at least ONE of the following:
(a) make the Modified Version available to the Copyright Holder of
the Standard Version, under the Original License, so that the
Copyright Holder may include your modifications in the Standard
Version.
(b) ensure that installation of your Modified Version does not
prevent the user installing or running the Standard Version. In
addition, the Modified Version must bear a name that is different
from the name of the Standard Version.
(c) allow anyone who receives a copy of the Modified Version to
make the Source form of the Modified Version available to others
under
(i) the Original License or
(ii) a license that permits the licensee to freely copy, modify
and redistribute the Modified Version using the same licensing
terms that apply to the copy that the licensee received, and
requires that the Source form of the Modified Version, and of
any works derived from it, be made freely available in that
license fees are prohibited but Distributor Fees are allowed.
Distribution of Compiled Forms of the Standard Version or
Modified Versions without the Source
(5) You may Distribute Compiled forms of the Standard Version without
the Source, provided that you include complete instructions on how to
get the Source of the Standard Version. Such instructions must be
valid at the time of your distribution. If these instructions, at any
time while you are carrying out such distribution, become invalid, you
must provide new instructions on demand or cease further
distribution. If you provide valid instructions or cease distribution
within thirty days after you become aware that the instructions are
invalid, then you do not forfeit any of your rights under this
license.
(6) You may Distribute a Modified Version in Compiled form without the
Source, provided that you comply with Section 4 with respect to the
Source of the Modified Version.
Aggregating or Linking the Package
(7) You may aggregate the Package (either the Standard Version or
Modified Version) with other packages and Distribute the resulting
aggregation provided that you do not charge a licensing fee for the
Package. Distributor Fees are permitted, and licensing fees for other
components in the aggregation are permitted. The terms of this license
apply to the use and Distribution of the Standard or Modified Versions
as included in the aggregation.
(8) You are permitted to link Modified and Standard Versions with
other works, to embed the Package in a larger work of your own, or to
build stand-alone binary or bytecode versions of applications that
include the Package, and Distribute the result without restriction,
provided the result does not expose a direct interface to the Package.
Items That are Not Considered Part of a Modified Version
(9) Works (including, but not limited to, modules and scripts) that
merely extend or make use of the Package, do not, by themselves, cause
the Package to be a Modified Version. In addition, such works are not
considered parts of the Package itself, and are not subject to the
terms of this license.
General Provisions
(10) Any use, modification, and distribution of the Standard or
Modified Versions is governed by this Artistic License. By using,
modifying or distributing the Package, you accept this license. Do not
use, modify, or distribute the Package, if you do not accept this
license.
(11) If your Modified Version has been derived from a Modified Version
made by someone other than you, you are nevertheless required to
ensure that your Modified Version complies with the requirements of
this license.
(12) This license does not grant you the right to use any trademark,
service mark, tradename, or logo of the Copyright Holder.
(13) This license includes the non-exclusive, worldwide,
free-of-charge patent license to make, have made, use, offer to sell,
sell, import and otherwise transfer the Package with respect to any
patent claims licensable by the Copyright Holder that are necessarily
infringed by the Package. If you institute patent litigation
(including a cross-claim or counterclaim) against any party alleging
that the Package constitutes direct or contributory patent
infringement, then this Artistic License to you shall terminate on the
date that such litigation is filed.
(14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT
HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT
PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT
HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE
OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

3
ChangeLog Normal file
View File

@ -0,0 +1,3 @@
2008-04-26 Jay Berkenbilt <ejb@ql.org>
* 2.0: initial public release

216
INSTALL Normal file
View File

@ -0,0 +1,216 @@
These instructions based on the generic INSTALL file from automake
1.10. However, qpdf does not use automake, so not all of that file
applies.
Basic Installation
==================
Briefly, the shell commands `./configure; make; make install' should
configure, build, and install this package. The following
more-detailed instructions are generic; see the `README' file for
instructions specific to this package.
The `configure' shell script attempts to guess correct values for
various system-dependent variables used during compilation. It uses
those values to create a `Makefile' in each directory of the package.
It may also create one or more `.h' files containing system-dependent
definitions. Finally, it creates a shell script `config.status' that
you can run in the future to recreate the current configuration, and a
file `config.log' containing compiler output (useful mainly for
debugging `configure').
It can also use an optional file (typically called `config.cache'
and enabled with `--cache-file=config.cache' or simply `-C') that saves
the results of its tests to speed up reconfiguring. Caching is
disabled by default to prevent problems with accidental use of stale
cache files.
If you need to do unusual things to compile the package, please try
to figure out how `configure' could check whether to do them, and mail
diffs or instructions to the address given in the `README' so they can
be considered for the next release. If you are using the cache, and at
some point `config.cache' contains results you don't want to keep, you
may remove or edit it.
The file `configure.ac' (or `configure.in') is used to create
`configure' by a program called `autoconf'. You need `configure.ac' if
you want to change it or regenerate `configure' using a newer version
of `autoconf'.
The simplest way to compile this package is:
1. `cd' to the directory containing the package's source code and type
`./configure' to configure the package for your system.
Running `configure' might take a while. While running, it prints
some messages telling which features it is checking for.
2. Type `make' to compile the package.
3. Optionally, type `make check' to run any self-tests that come with
the package.
4. Type `make install' to install the programs and any data files and
documentation.
5. You can remove the program binaries and object files from the
source code directory by typing `make clean'. To also remove the
files that `configure' created (so you can compile the package for
a different kind of computer), type `make distclean'. There is
also a `make maintainer-clean' target, but that is intended mainly
for the package's developers. If you use it, you may have to get
all sorts of other programs in order to regenerate files that came
with the distribution.
Compilers and Options
=====================
Some systems require unusual options for compilation or linking that the
`configure' script does not know about. Run `./configure --help' for
details on some of the pertinent environment variables.
You can give `configure' initial values for configuration parameters
by setting variables in the command line or in the environment. Here
is an example:
./configure CC=c99 CFLAGS=-g LIBS=-lposix
Installation Names
==================
By default, `make install' installs the package's commands under
`/usr/local/bin', include files under `/usr/local/include', etc. You
can specify an installation prefix other than `/usr/local' by giving
`configure' the option `--prefix=PREFIX'.
You can specify separate installation prefixes for
architecture-specific files and architecture-independent files. If you
pass the option `--exec-prefix=PREFIX' to `configure', the package uses
PREFIX as the prefix for installing programs and libraries.
Documentation and other data files still use the regular prefix.
In addition, if you use an unusual directory layout you can give
options like `--bindir=DIR' to specify different values for particular
kinds of files. Run `configure --help' for a list of the directories
you can set and what kinds of files go in them.
If the package supports it, you can cause programs to be installed
with an extra prefix or suffix on their names by giving `configure' the
option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
You can also define the variable DESTDIR when you run make install
to install the package in a separate subdirectory. This is useful for
packaging.
Optional Features
=================
Some packages pay attention to `--enable-FEATURE' options to
`configure', where FEATURE indicates an optional part of the package.
They may also pay attention to `--with-PACKAGE' options, where PACKAGE
is something like `gnu-as' or `x' (for the X Window System). The
`README' should mention any `--enable-' and `--with-' options that the
package recognizes.
For packages that use the X Window System, `configure' can usually
find the X include and library files automatically, but if it doesn't,
you can use the `configure' options `--x-includes=DIR' and
`--x-libraries=DIR' to specify their locations.
Specifying the System Type
==========================
There may be some features `configure' cannot figure out automatically,
but needs to determine by the type of machine the package will run on.
Usually, assuming the package is built to be run on the _same_
architectures, `configure' can figure that out, but if it prints a
message saying it cannot guess the machine type, give it the
`--build=TYPE' option. TYPE can either be a short name for the system
type, such as `sun4', or a canonical name which has the form:
CPU-COMPANY-SYSTEM
where SYSTEM can have one of these forms:
OS KERNEL-OS
See the file `config.sub' for the possible values of each field. If
`config.sub' isn't included in this package, then this package doesn't
need to know the machine type.
If you are _building_ compiler tools for cross-compiling, you should
use the option `--target=TYPE' to select the type of system they will
produce code for.
If you want to _use_ a cross compiler, that generates code for a
platform different from the build platform, you should specify the
"host" platform (i.e., that on which the generated programs will
eventually be run) with `--host=TYPE'.
Sharing Defaults
================
If you want to set default values for `configure' scripts to share, you
can create a site shell script called `config.site' that gives default
values for variables like `CC', `cache_file', and `prefix'.
`configure' looks for `PREFIX/share/config.site' if it exists, then
`PREFIX/etc/config.site' if it exists. Or, you can set the
`CONFIG_SITE' environment variable to the location of the site script.
A warning: not all `configure' scripts look for a site script.
Defining Variables
==================
Variables not defined in a site shell script can be set in the
environment passed to `configure'. However, some packages may run
configure again during the build, and the customized values of these
variables may be lost. In order to avoid this problem, you should set
them in the `configure' command line, using `VAR=value'. For example:
./configure CC=/usr/local2/bin/gcc
causes the specified `gcc' to be used as the C compiler (unless it is
overridden in the site shell script).
Unfortunately, this technique does not work for `CONFIG_SHELL' due to
an Autoconf bug. Until the bug is fixed you can use this workaround:
CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash
`configure' Invocation
======================
`configure' recognizes the following options to control how it operates.
`--help'
`-h'
Print a summary of the options to `configure', and exit.
`--version'
`-V'
Print the version of Autoconf used to generate the `configure'
script, and exit.
`--cache-file=FILE'
Enable the cache: use and save the results of the tests in FILE,
traditionally `config.cache'. FILE defaults to `/dev/null' to
disable caching.
`--config-cache'
`-C'
Alias for `--cache-file=config.cache'.
`--quiet'
`--silent'
`-q'
Do not print messages saying which checks are being made. To
suppress all normal output, redirect it to `/dev/null' (any error
messages will still be shown).
`--srcdir=DIR'
Look for the package's source code in directory DIR. Usually
`configure' can determine that directory automatically.
`configure' also accepts some other, not widely useful, options. Run
`configure --help' for more details.

144
Makefile Normal file
View File

@ -0,0 +1,144 @@
# This makefile is inspired by abuild (http://www.abuild.org), which
# was used during the development of qpdf. The goal here is to have a
# non-recursive build with all the proper dependencies so we can start
# the build from anywhere and get the right stuff. Each directory has
# a build.mk that is included from here and is written from this
# directory's perspective. Each directory also has a proxy Makefile
# that allows you to start the build from any directory and get
# reasonable semantics for the all, check, and clean targets.
# Our "build items" are directories. They are listed here such that
# no item precedes any item it depends on. Therefore, each item can
# safely reference variables set in its predecessors.
# For each build item B, you can run make build_B, make check_B, or
# make clean_B to build, test, or clean B. Full dependencies are
# represented across all the items, so it is possible to start
# anywhere. From the top level, the "all", "check", and "clean"
# targets build, test, or clean everything.
# Although this is not a GNU package and does not use automake, you
# can still run make clean to remove everything that is compiled, make
# distclean to remove everything that is generated by the end user,
# and make maintainer-clean to remove everything that is generated
# including things distributed with the source distribution. You can
# pass CLEAN=1 to prevent this Makefile from complaining if
# ./configure has not been run.
# The install target works as usual and obeys --prefix and so forth
# passed to ./configure. You can also pass DESTDIR=/dir to make
# install to install in a separate location. This is useful for
# packagers.
BUILD_ITEMS = manual libqpdf zlib-flate libtests qpdf examples
OUTPUT_DIR = build
ALL_TARGETS =
.PHONY: default
default: all
CLEAN ?=
ifneq ($(CLEAN),1)
ifeq ($(words $(wildcard autoconf.mk)),0)
DUMMY := $(shell echo 1>&2)
DUMMY := $(shell echo 1>&2 Please run ./configure before running $(MAKE))
DUMMY := $(shell echo 1>&2)
$(error unable to continue with build)
endif
autoconf.mk:
include autoconf.mk
endif
# Prevent gnu make from trying to rebuild .dep files
$(foreach B,$(BUILD_ITEMS),$(eval \
$(B)/$(OUTPUT_DIR)/%.dep: ;))
# Prevent gnu make from trying to rebuild .mk files
$(foreach B,$(BUILD_ITEMS),$(eval \
$(B)/%.mk: ;))
%.mk: ;
make/%.mk: ;
include make/rules.mk
DUMMY := $(shell mkdir $(foreach B,$(BUILD_ITEMS),$(B)/$(OUTPUT_DIR)) 2>/dev/null)
include $(foreach B,$(BUILD_ITEMS),$(B)/build.mk)
ALL_TARGETS = $(foreach B,$(BUILD_ITEMS),$(TARGETS_$(B)))
TEST_ITEMS = $(foreach D,\
$(wildcard $(foreach B,$(BUILD_ITEMS),$(B)/qtest)),\
$(subst /,,$(dir $(D))))
TEST_TARGETS = $(foreach B,$(TEST_ITEMS),check_$(B))
CLEAN_TARGETS = $(foreach B,$(BUILD_ITEMS),clean_$(B))
# For test suitse
export QPDF_BIN = $(abspath qpdf/$(OUTPUT_DIR)/qpdf)
export SKIP_TEST_COMPARE_IMAGES
clean: $(CLEAN_TARGETS)
.PHONY: $(CLEAN_TARGETS)
$(foreach B,$(BUILD_ITEMS),$(eval \
clean_$(B): ; \
$(RM) -r $(B)/$(OUTPUT_DIR)))
distclean: clean
$(RM) -r autoconf.mk autom4te.cache config.log config.status libtool
$(RM) manual/html.xsl
$(RM) manual/print.xsl
$(RM) doc/*.1
maintainer-clean: distclean
$(RM) configure doc/qpdf-manual.*
.PHONY: $(TEST_TARGETS)
$(foreach B,$(TEST_ITEMS),$(eval \
check_$(B): $(TARGETS_$(B))))
.PHONY: $(foreach B,$(BUILD_ITEMS),build_$(B))
$(foreach B,$(BUILD_ITEMS),$(eval \
build_$(B): $(TARGETS_$(B))))
.PHONY: all
all: $(ALL_TARGETS) ;
check: $(TEST_TARGETS)
install_docs::
install: all
./mkinstalldirs $(DESTDIR)$(libdir)
./mkinstalldirs $(DESTDIR)$(bindir)
./mkinstalldirs $(DESTDIR)$(includedir)/qpdf
./mkinstalldirs $(DESTDIR)$(docdir)
./mkinstalldirs $(DESTDIR)$(mandir)/man1
$(LIBTOOL) --mode=install install -s -c \
libqpdf/$(OUTPUT_DIR)/libqpdf.la \
$(DESTDIR)$(libdir)/libqpdf.la
$(LIBTOOL) --finish $(DESTDIR)$(libdir)
$(LIBTOOL) --mode=install install -s -c \
qpdf/$(OUTPUT_DIR)/qpdf \
$(DESTDIR)$(bindir)/qpdf
$(LIBTOOL) --mode=install install -s -c \
zlib-flate/$(OUTPUT_DIR)/zlib-flate \
$(DESTDIR)$(bindir)/zlib-flate
cp qpdf/fix-qdf $(DESTDIR)$(bindir)
cp include/qpdf/*.hh $(DESTDIR)$(includedir)/qpdf
cp doc/stylesheet.css $(DESTDIR)$(docdir)
cp doc/qpdf-manual.html $(DESTDIR)$(docdir)
cp doc/qpdf-manual.pdf $(DESTDIR)$(docdir)
cp doc/*.1 $(DESTDIR)$(mandir)/man1
QTEST=$(abspath qtest/bin/qtest-driver)
$(TEST_TARGETS):
@echo running qtest-driver for $(subst check_,,$@)
@(cd $(subst check_,,$@)/$(OUTPUT_DIR); \
TC_SRCS="$(foreach T,$(TC_SRCS_$(subst check_,,$@)),../../$(T))" \
$(QTEST) -bindirs .:.. -datadir ../qtest -covdir ..)

32
README Normal file
View File

@ -0,0 +1,32 @@
This is the QPDF package. Information about it can be found at
http://qpdf.qbilt.org.
QPDF is copyright (c) 2005-2008 Jay Berkenbilt
This software may be distributed under the terms of version 2 of the
Artistic License which may be found in the source distribution as
"Artistic-2.0". It is provided "as is" without express or implied
warranty.
To install this software, you can run
./configure
make
make install
For more detailed information, see the "INSTALL" in this directory.
The QPDF package provides some executables and a software library. A
user's manual can be found in the "doc" directory. The docbook
sources to the user's manual can be found in the "manual" directory.
The software library is just libqpdf, and all the header files are in
the qpdf subdirectory. If you link with -lqpdf and your system does
not know how to read libtool .la files, then you will also need to
link with -lpcre and -lz.
To learn about using the library, please read comments in the header
files in include/qpdf, especially QPDF.hh, QPDFObjectHandle.hh, and
QPDFWriter.hh. You can also study the code of qpdf/qpdf.cc, which
exercises most of the public interface. There are additional example
programs in the examples directory.

69
README.maintainer Normal file
View File

@ -0,0 +1,69 @@
Release Reminders
=================
* To create a source release, do an export from the version control
system to a directory called qpdf-version. From the parent of that
directory, run make_dist with the directory as an argument. For
internally testing releases, you can run make_dist with the
--no-tests option.
* Make sure version numbers are consistent in the following
locations:
configure.ac
qpdf.spec
qpdf/qpdf.cc
manual/qpdf-manual.xml
make_dist does this automatically.
* Each year, update copyright notices. Just search for Copyright.
Last updated: 2008.
* To construct a source distribution from a pristine checkout,
make_release does the following.
autoconf
./configure --enable-doc-maintenance
make build_manual
make distclean
* Remember to update documentation in the "files" subdirectory of the
website on sourceforge.net.
General Build Stuff
===================
QPDF supports autoconf and libtool but does not use automake. In
addition, there is no header file generated by autoconf. The only
file distributed with the qpdf source distribution that is not a
controlled file is "configure", and it is generated by just running
"autoconf". There is no need to run autoreconf, automake, autoheader,
aclocal, or any other autotools programs beyond autoconf.
A small handful of additional files have been taken from autotools
programs. These should probably be updated from time to time.
* config.guess, config.sub, ltmain.sh: these were created by running
libtoolize -c. To update, run libtoolize -f -c or remove the files
and rerun libtoolize.
* Other files copied as indicated:
cp /usr/share/aclocal/libtool.m4 aclocal.m4
cp /usr/share/automake-1.10/install-sh .
cp /usr/share/automake-1.10/mkinstalldirs .
The entire contents of aclocal.m4 came from libtool.m4. If we had
some additional local parts, we could manually combine them or we
could run aclocal. For now, the simple copy statement above is
sufficient.
If building or editing documentation, configure with
--enable-doc-maintenance. This will ensure that all tools or files
required to validate and build documentation are available.
If you want to run make maintainer-clean or make distclean and you
haven't run ./configure, you can pass CLEAN=1 to make on the command
line to prevent it from complaining about configure not having been
run.

68
TODO Normal file
View File

@ -0,0 +1,68 @@
General
=======
* See whether we can do anything with /V > 3 in the encryption
dictionary. (V = 4 is Crypt Filters.)
* The second xref stream for linearized files has to be padded only
because we need file_size as computed in pass 1 to be accurate. If
we were not allowing writing to a pipe, we could seek back to the
beginning and fill in the value of /L in the linearization
dictionary as an optimization to alleviate the need for this
padding. Doing so would require us to pad the /L value
individually and also to save the file descriptor and determine
whether it's seekable. This is probably not worth bothering with.
* The whole xref handling code in the QPDF object allows the same
object with more than one generation to coexist, but a lot of logic
assumes this isn't the case. Anything that creates mappings only
with the object number and not the generation is this way,
including most of the interaction between QPDFWriter and QPDF. If
we wanted to allow the same object with more than one generation to
coexist, which I'm not sure is allowed, we could fix this by
changing xref_table. Alternatively, we could detect and disallow
that case. In fact, it appears that Adobe reader and other PDF
viewing software silently ignores objects of this type, so this is
probably not a big deal.
* Pl_PNGFilter is only partially implemented. If we ever decoded
images, we'd have to finish implementing it along with the other
filter decode parameters and types. For just handling xref
streams, there's really no need as it wouldn't make sense to use
any kind of predictor other than 12 (PNG UP filter).
* If we ever want to have check mode check the integrity of the free
list, this can be done by looking at the code from prior to the
object stream support of 4/5/2008. It's in an if (0) block and
there's a comment about it. There's also something about it in
qpdf.test -- search for "free table". On the other hand, the value
of doing this seems very low since no viewer seems to care, so it's
probably not worth it.
* Embedded files streams: figure out why running qpdf over the pdf
1.7 spec results in a file that crashes acrobat reader when you try
to save nested documents.
* QPDFObjectHandle::getPageImages() doesn't notice images in
inherited resource dictionaries. See comments in that function.
Splitting by Pages
==================
Although qpdf does not currently support splitting a file into pages,
the work done for linearization covers almost all the work. To do
page splitting. If this functionality is needed, study
obj_user_to_objects and object_to_obj_users created in
QPDF_optimization for ideas. It's quite possible that the information
computed by calculateLinearizationData is actually sufficient to do
page splitting in many circumstances. That code knows which objects
are used by which pages, though it doesn't do anything page-specific
with outlines, thumbnails, page labels, or anything else.
Another approach would be to traverse only pages that are being output
taking care not to traverse into the pages tree, and then to fabricate
a new pages tree.
Either way, care must be taken to handle other things such as
outlines, page labels, thumbnails, threads, etc. in a sensible way.
This may include simply omitting information other than page content.

6672
aclocal.m4 vendored Normal file

File diff suppressed because it is too large Load Diff

30
autoconf.mk.in Normal file
View File

@ -0,0 +1,30 @@
PACKAGE_TARNAME=@PACKAGE_TARNAME@
PACKAGE_VERSION=@PACKAGE_VERSION@
top_builddir=@top_builddir@
prefix=@prefix@
exec_prefix=@exec_prefix@
bindir=@bindir@
libdir=@libdir@
includedir=@includedir@
datarootdir=@datarootdir@
mandir=@mandir@
docdir=@docdir@
htmldir=@htmldir@
pdfdir=@pdfdir
CC=@CC@
CFLAGS=@CFLAGS@
LDFLAGS=@LDFLAGS@
LIBS=@LIBS@
CPPFLAGS=@CPPFLAGS@
CXX=@CXX@
CXXFLAGS=@CXXFLAGS@
GENDEPS=@GENDEPS@
LIBTOOL=@LIBTOOL@
DOCBOOKX_DTD=@DOCBOOKX_DTD@
FOP=@FOP@
XSLTPROC=@XSLTPROC@
XMLLINT=@XMLLINT@
BUILD_HTML=@BUILD_HTML@
BUILD_PDF=@BUILD_PDF@
VALIDATE_DOC=@VALIDATE_DOC@
SKIP_TEST_COMPARE_IMAGES=@SKIP_TEST_COMPARE_IMAGES@

1526
config.guess vendored Executable file

File diff suppressed because it is too large Load Diff

1658
config.sub vendored Executable file

File diff suppressed because it is too large Load Diff

282
configure.ac Normal file
View File

@ -0,0 +1,282 @@
dnl Process this file with autoconf to produce a configure script.
dnl This config.in requires autoconf 2.5 or greater.
AC_PREREQ(2.59)
AC_INIT(qpdf,2.0)
dnl No AC_CONFIG_HEADERS. If this changes, update README.maintainer.
AC_CONFIG_FILES([autoconf.mk])
AC_CONFIG_FILES([manual/html.xsl manual/print.xsl])
AC_PROG_CC
AC_PROG_CXX
AC_HEADER_STDC
AC_PROG_LIBTOOL
AC_CHECK_HEADER(zlib.h,,[MISSING_ZLIB_H=1; MISSING_ANY=1])
AC_SEARCH_LIBS(deflate,z zlib,,[MISSING_ZLIB=1; MISSING_ANY=1])
AC_CHECK_HEADER(pcre.h,,[MISSING_PCRE_H=1; MISSING_ANY=1])
AC_SEARCH_LIBS(pcre_compile,pcre,,[MISSING_PCRE=1; MISSING_ANY=1])
AC_MSG_CHECKING(for gnu make >= 3.81)
make_okay=0
if make --version >/dev/null 2>&1; then
v=`make --version | grep 'GNU Make' | sed -e 's/.*Make //'`
maj=`echo $v | cut -d. -f 1`
min=`echo $v | cut -d. -f 2`
if test $maj -gt 3 -o '(' $maj -eq 3 -a $min -ge 81 ')'; then
make_okay=1
fi
fi
if test "$make_okay" = "1"; then
AC_MSG_RESULT(yes)
else
dnl Don't set MISSING_ANY=1 -- maybe user calls make something else
MISSING_MAKE_381=1
ISSUE_WARNINGS=1
AC_MSG_RESULT(no)
fi
AC_SUBST(GENDEPS)
GENDEPS=0
AC_MSG_CHECKING(for whether $CC supports -MD -MF x.dep -MP)
oCFLAGS=$CFLAGS
rm -f x.dep
CFLAGS="$CFLAGS -MD -MF x.dep -MP"
AC_TRY_COMPILE([#include <stdio.h>], [FILE* a = stdout],
qpdf_DEPFLAGS=yes,
qpdf_DEPFLAGS=no)
CFLAGS=$oCFLAGS
if test "$qpdf_DEPFLAGS" = "yes"; then
if ! grep stdio.h x.dep >/dev/null 2>&1; then
qpdf_DEPFLAGS=no
fi
fi
rm -f x.dep
if test "$qpdf_DEPFLAGS" = "yes"; then
AC_MSG_RESULT(yes)
GENDEPS=1
else
AC_MSG_RESULT(no)
fi
qpdf_USE_WALL=0
AC_MSG_CHECKING(for whether $CC supports -Wall)
oCFLAGS=$CFLAGS
CFLAGS="$CFLAGS -Wall"
AC_TRY_COMPILE([], [int a = 1; int b = a; a = b;],
qpdf_USE_WALL=1,
qpdf_USE_WALL=0)
if test "$qpdf_USE_WALL" = "1"; then
AC_MSG_RESULT(yes)
CXXFLAGS="$CXXFLAGS -Wall"
else
AC_MSG_RESULT(no)
CFLAGS=$oCFLAGS
fi
AC_MSG_CHECKING(for whether to use -Werror)
AC_ARG_ENABLE(werror,
AS_HELP_STRING([--enable-werror],
[whether to use werror (default is yes if -Wall works)]),
[if test "$enableval" = "yes"; then
qpdf_USE_WERROR=1;
else
qpdf_USE_WERROR=0;
fi], [qpdf_USE_WERROR=$qpdf_USE_WALL])
if test "$qpdf_USE_WERROR" = "1"; then
AC_MSG_RESULT(yes)
CFLAGS="$CFLAGS -Werror"
CXXFLAGS="$CXXFLAGS -Werror"
else
AC_MSG_RESULT(no)
fi
AC_SUBST(SKIP_TEST_COMPARE_IMAGES)
AC_ARG_ENABLE(test-compare-images,
AS_HELP_STRING([--enable-test-compare-images],
[whether to compare images in test suite; enabled by default, enabling requires ghostscript and tiffcmp to be available]),
[if test "$enableval" = "no"; then
SKIP_TEST_COMPARE_IMAGES=1
else
SKIP_TEST_COMPARE_IMAGES=0
fi],
[SKIP_TEST_COMPARE_IMAGES=0])
AC_ARG_WITH(docbook-xsl,
AS_HELP_STRING([--with-docbook-xsl=DIR],
[location of docbook 4.x xml stylesheets]),
[DOCBOOK_XSL=$withval],
[DOCBOOK_XSL=/usr/share/xml/docbook/stylesheet/nwalsh])
DOCBOOK_XHTML=
AC_SUBST(DOCBOOK_XHTML)
AC_MSG_CHECKING(for xml to xhtml docbook stylesheets)
if test -f "$DOCBOOK_XSL/xhtml/docbook.xsl"; then
DOCBOOK_XHTML="$DOCBOOK_XSL/xhtml/docbook.xsl"
AC_MSG_RESULT($DOCBOOK_XHTML)
else
AC_MSG_RESULT(no)
fi
DOCBOOK_FO=
AC_SUBST(DOCBOOK_FO)
AC_MSG_CHECKING(for xml to fo docbook stylesheets)
if test -f "$DOCBOOK_XSL/fo/docbook.xsl"; then
DOCBOOK_FO="$DOCBOOK_XSL/fo/docbook.xsl"
AC_MSG_RESULT($DOCBOOK_FO)
else
AC_MSG_RESULT(no)
fi
DOCBOOKX_DTD=
AC_SUBST(DOCBOOKX_DTD)
AC_ARG_WITH(docbookx-dtd,
AS_HELP_STRING([--with-docbookx-dtd=FILE],
[location of docbook 4.x xml DTD]),
[DOCBOOKX_DTD=$withval],
[DOCBOOKX_DTD=/usr/share/xml/docbook/schema/dtd/4/docbookx.dtd])
AC_MSG_CHECKING(for docbook 4.x xml DTD)
if test -f "$DOCBOOKX_DTD"; then
AC_MSG_RESULT($DOCBOOKX_DTD)
else
AC_MSG_RESULT(no)
fi
AC_CHECK_PROG(FOP,fop,fop,[])
AC_CHECK_PROG(XSLTPROC,xsltproc,xsltproc,[])
AC_CHECK_PROG(XMLLINT,xmllint,xmllint,[])
AC_ARG_ENABLE(doc-maintenance,
AS_HELP_STRING([--enable-doc-maintenance],
[if set, enables all documentation options]),
[if test "$enableval" = "yes"; then
doc_default=1;
else
doc_default=0;
fi],
[doc_default=0])
BUILD_HTML=0
AC_SUBST(BUILD_HTML)
AC_ARG_ENABLE(html-doc,
AS_HELP_STRING([--enable-html-doc],
[whether to build HTML documents]),
[if test "$enableval" = "yes"; then
BUILD_HTML=1;
else
BUILD_HTML=0;
fi],
[BUILD_HTML=$doc_default])
BUILD_PDF=0
AC_SUBST(BUILD_PDF)
AC_ARG_ENABLE(pdf-doc,
AS_HELP_STRING([--enable-pdf-doc],
[whether to build PDF documents]),
[if test "$enableval" = "yes"; then
BUILD_PDF=1;
else
BUILD_PDF=0;
fi],
[BUILD_PDF=$doc_default])
VALIDATE_DOC=0
AC_SUBST(VALIDATE_DOC)
AC_ARG_ENABLE(validate-doc,
AS_HELP_STRING([--enable-validate-doc],
[whether to validate xml document source]),
[if test "$enableval" = "yes"; then
VALIDATE_DOC=1;
else
VALIDATE_DOC=0;
fi],
[VALIDATE_DOC=$doc_default])
if test "$VALIDATE_DOC" = "1"; then
if test "$XMLLINT" = ""; then
MISSING_XMLLINT=1
MISSING_ANY=1
fi
fi
if test "$BUILD_HTML" = "1"; then
if test "$XSLTPROC" = ""; then
MISSING_XSLTPROC=1
MISSING_ANY=1
fi
if test "$DOCBOOK_XHTML" = ""; then
MISSING_DOCBOOK_XHTML=1
MISSING_ANY=1
fi
fi
if test "$BUILD_PDF" = "1"; then
if test "$XSLTPROC" = ""; then
MISSING_XSLTPROC=1
MISSING_ANY=1
fi
if test "$DOCBOOK_FO" = ""; then
MISSING_DOCBOOK_FO=1
MISSING_ANY=1
fi
if test "$FOP" = ""; then
MISSING_FOP=1
MISSING_ANY=1
fi
fi
if test "$MISSING_ANY" = "1"; then
ISSUE_WARNINGS=1
fi
if test "$ISSUE_WARNINGS" = "1"; then
echo ""
echo ""
fi
if test "$MISSING_MAKE_381" = "1"; then
AC_MSG_WARN(gnu make >= 3.81 is required)
fi
if test "$MISSING_ZLIB_H" = "1"; then
AC_MSG_WARN(unable to find required header zlib.h)
fi
if test "$MISSING_ZLIB" = "1"; then
AC_MSG_WARN(unable to find required library z (or zlib))
fi
if test "$MISSING_PCRE_H" = "1"; then
AC_MSG_WARN(unable to find required header pcre.h)
fi
if test "$MISSING_PCRE" = "1"; then
AC_MSG_WARN(unable to find required library pcre)
fi
if test "$MISSING_DOCBOOK_FO" = "1"; then
AC_MSG_WARN(docbook fo stylesheets are required to build PDF documentation)
fi
if test "$MISSING_DOCBOOK_XHTML" = "1"; then
AC_MSG_WARN(docbook xhmtl stylesheets are required to build HTML documentation)
fi
if test "$MISSING_FOP" = "1"; then
AC_MSG_WARN(apache fop is required to build PDF documentation)
fi
if test "$MISSING_XMLLINT" = "1"; then
AC_MSG_WARN(xmllint is required to validate documentation)
fi
if test "$MISSING_XSLTPROC" = "1"; then
AC_MSG_WARN(xsltproc is required to build documentation)
fi
if test "$ISSUE_WARNINGS" = "1"; then
echo ""
echo ""
fi
if test "$MISSING_ANY" = "1"; then
AC_MSG_ERROR(some required prerequisites were not found)
fi
AC_OUTPUT()

284
doc/stylesheet.css Normal file
View File

@ -0,0 +1,284 @@
/**************************************************************/
/* Custom style-sheet for the QPDF manual in HTML form. */
/**************************************************************/
/*
* This file is the CSS for the QPDF manual. It is based heavily on
* the CSS for the Subversion book. That file contains the following
* copyright and attribution:
*
* Copyright (c) 2003-2007
* Ben Collins-Sussman, Brian W. Fitzpatrick, C. Michael Pilato.
*
* This work is licensed under the Creative Commons Attribution License.
* To view a copy of this license, visit
* http://creativecommons.org/licenses/by/2.0/ or send a letter to
* Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305,
* USA.
*/
body
{
background: white;
margin: 0.5in;
}
p, li, ul, ol, dd, dt
{
font-style: normal;
font-weight: normal;
color: black;
}
tt, pre
{
font-family: courier new,courier,fixed;
}
a
{
color: blue;
text-decoration: underline;
}
a:hover
{
background: rgb(75%,75%,100%);
color: blue;
text-decoration: underline;
}
a:visited
{
color: purple;
text-decoration: underline;
}
img
{
border: none;
}
h1.title
{
font-size: 250%;
font-style: normal;
font-weight: bold;
color: black;
}
h2.subtitle
{
font-size: 150%;
font-style: italic;
color: black;
}
h2.title
{
font-size: 150%;
font-style: normal;
font-weight: bold;
color: black;
}
h3.title
{
font-size: 125%;
font-style: normal;
font-weight: bold;
color: black;
}
h4.title
{
font-size: 100%;
font-style: normal;
font-weight: bold;
color: black;
}
.toc b
{
font-size: 125%;
font-style: normal;
font-weight: bold;
color: black;
}
.screen, .programlisting, .literal
{
font-family: courier new,courier,fixed;
font-style: normal;
font-weight: normal;
}
.command, .option, .type
{
font-family: courier new,courier,fixed;
font-style: normal;
font-weight: normal;
}
.filename
{
font-family: arial,helvetica,sans-serif;
font-style: italic;
}
.property
{
font-family: arial,helvetica,sans-serif;
font-weight: bold;
}
.classname
{
font-family: arial,helvetica,sans-serif;
font-weight: bold;
font-style: italic;
}
.varname, .function, .envar
{
font-family: arial,helvetica,sans-serif;
font-style: italic;
}
.replaceable
{
font-style: italic;
font-size: 100%;
}
.figure, .example, .table
{
margin: 0.125in 0.25in;
}
.table table
{
border-width: 1px;
border-style: solid;
border-color: black;
border-spacing: 0;
background: rgb(240,240,240);
}
.table td
{
border: none;
border-right: 1px black solid;
border-bottom: 1px black solid;
padding: 2px;
}
.table th
{
background: rgb(180,180,180);
border: none;
border-right: 1px black solid;
border-bottom: 1px black solid;
padding: 2px;
}
.table p.title, .figure p.title, .example p.title
{
text-align: left !important;
font-size: 100% !important;
}
.author, .pubdate
{
margin: 0;
font-size: 100%;
font-style: italic;
font-weight: normal;
color: black;
}
.preface div.author, .preface .pubdate
{
font-size: 80%;
}
.sidebar
{
border-top: dotted 1px black;
border-left: dotted 1px black;
border-right: solid 2px black;
border-bottom: solid 2px black;
background: rgb(240,220,170);
padding: 0 0.12in;
margin: 0.25in;
}
.note .programlisting, .note .screen,
.tip .programlisting, .tip .screen,
.warning .programlisting, .warning .screen,
.sidebar .programlisting, .sidebar .screen
{
border: none;
background: none;
}
.sidebar p.title
{
text-align: center;
font-size: 125%;
}
.note
{
border: black solid 1px;
background: url(./images/note.png) no-repeat rgb(252,246,220);
margin: 0.125in 0;
padding: 0 55px;
}
.tip
{
border: black solid 1px;
background: url(./images/tip.png) no-repeat rgb(224,244,255);
margin: 0.125in 0;
padding: 0 55px;
}
.warning
{
border: black solid 1px;
background: url(./images/warning.png) no-repeat rgb(255,210,210);
margin: 0.125in 0;
padding: 0 55px;
}
/*
.note .title, .tip .title, .warning .title
{
display: none;
}
*/
.programlisting, .screen
{
font-size: 90%;
color: black;
margin: 1em 0.25in;
padding: 0.5em;
background: rgb(240,240,240);
border-top: black dotted 1px;
border-left: black dotted 1px;
border-right: black solid 2px;
border-bottom: black solid 2px;
}
.navheader, .navfooter
{
border: black solid 1px;
background: rgb(180,180,200);
}
.navheader hr, .navfooter hr
{
display: none;
}

1
examples/Makefile Normal file
View File

@ -0,0 +1 @@
include ../make/proxy.mk

26
examples/build.mk Normal file
View File

@ -0,0 +1,26 @@
BINS_examples = pdf-bookmarks pdf-mod-info pdf-npages
TARGETS_examples = $(foreach B,$(BINS_examples),examples/$(OUTPUT_DIR)/$(B))
$(TARGETS_examples): $(TARGETS_qpdf)
INCLUDES_examples = include
TC_SRCS_examples = $(wildcard examples/*.cc)
# -----
$(foreach B,$(BINS_examples),$(eval \
OBJS_$(B) = $(call src_to_obj,examples/$(B).cc)))
ifeq ($(GENDEPS),1)
-include $(foreach B,$(BINS_examples),$(call obj_to_dep,$(OBJS_$(B))))
endif
$(foreach B,$(BINS_examples),$(eval \
$(OBJS_$(B)): examples/$(OUTPUT_DIR)/%.o: examples/$(B).cc ; \
$(call compile,examples/$(B).cc,$(INCLUDES_examples))))
$(foreach B,$(BINS_examples),$(eval \
examples/$(OUTPUT_DIR)/$(B): $(OBJS_$(B)) ; \
$(call makebin,$(OBJS_$(B)),$$@)))

19
examples/examples.testcov Normal file
View File

@ -0,0 +1,19 @@
pdf-bookmarks lines 0
pdf-bookmarks numbers 0
pdf-bookmarks none 0
pdf-bookmarks has count 0
pdf-bookmarks no count 0
pdf-bookmarks open 0
pdf-bookmarks closed 0
pdf-bookmarks dest 0
pdf-bookmarks targets 0
pdf-mod-info --dump 0
pdf-mod-info no in file 0
pdf-mod-info in-place 0
pdf-mod-info -key 0
pdf-mod-info usage wrong val 0
pdf-mod-info -val 0
pdf-mod-info usage junk 0
pdf-mod-info no keys 0
pdf-mod-info has info 0
pdf-mod-info file no info 0

262
examples/pdf-bookmarks.cc Normal file
View File

@ -0,0 +1,262 @@
#include <iostream>
#include <string.h>
#include <qpdf/QPDF.hh>
#include <qpdf/QUtil.hh>
#include <qpdf/QTC.hh>
static char const* whoami = 0;
static enum { st_none, st_numbers, st_lines } style = st_none;
static bool show_open = false;
static bool show_targets = false;
static std::map<int, int> page_map;
void usage()
{
std::cerr << "Usage: " << whoami << " [options] file.pdf [password]"
<< std::endl
<< "Options:" << std::endl
<< " -numbers give bookmarks outline-style numbers"
<< std::endl
<< " -lines draw lines to show bookmark hierarchy"
<< std::endl
<< " -show-open indicate whether a bookmark is initially open"
<< std::endl
<< " -show-targets show target if possible"
<< std::endl;
exit(2);
}
void print_lines(std::vector<int>& numbers)
{
for (unsigned int i = 0; i < numbers.size() - 1; ++i)
{
if (numbers[i])
{
std::cout << "| ";
}
else
{
std::cout << " ";
}
}
}
void generate_page_map(QPDF& qpdf)
{
std::vector<QPDFObjectHandle> pages = qpdf.getAllPages();
int n = 0;
for (std::vector<QPDFObjectHandle>::iterator iter = pages.begin();
iter != pages.end(); ++iter)
{
QPDFObjectHandle& oh = *iter;
page_map[oh.getObjectID()] = ++n;
}
}
void extract_bookmarks(QPDFObjectHandle outlines, std::vector<int>& numbers)
{
if (outlines.hasKey("/Title"))
{
// No default so gcc will warn on missing tag
switch (style)
{
case st_none:
QTC::TC("examples", "pdf-bookmarks none");
break;
case st_numbers:
QTC::TC("examples", "pdf-bookmarks numbers");
for (std::vector<int>::iterator iter = numbers.begin();
iter != numbers.end(); ++iter)
{
std::cout << *iter << ".";
}
std::cout << " ";
break;
case st_lines:
QTC::TC("examples", "pdf-bookmarks lines");
print_lines(numbers);
std::cout << "|" << std::endl;
print_lines(numbers);
std::cout << "+-+ ";
break;
}
if (show_open)
{
if (outlines.hasKey("/Count"))
{
QTC::TC("examples", "pdf-bookmarks has count");
int count = outlines.getKey("/Count").getIntValue();
if (count > 0)
{
// hierarchy is open at this point
QTC::TC("examples", "pdf-bookmarks open");
std::cout << "(v) ";
}
else
{
QTC::TC("examples", "pdf-bookmarks closed");
std::cout << "(>) ";
}
}
else
{
QTC::TC("examples", "pdf-bookmarks no count");
std::cout << "( ) ";
}
}
if (show_targets)
{
QTC::TC("examples", "pdf-bookmarks targets");
std::string target = "unknown";
// Only explicit destinations supported for now
if (outlines.hasKey("/Dest"))
{
QTC::TC("examples", "pdf-bookmarks dest");
QPDFObjectHandle dest = outlines.getKey("/Dest");
if ((dest.isArray()) && (dest.getArrayNItems() > 0))
{
QPDFObjectHandle first = dest.getArrayItem(0);
int object_id = first.getObjectID();
if (page_map.count(object_id))
{
target = QUtil::int_to_string(page_map[object_id]);
}
}
std::cout << "[ -> " << target << " ] ";
}
}
std::cout << outlines.getKey("/Title").getUTF8Value() << std::endl;
}
if (outlines.hasKey("/First"))
{
numbers.push_back(0);
QPDFObjectHandle child = outlines.getKey("/First");
while (1)
{
++(numbers.back());
bool has_next = child.hasKey("/Next");
if ((style == st_lines) && (! has_next))
{
numbers.back() = 0;
}
extract_bookmarks(child, numbers);
if (has_next)
{
child = child.getKey("/Next");
}
else
{
break;
}
}
numbers.pop_back();
}
}
int main(int argc, char* argv[])
{
if ((whoami = strrchr(argv[0], '/')) == NULL)
{
whoami = argv[0];
}
else
{
++whoami;
}
// For libtool's sake....
if (strncmp(whoami, "lt-", 3) == 0)
{
whoami += 3;
}
if ((argc == 2) && (strcmp(argv[1], "--version") == 0))
{
std::cout << whoami << " version 1.5" << std::endl;
exit(0);
}
int arg;
for (arg = 1; arg < argc; ++arg)
{
if (argv[arg][0] == '-')
{
if (strcmp(argv[arg], "-numbers") == 0)
{
style = st_numbers;
}
else if (strcmp(argv[arg], "-lines") == 0)
{
style = st_lines;
}
else if (strcmp(argv[arg], "-show-open") == 0)
{
show_open = true;
}
else if (strcmp(argv[arg], "-show-targets") == 0)
{
show_targets = true;
}
else
{
usage();
}
}
else
{
break;
}
}
if (arg >= argc)
{
usage();
}
char const* filename = argv[arg++];
char const* password = "";
if (arg < argc)
{
password = argv[arg++];
}
if (arg != argc)
{
usage();
}
try
{
QPDF qpdf;
qpdf.processFile(filename, password);
QPDFObjectHandle root = qpdf.getRoot();
if (root.hasKey("/Outlines"))
{
std::vector<int> numbers;
if (show_targets)
{
generate_page_map(qpdf);
}
extract_bookmarks(root.getKey("/Outlines"), numbers);
}
else
{
std::cout << filename << " has no bookmarks" << std::endl;
}
}
catch (std::exception &e)
{
std::cerr << whoami << " processing file " << filename << ": "
<< e.what() << std::endl;
exit(2);
}
return 0;
}

219
examples/pdf-mod-info.cc Normal file
View File

@ -0,0 +1,219 @@
// Author: Vitaliy Pavlyuk
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFWriter.hh>
#include <qpdf/QPDFObjectHandle.hh>
#include <qpdf/QUtil.hh>
#include <qpdf/QTC.hh>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
static char const* version = "1.1";
static char const* whoami = 0;
void usage()
{
std::cerr
<< "Usage: " << whoami
<< " -in in_file [-out out_file] [-key key [-val val]?]+\n"
<< "Modifies/Adds/Removes PDF /Info entries in the in_file\n"
<< "and stores the result in out_file\n"
<< "Special mode: " << whoami << " --dump file\n"
<< "dumps all /Info entries to stdout\n";
exit(2);
}
void dumpInfoDict(QPDF& pdf,
std::ostream& os = std::cout,
std::string const& sep = ":\t")
{
QPDFObjectHandle trailer = pdf.getTrailer();
if (trailer.hasKey("/Info"))
{
QPDFObjectHandle info = trailer.getKey("/Info");
std::set<std::string> keys = info.getKeys();
for (std::set<std::string>::const_iterator it = keys.begin();
keys.end() != it; ++it)
{
QPDFObjectHandle elt = info.getKey(*it);
std::string val;
if (false) {}
else if (elt.isString())
{
val = elt.getStringValue();
}
else if (elt.isName())
{
val = elt.getName();
}
else // according to PDF Spec 1.5, shouldn't happen
{
val = elt.unparseResolved();
}
os << it->substr(1) << sep << val << std::endl; // skip '/'
}
}
}
void pdfDumpInfoDict(char const* fname)
{
try
{
QPDF pdf;
pdf.processFile(fname);
dumpInfoDict(pdf);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
exit(2);
}
}
int main(int argc, char* argv[])
{
bool static_id = false;
std::map<std::string, std::string> Keys;
if ((whoami = strrchr(argv[0], '/')) == NULL)
{
whoami = argv[0];
}
else
{
++whoami;
}
// For libtool's sake....
if (strncmp(whoami, "lt-", 3) == 0)
{
whoami += 3;
}
if ((argc == 2) && (! strcmp(argv[1], "--version")) )
{
std::cout << whoami << " version " << version << std::endl;
exit(0);
}
if ((argc == 4) && (! strcmp(argv[1], "--dump")) &&
(strcmp(argv[2], "-in") == 0) )
{
QTC::TC("examples", "pdf-mod-info --dump");
pdfDumpInfoDict(argv[3]);
exit(0);
}
char* fl_in = 0;
char* fl_out = 0;
char* cur_key = 0;
for (int i = 1; i < argc; ++i)
{
if ((! strcmp(argv[i], "-in")) && (++i < argc))
{
fl_in = argv[i];
}
else if ((! strcmp(argv[i], "-out")) && (++i < argc))
{
fl_out = argv[i];
}
else if (! strcmp(argv[i], "--static-id")) // don't document
{
static_id = true; // this should be used in test suites only
}
else if ((! strcmp(argv[i], "-key")) && (++i < argc))
{
QTC::TC("examples", "pdf-mod-info -key");
cur_key = argv[i];
Keys[cur_key] = "";
}
else if ((! strcmp(argv[i], "-val")) && (++i < argc))
{
if (cur_key == 0)
{
QTC::TC("examples", "pdf-mod-info usage wrong val");
usage();
}
QTC::TC("examples", "pdf-mod-info -val");
Keys[cur_key] = argv[i];
cur_key = 0;
}
else
{
QTC::TC("examples", "pdf-mod-info usage junk");
usage();
}
}
if (! fl_in)
{
QTC::TC("examples", "pdf-mod-info no in file");
usage();
}
if (! fl_out)
{
QTC::TC("examples", "pdf-mod-info in-place");
fl_out = fl_in;
}
if (Keys.size() == 0)
{
QTC::TC("examples", "pdf-mod-info no keys");
usage();
}
try
{
QPDF file;
file.processFile(fl_in);
QPDFObjectHandle filetrailer = file.getTrailer();
QPDFObjectHandle fileinfo;
for (std::map<std::string, std::string>::const_iterator it =
Keys.begin(); Keys.end() != it; ++it)
{
if (! fileinfo.isInitialized())
{
if (filetrailer.hasKey("/Info"))
{
QTC::TC("examples", "pdf-mod-info has info");
fileinfo = filetrailer.getKey("/Info");
}
else
{
QTC::TC("examples", "pdf-mod-info file no info");
std::map<std::string, QPDFObjectHandle> vacant;
fileinfo = fileinfo.newDictionary(vacant);
filetrailer.replaceKey("/Info", fileinfo);
}
}
if (it->second == "")
{
fileinfo.removeKey(it->first);
}
else
{
QPDFObjectHandle elt = fileinfo.newString(it->second);
elt.makeDirect();
fileinfo.replaceKey(it->first, elt);
}
}
std::string fl_tmp = fl_out;
fl_tmp += ".tmp";
QPDFWriter w(file, fl_tmp.c_str());
w.setStreamDataMode(QPDFWriter::s_preserve);
w.setLinearization(true);
w.setStaticID(static_id);
w.write();
QUtil::os_wrapper("rename " + fl_tmp + " " + std::string(fl_out),
rename(fl_tmp.c_str(), fl_out));
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
exit(2);
}
return 0;
}

61
examples/pdf-npages.cc Normal file
View File

@ -0,0 +1,61 @@
#include <iostream>
#include <string.h>
#include <qpdf/QPDF.hh>
static char const* whoami = 0;
void usage()
{
std::cerr << "Usage: " << whoami << " filename" << std::endl
<< "Prints the number of pages in filename" << std::endl;
exit(2);
}
int main(int argc, char* argv[])
{
if ((whoami = strrchr(argv[0], '/')) == NULL)
{
whoami = argv[0];
}
else
{
++whoami;
}
// For libtool's sake....
if (strncmp(whoami, "lt-", 3) == 0)
{
whoami += 3;
}
if ((argc == 2) && (strcmp(argv[1], "--version") == 0))
{
std::cout << whoami << " version 1.3" << std::endl;
exit(0);
}
if (argc != 2)
{
usage();
}
char const* filename = argv[1];
try
{
QPDF pdf;
pdf.processFile(filename);
QPDFObjectHandle root = pdf.getRoot();
QPDFObjectHandle pages = root.getKey("/Pages");
QPDFObjectHandle count = pages.getKey("/Count");
std::cout << count.getIntValue() << std::endl;
}
catch (std::exception& e)
{
std::cerr << whoami << ": " << e.what() << std::endl;
exit(2);
}
return 0;
}

View File

@ -0,0 +1,49 @@
#!/usr/bin/env perl
require 5.008;
BEGIN { $^W = 1; }
use strict;
chdir("bookmarks");
require TestDriver;
my $td = new TestDriver('pdf-bookmarks');
foreach my $show ("", " -show-open")
{
foreach my $style ("", " -lines", " -numbers")
{
my $out = "test.$show.$style.out";
$out =~ s/ //g;
$td->runtest("show:$show, style:$style",
{$td->COMMAND => "pdf-bookmarks $show $style 1.pdf"},
{$td->FILE => $out, $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
}
}
$td->runtest("no bookmarks",
{$td->COMMAND => "pdf-bookmarks 2.pdf"},
{$td->STRING => "2.pdf has no bookmarks\n",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("bad",
{$td->COMMAND => "pdf-bookmarks 3.pdf"},
{$td->STRING => "pdf-bookmarks processing file 3.pdf: " .
"3.pdf: offset 0: not a PDF file\n",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->runtest("encrypted, targets",
{$td->COMMAND => "pdf-bookmarks -show-targets 4.pdf user"},
{$td->FILE => "encrypted.out",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("bookmarks deleted",
{$td->COMMAND => "pdf-bookmarks 5.pdf user"},
{$td->STRING => "5.pdf has no bookmarks\n",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->report(10);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
%PDF-1.3
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [
3 0 R
]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/ProcSet 5 0 R
/Font <<
/F1 6 0 R
>>
>>
>>
endobj
4 0 obj
<<
/Length 44
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
5 0 obj
[
/PDF
/Text
]
endobj
6 0 obj
<<
/Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
>>
endobj
xref
0 7
0000000000 65535 f
0000000009 00000 n
0000000063 00000 n
0000000135 00000 n
0000000307 00000 n
0000000403 00000 n
0000000438 00000 n
trailer <<
/Size 7
/Root 1 0 R
>>
startxref
556
%%EOF

View File

@ -0,0 +1 @@
potato salad

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
[ -> 6 ] Isís 1 -> 5: /XYZ null null null
[ -> 12 ] Amanda 1.1 -> 11: /Fit
[ -> 13 ] Isosicle 1.1.1 -> 12: /FitV 100
[ -> 19 ] Isosicle 1.1.1.1 -> 18: /XYZ null null null
[ -> 20 ] Isosicle 1.1.1.2 -> 19: /XYZ null null null
[ -> 13 ] Isosicle 1.1.2 -> 12: /XYZ null null null
[ -> 23 ] Isosicle 1.1.2.1 -> 22: /XYZ null null null
[ -> 14 ] Sandy ÷Σανδι÷ 1.2 -> 13: /FitH 792
[ -> 2 ] Trepsichord 1.2.1 -> 1: /FitR 66 714 180 770
[ -> 1 ] Trepsicle 1.2.2 -> 0: /XYZ null null null
[ -> 16 ] Trepak 2 -> 15: /XYZ 66 756 3

View File

@ -0,0 +1,22 @@
|
+-+ ( ) Trepak 2 -> 15: /XYZ 66 756 3
|
+-+ (v) Isis 1 -> 5: /XYZ null null null
|
+-+ (>) Amanda 1.1 -> 11: /Fit
| |
| +-+ (>) Isosicle 1.1.1 -> 12: /FitV 100
| | |
| | +-+ ( ) Isosicle 1.1.1.1 -> 18: /XYZ null null null
| | |
| | +-+ ( ) Isosicle 1.1.1.2 -> 19: /XYZ null null null
| |
| +-+ (v) Isosicle 1.1.2 -> 12: /XYZ null null null
| |
| +-+ ( ) Isosicle 1.1.2.1 -> 22: /XYZ null null null
|
+-+ (v) Sandy 1.2 -> 13: /FitH 792
|
+-+ ( ) Trepsichord 1.2.1 -> 1: /FitR 66 714 180 770
|
+-+ ( ) Trepsicle 1.2.2 -> 0: /XYZ null null null

View File

@ -0,0 +1,11 @@
1. ( ) Trepak 2 -> 15: /XYZ 66 756 3
2. (v) Isis 1 -> 5: /XYZ null null null
2.1. (>) Amanda 1.1 -> 11: /Fit
2.1.1. (>) Isosicle 1.1.1 -> 12: /FitV 100
2.1.1.1. ( ) Isosicle 1.1.1.1 -> 18: /XYZ null null null
2.1.1.2. ( ) Isosicle 1.1.1.2 -> 19: /XYZ null null null
2.1.2. (v) Isosicle 1.1.2 -> 12: /XYZ null null null
2.1.2.1. ( ) Isosicle 1.1.2.1 -> 22: /XYZ null null null
2.2. (v) Sandy 1.2 -> 13: /FitH 792
2.2.1. ( ) Trepsichord 1.2.1 -> 1: /FitR 66 714 180 770
2.2.2. ( ) Trepsicle 1.2.2 -> 0: /XYZ null null null

View File

@ -0,0 +1,11 @@
( ) Trepak 2 -> 15: /XYZ 66 756 3
(v) Isis 1 -> 5: /XYZ null null null
(>) Amanda 1.1 -> 11: /Fit
(>) Isosicle 1.1.1 -> 12: /FitV 100
( ) Isosicle 1.1.1.1 -> 18: /XYZ null null null
( ) Isosicle 1.1.1.2 -> 19: /XYZ null null null
(v) Isosicle 1.1.2 -> 12: /XYZ null null null
( ) Isosicle 1.1.2.1 -> 22: /XYZ null null null
(v) Sandy 1.2 -> 13: /FitH 792
( ) Trepsichord 1.2.1 -> 1: /FitR 66 714 180 770
( ) Trepsicle 1.2.2 -> 0: /XYZ null null null

View File

@ -0,0 +1,22 @@
|
+-+ Trepak 2 -> 15: /XYZ 66 756 3
|
+-+ Isis 1 -> 5: /XYZ null null null
|
+-+ Amanda 1.1 -> 11: /Fit
| |
| +-+ Isosicle 1.1.1 -> 12: /FitV 100
| | |
| | +-+ Isosicle 1.1.1.1 -> 18: /XYZ null null null
| | |
| | +-+ Isosicle 1.1.1.2 -> 19: /XYZ null null null
| |
| +-+ Isosicle 1.1.2 -> 12: /XYZ null null null
| |
| +-+ Isosicle 1.1.2.1 -> 22: /XYZ null null null
|
+-+ Sandy 1.2 -> 13: /FitH 792
|
+-+ Trepsichord 1.2.1 -> 1: /FitR 66 714 180 770
|
+-+ Trepsicle 1.2.2 -> 0: /XYZ null null null

View File

@ -0,0 +1,11 @@
1. Trepak 2 -> 15: /XYZ 66 756 3
2. Isis 1 -> 5: /XYZ null null null
2.1. Amanda 1.1 -> 11: /Fit
2.1.1. Isosicle 1.1.1 -> 12: /FitV 100
2.1.1.1. Isosicle 1.1.1.1 -> 18: /XYZ null null null
2.1.1.2. Isosicle 1.1.1.2 -> 19: /XYZ null null null
2.1.2. Isosicle 1.1.2 -> 12: /XYZ null null null
2.1.2.1. Isosicle 1.1.2.1 -> 22: /XYZ null null null
2.2. Sandy 1.2 -> 13: /FitH 792
2.2.1. Trepsichord 1.2.1 -> 1: /FitR 66 714 180 770
2.2.2. Trepsicle 1.2.2 -> 0: /XYZ null null null

View File

@ -0,0 +1,11 @@
Trepak 2 -> 15: /XYZ 66 756 3
Isis 1 -> 5: /XYZ null null null
Amanda 1.1 -> 11: /Fit
Isosicle 1.1.1 -> 12: /FitV 100
Isosicle 1.1.1.1 -> 18: /XYZ null null null
Isosicle 1.1.1.2 -> 19: /XYZ null null null
Isosicle 1.1.2 -> 12: /XYZ null null null
Isosicle 1.1.2.1 -> 22: /XYZ null null null
Sandy 1.2 -> 13: /FitH 792
Trepsichord 1.2.1 -> 1: /FitR 66 714 180 770
Trepsicle 1.2.2 -> 0: /XYZ null null null

View File

@ -0,0 +1,93 @@
#!/usr/bin/env perl
require 5.008;
BEGIN { $^W = 1; }
use strict;
use File::Copy;
chdir("mod-info");
require TestDriver;
my $td = new TestDriver('pdf-mod-info');
my $prg = "pdf-mod-info";
my $qpdf = $ENV{'QPDF_BIN'} or die;
cleanup();
$td->runtest("usage #1",
{$td->COMMAND => "$prg -in target.pdf"},
{$td->FILE => "usage.out",
$td->EXIT_STATUS => 2});
$td->runtest("usage #2",
{$td->COMMAND => "$prg -key abc -val def"},
{$td->FILE => "usage.out",
$td->EXIT_STATUS => 2});
$td->runtest("usage #3",
{$td->COMMAND => "$prg -key abc -val def abc"},
{$td->FILE => "usage.out",
$td->EXIT_STATUS => 2});
$td->runtest("usage #4",
{$td->COMMAND => "$prg -in source1.pdf -key /date -val 01/01/01 -val 12/12/12"},
{$td->FILE => "usage.out",
$td->EXIT_STATUS => 2});
$td->runtest("dump #1",
{$td->COMMAND => "$prg --dump -in files/source1.pdf"},
{$td->FILE => "dump.out",
$td->EXIT_STATUS => 0});
$td->runtest("dump #2",
{$td->COMMAND => "$prg --dump -in files/no-info.pdf"},
{$td->STRING => "",
$td->EXIT_STATUS => 0});
$td->runtest("dump #3",
{$td->COMMAND => "$prg --dump -in files/empty-info.pdf"},
{$td->STRING => "",
$td->EXIT_STATUS => 0});
run_and_cmp("modify Subject",
"$prg -in files/source1.pdf -out out.pdf -key /Subject " .
"-val \"Export Business\"",
"", "out.pdf", "files/1.qdf");
run_and_cmp("add Subject, remove Producer, modify CreationDate",
"$prg -in files/source2.pdf -out out.pdf -key /Subject " .
"-val \"Tammlin\" -key /Producer -key /CreationDate -val 12/12",
"", "out.pdf", "files/2.qdf");
run_and_cmp("add Subject (empty-info file)",
"$prg -in files/empty-info.pdf -out out.pdf -key /Subject " .
"-val Tammlin",
"", "out.pdf", "files/3.qdf");
copy("files/no-info.pdf", "no-info.pdf") or die "can't copy no-info: $!";
run_and_cmp("in-place Producer added (no-info file)",
"$prg -in no-info.pdf -key /Producer -val \"Obivan Kinobi\"",
"", "no-info.pdf", "files/4.qdf");
cleanup();
$td->report(15);
sub cleanup
{
unlink (<*.pdf>);
}
sub run_and_cmp
{
my ($dsc, $cmd, $out, $fout, $fexp) = @_;
$td->runtest($dsc,
{$td->COMMAND => "$cmd --static-id"},
{$td->STRING => $out,
$td->EXIT_STATUS => 0});
$td->runtest("$dsc output",
{$td->COMMAND => "$qpdf --static-id -qdf $fout -"},
{$td->FILE => $fexp,
$td->EXIT_STATUS => 0});
}

View File

@ -0,0 +1,11 @@
Author: Yours Truly
ContentTemperature: 100F
CreationDate: D:20040212104653-05'00'
Creator: Adobe Acrobat 6.0
FormerlyKnownAs: target/branch/leaf/leaf.pdf
Keywords: 40, 128, public, encryption, ignition, primarily prime
ModDate: D:20040212112832-05'00'
Producer: Adobe Acrobat 6.0 Image Conversion Plug-in
Subject: Of The Matter
Title: My New Car Title
VeryImportantNote: pordofor stands for portable document format

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
Usage: pdf-mod-info -in in_file [-out out_file] [-key key [-val val]?]+
Modifies/Adds/Removes PDF /Info entries in the in_file
and stores the result in out_file
Special mode: pdf-mod-info --dump file
dumps all /Info entries to stdout

View File

@ -0,0 +1,23 @@
#!/usr/bin/env perl
require 5.008;
BEGIN { $^W = 1; }
use strict;
chdir("npages");
require TestDriver;
my $td = new TestDriver('pdf-npages');
$td->runtest("normal",
{$td->COMMAND => "pdf-npages minimal.pdf"},
{$td->STRING => "1\n", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("error",
{$td->COMMAND => "pdf-npages bad"},
{$td->STRING => "pdf-npages: bad: offset 0: not a PDF file\n",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->report(2);

View File

@ -0,0 +1 @@
test

View File

@ -0,0 +1,79 @@
%PDF-1.3
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [
3 0 R
]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/ProcSet 5 0 R
/Font <<
/F1 6 0 R
>>
>>
>>
endobj
4 0 obj
<<
/Length 44
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
5 0 obj
[
/PDF
/Text
]
endobj
6 0 obj
<<
/Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
>>
endobj
xref
0 7
0000000000 65535 f
0000000009 00000 n
0000000063 00000 n
0000000135 00000 n
0000000307 00000 n
0000000403 00000 n
0000000438 00000 n
trailer <<
/Size 7
/Root 1 0 R
>>
startxref
556
%%EOF

32
include/qpdf/Buffer.hh Normal file
View File

@ -0,0 +1,32 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __BUFFER_HH__
#define __BUFFER_HH__
class Buffer
{
public:
Buffer();
Buffer(unsigned long size);
Buffer(Buffer const&);
Buffer& operator=(Buffer const&);
~Buffer();
unsigned long getSize() const;
unsigned char const* getBuffer() const;
unsigned char* getBuffer();
private:
void init(unsigned long size);
void copy(Buffer const&);
void destroy();
unsigned long size;
unsigned char* buf;
};
#endif // __BUFFER_HH__

73
include/qpdf/Pipeline.hh Normal file
View File

@ -0,0 +1,73 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
// Generalized Pipeline interface. By convention, subclasses of
// Pipeline are called Pl_Something.
//
// When an instance of Pipeline is created with a pointer to a next
// pipeline, that pipeline writes its data to the next one when it
// finishes with it. In order to make possible a usage style in which
// a pipeline may be passed to a function which may stick other
// pipelines in front of it, the allocator of a pipeline is
// responsible for its destruction. In other words, one pipeline
// object does not attempt to manage the memory of its successor.
//
// The client is required to call finish() before destroying a
// Pipeline in order to avoid loss of data. A Pipeline class should
// not throw an exception in the destructor if this hasn't been done
// though since doing so causes too mcuh trouble when deleting
// pipelines during error conditions.
//
// Some pipelines are resuable (i.e., you can call write() after
// calling finish() and can call finish() multiple times) while others
// are not. It is up to the caller to use a pipeline according to its
// own restrictions.
#ifndef __PIPELINE_HH__
#define __PIPELINE_HH__
#include <qpdf/QEXC.hh>
class Pipeline
{
public:
class Exception: public QEXC::General
{
public:
Exception(std::string const& message) :
QEXC::General(message)
{
}
virtual ~Exception() throw()
{
}
};
Pipeline(char const* identifier, Pipeline* next);
virtual ~Pipeline();
// Subclasses should implement write and finish to do their jobs
// and then, if they are not end-of-line pipelines, call
// getNext()->write or getNext()->finish.
virtual void write(unsigned char* data, int len) = 0;
virtual void finish() = 0;
protected:
Pipeline* getNext(bool allow_null = false);
std::string identifier;
private:
// Do not implement copy or assign
Pipeline(Pipeline const&);
Pipeline& operator=(Pipeline const&);
Pipeline* next;
};
#endif // __PIPELINE_HH__

46
include/qpdf/Pl_Buffer.hh Normal file
View File

@ -0,0 +1,46 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __PL_BUFFER_HH__
#define __PL_BUFFER_HH__
// This pipeline accumulates the data passed to it into a memory
// buffer. Each subsequent use of this buffer appends to the data
// accumulated so far. getBuffer() may be called only after calling
// finish() and before calling any subsequent write(). At that point,
// a dynamically allocated Buffer object is returned and the internal
// buffer is reset. The caller is responseible for deleting the
// returned Buffer.
//
// For this pipeline, "next" may be null. If a next pointer is
// provided, this pipeline will also pass the data through to it.
#include <qpdf/Pipeline.hh>
#include <qpdf/PointerHolder.hh>
#include <qpdf/Buffer.hh>
#include <list>
class Pl_Buffer: public Pipeline
{
public:
Pl_Buffer(char const* identifier, Pipeline* next = 0);
virtual ~Pl_Buffer();
virtual void write(unsigned char*, int);
virtual void finish();
// Each call to getBuffer() resets this object -- see notes above.
// The caller is responsible for deleting the returned Buffer
// object.
Buffer* getBuffer();
private:
bool ready;
std::list<PointerHolder<Buffer> > data;
size_t total_size;
};
#endif // __PL_BUFFER_HH__

34
include/qpdf/Pl_Count.hh Normal file
View File

@ -0,0 +1,34 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __PL_COUNT_HH__
#define __PL_COUNT_HH__
// This pipeline is reusable; i.e., it is safe to call write() after
// calling finish().
#include <qpdf/Pipeline.hh>
class Pl_Count: public Pipeline
{
public:
Pl_Count(char const* identifier, Pipeline* next);
virtual ~Pl_Count();
virtual void write(unsigned char*, int);
virtual void finish();
// Returns the number of bytes written
int getCount() const;
// Returns the last character written, or '\0' if no characters
// have been written (in which case getCount() returns 0)
unsigned char getLastChar() const;
private:
int count;
unsigned char last_char;
};
#endif // __PL_COUNT_HH__

View File

@ -0,0 +1,28 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __PL_DISCARD_HH__
#define __PL_DISCARD_HH__
// This pipeline discards its output. It is an end-of-line pipeline
// (with no next).
// This pipeline is reusable; i.e., it is safe to call write() after
// calling finish().
#include <qpdf/Pipeline.hh>
class Pl_Discard: public Pipeline
{
public:
Pl_Discard();
virtual ~Pl_Discard();
virtual void write(unsigned char*, int);
virtual void finish();
};
#endif // __PL_DISCARD_HH__

53
include/qpdf/Pl_Flate.hh Normal file
View File

@ -0,0 +1,53 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __PL_FLATE_HH__
#define __PL_FLATE_HH__
#include <qpdf/Pipeline.hh>
#include <zlib.h>
class Pl_Flate: public Pipeline
{
public:
class Exception: public Pipeline::Exception
{
public:
Exception(std::string const& message) :
Pipeline::Exception(message)
{
}
virtual ~Exception() throw ()
{
}
};
static int const def_bufsize = 65536;
enum action_e { a_inflate, a_deflate };
Pl_Flate(char const* identifier, Pipeline* next,
action_e action, int out_bufsize = def_bufsize);
virtual ~Pl_Flate();
virtual void write(unsigned char* data, int len);
virtual void finish();
private:
void handleData(unsigned char* data, int len, int flush);
void checkError(char const* prefix, int error_code);
unsigned char* outbuf;
int out_bufsize;
action_e action;
bool initialized;
z_stream zstream;
};
#endif // __PL_FLATE_HH__

View File

@ -0,0 +1,49 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
// End-of-line pipeline that simply writes its data to a stdio FILE* object.
#ifndef __PL_STDIOFILE_HH__
#define __PL_STDIOFILE_HH__
#include <qpdf/Pipeline.hh>
#include <stdio.h>
//
// This pipeline is reusable.
//
class Pl_StdioFile: public Pipeline
{
public:
class Exception: public Pipeline::Exception
{
public:
Exception(std::string const& message) :
Pipeline::Exception(message)
{
}
virtual ~Exception() throw ()
{
}
};
// f is externally maintained; this class just writes to and
// flushes it. It does not close it.
Pl_StdioFile(char const* identifier, FILE* f);
virtual ~Pl_StdioFile();
virtual void write(unsigned char* buf, int len);
virtual void finish();
private:
FILE* file;
};
#endif // __PL_STDIOFILE_HH__

View File

@ -0,0 +1,170 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __POINTERHOLDER_HH__
#define __POINTERHOLDER_HH__
#include <iostream>
// This class is basically boost::shared_pointer but predates that by
// several years.
// This class expects to be initialized with a dynamically allocated
// object pointer. It keeps a reference count and deletes this once
// the reference count goes to zero. PointerHolder objects are
// explicitly safe for use in STL containers.
// It is very important that a client who pulls the pointer out of
// this holder does not let the holder go out of scope until it is
// finished with the pointer. It is also important that exactly one
// instance of this object ever gets initialized with a given pointer.
// Otherwise, the pointer will be deleted twice, and before that, some
// objects will be left with a pointer to a deleted object. In other
// words, the only legitimate way for two PointerHolder objects to
// contain the same pointer is for one to be a copy of the other.
// Copy and assignment semantics are well-defined and essentially
// allow you to use PointerHolder as a means to get pass-by-reference
// semantics in a pass-by-value environment without having to worry
// about memory management details.
// Comparison (== and <) are defined and operate on the internally
// stored pointers, not on the data. This makes it possible to store
// PointerHolder objects in sorted lists or to find them in STL
// containers just as one would be able to store pointers. Comparing
// the underlying pointers provides a well-defined, if not
// particularly meaningful, ordering.
template <class T>
class PointerHolder
{
private:
class Data
{
public:
Data(T* pointer, bool tracing) :
pointer(pointer),
tracing(tracing),
refcount(0)
{
static int next_id = 0;
this->unique_id = ++next_id;
}
~Data()
{
if (this->tracing)
{
std::cerr << "PointerHolder deleting pointer "
<< (void*)pointer
<< std::endl;
}
delete this->pointer;
if (this->tracing)
{
std::cerr << "PointerHolder done deleting pointer "
<< (void*)pointer
<< std::endl;
}
}
T* pointer;
bool tracing;
int refcount;
int unique_id;
private:
Data(Data const&);
Data& operator=(Data const&);
};
public:
PointerHolder(T* pointer = 0, bool tracing = false)
{
this->init(new Data(pointer, tracing));
}
PointerHolder(PointerHolder const& rhs)
{
this->copy(rhs);
}
PointerHolder& operator=(PointerHolder const& rhs)
{
if (this != &rhs)
{
this->destroy();
this->copy(rhs);
}
return *this;
}
~PointerHolder()
{
this->destroy();
}
bool operator==(PointerHolder const& rhs) const
{
return this->data->pointer == rhs.data->pointer;
}
bool operator<(PointerHolder const& rhs) const
{
return this->data->pointer < rhs.data->pointer;
}
// NOTE: The pointer returned by getPointer turns into a pumpkin
// when the last PointerHolder that contains it disappears.
T* getPointer()
{
return this->data->pointer;
}
T const* getPointer() const
{
return this->data->pointer;
}
int getRefcount() const
{
return this->data->refcount;
}
private:
void init(Data* data)
{
this->data = data;
{
++this->data->refcount;
if (this->data->tracing)
{
std::cerr << "PointerHolder " << this->data->unique_id
<< " refcount increased to " << this->data->refcount
<< std::endl;
}
}
}
void copy(PointerHolder const& rhs)
{
this->init(rhs.data);
}
void destroy()
{
bool gone = false;
{
if (--this->data->refcount == 0)
{
gone = true;
}
if (this->data->tracing)
{
std::cerr << "PointerHolder " << this->data->unique_id
<< " refcount decreased to "
<< this->data->refcount
<< std::endl;
}
}
if (gone)
{
delete this->data;
}
}
Data* data;
};
#endif // __POINTERHOLDER_HH__

119
include/qpdf/QEXC.hh Normal file
View File

@ -0,0 +1,119 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __QEXC_HH__
#define __QEXC_HH__
#include <string>
#include <exception>
#include <errno.h>
namespace QEXC
{
// This namespace contains all exception classes used by the
// library.
// The class hierarchy is as follows:
// std::exception
// |
// +-> QEXC::Base
// |
// +-> QEXC::General
// |
// +-> QEXC::Internal
// QEXC::General is the base class of all standard user-defined
// exceptions and "expected" error conditions raised by QClass.
// Applications or libraries using QClass are encouraged to derive
// their own exceptions from these classes if they wish. It is
// entirely reasonable for code to catch QEXC::General or specific
// subclasses of it as part of normal error handling.
// QEXC::Internal is reserved for internal errors. These should
// be used only for situations that indicate a likely bug in the
// software itself. This may include improper use of a library
// function. Operator errors should not be able to cause Internal
// errors. (There may be some exceptions to this such as users
// invoking programs that were intended only to be invoked by
// other programs.) QEXC::Internal should generally not be
// trapped except in terminate handlers or top-level exception
// handlers which will want to translate them into error messages
// and cause the program to exit. Such top-level handlers may
// want to catch std::exception instead.
// All subclasses of QEXC::Base implement a const unparse() method
// which returns a std::string const&. They also override
// std::exception::what() to return a char* with the same value.
// unparse() should be implemented in such a way that a program
// catching QEXC::Base or std::exception can use the text returned
// by unparse() (or what()) without any exception-specific
// adornment. (The program may prefix the program name or other
// general information.) Note that std::exception::what() is a
// const method that returns a const char*. For this reason, it
// is essential that unparse() return a const reference to a
// string so that what() can be implemented by calling unparse().
// This means that the string that unparse() returns a reference
// to must not be allocated on the stack in the call to unparse().
// The recommended way to do this is for derived exception classes
// to store their string descriptions by calling the protected
// setMessage() method and then to not override unparse().
class Base: public std::exception
{
// This is the common base class for all exceptions in qclass.
// Application/library code should not generally catch this
// directly. See above for caveats.
public:
Base();
Base(std::string const& message);
virtual ~Base() throw() {}
virtual std::string const& unparse() const;
virtual const char* what() const throw();
protected:
void setMessage(std::string const& message);
private:
std::string message;
};
class General: public Base
{
// This is the base class for normal user/library-defined
// error conditions.
public:
General();
General(std::string const& message);
virtual ~General() throw() {};
};
// Note that Internal is not derived from General. Internal
// errors are too severe. We don't want internal errors
// accidentally trapped as part of QEXC::General. If you are
// going to deal with internal errors, you have to do so
// explicitly.
class Internal: public Base
{
public:
Internal(std::string const& message);
virtual ~Internal() throw() {};
};
class System: public General
{
public:
System(std::string const& prefix, int sys_errno);
virtual ~System() throw() {};
int getErrno() const;
private:
int sys_errno;
};
};
#endif // __QEXC_HH__

750
include/qpdf/QPDF.hh Normal file
View File

@ -0,0 +1,750 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __QPDF_HH__
#define __QPDF_HH__
#include <stdio.h>
#include <string>
#include <map>
#include <list>
#include <qpdf/QPDFXRefEntry.hh>
#include <qpdf/QPDFObjectHandle.hh>
#include <qpdf/QPDFTokenizer.hh>
#include <qpdf/Buffer.hh>
class QPDF_Stream;
class BitStream;
class BitWriter;
class QPDFExc;
class QPDF
{
public:
QPDF();
~QPDF();
// Associate a file with a QPDF object and do initial parsing of
// the file. PDF objects are not read until they are needed. A
// QPDF object may be associated with only on file in its
// lifetime. This method must be called before any methods that
// potentially ask for information about the PDF file are called.
// Prior to calling this, the only methods that are allowed are
// those that set parameters.
void processFile(char const* filename, char const* password = "");
// Parameter settings
// If true, ignore any cross-reference streams in a hybrid file
// (one that contains both cross-reference streams and
// cross-reference tables). This can be useful for testing to
// ensure that a hybrid file would work with an older reader.
void setIgnoreXRefStreams(bool);
// By default, any warnings are issued to stderr as they are
// encountered. If this is called with a true value, reporitng of
// warnings is suppressed. You may still retrieve warnings by
// calling getWarnings.
void setSuppressWarnings(bool);
// By default, QPDF will try to recover if it finds certain types
// of errors in PDF files. If turned off, it will throw an
// exception on the first such problem it finds without attempting
// recovery.
void setAttemptRecovery(bool);
// Other public methods
// Return the list of warnings that have been issued so far and
// clear the list. This method may be called even if processFile
// throws an exception. Note that if setSuppressWarnings was not
// called or was called with a false value, any warnings retrieved
// here will have already been issued to stderr.
std::vector<std::string> getWarnings();
std::string getFilename() const;
std::string getPDFVersion() const;
QPDFObjectHandle getTrailer();
QPDFObjectHandle getRoot();
// Install this object handle as an indirect object and return an
// indirect reference to it.
QPDFObjectHandle makeIndirectObject(QPDFObjectHandle);
// Retrieve an object by object ID and generation. Returns an
// indirect reference to it.
QPDFObjectHandle getObjectByID(int objid, int generation);
// Encryption support
struct EncryptionData
{
// This class holds data read from the encryption dictionary.
EncryptionData(int V, int R, int Length_bytes, long P,
std::string const& O, std::string const& U,
std::string const& id1) :
V(V),
R(R),
Length_bytes(Length_bytes),
P(P),
O(O),
U(U),
id1(id1)
{
}
int V;
int R;
int Length_bytes;
long P;
std::string O;
std::string U;
std::string id1;
};
static void trim_user_password(std::string& user_password);
static std::string compute_data_key(
std::string const& encryption_key, int objid, int generation);
static std::string compute_encryption_key(
std::string const& password, EncryptionData const& data);
static void compute_encryption_O_U(
char const* user_password, char const* owner_password,
int V, int R, int key_len, unsigned long P,
std::string const& id1,
std::string& O, std::string& U);
std::string const& getUserPassword() const;
// Linearization support
// Returns true iff the file starts with a linearization parameter
// dictionary. Does no additional validation.
bool isLinearized();
// Performs various sanity checks on a linearized file. Return
// true if no errors or warnings. Otherwise, return false and
// output errors and warnings to stdout.
bool checkLinearization();
// Calls checkLinearization() and, if possible, prints normalized
// contents of some of the hints tables to stdout. Normalization
// includes adding min values to delta values and adjusting
// offsets based on the location and size of the primary hint
// stream.
void showLinearizationData();
// Shows the contents of the cross-reference table
void showXRefTable();
// Optimization support -- see doc/optimization. Implemented in
// QPDF_optimization.cc
// The object_stream_data map maps from a "compressed" object to
// the object stream that contains it. This enables optimize to
// populate the object <-> user maps with only uncompressed
// objects. If allow_changes is false, an exception will be
// thrown if any changes are made during the optimization process.
// This is available so that the test suite can make sure that a
// linearized file is already optimized. When called in this way,
// optimize() still populates the object <-> user maps
void optimize(std::map<int, int> const& object_stream_data,
bool allow_changes = true);
// Replace all references to indirect objects that are "scalars"
// (i.e., things that don't have children: not arrays, streams, or
// dictionaries) with direct objects.
void flattenScalarReferences();
// For QPDFWriter:
// Remove /ID, /Encrypt, and /Prev keys from the trailer
// dictionary since these are regenerated during write.
void trimTrailerForWrite();
// Get lists of all objects in order according to the part of a
// linearized file that they belong to.
void getLinearizedParts(
std::map<int, int> const& object_stream_data,
std::vector<QPDFObjectHandle>& part4,
std::vector<QPDFObjectHandle>& part6,
std::vector<QPDFObjectHandle>& part7,
std::vector<QPDFObjectHandle>& part8,
std::vector<QPDFObjectHandle>& part9);
void generateHintStream(std::map<int, QPDFXRefEntry> const& xref,
std::map<int, size_t> const& lengths,
std::map<int, int> const& obj_renumber,
PointerHolder<Buffer>& hint_stream,
int& S, int& O);
// Map object to object stream that contains it
void getObjectStreamData(std::map<int, int>&);
// Get a list of objects that would be permitted in an object
// stream
std::vector<int> getCompressibleObjects();
// Convenience routines for common functions. See also
// QPDFObjectHandle.hh for additional convenience routines.
// Traverse page tree return all /Page objects.
std::vector<QPDFObjectHandle> const& getAllPages();
// Resolver class is restricted to QPDFObjectHandle so that only
// it can resolve indirect references.
class Resolver
{
friend class QPDFObjectHandle;
private:
static PointerHolder<QPDFObject> resolve(
QPDF* qpdf, int objid, int generation)
{
return qpdf->resolve(objid, generation);
}
};
friend class Resolver;
// Pipe class is restricted to QPDF_Stream
class Pipe
{
friend class QPDF_Stream;
private:
static void pipeStreamData(QPDF* qpdf, int objid, int generation,
off_t offset, size_t length,
QPDFObjectHandle dict,
Pipeline* pipeline)
{
qpdf->pipeStreamData(
objid, generation, offset, length, dict, pipeline);
}
};
friend class Pipe;
private:
class InputSource
{
public:
InputSource() :
last_offset(0)
{
}
virtual ~InputSource()
{
}
void setLastOffset(off_t);
off_t getLastOffset() const;
std::string readLine();
virtual std::string const& getName() const = 0;
virtual off_t tell() = 0;
virtual void seek(off_t offset, int whence) = 0;
virtual void rewind() = 0;
virtual size_t read(char* buffer, int length) = 0;
virtual void unreadCh(char ch) = 0;
protected:
off_t last_offset;
};
class FileInputSource: public InputSource
{
public:
FileInputSource();
void setFilename(char const* filename);
virtual ~FileInputSource();
virtual std::string const& getName() const;
virtual off_t tell();
virtual void seek(off_t offset, int whence);
virtual void rewind();
virtual size_t read(char* buffer, int length);
virtual void unreadCh(char ch);
private:
FileInputSource(FileInputSource const&);
FileInputSource& operator=(FileInputSource const&);
void destroy();
std::string filename;
FILE* file;
};
class BufferInputSource: public InputSource
{
public:
BufferInputSource(std::string const& description, Buffer* buf);
virtual ~BufferInputSource();
virtual std::string const& getName() const;
virtual off_t tell();
virtual void seek(off_t offset, int whence);
virtual void rewind();
virtual size_t read(char* buffer, int length);
virtual void unreadCh(char ch);
private:
std::string description;
Buffer* buf;
off_t cur_offset;
};
class ObjGen
{
public:
ObjGen();
ObjGen(int obj, int gen);
bool operator<(ObjGen const&) const;
int obj;
int gen;
};
class ObjCache
{
public:
ObjCache() :
end_before_space(0),
end_after_space(0)
{
}
ObjCache(PointerHolder<QPDFObject> object,
off_t end_before_space,
off_t end_after_space) :
object(object),
end_before_space(end_before_space),
end_after_space(end_after_space)
{
}
PointerHolder<QPDFObject> object;
off_t end_before_space;
off_t end_after_space;
};
void parse();
void warn(QPDFExc const& e);
void setTrailer(QPDFObjectHandle obj);
void read_xref(off_t offset);
void reconstruct_xref(QPDFExc& e);
int read_xrefTable(off_t offset);
int read_xrefStream(off_t offset);
int processXRefStream(off_t offset, QPDFObjectHandle& xref_stream);
void insertXrefEntry(int obj, int f0, int f1, int f2);
QPDFObjectHandle readObject(
InputSource*, int objid, int generation,
bool in_object_stream);
QPDFObjectHandle readObjectInternal(
InputSource* input, int objid, int generation,
bool in_object_stream,
bool in_array, bool in_dictionary);
int recoverStreamLength(
InputSource* input, int objid, int generation, off_t stream_offset);
QPDFTokenizer::Token readToken(InputSource*);
QPDFObjectHandle readObjectAtOffset(
off_t offset,
int exp_objid, int exp_generation,
int& act_objid, int& act_generation);
PointerHolder<QPDFObject> resolve(int objid, int generation);
void resolveObjectsInStream(int obj_stream_number);
// Calls finish() on the pipeline when done but does not delete it
void pipeStreamData(int objid, int generation,
off_t offset, size_t length,
QPDFObjectHandle dict,
Pipeline* pipeline);
void getAllPagesInternal(QPDFObjectHandle cur_pages,
std::vector<QPDFObjectHandle>& result);
// methods to support encryption -- implemented in QPDF_encryption.cc
void initializeEncryption();
std::string getKeyForObject(int objid, int generation);
void decryptString(std::string&, int objid, int generation);
void decryptStream(Pipeline*& pipeline, int objid, int generation,
std::vector<PointerHolder<Pipeline> >& heap);
// Linearization Hint table structures.
// Naming conventions:
// HSomething is the Something Hint Table or table header
// HSomethingEntry is an entry in the Something table
// delta_something + min_something = something
// nbits_something = number of bits required for something
// something_offset is the pre-adjusted offset in the file. If >=
// H0_offset, H0_length must be added to get an actual file
// offset.
// PDF 1.4: Table F.4
struct HPageOffsetEntry
{
HPageOffsetEntry() :
delta_nobjects(0),
delta_page_length(0),
nshared_objects(0),
delta_content_offset(0),
delta_content_length(0)
{
}
int delta_nobjects; // 1
int delta_page_length; // 2
int nshared_objects; // 3
// vectors' sizes = nshared_objects
std::vector<int> shared_identifiers; // 4
std::vector<int> shared_numerators; // 5
int delta_content_offset; // 6
int delta_content_length; // 7
};
// PDF 1.4: Table F.3
struct HPageOffset
{
HPageOffset() :
min_nobjects(0),
first_page_offset(0),
nbits_delta_nobjects(0),
min_page_length(0),
nbits_delta_page_length(0),
min_content_offset(0),
nbits_delta_content_offset(0),
min_content_length(0),
nbits_delta_content_length(0),
nbits_nshared_objects(0),
nbits_shared_identifier(0),
nbits_shared_numerator(0),
shared_denominator(0)
{
}
int min_nobjects; // 1
int first_page_offset; // 2
int nbits_delta_nobjects; // 3
int min_page_length; // 4
int nbits_delta_page_length; // 5
int min_content_offset; // 6
int nbits_delta_content_offset; // 7
int min_content_length; // 8
int nbits_delta_content_length; // 9
int nbits_nshared_objects; // 10
int nbits_shared_identifier; // 11
int nbits_shared_numerator; // 12
int shared_denominator; // 13
// vector size is npages
std::vector<HPageOffsetEntry> entries;
};
// PDF 1.4: Table F.6
struct HSharedObjectEntry
{
HSharedObjectEntry() :
delta_group_length(0),
signature_present(0),
nobjects_minus_one(0)
{
}
// Item 3 is a 128-bit signature (unsupported by Acrobat)
int delta_group_length; // 1
int signature_present; // 2 -- always 0
int nobjects_minus_one; // 4 -- always 0
};
// PDF 1.4: Table F.5
struct HSharedObject
{
HSharedObject() :
first_shared_obj(0),
first_shared_offset(0),
nshared_first_page(0),
nshared_total(0),
nbits_nobjects(0),
min_group_length(0),
nbits_delta_group_length(0)
{
}
int first_shared_obj; // 1
int first_shared_offset; // 2
int nshared_first_page; // 3
int nshared_total; // 4
int nbits_nobjects; // 5
int min_group_length; // 6
int nbits_delta_group_length; // 7
// vector size is nshared_total
std::vector<HSharedObjectEntry> entries;
};
// PDF 1.4: Table F.9
struct HGeneric
{
HGeneric() :
first_object(0),
first_object_offset(0),
nobjects(0),
group_length(0)
{
}
int first_object; // 1
int first_object_offset; // 2
int nobjects; // 3
int group_length; // 4
};
// Other linearization data structures
// Initialized from Linearization Parameter dictionary
struct LinParameters
{
LinParameters() :
file_size(0),
first_page_object(0),
first_page_end(0),
npages(0),
xref_zero_offset(0),
first_page(0),
H_offset(0),
H_length(0)
{
}
int file_size; // /L
int first_page_object; // /O
int first_page_end; // /E
int npages; // /N
int xref_zero_offset; // /T
int first_page; // /P
int H_offset; // offset of primary hint stream
int H_length; // length of primary hint stream
};
// Computed hint table value data structures. These tables
// contain the computed values on which the hint table values are
// based. They exclude things like number of bits and store
// actual values instead of mins and deltas. File offsets are
// also absolute rather than being offset by the size of the
// primary hint table. We populate the hint table structures from
// these during writing and compare the hint table values with
// these during validation. We ignore some values for various
// reasons described in the code. Those values are omitted from
// these structures. Note also that object numbers are object
// numbers from the input file, not the output file.
// Naming convention: CHSomething is analogous to HSomething
// above. "CH" is computed hint.
struct CHPageOffsetEntry
{
CHPageOffsetEntry() :
nobjects(0),
nshared_objects(0)
{
}
int nobjects;
int nshared_objects;
// vectors' sizes = nshared_objects
std::vector<int> shared_identifiers;
};
struct CHPageOffset
{
// vector size is npages
std::vector<CHPageOffsetEntry> entries;
};
struct CHSharedObjectEntry
{
CHSharedObjectEntry(int object) :
object(object)
{
}
int object;
};
// PDF 1.4: Table F.5
struct CHSharedObject
{
CHSharedObject() :
first_shared_obj(0),
nshared_first_page(0),
nshared_total(0)
{
}
int first_shared_obj;
int nshared_first_page;
int nshared_total;
// vector size is nshared_total
std::vector<CHSharedObjectEntry> entries;
};
// No need for CHGeneric -- HGeneric is fine as is.
// Data structures to support optimization -- implemented in
// QPDF_optimization.cc
class ObjUser
{
public:
enum user_e
{
ou_bad,
ou_page,
ou_thumb,
ou_trailer_key,
ou_root_key,
ou_root
};
// type is set to ou_bad
ObjUser();
// type must be ou_root
ObjUser(user_e type);
// type must be one of ou_page or ou_thumb
ObjUser(user_e type, int pageno);
// type must be one of ou_trailer_key or ou_root_key
ObjUser(user_e type, std::string const& key);
bool operator<(ObjUser const&) const;
user_e ou_type;
int pageno; // if ou_page;
std::string key; // if ou_trailer_key or ou_root_key
};
// methods to support linearization checking -- implemented in
// QPDF_linearization.cc
void readLinearizationData();
bool checkLinearizationInternal();
void dumpLinearizationDataInternal();
QPDFObjectHandle readHintStream(Pipeline&, off_t offset, size_t length);
void readHPageOffset(BitStream);
void readHSharedObject(BitStream);
void readHGeneric(BitStream, HGeneric&);
int maxEnd(ObjUser const& ou);
int getLinearizationOffset(ObjGen const&);
QPDFObjectHandle getUncompressedObject(
QPDFObjectHandle&, std::map<int, int> const& object_stream_data);
int lengthNextN(int first_object, int n,
std::list<std::string>& errors);
void checkHPageOffset(std::list<std::string>& errors,
std::list<std::string>& warnings,
std::vector<QPDFObjectHandle> const& pages,
std::map<int, int>& idx_to_obj);
void checkHSharedObject(std::list<std::string>& warnings,
std::list<std::string>& errors,
std::vector<QPDFObjectHandle> const& pages,
std::map<int, int>& idx_to_obj);
void checkHOutlines(std::list<std::string>& warnings);
void dumpHPageOffset();
void dumpHSharedObject();
void dumpHGeneric(HGeneric&);
int adjusted_offset(int offset);
QPDFObjectHandle objGenToIndirect(ObjGen const&);
void calculateLinearizationData(
std::map<int, int> const& object_stream_data);
void pushOutlinesToPart(
std::vector<QPDFObjectHandle>& part,
std::set<ObjGen>& lc_outlines,
std::map<int, int> const& object_stream_data);
int outputLengthNextN(
int in_object, int n,
std::map<int, size_t> const& lengths,
std::map<int, int> const& obj_renumber);
void calculateHPageOffset(
std::map<int, QPDFXRefEntry> const& xref,
std::map<int, size_t> const& lengths,
std::map<int, int> const& obj_renumber);
void calculateHSharedObject(
std::map<int, QPDFXRefEntry> const& xref,
std::map<int, size_t> const& lengths,
std::map<int, int> const& obj_renumber);
void calculateHOutline(
std::map<int, QPDFXRefEntry> const& xref,
std::map<int, size_t> const& lengths,
std::map<int, int> const& obj_renumber);
void writeHPageOffset(BitWriter&);
void writeHSharedObject(BitWriter&);
void writeHGeneric(BitWriter&, HGeneric&);
// Methods to support optimization
void optimizePagesTree(
QPDFObjectHandle,
std::map<std::string, std::vector<QPDFObjectHandle> >&,
int& pageno, bool allow_changes);
void updateObjectMaps(ObjUser const& ou, QPDFObjectHandle oh);
void updateObjectMapsInternal(ObjUser const& ou, QPDFObjectHandle oh,
std::set<ObjGen>& visited, bool top);
void filterCompressedObjects(std::map<int, int> const& object_stream_data);
QPDFTokenizer tokenizer;
FileInputSource file;
bool encrypted;
bool encryption_initialized;
bool ignore_xref_streams;
bool suppress_warnings;
bool attempt_recovery;
std::string provided_password;
std::string user_password;
std::string encryption_key;
std::string cached_object_encryption_key;
int cached_key_objid;
int cached_key_generation;
std::string pdf_version;
std::map<ObjGen, QPDFXRefEntry> xref_table;
std::set<int> deleted_objects;
std::map<ObjGen, ObjCache> obj_cache;
QPDFObjectHandle trailer;
std::vector<QPDFObjectHandle> all_pages;
std::vector<std::string> warnings;
// Linearization data
int first_xref_item_offset; // actual value from file
bool uncompressed_after_compressed;
// Linearization parameter dictionary and hint table data: may be
// read from file or computed prior to writing a linearized file
QPDFObjectHandle lindict;
LinParameters linp;
HPageOffset page_offset_hints;
HSharedObject shared_object_hints;
HGeneric outline_hints;
// Computed linearization data: used to populate above tables
// during writing and to compare with them during validation. c_
// means computed.
LinParameters c_linp;
CHPageOffset c_page_offset_data;
CHSharedObject c_shared_object_data;
HGeneric c_outline_data;
// Object ordering data for linearized files: initialized by
// calculateLinearizationData(). Part numbers refer to the PDF
// 1.4 specification.
std::vector<QPDFObjectHandle> part4;
std::vector<QPDFObjectHandle> part6;
std::vector<QPDFObjectHandle> part7;
std::vector<QPDFObjectHandle> part8;
std::vector<QPDFObjectHandle> part9;
// Optimization data
std::map<ObjUser, std::set<ObjGen> > obj_user_to_objects;
std::map<ObjGen, std::set<ObjUser> > object_to_obj_users;
};
#endif // __QPDF_HH__

22
include/qpdf/QPDFExc.hh Normal file
View File

@ -0,0 +1,22 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __QPDFEXC_HH__
#define __QPDFEXC_HH__
#include <qpdf/QEXC.hh>
class QPDFExc: public QEXC::General
{
public:
QPDFExc(std::string const& message);
QPDFExc(std::string const& filename, int offset,
std::string const& message);
virtual ~QPDFExc() throw ();
};
#endif // __QPDFEXC_HH__

View File

@ -0,0 +1,20 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __QPDFOBJECT_HH__
#define __QPDFOBJECT_HH__
#include <string>
class QPDFObject
{
public:
virtual ~QPDFObject() {}
virtual std::string unparse() = 0;
};
#endif // __QPDFOBJECT_HH__

View File

@ -0,0 +1,221 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __QPDFOBJECTHANDLE_HH__
#define __QPDFOBJECTHANDLE_HH__
#include <string>
#include <vector>
#include <set>
#include <map>
#include <qpdf/PointerHolder.hh>
#include <qpdf/Buffer.hh>
#include <qpdf/QPDFObject.hh>
class Pipeline;
class QPDF;
class QPDFObjectHandle
{
public:
QPDFObjectHandle();
bool isInitialized() const;
// Exactly one of these will return true for any object.
bool isBool();
bool isNull();
bool isInteger();
bool isReal();
bool isName();
bool isString();
bool isArray();
bool isDictionary();
bool isStream();
// This returns true in addition to the query for the specific
// type for indirect objects.
bool isIndirect();
// True for everything except array, dictionary, and stream
bool isScalar();
// Public factory methods
static QPDFObjectHandle newNull();
static QPDFObjectHandle newBool(bool value);
static QPDFObjectHandle newInteger(int value);
static QPDFObjectHandle newReal(std::string const& value);
static QPDFObjectHandle newName(std::string const& name);
static QPDFObjectHandle newString(std::string const& str);
static QPDFObjectHandle newArray(
std::vector<QPDFObjectHandle> const& items);
static QPDFObjectHandle newDictionary(
std::map<std::string, QPDFObjectHandle> const& items);
// Accessor methods. If an accessor method that is valid for only
// a particular object type is called on an object of the wrong
// type, an exception is thrown.
// Methods for bool objects
bool getBoolValue();
// Methods for integer objects
int getIntValue();
// Methods for real objects
std::string getRealValue();
// Methods that work for both integer and real objects
bool isNumber();
double getNumericValue();
// Methods for name objects
std::string getName();
// Methods for string objects
std::string getStringValue();
std::string getUTF8Value();
// Methods for array objects
int getArrayNItems();
QPDFObjectHandle getArrayItem(int n);
// Methods for dictionary objects
bool hasKey(std::string const&);
QPDFObjectHandle getKey(std::string const&);
std::set<std::string> getKeys();
// Mutator methods. Use with caution.
// Recursively copy this object, making it direct. Throws an
// exception if a loop is detected or any sub-object is a stream.
void makeDirect();
// Mutator methods for array objects
void setArrayItem(int, QPDFObjectHandle const&);
// Mutator methods for dictionary objects
// Replace value of key, adding it if it does not exist
void replaceKey(std::string const& key, QPDFObjectHandle const&);
// Remove key, doing nothing if key does not exist
void removeKey(std::string const& key);
// Methods for stream objects
QPDFObjectHandle getDict();
// Returns filtered (uncompressed) stream data. Throws an
// exception if the stream is filtered and we can't decode it.
PointerHolder<Buffer> getStreamData();
// Write stream data through the given pipeline. A null pipeline
// value may be used if all you want to do is determine whether a
// stream is filterable. If filter is false, write raw stream
// data and return false. If filter is true, then attempt to
// apply all the decoding filters to the stream data. If we are
// successful, return true. Otherwise, return false and write raw
// data. If filtering is requested and successfully performed,
// then the normalize and compress flags are used to determine
// whether stream data should be normalized and compressed. In
// all cases, if this function returns false, raw data has been
// written. If it returns true, then any requested filtering has
// been performed. Note that if the original stream data has no
// filters applied to it, the return value will be equal to the
// value of the filter parameter. Callers may use the return
// value of this function to determine whether or not the /Filter
// and /DecodeParms keys in the stream dictionary should be
// replaced if writing a new stream object.
bool pipeStreamData(Pipeline*, bool filter,
bool normalize, bool compress);
// return 0 for direct objects
int getObjectID() const;
int getGeneration() const;
std::string unparse();
std::string unparseResolved();
// Convenience routines for commonly performed functions
// Throws an exception if this is not a Page object. Returns an
// empty map if there are no images or no resources. This
// function does not presently support inherited resources. See
// comment in the source for details. Return value is a map from
// XObject name to the image object, which is always a stream.
std::map<std::string, QPDFObjectHandle> getPageImages();
// Throws an exception if this is not a Page object. Returns a
// vector of stream objects representing the content streams for
// the given page. This routine allows the caller to not care
// whether there are one or more than one content streams for a
// page.
std::vector<QPDFObjectHandle> getPageContents();
// Initializers for objects. This Factory class gives the QPDF
// class specific permission to call factory methods without
// making it a friend of the whole QPDFObjectHandle class.
class Factory
{
friend class QPDF;
private:
static QPDFObjectHandle newIndirect(QPDF* qpdf,
int objid, int generation)
{
return QPDFObjectHandle::newIndirect(qpdf, objid, generation);
}
// object must be dictionary object
static QPDFObjectHandle newStream(
QPDF* qpdf, int objid, int generation,
QPDFObjectHandle stream_dict, off_t offset, int length)
{
return QPDFObjectHandle::newStream(
qpdf, objid, generation, stream_dict, offset, length);
}
};
friend class Factory;
// Accessor for raw underlying object -- only QPDF is allowed to
// call this.
class ObjAccessor
{
friend class QPDF;
private:
static PointerHolder<QPDFObject> getObject(QPDFObjectHandle& o)
{
o.dereference();
return o.obj;
}
};
friend class ObjAccessor;
private:
QPDFObjectHandle(QPDF*, int objid, int generation);
QPDFObjectHandle(QPDFObject*);
// Private object factory methods
static QPDFObjectHandle newIndirect(QPDF*, int objid, int generation);
static QPDFObjectHandle newStream(
QPDF* qpdf, int objid, int generation,
QPDFObjectHandle stream_dict, off_t offset, int length);
void assertInitialized() const;
void assertType(char const* type_name, bool istype);
void assertPageObject();
void dereference();
void makeDirectInternal(std::set<int>& visited);
bool initialized;
QPDF* qpdf; // 0 for direct object
int objid; // 0 for direct object
int generation;
PointerHolder<QPDFObject> obj;
};
#endif // __QPDFOBJECTHANDLE_HH__

View File

@ -0,0 +1,141 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __QPDFTOKENIZER_HH__
#define __QPDFTOKENIZER_HH__
#include <string>
#include <stdio.h>
class QPDFTokenizer
{
public:
enum token_type_e
{
tt_bad,
tt_array_close,
tt_array_open,
tt_brace_close,
tt_brace_open,
tt_dict_close,
tt_dict_open,
tt_integer,
tt_name,
tt_real,
tt_string,
tt_null,
tt_bool,
tt_word,
};
class Token
{
public:
Token() : type(tt_bad) {}
Token(token_type_e type, std::string const& value) :
type(type),
value(value)
{
}
Token(token_type_e type, std::string const& value,
std::string raw_value, std::string error_message) :
type(type),
value(value),
raw_value(raw_value),
error_message(error_message)
{
}
token_type_e getType() const
{
return this->type;
}
std::string const& getValue() const
{
return this->value;
}
std::string const& getRawValue() const
{
return this->raw_value;
}
std::string const& getErrorMessage() const
{
return this->error_message;
}
bool operator==(Token const& rhs)
{
// Ignore fields other than type and value
return ((this->type != tt_bad) &&
(this->type == rhs.type) &&
(this->value == rhs.value));
}
private:
token_type_e type;
std::string value;
std::string raw_value;
std::string error_message;
};
QPDFTokenizer();
// PDF files with version < 1.2 allowed the pound character
// anywhere in a name. Starting with version 1.2, the pound
// character was allowed only when followed by two hexadecimal
// digits. This method should be called when parsing a PDF file
// whose version is older than 1.2.
void allowPoundAnywhereInName();
// Mode of operation:
// Keep presenting characters and calling getToken() until
// getToken() returns true. When it does, be sure to check
// unread_ch and to unread ch if it is true.
// It these are called when a token is available, an exception
// will be thrown.
void presentCharacter(char ch);
void presentEOF();
// If a token is available, return true and initialize token with
// the token, unread_char with whether or not we have to unread
// the last character, and if unread_char, ch with the character
// to unread.
bool getToken(Token& token, bool& unread_char, char& ch);
// This function returns true of the current character is between
// tokens (i.e., white space that is not part of a string) or is
// part of a comment. A tokenizing filter can call this to
// determine whether to output the character.
bool betweenTokens();
private:
void reset();
// Lexer state
enum { st_top, st_in_comment, st_in_string, st_lt, st_gt,
st_literal, st_in_hexstring, st_token_ready } state;
bool pound_special_in_name;
// Current token accumulation
token_type_e type;
std::string val;
std::string raw_val;
std::string error_message;
bool unread_char;
char char_to_unread;
// State for strings
int string_depth;
bool string_ignoring_newline;
char bs_num_register[4];
bool last_char_was_bs;
};
#endif // __QPDFTOKENIZER_HH__

243
include/qpdf/QPDFWriter.hh Normal file
View File

@ -0,0 +1,243 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
// This class implements a simple writer for saving QPDF objects to
// new PDF files. See comments through the header file for additional
// details.
#ifndef __QPDFWRITER_HH__
#define __QPDFWRITER_HH__
#include <stdio.h>
#include <string>
#include <list>
#include <vector>
#include <set>
#include <map>
#include <qpdf/QPDFXRefEntry.hh>
#include <qpdf/PointerHolder.hh>
#include <qpdf/Pipeline.hh>
#include <qpdf/Buffer.hh>
class QPDF;
class QPDFObjectHandle;
class Pl_Count;
class QPDFWriter
{
public:
// Passing null as filename means write to stdout
QPDFWriter(QPDF& pdf, char const* filename);
~QPDFWriter();
// Set the value of object stream mode. In disable mode, we never
// generate any object streams. In preserve mode, we preserve
// object stream structure from the original file. In generate
// mode, we generate our own object streams. In all cases, we
// generate a conventional cross-reference table if there are no
// object streams and a cross-reference stream if there are object
// streams. The default is o_preserve.
enum object_stream_e { o_disable, o_preserve, o_generate };
void setObjectStreamMode(object_stream_e);
// Set value of stream data mode. In uncompress mode, we attempt
// to uncompress any stream that we can. In preserve mode, we
// preserve any filtering applied to streams. In compress mode,
// if we can apply all filters and the stream is not already
// optimally compressed, recompress the stream.
enum stream_data_e { s_uncompress, s_preserve, s_compress };
void setStreamDataMode(stream_data_e);
// Set value of content stream normalization. The default is
// "false". If true, we attempt to normalize newlines inside of
// content streams. Some constructs such as inline images may
// thwart our efforts. There may be some cases where this can
// damage the content stream. This flag should be used only for
// debugging and experimenting with PDF content streams. Never
// use it for production files.
void setContentNormalization(bool);
// Set QDF mode. QDF mode causes special "pretty printing" of
// PDF objects, adds comments for easier perusing of files.
// Resulting PDF files can be edited in a text editor and then run
// through fix-qdf to update cross reference tables and stream
// lengths.
void setQDFMode(bool);
// Cause a static /ID value to be generated. Use only in test
// suites.
void setStaticID(bool);
// Preserve encryption. The default is true unless prefilering,
// content normalization, or qdf mode has been selected in which
// case encryption is never preserved. Encryption is also not
// preserved if we explicitly set encryption parameters.
void setPreserveEncryption(bool);
// Set up for encrypted output. Disables stream prefiltering and
// content normalization. Note that setting R2 encryption
// parameters sets the PDF version to at least 1.3, and setting R3
// encryption parameters pushes the PDF version number to at least
// 1.4.
void setR2EncryptionParameters(
char const* user_password, char const* owner_password,
bool allow_print, bool allow_modify,
bool allow_extract, bool allow_annotate);
enum r3_print_e
{
r3p_full, // allow all printing
r3p_low, // allow only low-resolution printing
r3p_none // allow no printing
};
enum r3_modify_e
{
r3m_all, // allow all modification
r3m_annotate, // allow comment authoring and form operations
r3m_form, // allow form field fill-in or signing
r3m_assembly, // allow only document assembly
r3m_none // allow no modification
};
void setR3EncryptionParameters(
char const* user_password, char const* owner_password,
bool allow_accessibility, bool allow_extract,
r3_print_e print, r3_modify_e modify);
// Create linearized output. Disables qdf mode, content
// normalization, and stream prefiltering.
void setLinearization(bool);
void write();
private:
// flags used by unparseObject
static int const f_stream = 1 << 0;
static int const f_filtered = 1 << 1;
static int const f_in_ostream = 1 << 2;
enum trailer_e { t_normal, t_lin_first, t_lin_second };
int bytesNeeded(unsigned long n);
void writeBinary(unsigned long val, unsigned int bytes);
void writeString(std::string const& str);
void writeBuffer(PointerHolder<Buffer>&);
void writeStringQDF(std::string const& str);
void writeStringNoQDF(std::string const& str);
void assignCompressedObjectNumbers(int objid);
void enqueueObject(QPDFObjectHandle object);
void writeObjectStreamOffsets(std::vector<int>& offsets, int first_obj);
void writeObjectStream(QPDFObjectHandle object);
void writeObject(QPDFObjectHandle object, int object_stream_index = -1);
void writeTrailer(trailer_e which, int size,
bool xref_stream, int prev = 0);
void unparseObject(QPDFObjectHandle object, int level,
unsigned int flags);
void unparseObject(QPDFObjectHandle object, int level,
unsigned int flags,
// for stream dictionaries
int stream_length, bool compress);
void unparseChild(QPDFObjectHandle child, int level, int flags);
void initializeSpecialStreams();
void preserveObjectStreams();
void generateObjectStreams();
void generateID();
void setEncryptionParameters(
char const* user_password, char const* owner_password,
int V, int R, int key_len, std::set<int>& bits_to_clear);
void setEncryptionParametersInternal(
int V, int R, int key_len, long P,
std::string const& O, std::string const& U,
std::string const& id1, std::string const& user_password);
void copyEncryptionParameters();
void setDataKey(int objid);
int openObject(int objid = 0);
void closeObject(int objid);
void writeStandard();
void writeLinearized();
void enqueuePart(std::vector<QPDFObjectHandle>& part);
void writeEncryptionDictionary();
void writeHeader();
void writeHintStream(int hint_id);
int writeXRefTable(trailer_e which, int first, int last, int size);
int writeXRefTable(trailer_e which, int first, int last, int size,
// for linearization
int prev,
bool suppress_offsets,
int hint_id,
int hint_offset,
int hint_length);
int writeXRefStream(int objid, int max_id, int max_offset,
trailer_e which, int first, int last, int size);
int writeXRefStream(int objid, int max_id, int max_offset,
trailer_e which, int first, int last, int size,
// for linearization
int prev,
int hint_id,
int hint_offset,
int hint_length);
// When filtering subsections, push additional pipelines to the
// stack. When ready to switch, activate the pipeline stack.
// Pipelines passed to pushPipeline are deleted when
// clearPipelineStack is called.
Pipeline* pushPipeline(Pipeline*);
void activatePipelineStack();
// Calls finish on the current pipeline and pops the pipeline
// stack until the top of stack is a previous active top of stack,
// and restores the pipeline to that point. Deletes any piplines
// that it pops. If the bp argument is non-null and any of the
// stack items are of type Pl_Buffer, the buffer is retrieved.
void popPipelineStack(PointerHolder<Buffer>* bp = 0);
void pushEncryptionFilter();
void pushDiscardFilter();
QPDF& pdf;
char const* filename;
FILE* file;
bool close_file;
bool normalize_content_set;
bool normalize_content;
bool stream_data_mode_set;
stream_data_e stream_data_mode;
bool qdf_mode;
bool static_id;
bool direct_stream_lengths;
bool encrypted;
bool preserve_encryption;
bool linearized;
object_stream_e object_stream_mode;
std::string encryption_key;
std::map<std::string, std::string> encryption_dictionary;
std::string id1; // for /ID key of
std::string id2; // trailer dictionary
std::string min_pdf_version;
int encryption_dict_objid;
std::string cur_data_key;
std::list<PointerHolder<Pipeline> > to_delete;
Pl_Count* pipeline;
std::list<QPDFObjectHandle> object_queue;
std::map<int, int> obj_renumber;
std::map<int, QPDFXRefEntry> xref;
std::map<int, size_t> lengths;
int next_objid;
int cur_stream_length_id;
int cur_stream_length;
bool added_newline;
int max_ostream_index;
std::set<int> normalized_streams;
std::map<int, int> page_object_to_seq;
std::map<int, int> contents_to_page_seq;
std::map<int, int> object_to_object_stream;
std::map<int, std::set<int> > object_stream_to_objects;
std::list<Pipeline*> pipeline_stack;
};
#endif // __QPDFWRITER_HH__

View File

@ -0,0 +1,34 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __QPDFXREFENTRY_HH__
#define __QPDFXREFENTRY_HH__
class QPDFXRefEntry
{
public:
// Type constants are from the PDF spec section
// "Cross-Reference Streams":
// 0 = free entry; not used
// 1 = "uncompressed"; field 1 = offset
// 2 = "compressed"; field 1 = object stream number, field 2 = index
QPDFXRefEntry();
QPDFXRefEntry(int type, int field1, int field2);
int getType() const;
int getOffset() const; // only for type 1
int getObjStreamNumber() const; // only for type 2
int getObjStreamIndex() const; // only for type 2
private:
int type;
int field1;
int field2;
};
#endif // __QPDFXREFENTRY_HH__

16
include/qpdf/QTC.hh Normal file
View File

@ -0,0 +1,16 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __QTC_HH__
#define __QTC_HH__
namespace QTC
{
void TC(char const* const scope, char const* const ccase, int n = 0);
};
#endif // __QTC_HH__

45
include/qpdf/QUtil.hh Normal file
View File

@ -0,0 +1,45 @@
// Copyright (c) 2005-2008 Jay Berkenbilt
//
// This file is part of qpdf. This software may be distributed under
// the terms of version 2 of the Artistic License which may be found
// in the source distribution. It is provided "as is" without express
// or implied warranty.
#ifndef __QUTIL_HH__
#define __QUTIL_HH__
#include <string>
#include <list>
#include <stdio.h>
#include <sys/stat.h>
#include <qpdf/QEXC.hh>
namespace QUtil
{
// This is a collection of useful utility functions that don't
// really go anywhere else.
std::string int_to_string(int, int length = 0);
std::string double_to_string(double, int decimal_places = 0);
// If status is -1, convert the current value of errno to a
// QEXC::System exception. Otherwise, return status.
int os_wrapper(std::string const& description, int status)
throw (QEXC::System);
FILE* fopen_wrapper(std::string const&, FILE*)
throw (QEXC::System);
char* copy_string(std::string const&);
// Get the value of an environment variable in a portable fashion.
// Returns true iff the variable is defined. If `value' is
// non-null, initializes it with the value of the variable.
bool get_env(std::string const& var, std::string* value = 0);
// Return a string containing the byte representation of the UTF-8
// encoding for the unicode value passed in.
std::string toUTF8(unsigned long uval);
};
#endif // __QUTIL_HH__

519
install-sh Executable file
View File

@ -0,0 +1,519 @@
#!/bin/sh
# install - install a program, script, or datafile
scriptversion=2006-12-25.00
# This originates from X11R5 (mit/util/scripts/install.sh), which was
# later released in X11R6 (xc/config/util/install.sh) with the
# following copyright and license.
#
# Copyright (C) 1994 X Consortium
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name of the X Consortium shall not
# be used in advertising or otherwise to promote the sale, use or other deal-
# ings in this Software without prior written authorization from the X Consor-
# tium.
#
#
# FSF changes to this file are in the public domain.
#
# Calling this script install-sh is preferred over install.sh, to prevent
# `make' implicit rules from creating a file called install from it
# when there is no Makefile.
#
# This script is compatible with the BSD install script, but was written
# from scratch.
nl='
'
IFS=" "" $nl"
# set DOITPROG to echo to test this script
# Don't use :- since 4.3BSD and earlier shells don't like it.
doit=${DOITPROG-}
if test -z "$doit"; then
doit_exec=exec
else
doit_exec=$doit
fi
# Put in absolute file names if you don't have them in your path;
# or use environment vars.
chgrpprog=${CHGRPPROG-chgrp}
chmodprog=${CHMODPROG-chmod}
chownprog=${CHOWNPROG-chown}
cmpprog=${CMPPROG-cmp}
cpprog=${CPPROG-cp}
mkdirprog=${MKDIRPROG-mkdir}
mvprog=${MVPROG-mv}
rmprog=${RMPROG-rm}
stripprog=${STRIPPROG-strip}
posix_glob='?'
initialize_posix_glob='
test "$posix_glob" != "?" || {
if (set -f) 2>/dev/null; then
posix_glob=
else
posix_glob=:
fi
}
'
posix_mkdir=
# Desired mode of installed file.
mode=0755
chgrpcmd=
chmodcmd=$chmodprog
chowncmd=
mvcmd=$mvprog
rmcmd="$rmprog -f"
stripcmd=
src=
dst=
dir_arg=
dst_arg=
copy_on_change=false
no_target_directory=
usage="\
Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
or: $0 [OPTION]... SRCFILES... DIRECTORY
or: $0 [OPTION]... -t DIRECTORY SRCFILES...
or: $0 [OPTION]... -d DIRECTORIES...
In the 1st form, copy SRCFILE to DSTFILE.
In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
In the 4th, create DIRECTORIES.
Options:
--help display this help and exit.
--version display version info and exit.
-c (ignored)
-C install only if different (preserve the last data modification time)
-d create directories instead of installing files.
-g GROUP $chgrpprog installed files to GROUP.
-m MODE $chmodprog installed files to MODE.
-o USER $chownprog installed files to USER.
-s $stripprog installed files.
-t DIRECTORY install into DIRECTORY.
-T report an error if DSTFILE is a directory.
Environment variables override the default commands:
CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
RMPROG STRIPPROG
"
while test $# -ne 0; do
case $1 in
-c) ;;
-C) copy_on_change=true;;
-d) dir_arg=true;;
-g) chgrpcmd="$chgrpprog $2"
shift;;
--help) echo "$usage"; exit $?;;
-m) mode=$2
case $mode in
*' '* | *' '* | *'
'* | *'*'* | *'?'* | *'['*)
echo "$0: invalid mode: $mode" >&2
exit 1;;
esac
shift;;
-o) chowncmd="$chownprog $2"
shift;;
-s) stripcmd=$stripprog;;
-t) dst_arg=$2
shift;;
-T) no_target_directory=true;;
--version) echo "$0 $scriptversion"; exit $?;;
--) shift
break;;
-*) echo "$0: invalid option: $1" >&2
exit 1;;
*) break;;
esac
shift
done
if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
# When -d is used, all remaining arguments are directories to create.
# When -t is used, the destination is already specified.
# Otherwise, the last argument is the destination. Remove it from $@.
for arg
do
if test -n "$dst_arg"; then
# $@ is not empty: it contains at least $arg.
set fnord "$@" "$dst_arg"
shift # fnord
fi
shift # arg
dst_arg=$arg
done
fi
if test $# -eq 0; then
if test -z "$dir_arg"; then
echo "$0: no input file specified." >&2
exit 1
fi
# It's OK to call `install-sh -d' without argument.
# This can happen when creating conditional directories.
exit 0
fi
if test -z "$dir_arg"; then
trap '(exit $?); exit' 1 2 13 15
# Set umask so as not to create temps with too-generous modes.
# However, 'strip' requires both read and write access to temps.
case $mode in
# Optimize common cases.
*644) cp_umask=133;;
*755) cp_umask=22;;
*[0-7])
if test -z "$stripcmd"; then
u_plus_rw=
else
u_plus_rw='% 200'
fi
cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
*)
if test -z "$stripcmd"; then
u_plus_rw=
else
u_plus_rw=,u+rw
fi
cp_umask=$mode$u_plus_rw;;
esac
fi
for src
do
# Protect names starting with `-'.
case $src in
-*) src=./$src;;
esac
if test -n "$dir_arg"; then
dst=$src
dstdir=$dst
test -d "$dstdir"
dstdir_status=$?
else
# Waiting for this to be detected by the "$cpprog $src $dsttmp" command
# might cause directories to be created, which would be especially bad
# if $src (and thus $dsttmp) contains '*'.
if test ! -f "$src" && test ! -d "$src"; then
echo "$0: $src does not exist." >&2
exit 1
fi
if test -z "$dst_arg"; then
echo "$0: no destination specified." >&2
exit 1
fi
dst=$dst_arg
# Protect names starting with `-'.
case $dst in
-*) dst=./$dst;;
esac
# If destination is a directory, append the input filename; won't work
# if double slashes aren't ignored.
if test -d "$dst"; then
if test -n "$no_target_directory"; then
echo "$0: $dst_arg: Is a directory" >&2
exit 1
fi
dstdir=$dst
dst=$dstdir/`basename "$src"`
dstdir_status=0
else
# Prefer dirname, but fall back on a substitute if dirname fails.
dstdir=`
(dirname "$dst") 2>/dev/null ||
expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$dst" : 'X\(//\)[^/]' \| \
X"$dst" : 'X\(//\)$' \| \
X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
echo X"$dst" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
s//\1/
q
}
s/.*/./; q'
`
test -d "$dstdir"
dstdir_status=$?
fi
fi
obsolete_mkdir_used=false
if test $dstdir_status != 0; then
case $posix_mkdir in
'')
# Create intermediate dirs using mode 755 as modified by the umask.
# This is like FreeBSD 'install' as of 1997-10-28.
umask=`umask`
case $stripcmd.$umask in
# Optimize common cases.
*[2367][2367]) mkdir_umask=$umask;;
.*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
*[0-7])
mkdir_umask=`expr $umask + 22 \
- $umask % 100 % 40 + $umask % 20 \
- $umask % 10 % 4 + $umask % 2
`;;
*) mkdir_umask=$umask,go-w;;
esac
# With -d, create the new directory with the user-specified mode.
# Otherwise, rely on $mkdir_umask.
if test -n "$dir_arg"; then
mkdir_mode=-m$mode
else
mkdir_mode=
fi
posix_mkdir=false
case $umask in
*[123567][0-7][0-7])
# POSIX mkdir -p sets u+wx bits regardless of umask, which
# is incompatible with FreeBSD 'install' when (umask & 300) != 0.
;;
*)
tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
if (umask $mkdir_umask &&
exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
then
if test -z "$dir_arg" || {
# Check for POSIX incompatibilities with -m.
# HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
# other-writeable bit of parent directory when it shouldn't.
# FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
ls_ld_tmpdir=`ls -ld "$tmpdir"`
case $ls_ld_tmpdir in
d????-?r-*) different_mode=700;;
d????-?--*) different_mode=755;;
*) false;;
esac &&
$mkdirprog -m$different_mode -p -- "$tmpdir" && {
ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
}
}
then posix_mkdir=:
fi
rmdir "$tmpdir/d" "$tmpdir"
else
# Remove any dirs left behind by ancient mkdir implementations.
rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
fi
trap '' 0;;
esac;;
esac
if
$posix_mkdir && (
umask $mkdir_umask &&
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
)
then :
else
# The umask is ridiculous, or mkdir does not conform to POSIX,
# or it failed possibly due to a race condition. Create the
# directory the slow way, step by step, checking for races as we go.
case $dstdir in
/*) prefix='/';;
-*) prefix='./';;
*) prefix='';;
esac
eval "$initialize_posix_glob"
oIFS=$IFS
IFS=/
$posix_glob set -f
set fnord $dstdir
shift
$posix_glob set +f
IFS=$oIFS
prefixes=
for d
do
test -z "$d" && continue
prefix=$prefix$d
if test -d "$prefix"; then
prefixes=
else
if $posix_mkdir; then
(umask=$mkdir_umask &&
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
# Don't fail if two instances are running concurrently.
test -d "$prefix" || exit 1
else
case $prefix in
*\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
*) qprefix=$prefix;;
esac
prefixes="$prefixes '$qprefix'"
fi
fi
prefix=$prefix/
done
if test -n "$prefixes"; then
# Don't fail if two instances are running concurrently.
(umask $mkdir_umask &&
eval "\$doit_exec \$mkdirprog $prefixes") ||
test -d "$dstdir" || exit 1
obsolete_mkdir_used=true
fi
fi
fi
if test -n "$dir_arg"; then
{ test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
{ test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
else
# Make a couple of temp file names in the proper directory.
dsttmp=$dstdir/_inst.$$_
rmtmp=$dstdir/_rm.$$_
# Trap to clean up those temp files at exit.
trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
# Copy the file name to the temp name.
(umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
# and set any options; do chmod last to preserve setuid bits.
#
# If any of these fail, we abort the whole thing. If we want to
# ignore errors from any of these, just make sure not to ignore
# errors from the above "$doit $cpprog $src $dsttmp" command.
#
{ test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
{ test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
{ test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
# If -C, don't bother to copy if it wouldn't change the file.
if $copy_on_change &&
old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
eval "$initialize_posix_glob" &&
$posix_glob set -f &&
set X $old && old=:$2:$4:$5:$6 &&
set X $new && new=:$2:$4:$5:$6 &&
$posix_glob set +f &&
test "$old" = "$new" &&
$cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
then
rm -f "$dsttmp"
else
# Rename the file to the real destination.
$doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
# The rename failed, perhaps because mv can't rename something else
# to itself, or perhaps because mv is so ancient that it does not
# support -f.
{
# Now remove or move aside any old file at destination location.
# We try this two ways since rm can't unlink itself on some
# systems and the destination file might be busy for other
# reasons. In this case, the final cleanup might fail but the new
# file should still install successfully.
{
test ! -f "$dst" ||
$doit $rmcmd -f "$dst" 2>/dev/null ||
{ $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
{ $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
} ||
{ echo "$0: cannot unlink or rename $dst" >&2
(exit 1); exit 1
}
} &&
# Now rename the file to the real destination.
$doit $mvcmd "$dsttmp" "$dst"
}
fi || exit 1
trap '' 0
fi
done
# Local variables:
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-end: "$"
# End:

45
libqpdf/BitStream.cc Normal file
View File

@ -0,0 +1,45 @@
#include <qpdf/BitStream.hh>
// See comments in bits.cc
#define BITS_READ 1
#include "bits.icc"
BitStream::BitStream(unsigned char const* p, int nbytes) :
start(p),
nbytes(nbytes)
{
reset();
}
void
BitStream::reset()
{
p = start;
bit_offset = 7;
bits_available = 8 * nbytes;
}
unsigned long
BitStream::getBits(int nbits)
{
return read_bits(this->p, this->bit_offset,
this->bits_available, nbits);
}
void
BitStream::skipToNextByte()
{
if (bit_offset != 7)
{
unsigned int bits_to_skip = bit_offset + 1;
if (bits_available < bits_to_skip)
{
throw QEXC::Internal("overflow skipping to next byte in bitstream");
}
bit_offset = 7;
++p;
bits_available -= bits_to_skip;
}
}

30
libqpdf/BitWriter.cc Normal file
View File

@ -0,0 +1,30 @@
#include <qpdf/BitWriter.hh>
// See comments in bits.cc
#define BITS_WRITE 1
#include "bits.icc"
BitWriter::BitWriter(Pipeline* pl) :
pl(pl),
ch(0),
bit_offset(7)
{
}
void
BitWriter::writeBits(unsigned long val, int bits)
{
write_bits(this->ch, this->bit_offset, val, bits, this->pl);
}
void
BitWriter::flush()
{
if (bit_offset < 7)
{
int bits_to_write = bit_offset + 1;
write_bits(this->ch, this->bit_offset, 0, bits_to_write, this->pl);
}
}

79
libqpdf/Buffer.cc Normal file
View File

@ -0,0 +1,79 @@
#include <qpdf/Buffer.hh>
#include <string.h>
Buffer::Buffer()
{
init(0);
}
Buffer::Buffer(unsigned long size)
{
init(size);
}
Buffer::Buffer(Buffer const& rhs)
{
init(0);
copy(rhs);
}
Buffer&
Buffer::operator=(Buffer const& rhs)
{
copy(rhs);
return *this;
}
Buffer::~Buffer()
{
destroy();
}
void
Buffer::init(unsigned long size)
{
this->size = size;
this->buf = (size ? new unsigned char[size] : 0);
}
void
Buffer::copy(Buffer const& rhs)
{
if (this != &rhs)
{
this->destroy();
this->init(rhs.size);
if (this->size)
{
memcpy(this->buf, rhs.buf, this->size);
}
}
}
void
Buffer::destroy()
{
delete [] this->buf;
this->size = 0;
this->buf = 0;
}
unsigned long
Buffer::getSize() const
{
return this->size;
}
unsigned char const*
Buffer::getBuffer() const
{
return this->buf;
}
unsigned char*
Buffer::getBuffer()
{
return this->buf;
}

441
libqpdf/MD5.cc Normal file
View File

@ -0,0 +1,441 @@
// This file implements a class for computation of MD5 checksums.
// It is derived from the reference algorithm for MD5 as given in
// RFC 1321. The original copyright notice is as follows:
//
/////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
// rights reserved.
//
// License to copy and use this software is granted provided that it
// is identified as the "RSA Data Security, Inc. MD5 Message-Digest
// Algorithm" in all material mentioning or referencing this software
// or this function.
//
// License is also granted to make and use derivative works provided
// that such works are identified as "derived from the RSA Data
// Security, Inc. MD5 Message-Digest Algorithm" in all material
// mentioning or referencing the derived work.
//
// RSA Data Security, Inc. makes no representations concerning either
// the merchantability of this software or the suitability of this
// software for any particular purpose. It is provided "as is"
// without express or implied warranty of any kind.
//
// These notices must be retained in any copies of any part of this
// documentation and/or software.
//
/////////////////////////////////////////////////////////////////////////
#include <qpdf/MD5.hh>
#include <stdio.h>
#include <memory.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int const S11 = 7;
int const S12 = 12;
int const S13 = 17;
int const S14 = 22;
int const S21 = 5;
int const S22 = 9;
int const S23 = 14;
int const S24 = 20;
int const S31 = 4;
int const S32 = 11;
int const S33 = 16;
int const S34 = 23;
int const S41 = 6;
int const S42 = 10;
int const S43 = 15;
int const S44 = 21;
static unsigned char PADDING[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
// F, G, H and I are basic MD5 functions.
#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | (~z)))
// ROTATE_LEFT rotates x left n bits.
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
// Rotation is separate from addition to prevent recomputation.
#define FF(a, b, c, d, x, s, ac) { \
(a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define GG(a, b, c, d, x, s, ac) { \
(a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define HH(a, b, c, d, x, s, ac) { \
(a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define II(a, b, c, d, x, s, ac) { \
(a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
// MD5 initialization. Begins an MD5 operation, writing a new context.
void MD5::init()
{
count[0] = count[1] = 0;
// Load magic initialization constants.
state[0] = 0x67452301;
state[1] = 0xefcdab89;
state[2] = 0x98badcfe;
state[3] = 0x10325476;
finalized = false;
memset(digest_val, 0, sizeof(digest_val));
}
// MD5 block update operation. Continues an MD5 message-digest
// operation, processing another message block, and updating the
// context.
void MD5::update(unsigned char *input,
unsigned int inputLen)
{
unsigned int i, index, partLen;
// Compute number of bytes mod 64
index = (unsigned int)((count[0] >> 3) & 0x3F);
// Update number of bits
if ((count[0] += ((UINT4)inputLen << 3))
< ((UINT4)inputLen << 3))
count[1]++;
count[1] += ((UINT4)inputLen >> 29);
partLen = 64 - index;
// Transform as many times as possible.
if (inputLen >= partLen) {
memcpy
((POINTER)&buffer[index], (POINTER)input, partLen);
transform(state, buffer);
for (i = partLen; i + 63 < inputLen; i += 64)
transform(state, &input[i]);
index = 0;
}
else
i = 0;
// Buffer remaining input
memcpy
((POINTER)&buffer[index], (POINTER)&input[i],
inputLen-i);
}
// MD5 finalization. Ends an MD5 message-digest operation, writing the
// the message digest and zeroizing the context.
void MD5::final()
{
if (finalized)
{
return;
}
unsigned char bits[8];
unsigned int index, padLen;
// Save number of bits
encode(bits, count, 8);
// Pad out to 56 mod 64.
index = (unsigned int)((count[0] >> 3) & 0x3f);
padLen = (index < 56) ? (56 - index) : (120 - index);
update(PADDING, padLen);
// Append length (before padding)
update(bits, 8);
// Store state in digest_val
encode(digest_val, state, 16);
// Zeroize sensitive information.
memset(state, 0, sizeof(state));
memset(count, 0, sizeof(count));
memset(buffer, 0, sizeof(buffer));
finalized = true;
}
// MD5 basic transformation. Transforms state based on block.
void MD5::transform(UINT4 state[4], unsigned char block[64])
{
UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
decode(x, block, 64);
// Round 1
FF (a, b, c, d, x[ 0], S11, 0xd76aa478); // 1
FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); // 2
FF (c, d, a, b, x[ 2], S13, 0x242070db); // 3
FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); // 4
FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); // 5
FF (d, a, b, c, x[ 5], S12, 0x4787c62a); // 6
FF (c, d, a, b, x[ 6], S13, 0xa8304613); // 7
FF (b, c, d, a, x[ 7], S14, 0xfd469501); // 8
FF (a, b, c, d, x[ 8], S11, 0x698098d8); // 9
FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); // 10
FF (c, d, a, b, x[10], S13, 0xffff5bb1); // 11
FF (b, c, d, a, x[11], S14, 0x895cd7be); // 12
FF (a, b, c, d, x[12], S11, 0x6b901122); // 13
FF (d, a, b, c, x[13], S12, 0xfd987193); // 14
FF (c, d, a, b, x[14], S13, 0xa679438e); // 15
FF (b, c, d, a, x[15], S14, 0x49b40821); // 16
// Round 2
GG (a, b, c, d, x[ 1], S21, 0xf61e2562); // 17
GG (d, a, b, c, x[ 6], S22, 0xc040b340); // 18
GG (c, d, a, b, x[11], S23, 0x265e5a51); // 19
GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); // 20
GG (a, b, c, d, x[ 5], S21, 0xd62f105d); // 21
GG (d, a, b, c, x[10], S22, 0x2441453); // 22
GG (c, d, a, b, x[15], S23, 0xd8a1e681); // 23
GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); // 24
GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); // 25
GG (d, a, b, c, x[14], S22, 0xc33707d6); // 26
GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); // 27
GG (b, c, d, a, x[ 8], S24, 0x455a14ed); // 28
GG (a, b, c, d, x[13], S21, 0xa9e3e905); // 29
GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); // 30
GG (c, d, a, b, x[ 7], S23, 0x676f02d9); // 31
GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); // 32
// Round 3
HH (a, b, c, d, x[ 5], S31, 0xfffa3942); // 33
HH (d, a, b, c, x[ 8], S32, 0x8771f681); // 34
HH (c, d, a, b, x[11], S33, 0x6d9d6122); // 35
HH (b, c, d, a, x[14], S34, 0xfde5380c); // 36
HH (a, b, c, d, x[ 1], S31, 0xa4beea44); // 37
HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); // 38
HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); // 39
HH (b, c, d, a, x[10], S34, 0xbebfbc70); // 40
HH (a, b, c, d, x[13], S31, 0x289b7ec6); // 41
HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); // 42
HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); // 43
HH (b, c, d, a, x[ 6], S34, 0x4881d05); // 44
HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); // 45
HH (d, a, b, c, x[12], S32, 0xe6db99e5); // 46
HH (c, d, a, b, x[15], S33, 0x1fa27cf8); // 47
HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); // 48
// Round 4
II (a, b, c, d, x[ 0], S41, 0xf4292244); // 49
II (d, a, b, c, x[ 7], S42, 0x432aff97); // 50
II (c, d, a, b, x[14], S43, 0xab9423a7); // 51
II (b, c, d, a, x[ 5], S44, 0xfc93a039); // 52
II (a, b, c, d, x[12], S41, 0x655b59c3); // 53
II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); // 54
II (c, d, a, b, x[10], S43, 0xffeff47d); // 55
II (b, c, d, a, x[ 1], S44, 0x85845dd1); // 56
II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); // 57
II (d, a, b, c, x[15], S42, 0xfe2ce6e0); // 58
II (c, d, a, b, x[ 6], S43, 0xa3014314); // 59
II (b, c, d, a, x[13], S44, 0x4e0811a1); // 60
II (a, b, c, d, x[ 4], S41, 0xf7537e82); // 61
II (d, a, b, c, x[11], S42, 0xbd3af235); // 62
II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); // 63
II (b, c, d, a, x[ 9], S44, 0xeb86d391); // 64
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
// Zeroize sensitive information.
memset ((POINTER)x, 0, sizeof (x));
}
// Encodes input (UINT4) into output (unsigned char). Assumes len is a
// multiple of 4.
void MD5::encode(unsigned char *output, UINT4 *input, unsigned int len)
{
unsigned int i, j;
for (i = 0, j = 0; j < len; i++, j += 4) {
output[j] = (unsigned char)(input[i] & 0xff);
output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
}
}
// Decodes input (unsigned char) into output (UINT4). Assumes len is a
// multiple of 4.
void MD5::decode(UINT4 *output, unsigned char *input, unsigned int len)
{
unsigned int i, j;
for (i = 0, j = 0; j < len; i++, j += 4)
output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
(((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
}
// Public functions
MD5::MD5()
{
init();
}
void MD5::reset()
{
init();
}
void MD5::encodeString(char const* str)
{
unsigned int len = strlen(str);
update((unsigned char *)str, len);
final();
}
void MD5::appendString(char const* input_string)
{
update((unsigned char *)input_string, strlen(input_string));
}
void MD5::encodeDataIncrementally(char const* data, int len)
{
update((unsigned char *)data, len);
}
void MD5::encodeFile(char const *filename, int up_to_size)
throw (QEXC::System)
{
FILE *file;
unsigned char buffer[1024];
if ((file = fopen (filename, "rb")) == NULL)
{
throw QEXC::System(std::string("MD5: can't open ") + filename, errno);
}
int len;
int so_far = 0;
int to_try = 1024;
do
{
if ((up_to_size >= 0) && ((so_far + to_try) > up_to_size))
{
to_try = up_to_size - so_far;
}
len = fread(buffer, 1, to_try, file);
if (len > 0)
{
update(buffer, len);
so_far += len;
if ((up_to_size >= 0) && (so_far >= up_to_size))
{
break;
}
}
} while (len > 0);
if (ferror(file))
{
// Assume, perhaps incorrectly, that errno was set by the
// underlying call to read....
(void) fclose(file);
throw QEXC::System(std::string("MD5: read error on ") + filename, errno);
}
(void) fclose(file);
final();
}
void MD5::digest(Digest result)
{
final();
memcpy(result, digest_val, sizeof(digest_val));
}
void MD5::print()
{
final();
unsigned int i;
for (i = 0; i < 16; ++i)
{
printf("%02x", digest_val[i]);
}
printf("\n");
}
std::string MD5::unparse()
{
final();
char result[33];
char* p = result;
unsigned int i;
for (i = 0; i < 16; ++i)
{
sprintf(p, "%02x", digest_val[i]);
p += 2;
}
return result;
}
std::string
MD5::getDataChecksum(char const* buf, int len)
{
MD5 m;
m.encodeDataIncrementally(buf, len);
return m.unparse();
}
std::string
MD5::getFileChecksum(char const* filename, int up_to_size)
{
MD5 m;
m.encodeFile(filename, up_to_size);
return m.unparse();
}
bool
MD5::checkDataChecksum(char const* const checksum,
char const* buf, int len)
{
std::string actual_checksum = getDataChecksum(buf, len);
return (checksum == actual_checksum);
}
bool
MD5::checkFileChecksum(char const* const checksum,
char const* filename, int up_to_size)
{
bool result = false;
try
{
std::string actual_checksum = getFileChecksum(filename, up_to_size);
result = (checksum == actual_checksum);
}
catch (QEXC::System)
{
// Ignore -- return false
}
return result;
}

1
libqpdf/Makefile Normal file
View File

@ -0,0 +1 @@
include ../make/proxy.mk

365
libqpdf/PCRE.cc Normal file
View File

@ -0,0 +1,365 @@
#include <qpdf/PCRE.hh>
#include <qpdf/QUtil.hh>
#include <iostream>
PCRE::Exception::Exception(std::string const& message)
{
this->setMessage("PCRE error: " + message);
}
PCRE::NoBackref::NoBackref() :
Exception("no match")
{
}
PCRE::Match::Match(int nbackrefs, char const* subject)
{
this->init(-1, nbackrefs, subject);
}
PCRE::Match::~Match()
{
this->destroy();
}
PCRE::Match::Match(Match const& rhs)
{
this->copy(rhs);
}
PCRE::Match&
PCRE::Match::operator=(Match const& rhs)
{
if (this != &rhs)
{
this->destroy();
this->copy(rhs);
}
return *this;
}
void
PCRE::Match::init(int nmatches, int nbackrefs, char const* subject)
{
this->nmatches = nmatches;
this->nbackrefs = nbackrefs;
this->subject = subject;
this->ovecsize = 3 * (1 + nbackrefs);
this->ovector = 0;
if (this->ovecsize)
{
this->ovector = new int[this->ovecsize];
}
}
void
PCRE::Match::copy(Match const& rhs)
{
this->init(rhs.nmatches, rhs.nbackrefs, rhs.subject);
int i;
for (i = 0; i < this->ovecsize; ++i)
{
this->ovector[i] = rhs.ovector[i];
}
}
void
PCRE::Match::destroy()
{
delete [] this->ovector;
}
PCRE::Match::operator bool()
{
return (this->nmatches >= 0);
}
std::string
PCRE::Match::getMatch(int n, int flags)
throw(QEXC::General, Exception)
{
// This method used to be implemented in terms of
// pcre_get_substring, but that function gives you an empty string
// for an unmatched backreference that is in range.
int offset;
int length;
try
{
getOffsetLength(n, offset, length);
}
catch (NoBackref&)
{
if (flags & gm_no_substring_returns_empty)
{
return "";
}
else
{
throw;
}
}
return std::string(this->subject).substr(offset, length);
}
void
PCRE::Match::getOffsetLength(int n, int& offset, int& length) throw(Exception)
{
if ((this->nmatches < 0) ||
(n > this->nmatches - 1) ||
(this->ovector[n * 2] == -1))
{
throw NoBackref();
}
offset = this->ovector[n * 2];
length = this->ovector[n * 2 + 1] - offset;
}
int
PCRE::Match::getOffset(int n) throw(Exception)
{
int offset;
int length;
this->getOffsetLength(n, offset, length);
return offset;
}
int
PCRE::Match::getLength(int n) throw(Exception)
{
int offset;
int length;
this->getOffsetLength(n, offset, length);
return length;
}
int
PCRE::Match::nMatches() const
{
return this->nmatches;
}
PCRE::PCRE(char const* pattern, int options) throw (Exception)
{
char const *errptr;
int erroffset;
this->code = pcre_compile(pattern, options, &errptr, &erroffset, 0);
if (this->code)
{
this->nbackrefs = pcre_info(this->code, 0, 0);
}
else
{
std::string message = (std::string("compilation of ") + pattern +
" failed at offset " +
QUtil::int_to_string(erroffset) + ": " +
errptr);
throw Exception(message);
}
}
PCRE::~PCRE()
{
pcre_free(this->code);
}
PCRE::Match
PCRE::match(char const* subject, int options, int startoffset, int size)
throw (QEXC::General, Exception)
{
if (size == -1)
{
size = strlen(subject);
}
Match result(this->nbackrefs, subject);
int status = pcre_exec(this->code, 0, subject, size,
startoffset, options,
result.ovector, result.ovecsize);
if (status >= 0)
{
result.nmatches = status;
}
else
{
std::string message;
switch (status)
{
case PCRE_ERROR_NOMATCH:
break;
case PCRE_ERROR_BADOPTION:
message = "bad option passed to PCRE::match()";
throw Exception(message);
break;
case PCRE_ERROR_NOMEMORY:
message = "insufficient memory";
throw Exception(message);
break;
case PCRE_ERROR_NULL:
case PCRE_ERROR_BADMAGIC:
case PCRE_ERROR_UNKNOWN_NODE:
default:
message = "pcre_exec returned " + QUtil::int_to_string(status);
throw QEXC::Internal(message);
}
}
return result;
}
void
PCRE::test(int n)
{
try
{
if (n == 1)
{
static char const* utf8 = "abπdefq";
PCRE u1("^([[:alpha:]]+)");
PCRE u2("^([\\p{L}]+)", PCRE_UTF8);
PCRE::Match m1 = u1.match(utf8);
if (m1)
{
std::cout << "no utf8: " << m1.getMatch(1) << std::endl;
}
PCRE::Match m2 = u2.match(utf8);
if (m2)
{
std::cout << "utf8: " << m2.getMatch(1) << std::endl;
}
return;
}
try
{
PCRE pcre1("a**");
}
catch (Exception& e)
{
std::cout << e.unparse() << std::endl;
}
PCRE pcre2("^([^\\s:]*)\\s*:\\s*(.*?)\\s*$");
PCRE::Match m2 = pcre2.match("key: value one two three ");
if (m2)
{
std::cout << m2.nMatches() << std::endl;
std::cout << m2.getMatch(0) << std::endl;
std::cout << m2.getOffset(0) << std::endl;
std::cout << m2.getLength(0) << std::endl;
std::cout << m2.getMatch(1) << std::endl;
std::cout << m2.getOffset(1) << std::endl;
std::cout << m2.getLength(1) << std::endl;
std::cout << m2.getMatch(2) << std::endl;
std::cout << m2.getOffset(2) << std::endl;
std::cout << m2.getLength(2) << std::endl;
try
{
std::cout << m2.getMatch(3) << std::endl;
}
catch (Exception& e)
{
std::cout << e.unparse() << std::endl;
}
try
{
std::cout << m2.getOffset(3) << std::endl;
}
catch (Exception& e)
{
std::cout << e.unparse() << std::endl;
}
}
PCRE pcre3("^(a+)(b+)?$");
PCRE::Match m3 = pcre3.match("aaa");
try
{
if (m3)
{
std::cout << m3.nMatches() << std::endl;
std::cout << m3.getMatch(0) << std::endl;
std::cout << m3.getMatch(1) << std::endl;
std::cout << "-"
<< m3.getMatch(
2, Match::gm_no_substring_returns_empty)
<< "-" << std::endl;
std::cout << "hello" << std::endl;
std::cout << m3.getMatch(2) << std::endl;
std::cout << "can't see this" << std::endl;
}
}
catch (Exception& e)
{
std::cout << e.unparse() << std::endl;
}
// backref: 1 2 3 4 5
PCRE pcre4("^((?:(a(b)?)(?:,(c))?)|(c))?$");
static char const* candidates[] = {
"qqqcqqq", // no match
"ab,c", // backrefs: 0, 1, 2, 3, 4
"ab", // backrefs: 0, 1, 2, 3
"a", // backrefs: 0, 1, 2
"a,c", // backrefs: 0, 1, 2, 4
"c", // backrefs: 0, 1, 5
"", // backrefs: 0
0
};
for (char const** p = candidates; *p; ++p)
{
PCRE::Match m(pcre4.match(*p));
if (m)
{
int nmatches = m.nMatches();
for (int i = 0; i < nmatches; ++i)
{
std::cout << *p << ": " << i << ": ";
try
{
std::string match = m.getMatch(i);
std::cout << match;
}
catch (NoBackref&)
{
std::cout << "no backref (getMatch)";
}
std::cout << std::endl;
std::cout << *p << ": " << i << ": ";
try
{
int offset;
int length;
m.getOffsetLength(i, offset, length);
std::cout << offset << ", " << length;
}
catch (NoBackref&)
{
std::cout << "no backref (getOffsetLength)";
}
std:: cout << std::endl;
}
}
else
{
std::cout << *p << ": no match" << std::endl;
}
}
}
catch (QEXC::General& e)
{
std::cout << "unexpected exception: " << e.unparse() << std::endl;
}
}

25
libqpdf/Pipeline.cc Normal file
View File

@ -0,0 +1,25 @@
#include <qpdf/Pipeline.hh>
Pipeline::Pipeline(char const* identifier, Pipeline* next) :
identifier(identifier),
next(next)
{
}
Pipeline::~Pipeline()
{
}
Pipeline*
Pipeline::getNext(bool allow_null)
{
if ((next == 0) && (! allow_null))
{
throw Exception(
this->identifier +
": Pipeline::getNext() called on pipeline with no next");
}
return this->next;
}

View File

@ -0,0 +1,131 @@
#include <qpdf/Pl_ASCII85Decoder.hh>
#include <qpdf/QEXC.hh>
#include <qpdf/QTC.hh>
#include <string.h>
Pl_ASCII85Decoder::Pl_ASCII85Decoder(char const* identifier, Pipeline* next) :
Pipeline(identifier, next),
pos(0),
eod(0)
{
memset(this->inbuf, 117, 5);
}
Pl_ASCII85Decoder::~Pl_ASCII85Decoder()
{
}
void
Pl_ASCII85Decoder::write(unsigned char* buf, int len)
{
if (eod > 1)
{
return;
}
for (int i = 0; i < len; ++i)
{
if (eod > 1)
{
break;
}
else if (eod == 1)
{
if (buf[i] == '>')
{
flush();
eod = 2;
}
else
{
throw QEXC::General(
"broken end-of-data sequence in base 85 data");
}
}
else
{
switch (buf[i])
{
case ' ':
case '\f':
case '\v':
case '\t':
case '\r':
case '\n':
QTC::TC("libtests", "Pl_ASCII85Decoder ignore space");
// ignore whitespace
break;
case '~':
eod = 1;
break;
case 'z':
if (pos != 0)
{
throw QEXC::General(
"unexpected z during base 85 decode");
}
else
{
QTC::TC("libtests", "Pl_ASCII85Decoder read z");
getNext()->write((unsigned char*)"\000\000\000\000", 4);
}
break;
default:
if ((buf[i] < 33) || (buf[i] > 117))
{
throw QEXC::General
("character out of range during base 85 decode");
}
else
{
this->inbuf[this->pos++] = buf[i];
if (pos == 5)
{
flush();
}
}
break;
}
}
}
}
void
Pl_ASCII85Decoder::flush()
{
if (this->pos == 0)
{
QTC::TC("libtests", "Pl_ASCII85Decoder no-op flush");
return;
}
unsigned long lval = 0;
for (int i = 0; i < 5; ++i)
{
lval *= 85;
lval += (this->inbuf[i] - 33);
}
unsigned char outbuf[4];
memset(outbuf, 0, 4);
for (int i = 3; i >= 0; --i)
{
outbuf[i] = lval & 0xff;
lval >>= 8;
}
QTC::TC("libtests", "Pl_ASCII85Decoder partial flush",
(this->pos == 5) ? 0 : 1);
getNext()->write(outbuf, this->pos - 1);
this->pos = 0;
memset(this->inbuf, 117, 5);
}
void
Pl_ASCII85Decoder::finish()
{
flush();
getNext()->finish();
}

View File

@ -0,0 +1,108 @@
#include <qpdf/Pl_ASCIIHexDecoder.hh>
#include <qpdf/QEXC.hh>
#include <qpdf/QTC.hh>
#include <string.h>
#include <ctype.h>
Pl_ASCIIHexDecoder::Pl_ASCIIHexDecoder(char const* identifier, Pipeline* next) :
Pipeline(identifier, next),
pos(0),
eod(false)
{
strcpy(this->inbuf, "00");
}
Pl_ASCIIHexDecoder::~Pl_ASCIIHexDecoder()
{
}
void
Pl_ASCIIHexDecoder::write(unsigned char* buf, int len)
{
if (this->eod)
{
return;
}
for (int i = 0; i < len; ++i)
{
char ch = toupper(buf[i]);
switch (ch)
{
case ' ':
case '\f':
case '\v':
case '\t':
case '\r':
case '\n':
QTC::TC("libtests", "Pl_ASCIIHexDecoder ignore space");
// ignore whitespace
break;
case '>':
this->eod = true;
flush();
break;
default:
if (((ch >= '0') && (ch <= '9')) ||
((ch >= 'A') && (ch <= 'F')))
{
this->inbuf[this->pos++] = ch;
if (this->pos == 2)
{
flush();
}
}
else
{
char t[2];
t[0] = ch;
t[1] = 0;
throw QEXC::General(
std::string("character out of range during base Hex decode: ") + t);
}
break;
}
if (this->eod)
{
break;
}
}
}
void
Pl_ASCIIHexDecoder::flush()
{
if (this->pos == 0)
{
QTC::TC("libtests", "Pl_ASCIIHexDecoder no-op flush");
return;
}
int b[2];
for (int i = 0; i < 2; ++i)
{
if (this->inbuf[i] >= 'A')
{
b[i] = this->inbuf[i] - 'A' + 10;
}
else
{
b[i] = this->inbuf[i] - '0';
}
}
unsigned char ch = (unsigned char)((b[0] << 4) + b[1]);
QTC::TC("libtests", "Pl_ASCIIHexDecoder partial flush",
(this->pos == 2) ? 0 : 1);
getNext()->write(&ch, 1);
this->pos = 0;
strcpy(this->inbuf, "00");
}
void
Pl_ASCIIHexDecoder::finish()
{
flush();
getNext()->finish();
}

67
libqpdf/Pl_Buffer.cc Normal file
View File

@ -0,0 +1,67 @@
#include <qpdf/Pl_Buffer.hh>
#include <qpdf/QEXC.hh>
#include <assert.h>
Pl_Buffer::Pl_Buffer(char const* identifier, Pipeline* next) :
Pipeline(identifier, next),
ready(false),
total_size(0)
{
}
Pl_Buffer::~Pl_Buffer()
{
}
void
Pl_Buffer::write(unsigned char* buf, int len)
{
Buffer* b = new Buffer(len);
memcpy(b->getBuffer(), buf, len);
this->data.push_back(b);
this->ready = false;
this->total_size += len;
if (getNext(true))
{
getNext()->write(buf, len);
}
}
void
Pl_Buffer::finish()
{
this->ready = true;
if (getNext(true))
{
getNext()->finish();
}
}
Buffer*
Pl_Buffer::getBuffer()
{
if (! this->ready)
{
throw QEXC::Internal("Pl_Buffer::getBuffer() called when not ready");
}
Buffer* b = new Buffer(this->total_size);
unsigned char* p = b->getBuffer();
while (! this->data.empty())
{
PointerHolder<Buffer> bph = this->data.front();
this->data.pop_front();
Buffer* bp = bph.getPointer();
size_t bytes = bp->getSize();
memcpy(p, bp->getBuffer(), bytes);
p += bytes;
this->total_size -= bytes;
}
assert(this->total_size == 0);
this->ready = false;
return b;
}

42
libqpdf/Pl_Count.cc Normal file
View File

@ -0,0 +1,42 @@
#include <qpdf/Pl_Count.hh>
Pl_Count::Pl_Count(char const* identifier, Pipeline* next) :
Pipeline(identifier, next),
count(0),
last_char('\0')
{
}
Pl_Count::~Pl_Count()
{
}
void
Pl_Count::write(unsigned char* buf, int len)
{
if (len)
{
this->count += len;
getNext()->write(buf, len);
this->last_char = buf[len - 1];
}
}
void
Pl_Count::finish()
{
getNext()->finish();
}
int
Pl_Count::getCount() const
{
return this->count;
}
unsigned char
Pl_Count::getLastChar() const
{
return this->last_char;
}

23
libqpdf/Pl_Discard.cc Normal file
View File

@ -0,0 +1,23 @@
#include <qpdf/Pl_Discard.hh>
// Exercised in md5 test suite
Pl_Discard::Pl_Discard() :
Pipeline("discard", 0)
{
}
Pl_Discard::~Pl_Discard()
{
}
void
Pl_Discard::write(unsigned char* buf, int len)
{
}
void
Pl_Discard::finish()
{
}

198
libqpdf/Pl_Flate.cc Normal file
View File

@ -0,0 +1,198 @@
#include <qpdf/Pl_Flate.hh>
#include <qpdf/QUtil.hh>
Pl_Flate::Pl_Flate(char const* identifier, Pipeline* next,
action_e action, int out_bufsize) :
Pipeline(identifier, next),
out_bufsize(out_bufsize),
action(action),
initialized(false)
{
this->outbuf = new unsigned char[out_bufsize];
zstream.zalloc = (alloc_func)0;
zstream.zfree = (free_func)0;
zstream.opaque = (voidpf)0;
zstream.next_in = 0;
zstream.avail_in = 0;
zstream.next_out = this->outbuf;
zstream.avail_out = out_bufsize;
}
Pl_Flate::~Pl_Flate()
{
if (this->outbuf)
{
delete [] this->outbuf;
this->outbuf = 0;
}
}
void
Pl_Flate::write(unsigned char* data, int len)
{
if (this->outbuf == 0)
{
throw Exception(
this->identifier +
": Pl_Flate: write() called after finish() called");
}
handleData(data, len, Z_NO_FLUSH);
}
void
Pl_Flate::handleData(unsigned char* data, int len, int flush)
{
this->zstream.next_in = data;
this->zstream.avail_in = len;
if (! this->initialized)
{
int err = Z_OK;
if (this->action == a_deflate)
{
err = deflateInit(&this->zstream, Z_DEFAULT_COMPRESSION);
}
else
{
err = inflateInit(&this->zstream);
}
checkError("Init", err);
this->initialized = true;
}
int err = Z_OK;
bool done = false;
while (! done)
{
if (action == a_deflate)
{
err = deflate(&this->zstream, flush);
}
else
{
err = inflate(&this->zstream, flush);
}
switch (err)
{
case Z_BUF_ERROR:
// Probably shouldn't be able to happen, but possible as a
// boundary condition: if the last call to inflate exactly
// filled the output buffer, it's possible that the next
// call to inflate could have nothing to do.
done = true;
break;
case Z_STREAM_END:
done = true;
// fall through
case Z_OK:
{
if ((this->zstream.avail_in == 0) &&
(this->zstream.avail_out > 0))
{
// There is nothing left to read, and there was
// sufficient buffer space to write everything we
// needed, so we're done for now.
done = true;
}
uLong ready = (this->out_bufsize - this->zstream.avail_out);
if (ready > 0)
{
this->getNext()->write(this->outbuf, ready);
this->zstream.next_out = this->outbuf;
this->zstream.avail_out = this->out_bufsize;
}
}
break;
default:
this->checkError("data", err);
break;
}
}
}
void
Pl_Flate::finish()
{
if (this->outbuf)
{
if (this->initialized)
{
unsigned char buf[1];
buf[0] = '\0';
handleData(buf, 0, Z_FINISH);
int err = Z_OK;
if (action == a_deflate)
{
err = deflateEnd(&this->zstream);
}
else
{
err = inflateEnd(&this->zstream);
}
checkError("End", err);
}
delete [] this->outbuf;
this->outbuf = 0;
}
this->getNext()->finish();
}
void
Pl_Flate::checkError(char const* prefix, int error_code)
{
if (error_code != Z_OK)
{
char const* action_str = (action == a_deflate ? "deflate" : "inflate");
std::string msg =
this->identifier + ": " + action_str + ": " + prefix + ": ";
if (this->zstream.msg)
{
msg += this->zstream.msg;
}
else
{
switch (error_code)
{
case Z_ERRNO:
msg += "zlib system error";
break;
case Z_STREAM_ERROR:
msg += "zlib stream error";
break;
case Z_DATA_ERROR:
msg += "zlib data error";
break;
case Z_MEM_ERROR:
msg += "zlib memory error";
break;
case Z_BUF_ERROR:
msg += "zlib buffer error";
break;
case Z_VERSION_ERROR:
msg += "zlib version error";
break;
default:
msg += std::string("zlib unknown error (") +
QUtil::int_to_string(error_code) + ")";
break;
}
}
throw Exception(msg);
}
}

229
libqpdf/Pl_LZWDecoder.cc Normal file
View File

@ -0,0 +1,229 @@
#include <qpdf/Pl_LZWDecoder.hh>
#include <qpdf/QEXC.hh>
#include <qpdf/QTC.hh>
#include <string.h>
#include <assert.h>
Pl_LZWDecoder::Pl_LZWDecoder(char const* identifier, Pipeline* next,
bool early_code_change) :
Pipeline(identifier, next),
code_size(9),
next(0),
byte_pos(0),
bit_pos(0),
bits_available(0),
code_change_delta(early_code_change ? 1 : 0),
eod(false),
last_code(256)
{
memset(buf, 0, 3);
}
Pl_LZWDecoder::~Pl_LZWDecoder()
{
}
void
Pl_LZWDecoder::write(unsigned char* bytes, int len)
{
for (int i = 0; i < len; ++i)
{
this->buf[next++] = bytes[i];
if (this->next == 3)
{
this->next = 0;
}
this->bits_available += 8;
if (this->bits_available >= this->code_size)
{
sendNextCode();
}
}
}
void
Pl_LZWDecoder::finish()
{
getNext()->finish();
}
void
Pl_LZWDecoder::sendNextCode()
{
int high = this->byte_pos;
int med = (this->byte_pos + 1) % 3;
int low = (this->byte_pos + 2) % 3;
int bits_from_high = 8 - this->bit_pos;
int bits_from_med = this->code_size - bits_from_high;
int bits_from_low = 0;
if (bits_from_med > 8)
{
bits_from_low = bits_from_med - 8;
bits_from_med = 8;
}
int high_mask = (1 << bits_from_high) - 1;
int med_mask = 0xff - ((1 << (8 - bits_from_med)) - 1);
int low_mask = 0xff - ((1 << (8 - bits_from_low)) - 1);
int code = 0;
code += (this->buf[high] & high_mask) << bits_from_med;
code += ((this->buf[med] & med_mask) >> (8 - bits_from_med));
if (bits_from_low)
{
code <<= bits_from_low;
code += ((this->buf[low] & low_mask) >> (8 - bits_from_low));
this->byte_pos = low;
this->bit_pos = bits_from_low;
}
else
{
this->byte_pos = med;
this->bit_pos = bits_from_med;
}
if (this->bit_pos == 8)
{
this->bit_pos = 0;
++this->byte_pos;
this->byte_pos %= 3;
}
this->bits_available -= this->code_size;
handleCode(code);
}
unsigned char
Pl_LZWDecoder::getFirstChar(int code)
{
unsigned char result = '\0';
if (code < 256)
{
result = (unsigned char) code;
}
else
{
assert(code > 257);
unsigned int idx = code - 258;
assert(idx < table.size());
Buffer& b = table[idx];
result = b.getBuffer()[0];
}
return result;
}
void
Pl_LZWDecoder::addToTable(unsigned char next)
{
unsigned int last_size = 0;
unsigned char const* last_data = 0;
unsigned char tmp[1];
if (this->last_code < 256)
{
tmp[0] = this->last_code;
last_data = tmp;
last_size = 1;
}
else
{
assert(this->last_code > 257);
unsigned int idx = this->last_code - 258;
assert(idx < table.size());
Buffer& b = table[idx];
last_data = b.getBuffer();
last_size = b.getSize();
}
Buffer entry(1 + last_size);
unsigned char* new_data = entry.getBuffer();
memcpy(new_data, last_data, last_size);
new_data[last_size] = next;
this->table.push_back(entry);
}
void
Pl_LZWDecoder::handleCode(int code)
{
if (this->eod)
{
return;
}
if (code == 256)
{
if (! this->table.empty())
{
QTC::TC("libtests", "Pl_LZWDecoder intermediate reset");
}
this->table.clear();
this->code_size = 9;
}
else if (code == 257)
{
this->eod = true;
}
else
{
if (this->last_code != 256)
{
// Add to the table from last time. New table entry would
// be what we read last plus the first character of what
// we're reading now.
unsigned char next = '\0';
unsigned int table_size = table.size();
if (code < 256)
{
// just read < 256; last time's next was code
next = code;
}
else if (code > 257)
{
unsigned int idx = code - 258;
if (idx > table_size)
{
throw QEXC::General("LZWDecoder: bad code received");
}
else if (idx == table_size)
{
// The encoder would have just created this entry,
// so the first character of this entry would have
// been the same as the first character of the
// last entry.
QTC::TC("libtests", "Pl_LZWDecoder last was table size");
next = getFirstChar(this->last_code);
}
else
{
next = getFirstChar(code);
}
}
unsigned int last_idx = 258 + table_size;
if (last_idx == 4095)
{
throw QEXC::General("LZWDecoder: table full");
}
addToTable(next);
unsigned int change_idx = last_idx + code_change_delta;
if ((change_idx == 511) ||
(change_idx == 1023) ||
(change_idx == 2047))
{
++this->code_size;
}
}
if (code < 256)
{
unsigned char ch = (unsigned char) code;
getNext()->write(&ch, 1);
}
else
{
Buffer& b = table[code - 258];
getNext()->write(b.getBuffer(), b.getSize());
}
}
this->last_code = code;
}

43
libqpdf/Pl_MD5.cc Normal file
View File

@ -0,0 +1,43 @@
#include <qpdf/Pl_MD5.hh>
#include <qpdf/QEXC.hh>
Pl_MD5::Pl_MD5(char const* identifier, Pipeline* next) :
Pipeline(identifier, next),
in_progress(false)
{
}
Pl_MD5::~Pl_MD5()
{
}
void
Pl_MD5::write(unsigned char* buf, int len)
{
if (! this->in_progress)
{
this->md5.reset();
this->in_progress = true;
}
this->md5.encodeDataIncrementally((char*) buf, len);
this->getNext()->write(buf, len);
}
void
Pl_MD5::finish()
{
this->getNext()->finish();
this->in_progress = false;
}
std::string
Pl_MD5::getHexDigest()
{
if (this->in_progress)
{
throw QEXC::General("digest requested for in-progress MD5 Pipeline");
}
return this->md5.unparse();
}

146
libqpdf/Pl_PNGFilter.cc Normal file
View File

@ -0,0 +1,146 @@
#include <qpdf/Pl_PNGFilter.hh>
#include <string.h>
Pl_PNGFilter::Pl_PNGFilter(char const* identifier, Pipeline* next,
action_e action, unsigned int columns,
unsigned int bytes_per_pixel) :
Pipeline(identifier, next),
action(action),
columns(columns),
cur_row(0),
prev_row(0),
buf1(0),
buf2(0),
pos(0)
{
this->buf1 = new unsigned char[columns + 1];
this->buf2 = new unsigned char[columns + 1];
this->cur_row = buf1;
// number of bytes per incoming row
this->incoming = (action == a_encode ? columns : columns + 1);
}
Pl_PNGFilter::~Pl_PNGFilter()
{
delete [] buf1;
delete [] buf2;
}
void
Pl_PNGFilter::write(unsigned char* data, int len)
{
int left = this->incoming - this->pos;
unsigned int offset = 0;
while (len >= left)
{
// finish off current row
memcpy(this->cur_row + this->pos, data + offset, left);
offset += left;
len -= left;
processRow();
// Swap rows
unsigned char* t = this->prev_row;
this->prev_row = this->cur_row;
this->cur_row = t ? t : this->buf2;
memset(this->cur_row, 0, this->columns + 1);
left = this->incoming;
this->pos = 0;
}
if (len)
{
memcpy(this->cur_row + this->pos, data + offset, len);
}
this->pos += len;
}
void
Pl_PNGFilter::processRow()
{
if (this->action == a_encode)
{
encodeRow();
}
else
{
decodeRow();
}
}
void
Pl_PNGFilter::decodeRow()
{
int filter = (int) this->cur_row[0];
if (this->prev_row)
{
switch (filter)
{
case 0: // none
break;
case 1: // sub
throw Exception("sub filter not implemented");
break;
case 2: // up
for (unsigned int i = 1; i <= this->columns; ++i)
{
this->cur_row[i] += this->prev_row[i];
}
break;
case 3: // average
throw Exception("average filter not implemented");
break;
case 4: // Paeth
throw Exception("Paeth filter not implemented");
break;
default:
// ignore
break;
}
}
getNext()->write(this->cur_row + 1, this->columns);
}
void
Pl_PNGFilter::encodeRow()
{
// For now, hard-code to using UP filter.
unsigned char ch = 2;
getNext()->write(&ch, 1);
if (this->prev_row)
{
for (unsigned int i = 0; i < this->columns; ++i)
{
ch = this->cur_row[i] - this->prev_row[i];
getNext()->write(&ch, 1);
}
}
else
{
getNext()->write(this->cur_row, this->columns);
}
}
void
Pl_PNGFilter::finish()
{
if (this->pos)
{
// write partial row
processRow();
}
this->prev_row = 0;
this->cur_row = buf1;
this->pos = 0;
memset(this->cur_row, 0, this->columns + 1);
getNext()->finish();
}

179
libqpdf/Pl_QPDFTokenizer.cc Normal file
View File

@ -0,0 +1,179 @@
#include <qpdf/Pl_QPDFTokenizer.hh>
#include <qpdf/QPDF_String.hh>
#include <qpdf/QPDF_Name.hh>
Pl_QPDFTokenizer::Pl_QPDFTokenizer(char const* identifier, Pipeline* next) :
Pipeline(identifier, next),
newline_after_next_token(false),
just_wrote_nl(false),
last_char_was_cr(false),
unread_char(false),
char_to_unread('\0'),
pass_through(false)
{
}
Pl_QPDFTokenizer::~Pl_QPDFTokenizer()
{
}
void
Pl_QPDFTokenizer::writeNext(char const* buf, int len)
{
if (len)
{
unsigned char* t = new unsigned char[len];
memcpy(t, buf, len);
getNext()->write(t, len);
delete [] t;
this->just_wrote_nl = (buf[len-1] == '\n');
}
}
void
Pl_QPDFTokenizer::writeToken(QPDFTokenizer::Token& token)
{
std::string value = token.getRawValue();
switch (token.getType())
{
case QPDFTokenizer::tt_string:
value = QPDF_String(token.getValue()).unparse();
break;
case QPDFTokenizer::tt_name:
value = QPDF_Name(token.getValue()).unparse();
break;
default:
break;
}
writeNext(value.c_str(), value.length());
}
void
Pl_QPDFTokenizer::processChar(char ch)
{
if (this->pass_through)
{
// We're not noramlizing anymore -- just write this without
// looking at it.
writeNext(&ch, 1);
return;
}
tokenizer.presentCharacter(ch);
QPDFTokenizer::Token token;
if (tokenizer.getToken(token, this->unread_char, this->char_to_unread))
{
writeToken(token);
if (this->newline_after_next_token)
{
writeNext("\n", 1);
this->newline_after_next_token = false;
}
if ((token.getType() == QPDFTokenizer::tt_word) &&
(token.getValue() == "BI"))
{
// Uh oh.... we're not sophisticated enough to handle
// inline images safely. We'd have to to set up all the
// filters and pipe the iamge data through it until the
// filtered output was the right size for an image of the
// specified dimensions. Then we'd either have to write
// out raw image data or continue to write filtered data,
// resuming normalization when we get to the end.
// Insetad, for now, we'll just turn off noramlization for
// the remainder of this stream.
this->pass_through = true;
if (this->unread_char)
{
writeNext(&this->char_to_unread, 1);
this->unread_char = false;
}
}
}
else
{
bool suppress = false;
if ((ch == '\n') && (this->last_char_was_cr))
{
// Always ignore \n following \r
suppress = true;
}
if ((this->last_char_was_cr = (ch == '\r')))
{
ch = '\n';
}
if (this->tokenizer.betweenTokens())
{
if (! suppress)
{
writeNext(&ch, 1);
}
}
else
{
if (ch == '\n')
{
this->newline_after_next_token = true;
}
}
}
}
void
Pl_QPDFTokenizer::checkUnread()
{
if (this->unread_char)
{
processChar(this->char_to_unread);
if (this->unread_char)
{
throw QEXC::Internal("unread_char still true after processing "
"unread character");
}
}
}
void
Pl_QPDFTokenizer::write(unsigned char* buf, int len)
{
checkUnread();
for (int i = 0; i < len; ++i)
{
processChar(buf[i]);
checkUnread();
}
}
void
Pl_QPDFTokenizer::finish()
{
this->tokenizer.presentEOF();
if (! this->pass_through)
{
QPDFTokenizer::Token token;
if (tokenizer.getToken(token, this->unread_char, this->char_to_unread))
{
writeToken(token);
if (unread_char)
{
if (this->char_to_unread == '\r')
{
this->char_to_unread = '\n';
}
writeNext(&this->char_to_unread, 1);
}
}
}
if (! this->just_wrote_nl)
{
writeNext("\n", 1);
}
getNext()->finish();
}

57
libqpdf/Pl_RC4.cc Normal file
View File

@ -0,0 +1,57 @@
#include <qpdf/Pl_RC4.hh>
#include <qpdf/QUtil.hh>
Pl_RC4::Pl_RC4(char const* identifier, Pipeline* next,
unsigned char const* key_data, int key_len,
int out_bufsize) :
Pipeline(identifier, next),
out_bufsize(out_bufsize),
rc4(key_data, key_len)
{
this->outbuf = new unsigned char[out_bufsize];
}
Pl_RC4::~Pl_RC4()
{
if (this->outbuf)
{
delete [] this->outbuf;
this->outbuf = 0;
}
}
void
Pl_RC4::write(unsigned char* data, int len)
{
if (this->outbuf == 0)
{
throw Exception(
this->identifier +
": Pl_RC4: write() called after finish() called");
}
int bytes_left = len;
unsigned char* p = data;
while (bytes_left > 0)
{
int bytes = (bytes_left < this->out_bufsize ? bytes_left : out_bufsize);
bytes_left -= bytes;
rc4.process(p, bytes, outbuf);
p += bytes;
getNext()->write(outbuf, bytes);
}
}
void
Pl_RC4::finish()
{
if (this->outbuf)
{
delete [] this->outbuf;
this->outbuf = 0;
}
this->getNext()->finish();
}

48
libqpdf/Pl_StdioFile.cc Normal file
View File

@ -0,0 +1,48 @@
#include <qpdf/Pl_StdioFile.hh>
#include <errno.h>
Pl_StdioFile::Pl_StdioFile(char const* identifier, FILE* f) :
Pipeline(identifier, 0),
file(f)
{
}
Pl_StdioFile::~Pl_StdioFile()
{
}
void
Pl_StdioFile::write(unsigned char* buf, int len)
{
size_t so_far = 0;
while (len > 0)
{
so_far = fwrite(buf, 1, len, this->file);
if (so_far == 0)
{
throw QEXC::System(this->identifier + ": Pl_StdioFile::write",
errno);
}
else
{
buf += so_far;
len -= so_far;
}
}
}
void
Pl_StdioFile::finish()
{
if (fileno(this->file) != -1)
{
fflush(this->file);
}
else
{
throw QEXC::Internal(this->identifier +
": Pl_StdioFile::finish: stream already closed");
}
}

67
libqpdf/QEXC.cc Normal file
View File

@ -0,0 +1,67 @@
#include <qpdf/QEXC.hh>
#include <string.h>
#include <errno.h>
QEXC::Base::Base()
{
// nothing needed
}
QEXC::Base::Base(std::string const& message) :
message(message)
{
// nothing needed
}
std::string const&
QEXC::Base::unparse() const
{
return this->message;
}
void
QEXC::Base::setMessage(std::string const& message)
{
this->message = message;
}
const char*
QEXC::Base::what() const throw()
{
// Since unparse() returns a const string reference, its
// implementors must arrange to have it return a reference to a
// string that is not going to disappear. It is therefore safe
// for us to return it's c_str() pointer.
return this->unparse().c_str();
}
QEXC::General::General()
{
// nothing needed
}
QEXC::General::General(std::string const& message) :
Base(message)
{
// nothing needed
}
QEXC::System::System(std::string const& prefix, int sys_errno)
{
// Note: using sys_errno in case errno is a macro.
this->sys_errno = sys_errno;
this->setMessage(prefix + ": " + strerror(sys_errno));
}
int
QEXC::System::getErrno() const
{
return this->sys_errno;
}
QEXC::Internal::Internal(std::string const& message) :
Base("INTERNAL ERROR: " + message)
{
// nothing needed
}

1851
libqpdf/QPDF.cc Normal file

File diff suppressed because it is too large Load Diff

20
libqpdf/QPDFExc.cc Normal file
View File

@ -0,0 +1,20 @@
#include <qpdf/QPDFExc.hh>
#include <qpdf/QUtil.hh>
QPDFExc::QPDFExc(std::string const& message) :
QEXC::General(message)
{
}
QPDFExc::QPDFExc(std::string const& filename, int offset,
std::string const& message) :
QEXC::General(filename + ": offset " + QUtil::int_to_string(offset) +
": " + message)
{
}
QPDFExc::~QPDFExc() throw ()
{
}

2
libqpdf/QPDFObject.cc Normal file
View File

@ -0,0 +1,2 @@
#include <qpdf/QPDFObject.hh>

637
libqpdf/QPDFObjectHandle.cc Normal file
View File

@ -0,0 +1,637 @@
#include <qpdf/QPDFObjectHandle.hh>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDF_Bool.hh>
#include <qpdf/QPDF_Null.hh>
#include <qpdf/QPDF_Integer.hh>
#include <qpdf/QPDF_Real.hh>
#include <qpdf/QPDF_Name.hh>
#include <qpdf/QPDF_String.hh>
#include <qpdf/QPDF_Array.hh>
#include <qpdf/QPDF_Dictionary.hh>
#include <qpdf/QPDF_Stream.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QEXC.hh>
#include <qpdf/QUtil.hh>
QPDFObjectHandle::QPDFObjectHandle() :
initialized(false),
objid(0),
generation(0)
{
}
QPDFObjectHandle::QPDFObjectHandle(QPDF* qpdf, int objid, int generation) :
initialized(true),
qpdf(qpdf),
objid(objid),
generation(generation)
{
}
QPDFObjectHandle::QPDFObjectHandle(QPDFObject* data) :
initialized(true),
qpdf(0),
objid(0),
generation(0),
obj(data)
{
}
bool
QPDFObjectHandle::isInitialized() const
{
return this->initialized;
}
template <class T>
class QPDFObjectTypeAccessor
{
public:
static bool check(QPDFObject* o)
{
return (o && dynamic_cast<T*>(o));
}
};
bool
QPDFObjectHandle::isBool()
{
dereference();
return QPDFObjectTypeAccessor<QPDF_Bool>::check(obj.getPointer());
}
bool
QPDFObjectHandle::isNull()
{
dereference();
return QPDFObjectTypeAccessor<QPDF_Null>::check(obj.getPointer());
}
bool
QPDFObjectHandle::isInteger()
{
dereference();
return QPDFObjectTypeAccessor<QPDF_Integer>::check(obj.getPointer());
}
bool
QPDFObjectHandle::isReal()
{
dereference();
return QPDFObjectTypeAccessor<QPDF_Real>::check(obj.getPointer());
}
bool
QPDFObjectHandle::isNumber()
{
return (isInteger() || isReal());
}
double
QPDFObjectHandle::getNumericValue()
{
double result = 0.0;
if (isInteger())
{
result = getIntValue();
}
else if (isReal())
{
result = atof(getRealValue().c_str());
}
else
{
throw QEXC::Internal("getNumericValue called for non-numeric object");
}
return result;
}
bool
QPDFObjectHandle::isName()
{
dereference();
return QPDFObjectTypeAccessor<QPDF_Name>::check(obj.getPointer());
}
bool
QPDFObjectHandle::isString()
{
dereference();
return QPDFObjectTypeAccessor<QPDF_String>::check(obj.getPointer());
}
bool
QPDFObjectHandle::isArray()
{
dereference();
return QPDFObjectTypeAccessor<QPDF_Array>::check(obj.getPointer());
}
bool
QPDFObjectHandle::isDictionary()
{
dereference();
return QPDFObjectTypeAccessor<QPDF_Dictionary>::check(obj.getPointer());
}
bool
QPDFObjectHandle::isStream()
{
dereference();
return QPDFObjectTypeAccessor<QPDF_Stream>::check(obj.getPointer());
}
bool
QPDFObjectHandle::isIndirect()
{
assertInitialized();
return (this->objid != 0);
}
bool
QPDFObjectHandle::isScalar()
{
return (! (isArray() || isDictionary() || isStream()));
}
// Bool accessors
bool
QPDFObjectHandle::getBoolValue()
{
assertType("Boolean", isBool());
return dynamic_cast<QPDF_Bool*>(obj.getPointer())->getVal();
}
// Integer accessors
int
QPDFObjectHandle::getIntValue()
{
assertType("Integer", isInteger());
return dynamic_cast<QPDF_Integer*>(obj.getPointer())->getVal();
}
// Real accessors
std::string
QPDFObjectHandle::getRealValue()
{
assertType("Real", isReal());
return dynamic_cast<QPDF_Real*>(obj.getPointer())->getVal();
}
// Name acessors
std::string
QPDFObjectHandle::getName()
{
assertType("Name", isName());
return dynamic_cast<QPDF_Name*>(obj.getPointer())->getName();
}
// String accessors
std::string
QPDFObjectHandle::getStringValue()
{
assertType("String", isString());
return dynamic_cast<QPDF_String*>(obj.getPointer())->getVal();
}
std::string
QPDFObjectHandle::getUTF8Value()
{
assertType("String", isString());
return dynamic_cast<QPDF_String*>(obj.getPointer())->getUTF8Val();
}
// Array acessors
int
QPDFObjectHandle::getArrayNItems()
{
assertType("Array", isArray());
return dynamic_cast<QPDF_Array*>(obj.getPointer())->getNItems();
}
QPDFObjectHandle
QPDFObjectHandle::getArrayItem(int n)
{
assertType("Array", isArray());
return dynamic_cast<QPDF_Array*>(obj.getPointer())->getItem(n);
}
// Array mutators
void
QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item)
{
assertType("Array", isArray());
return dynamic_cast<QPDF_Array*>(obj.getPointer())->setItem(n, item);
}
// Dictionary accesors
bool
QPDFObjectHandle::hasKey(std::string const& key)
{
assertType("Dictionary", isDictionary());
return dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->hasKey(key);
}
QPDFObjectHandle
QPDFObjectHandle::getKey(std::string const& key)
{
assertType("Dictionary", isDictionary());
return dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->getKey(key);
}
std::set<std::string>
QPDFObjectHandle::getKeys()
{
assertType("Dictionary", isDictionary());
return dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->getKeys();
}
// Dictionary mutators
void
QPDFObjectHandle::replaceKey(std::string const& key,
QPDFObjectHandle const& value)
{
assertType("Dictionary", isDictionary());
return dynamic_cast<QPDF_Dictionary*>(
obj.getPointer())->replaceKey(key, value);
}
void
QPDFObjectHandle::removeKey(std::string const& key)
{
assertType("Dictionary", isDictionary());
return dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->removeKey(key);
}
// Stream accessors
QPDFObjectHandle
QPDFObjectHandle::getDict()
{
assertType("Stream", isStream());
return dynamic_cast<QPDF_Stream*>(obj.getPointer())->getDict();
}
PointerHolder<Buffer>
QPDFObjectHandle::getStreamData()
{
assertType("Stream", isStream());
return dynamic_cast<QPDF_Stream*>(obj.getPointer())->getStreamData();
}
bool
QPDFObjectHandle::pipeStreamData(Pipeline* p, bool filter,
bool normalize, bool compress)
{
assertType("Stream", isStream());
return dynamic_cast<QPDF_Stream*>(obj.getPointer())->pipeStreamData(
p, filter, normalize, compress);
}
int
QPDFObjectHandle::getObjectID() const
{
return this->objid;
}
int
QPDFObjectHandle::getGeneration() const
{
return this->generation;
}
std::map<std::string, QPDFObjectHandle>
QPDFObjectHandle::getPageImages()
{
assertPageObject();
// Note: this code doesn't handle inherited resources. If this
// page dictionary doesn't have a /Resources key or has one whose
// value is null or an empty dictionary, you are supposed to walk
// up the page tree until you find a /Resources dictionary. As of
// this writing, I don't have any test files that use inherited
// resources, and hand-generating one won't be a good test beacuse
// any mistakes in my understanding would be present in both the
// code and the test file.
// NOTE: If support of inherited resources (see above comment) is
// implemented, edit comment in QPDFObjectHandle.hh for this
// function.
std::map<std::string, QPDFObjectHandle> result;
if (this->hasKey("/Resources"))
{
QPDFObjectHandle resources = this->getKey("/Resources");
if (resources.hasKey("/XObject"))
{
QPDFObjectHandle xobject = resources.getKey("/XObject");
std::set<std::string> keys = xobject.getKeys();
for (std::set<std::string>::iterator iter = keys.begin();
iter != keys.end(); ++iter)
{
std::string key = (*iter);
QPDFObjectHandle value = xobject.getKey(key);
if (value.isStream())
{
QPDFObjectHandle dict = value.getDict();
if (dict.hasKey("/Subtype") &&
(dict.getKey("/Subtype").getName() == "/Image") &&
(! dict.hasKey("/ImageMask")))
{
result[key] = value;
}
}
}
}
}
return result;
}
std::vector<QPDFObjectHandle>
QPDFObjectHandle::getPageContents()
{
assertPageObject();
std::vector<QPDFObjectHandle> result;
QPDFObjectHandle contents = this->getKey("/Contents");
if (contents.isArray())
{
int n_items = contents.getArrayNItems();
for (int i = 0; i < n_items; ++i)
{
QPDFObjectHandle item = contents.getArrayItem(i);
if (item.isStream())
{
result.push_back(item);
}
else
{
throw QEXC::General("unknown item type while inspecting "
"element of /Contents array in page "
"dictionary");
}
}
}
else if (contents.isStream())
{
result.push_back(contents);
}
else
{
throw QEXC::General("unknown object type inspecting /Contents "
"key in page dictionary");
}
return result;
}
std::string
QPDFObjectHandle::unparse()
{
std::string result;
if (this->isIndirect())
{
result = QUtil::int_to_string(this->objid) + " " +
QUtil::int_to_string(this->generation) + " R";
}
else
{
result = unparseResolved();
}
return result;
}
std::string
QPDFObjectHandle::unparseResolved()
{
dereference();
return this->obj.getPointer()->unparse();
}
QPDFObjectHandle
QPDFObjectHandle::newIndirect(QPDF* qpdf, int objid, int generation)
{
return QPDFObjectHandle(qpdf, objid, generation);
}
QPDFObjectHandle
QPDFObjectHandle::newBool(bool value)
{
return QPDFObjectHandle(new QPDF_Bool(value));
}
QPDFObjectHandle
QPDFObjectHandle::newNull()
{
return QPDFObjectHandle(new QPDF_Null());
}
QPDFObjectHandle
QPDFObjectHandle::newInteger(int value)
{
return QPDFObjectHandle(new QPDF_Integer(value));
}
QPDFObjectHandle
QPDFObjectHandle::newReal(std::string const& value)
{
return QPDFObjectHandle(new QPDF_Real(value));
}
QPDFObjectHandle
QPDFObjectHandle::newName(std::string const& name)
{
return QPDFObjectHandle(new QPDF_Name(name));
}
QPDFObjectHandle
QPDFObjectHandle::newString(std::string const& str)
{
return QPDFObjectHandle(new QPDF_String(str));
}
QPDFObjectHandle
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
{
return QPDFObjectHandle(new QPDF_Array(items));
}
QPDFObjectHandle
QPDFObjectHandle::newDictionary(
std::map<std::string, QPDFObjectHandle> const& items)
{
return QPDFObjectHandle(new QPDF_Dictionary(items));
}
QPDFObjectHandle
QPDFObjectHandle::newStream(QPDF* qpdf, int objid, int generation,
QPDFObjectHandle stream_dict,
off_t offset, int length)
{
return QPDFObjectHandle(new QPDF_Stream(
qpdf, objid, generation,
stream_dict, offset, length));
}
void
QPDFObjectHandle::makeDirectInternal(std::set<int>& visited)
{
assertInitialized();
if (isStream())
{
QTC::TC("qpdf", "QPDFObjectHandle ERR clone stream");
throw QEXC::General("attempt to make a stream into a direct object");
}
int cur_objid = this->objid;
if (cur_objid != 0)
{
if (visited.count(cur_objid))
{
QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
throw QEXC::General("loop detected while converting object from "
"indirect to direct");
}
visited.insert(cur_objid);
}
dereference();
this->objid = 0;
this->generation = 0;
QPDFObject* new_obj = 0;
if (isBool())
{
QTC::TC("qpdf", "QPDFObjectHandle clone bool");
new_obj = new QPDF_Bool(getBoolValue());
}
else if (isNull())
{
QTC::TC("qpdf", "QPDFObjectHandle clone null");
new_obj = new QPDF_Null();
}
else if (isInteger())
{
QTC::TC("qpdf", "QPDFObjectHandle clone integer");
new_obj = new QPDF_Integer(getIntValue());
}
else if (isReal())
{
QTC::TC("qpdf", "QPDFObjectHandle clone real");
new_obj = new QPDF_Real(getRealValue());
}
else if (isName())
{
QTC::TC("qpdf", "QPDFObjectHandle clone name");
new_obj = new QPDF_Name(getName());
}
else if (isString())
{
QTC::TC("qpdf", "QPDFObjectHandle clone string");
new_obj = new QPDF_String(getStringValue());
}
else if (isArray())
{
QTC::TC("qpdf", "QPDFObjectHandle clone array");
std::vector<QPDFObjectHandle> items;
int n = getArrayNItems();
for (int i = 0; i < n; ++i)
{
items.push_back(getArrayItem(i));
items.back().makeDirectInternal(visited);
}
new_obj = new QPDF_Array(items);
}
else if (isDictionary())
{
QTC::TC("qpdf", "QPDFObjectHandle clone dictionary");
std::set<std::string> keys = getKeys();
std::map<std::string, QPDFObjectHandle> items;
for (std::set<std::string>::iterator iter = keys.begin();
iter != keys.end(); ++iter)
{
items[*iter] = getKey(*iter);
items[*iter].makeDirectInternal(visited);
}
new_obj = new QPDF_Dictionary(items);
}
else
{
throw QEXC::Internal("QPDFObjectHandle::makeIndirect: "
"unknown object type");
}
this->obj = new_obj;
if (cur_objid)
{
visited.erase(cur_objid);
}
}
void
QPDFObjectHandle::makeDirect()
{
std::set<int> visited;
makeDirectInternal(visited);
}
void
QPDFObjectHandle::assertInitialized() const
{
if (! this->initialized)
{
throw QEXC::Internal("operation attempted on uninitialized "
"QPDFObjectHandle");
}
}
void
QPDFObjectHandle::assertType(char const* type_name, bool istype)
{
if (! istype)
{
throw QEXC::Internal(std::string("operation for ") + type_name +
" object attempted on object of wrong type");
}
}
void
QPDFObjectHandle::assertPageObject()
{
if (! (this->isDictionary() && this->hasKey("/Type") &&
(this->getKey("/Type").getName() == "/Page")))
{
throw QEXC::Internal("page operation called on non-Page object");
}
}
void
QPDFObjectHandle::dereference()
{
if (this->obj.getPointer() == 0)
{
this->obj = QPDF::Resolver::resolve(
this->qpdf, this->objid, this->generation);
if (this->obj.getPointer() == 0)
{
QTC::TC("qpdf", "QPDFObjectHandle indirect to unknown");
this->obj = new QPDF_Null();
}
}
}

458
libqpdf/QPDFTokenizer.cc Normal file
View File

@ -0,0 +1,458 @@
#include <qpdf/QPDFTokenizer.hh>
// DO NOT USE ctype -- it is locale dependent for some things, and
// it's not worth the risk of including it in case it may accidentally
// be used.
#include <qpdf/PCRE.hh>
#include <qpdf/QEXC.hh>
#include <qpdf/QTC.hh>
// See note above about ctype.
static bool is_hex_digit(char ch)
{
return (strchr("0123456789abcdefABCDEF", ch) != 0);
}
QPDFTokenizer::QPDFTokenizer() :
pound_special_in_name(true)
{
reset();
}
void
QPDFTokenizer::allowPoundAnywhereInName()
{
QTC::TC("qpdf", "QPDFTokenizer allow pound anywhere in name");
this->pound_special_in_name = false;
}
void
QPDFTokenizer::reset()
{
state = st_top;
type = tt_bad;
val = "";
raw_val = "";
error_message = "";
unread_char = false;
char_to_unread = '\0';
string_depth = 0;
string_ignoring_newline = false;
last_char_was_bs = false;
}
void
QPDFTokenizer::presentCharacter(char ch)
{
static PCRE num_re("^[\\+\\-]?(?:\\.\\d+|\\d+(?:\\.\\d+)?)$");
if (state == st_token_ready)
{
throw QEXC::Internal("QPDF tokenizer presented character "
"while token is waiting");
}
char orig_ch = ch;
// State machine is implemented such that some characters may be
// handled more than once. This happens whenever you have to use
// the character that caused a state change in the new state.
bool handled = true;
if (state == st_top)
{
// Note: we specifically do not use ctype here. It is
// locale-dependent.
if (strchr(" \t\n\v\f\r", ch))
{
// ignore
}
else if (ch == '%')
{
// Discard comments
state = st_in_comment;
}
else if (ch == '(')
{
string_depth = 1;
string_ignoring_newline = false;
memset(bs_num_register, '\0', sizeof(bs_num_register));
last_char_was_bs = false;
state = st_in_string;
}
else if (ch == '<')
{
state = st_lt;
}
else if (ch == '>')
{
state = st_gt;
}
else
{
val += ch;
if (ch == ')')
{
type = tt_bad;
QTC::TC("qpdf", "QPDF_Tokenizer bad )");
error_message = "unexpected )";
state = st_token_ready;
}
else if (ch == '[')
{
type = tt_array_open;
state = st_token_ready;
}
else if (ch == ']')
{
type = tt_array_close;
state = st_token_ready;
}
else if (ch == '{')
{
type = tt_brace_open;
state = st_token_ready;
}
else if (ch == '}')
{
type = tt_brace_close;
state = st_token_ready;
}
else
{
state = st_literal;
}
}
}
else if (state == st_in_comment)
{
if ((ch == '\r') || (ch == '\n'))
{
state = st_top;
}
}
else if (state == st_lt)
{
if (ch == '<')
{
val = "<<";
type = tt_dict_open;
state = st_token_ready;
}
else
{
handled = false;
state = st_in_hexstring;
}
}
else if (state == st_gt)
{
if (ch == '>')
{
val = ">>";
type = tt_dict_close;
state = st_token_ready;
}
else
{
val = ">";
type = tt_bad;
QTC::TC("qpdf", "QPDF_Tokenizer bad >");
error_message = "unexpected >";
unread_char = true;
char_to_unread = ch;
state = st_token_ready;
}
}
else if (state == st_in_string)
{
if (string_ignoring_newline && (! ((ch == '\r') || (ch == '\n'))))
{
string_ignoring_newline = false;
}
unsigned int bs_num_count = strlen(bs_num_register);
bool ch_is_octal = ((ch >= '0') && (ch <= '7'));
if ((bs_num_count == 3) || ((bs_num_count > 0) && (! ch_is_octal)))
{
// We've accumulated \ddd. PDF Spec says to ignore
// high-order overflow.
val += (char) strtol(bs_num_register, 0, 8);
memset(bs_num_register, '\0', sizeof(bs_num_register));
bs_num_count = 0;
}
if (string_ignoring_newline && ((ch == '\r') || (ch == '\n')))
{
// ignore
}
else if (ch_is_octal && (last_char_was_bs || (bs_num_count > 0)))
{
bs_num_register[bs_num_count++] = ch;
}
else if (last_char_was_bs)
{
switch (ch)
{
case 'n':
val += '\n';
break;
case 'r':
val += '\r';
break;
case 't':
val += '\t';
break;
case 'b':
val += '\b';
break;
case 'f':
val += '\f';
break;
case '\r':
case '\n':
string_ignoring_newline = true;
break;
default:
// PDF spec says backslash is ignored before anything else
val += ch;
break;
}
}
else if (ch == '\\')
{
// last_char_was_bs is set/cleared below as appropriate
if (bs_num_count)
{
throw QEXC::Internal("QPDFTokenizer: bs_num_count != 0 "
"when ch == '\\'");
}
}
else if (ch == '(')
{
val += ch;
++string_depth;
}
else if ((ch == ')') && (--string_depth == 0))
{
type = tt_string;
state = st_token_ready;
}
else
{
val += ch;
}
last_char_was_bs = ((! last_char_was_bs) && (ch == '\\'));
}
else if (state == st_literal)
{
if (strchr(" \t\n\v\f\r()<>[]{}/%", ch) != 0)
{
// A C-loacle whitespace character or delimiter terminates
// token. It is important to unread the whitespace
// character even though it is ignored since it may be the
// newline after a stream keyword. Removing it here could
// make the stream-reading code break on some files,
// though not on any files in the test suite as of this
// writing.
type = tt_word;
unread_char = true;
char_to_unread = ch;
state = st_token_ready;
}
else
{
val += ch;
}
}
else
{
handled = false;
}
if (handled)
{
// okay
}
else if (state == st_in_hexstring)
{
if (ch == '>')
{
type = tt_string;
state = st_token_ready;
if (val.length() % 2)
{
// PDF spec says odd hexstrings have implicit
// trailing 0.
val += '0';
}
char num[3];
num[2] = '\0';
std::string nval;
for (unsigned int i = 0; i < val.length(); i += 2)
{
num[0] = val[i];
num[1] = val[i+1];
char nch = (char)(strtol(num, 0, 16));
nval += nch;
}
val = nval;
}
else if (is_hex_digit(ch))
{
val += ch;
}
else if (strchr(" \t\n\v\f\r", ch))
{
// ignore
}
else
{
type = tt_bad;
QTC::TC("qpdf", "QPDF_Tokenizer bad (");
error_message = std::string("invalid character (") +
ch + ") in hexstring";
state = st_token_ready;
}
}
else
{
throw QEXC::Internal("invalid state while reading token");
}
if ((state == st_token_ready) && (type == tt_word))
{
if ((val.length() > 0) && (val[0] == '/'))
{
type = tt_name;
// Deal with # in name token. Note: '/' by itself is a
// valid name, so don't strip leading /. That way we
// don't have to deal with the empty string as a name.
std::string nval = "/";
char const* valstr = val.c_str() + 1;
for (char const* p = valstr; *p; ++p)
{
if ((*p == '#') && this->pound_special_in_name)
{
if (p[1] && p[2] &&
is_hex_digit(p[1]) && is_hex_digit(p[2]))
{
char num[3];
num[0] = p[1];
num[1] = p[2];
num[2] = '\0';
char ch = (char)(strtol(num, 0, 16));
if (ch == '\0')
{
type = tt_bad;
QTC::TC("qpdf", "QPDF_Tokenizer null in name");
error_message =
"null character not allowed in name token";
nval += "#00";
}
else
{
nval += ch;
}
p += 2;
}
else
{
QTC::TC("qpdf", "QPDF_Tokenizer bad name");
type = tt_bad;
error_message = "invalid name token";
nval += *p;
}
}
else
{
nval += *p;
}
}
val = nval;
}
else if (num_re.match(val.c_str()))
{
if (val.find('.') != std::string::npos)
{
type = tt_real;
}
else
{
type = tt_integer;
}
}
else if ((val == "true") || (val == "false"))
{
type = tt_bool;
}
else if (val == "null")
{
type = tt_null;
}
else
{
// I don't really know what it is, so leave it as tt_word.
// Lots of cases ($, #, etc.) other than actual words fall
// into this category, but that's okay at least for now.
type = tt_word;
}
}
if (! (betweenTokens() || ((state == st_token_ready) && unread_char)))
{
this->raw_val += orig_ch;
}
}
void
QPDFTokenizer::presentEOF()
{
switch (state)
{
case st_token_ready:
case st_top:
// okay
break;
case st_in_comment:
state = st_top;
break;
default:
type = tt_bad;
error_message = "EOF while reading token";
state = st_token_ready;
}
}
bool
QPDFTokenizer::getToken(Token& token, bool& unread_char, char& ch)
{
bool ready = (this->state == st_token_ready);
unread_char = this->unread_char;
ch = this->char_to_unread;
if (ready)
{
token = Token(type, val, raw_val, error_message);
reset();
}
return ready;
}
bool
QPDFTokenizer::betweenTokens()
{
return ((state == st_top) || (state == st_in_comment));
}

2021
libqpdf/QPDFWriter.cc Normal file

File diff suppressed because it is too large Load Diff

61
libqpdf/QPDFXRefEntry.cc Normal file
View File

@ -0,0 +1,61 @@
#include <qpdf/QPDFXRefEntry.hh>
#include <qpdf/QPDFExc.hh>
#include <qpdf/QUtil.hh>
QPDFXRefEntry::QPDFXRefEntry() :
type(0),
field1(0),
field2(0)
{
}
QPDFXRefEntry::QPDFXRefEntry(int type, int field1, int field2) :
type(type),
field1(field1),
field2(field2)
{
if ((type < 1) || (type > 2))
{
throw QPDFExc("invalid xref type " + QUtil::int_to_string(type));
}
}
int
QPDFXRefEntry::getType() const
{
return this->type;
}
int
QPDFXRefEntry::getOffset() const
{
if (this->type != 1)
{
throw QPDFExc(
"getOffset called for xref entry of type != 1");
}
return this->field1;
}
int
QPDFXRefEntry::getObjStreamNumber() const
{
if (this->type != 2)
{
throw QPDFExc(
"getObjStreamNumber called for xref entry of type != 2");
}
return this->field1;
}
int
QPDFXRefEntry::getObjStreamIndex() const
{
if (this->type != 2)
{
throw QPDFExc(
"getObjStreamIndex called for xref entry of type != 2");
}
return this->field2;
}

51
libqpdf/QPDF_Array.cc Normal file
View File

@ -0,0 +1,51 @@
#include <qpdf/QPDF_Array.hh>
#include <qpdf/QEXC.hh>
QPDF_Array::QPDF_Array(std::vector<QPDFObjectHandle> const& items) :
items(items)
{
}
QPDF_Array::~QPDF_Array()
{
}
std::string
QPDF_Array::unparse()
{
std::string result = "[ ";
for (std::vector<QPDFObjectHandle>::iterator iter = this->items.begin();
iter != this->items.end(); ++iter)
{
result += (*iter).unparse();
result += " ";
}
result += "]";
return result;
}
int
QPDF_Array::getNItems() const
{
return this->items.size();
}
QPDFObjectHandle
QPDF_Array::getItem(int n) const
{
if ((n < 0) || (n >= (int)this->items.size()))
{
throw QEXC::Internal("bounds array accessing QPDF_Array element");
}
return this->items[n];
}
void
QPDF_Array::setItem(int n, QPDFObjectHandle const& oh)
{
// Call getItem for bounds checking
(void) getItem(n);
this->items[n] = oh;
}

23
libqpdf/QPDF_Bool.cc Normal file
View File

@ -0,0 +1,23 @@
#include <qpdf/QPDF_Bool.hh>
QPDF_Bool::QPDF_Bool(bool val) :
val(val)
{
}
QPDF_Bool::~QPDF_Bool()
{
}
std::string
QPDF_Bool::unparse()
{
return (val ? "true" : "false");
}
bool
QPDF_Bool::getVal() const
{
return this->val;
}

View File

@ -0,0 +1,84 @@
#include <qpdf/QPDF_Dictionary.hh>
#include <qpdf/QPDF_Null.hh>
#include <qpdf/QPDF_Name.hh>
QPDF_Dictionary::QPDF_Dictionary(
std::map<std::string, QPDFObjectHandle> const& items) :
items(items)
{
}
QPDF_Dictionary::~QPDF_Dictionary()
{
}
std::string
QPDF_Dictionary::unparse()
{
std::string result = "<< ";
for (std::map<std::string, QPDFObjectHandle>::iterator iter =
this->items.begin();
iter != this->items.end(); ++iter)
{
result += QPDF_Name::normalizeName((*iter).first) +
" " + (*iter).second.unparse() + " ";
}
result += ">>";
return result;
}
bool
QPDF_Dictionary::hasKey(std::string const& key)
{
return ((this->items.count(key) > 0) &&
(! this->items[key].isNull()));
}
QPDFObjectHandle
QPDF_Dictionary::getKey(std::string const& key)
{
// PDF spec says fetching a non-existent key from a dictionary
// returns the null object.
if (this->items.count(key))
{
// May be a null object
return (*(this->items.find(key))).second;
}
else
{
return QPDFObjectHandle::newNull();
}
}
std::set<std::string>
QPDF_Dictionary::getKeys()
{
std::set<std::string> result;
for (std::map<std::string, QPDFObjectHandle>::const_iterator iter =
this->items.begin();
iter != this->items.end(); ++iter)
{
if (hasKey((*iter).first))
{
result.insert((*iter).first);
}
}
return result;
}
void
QPDF_Dictionary::replaceKey(std::string const& key,
QPDFObjectHandle const& value)
{
// add or replace value
this->items[key] = value;
}
void
QPDF_Dictionary::removeKey(std::string const& key)
{
// no-op if key does not exist
this->items.erase(key);
}

25
libqpdf/QPDF_Integer.cc Normal file
View File

@ -0,0 +1,25 @@
#include <qpdf/QPDF_Integer.hh>
#include <qpdf/QUtil.hh>
QPDF_Integer::QPDF_Integer(int val) :
val(val)
{
}
QPDF_Integer::~QPDF_Integer()
{
}
std::string
QPDF_Integer::unparse()
{
return QUtil::int_to_string(this->val);
}
int
QPDF_Integer::getVal() const
{
return this->val;
}

46
libqpdf/QPDF_Name.cc Normal file
View File

@ -0,0 +1,46 @@
#include <qpdf/QPDF_Name.hh>
QPDF_Name::QPDF_Name(std::string const& name) :
name(name)
{
}
QPDF_Name::~QPDF_Name()
{
}
std::string
QPDF_Name::normalizeName(std::string const& name)
{
std::string result;
char num[4];
result += name[0];
for (unsigned int i = 1; i < name.length(); ++i)
{
char ch = name[i];
// Don't use locale/ctype here; follow PDF spec guidlines.
if (strchr("#()<>[]{}/%", ch) || (ch < 33) || (ch > 126))
{
sprintf(num, "#%02x", (unsigned char) ch);
result += num;
}
else
{
result += ch;
}
}
return result;
}
std::string
QPDF_Name::unparse()
{
return normalizeName(this->name);
}
std::string
QPDF_Name::getName() const
{
return this->name;
}

12
libqpdf/QPDF_Null.cc Normal file
View File

@ -0,0 +1,12 @@
#include <qpdf/QPDF_Null.hh>
QPDF_Null::~QPDF_Null()
{
}
std::string
QPDF_Null::unparse()
{
return "null";
}

23
libqpdf/QPDF_Real.cc Normal file
View File

@ -0,0 +1,23 @@
#include <qpdf/QPDF_Real.hh>
QPDF_Real::QPDF_Real(std::string const& val) :
val(val)
{
}
QPDF_Real::~QPDF_Real()
{
}
std::string
QPDF_Real::unparse()
{
return this->val;
}
std::string
QPDF_Real::getVal()
{
return this->val;
}

309
libqpdf/QPDF_Stream.cc Normal file
View File

@ -0,0 +1,309 @@
#include <qpdf/QPDF_Stream.hh>
#include <qpdf/QEXC.hh>
#include <qpdf/QUtil.hh>
#include <qpdf/Pipeline.hh>
#include <qpdf/Pl_Flate.hh>
#include <qpdf/Pl_PNGFilter.hh>
#include <qpdf/Pl_RC4.hh>
#include <qpdf/Pl_Buffer.hh>
#include <qpdf/Pl_ASCII85Decoder.hh>
#include <qpdf/Pl_ASCIIHexDecoder.hh>
#include <qpdf/Pl_LZWDecoder.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFExc.hh>
#include <qpdf/Pl_QPDFTokenizer.hh>
QPDF_Stream::QPDF_Stream(QPDF* qpdf, int objid, int generation,
QPDFObjectHandle stream_dict,
off_t offset, int length) :
qpdf(qpdf),
objid(objid),
generation(generation),
stream_dict(stream_dict),
offset(offset),
length(length)
{
if (! stream_dict.isDictionary())
{
throw QEXC::Internal("stream object instantiated with non-dictionary "
"object for dictionary");
}
}
QPDF_Stream::~QPDF_Stream()
{
}
std::string
QPDF_Stream::unparse()
{
// Unparse stream objects as indirect references
return QUtil::int_to_string(this->objid) + " " +
QUtil::int_to_string(this->generation) + " R";
}
QPDFObjectHandle
QPDF_Stream::getDict() const
{
return this->stream_dict;
}
PointerHolder<Buffer>
QPDF_Stream::getStreamData()
{
Pl_Buffer buf("stream data buffer");
if (! pipeStreamData(&buf, true, false, false))
{
throw QPDFExc("getStreamData called on unfilterable stream");
}
return buf.getBuffer();
}
bool
QPDF_Stream::filterable(std::vector<std::string>& filters,
int& predictor, int& columns,
bool& early_code_change)
{
// Initialize values to their defaults as per the PDF spec
predictor = 1;
columns = 0;
early_code_change = true;
bool filterable = true;
// See if we can support any decode parameters that are specified.
QPDFObjectHandle decode_obj =
this->stream_dict.getKey("/DecodeParms");
if (decode_obj.isNull())
{
// no problem
}
else if (decode_obj.isDictionary())
{
std::set<std::string> keys = decode_obj.getKeys();
for (std::set<std::string>::iterator iter = keys.begin();
iter != keys.end(); ++iter)
{
std::string const& key = *iter;
if (key == "/Predictor")
{
QPDFObjectHandle predictor_obj = decode_obj.getKey(key);
if (predictor_obj.isInteger())
{
predictor = predictor_obj.getIntValue();
if (! ((predictor == 1) || (predictor == 12)))
{
filterable = false;
}
}
else
{
filterable = false;
}
}
else if (key == "/EarlyChange")
{
QPDFObjectHandle earlychange_obj = decode_obj.getKey(key);
if (earlychange_obj.isInteger())
{
int earlychange = earlychange_obj.getIntValue();
early_code_change = (earlychange == 1);
if (! ((earlychange == 0) || (earlychange == 1)))
{
filterable = false;
}
}
else
{
filterable = false;
}
}
else if (key == "/Columns")
{
QPDFObjectHandle columns_obj = decode_obj.getKey(key);
if (columns_obj.isInteger())
{
columns = columns_obj.getIntValue();
}
else
{
filterable = false;
}
}
else
{
filterable = false;
}
}
}
else
{
throw QPDFExc(qpdf->getFilename(), this->offset,
"invalid decode parameters object type for this stream");
}
if ((predictor > 1) && (columns == 0))
{
// invalid
filterable = false;
}
if (! filterable)
{
return false;
}
// Check filters
QPDFObjectHandle filter_obj = this->stream_dict.getKey("/Filter");
bool filters_okay = true;
if (filter_obj.isNull())
{
// No filters
}
else if (filter_obj.isName())
{
// One filter
filters.push_back(filter_obj.getName());
}
else if (filter_obj.isArray())
{
// Potentially multiple filters
int n = filter_obj.getArrayNItems();
for (int i = 0; i < n; ++i)
{
QPDFObjectHandle item = filter_obj.getArrayItem(i);
if (item.isName())
{
filters.push_back(item.getName());
}
else
{
filters_okay = false;
}
}
}
else
{
filters_okay = false;
}
if (! filters_okay)
{
QTC::TC("qpdf", "QPDF_Stream invalid filter");
throw QPDFExc(qpdf->getFilename(), this->offset,
"invalid filter object type for this stream");
}
// `filters' now contains a list of filters to be applied in
// order. See which ones we can support.
for (std::vector<std::string>::iterator iter = filters.begin();
iter != filters.end(); ++iter)
{
std::string const& filter = *iter;
if (! ((filter == "/FlateDecode") ||
(filter == "/LZWDecode") ||
(filter == "/ASCII85Decode") ||
(filter == "/ASCIIHexDecode")))
{
filterable = false;
}
}
return filterable;
}
bool
QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter,
bool normalize, bool compress)
{
std::vector<std::string> filters;
int predictor = 1;
int columns = 0;
bool early_code_change = true;
if (filter)
{
filter = filterable(filters, predictor, columns, early_code_change);
}
if (pipeline == 0)
{
QTC::TC("qpdf", "QPDF_Stream pipeStreamData with null pipeline");
return filter;
}
// Construct the pipeline in reverse order. Force pipelines we
// create to be deleted when this function finishes.
std::vector<PointerHolder<Pipeline> > to_delete;
if (filter)
{
if (compress)
{
pipeline = new Pl_Flate("compress object stream", pipeline,
Pl_Flate::a_deflate);
to_delete.push_back(pipeline);
}
if (normalize)
{
pipeline = new Pl_QPDFTokenizer("normalizer", pipeline);
to_delete.push_back(pipeline);
}
for (std::vector<std::string>::reverse_iterator iter = filters.rbegin();
iter != filters.rend(); ++iter)
{
std::string const& filter = *iter;
if (filter == "/FlateDecode")
{
if (predictor == 12)
{
QTC::TC("qpdf", "QPDF_Stream PNG filter");
pipeline = new Pl_PNGFilter(
"png decode", pipeline, Pl_PNGFilter::a_decode,
columns, 0 /* not used */);
to_delete.push_back(pipeline);
}
pipeline = new Pl_Flate("stream inflate",
pipeline, Pl_Flate::a_inflate);
to_delete.push_back(pipeline);
}
else if (filter == "/ASCII85Decode")
{
pipeline = new Pl_ASCII85Decoder("ascii85 decode", pipeline);
to_delete.push_back(pipeline);
}
else if (filter == "/ASCIIHexDecode")
{
pipeline = new Pl_ASCIIHexDecoder("asciiHex decode", pipeline);
to_delete.push_back(pipeline);
}
else if (filter == "/LZWDecode")
{
pipeline = new Pl_LZWDecoder("lzw decode", pipeline,
early_code_change);
to_delete.push_back(pipeline);
}
else
{
throw QEXC::Internal("QPDFStream: unknown filter "
"encountered after check");
}
}
}
QPDF::Pipe::pipeStreamData(this->qpdf, this->objid, this->generation,
this->offset, this->length,
this->stream_dict, pipeline);
return filter;
}

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